287 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * MFD driver for twl4030 audio submodule, which contains an audio codec, and4 * the vibra control.5 *6 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>7 *8 * Copyright: (C) 2009 Nokia Corporation9 */10 11#include <linux/module.h>12#include <linux/types.h>13#include <linux/slab.h>14#include <linux/kernel.h>15#include <linux/fs.h>16#include <linux/platform_device.h>17#include <linux/of.h>18#include <linux/of_platform.h>19#include <linux/mfd/twl.h>20#include <linux/mfd/core.h>21#include <linux/mfd/twl4030-audio.h>22 23#define TWL4030_AUDIO_CELLS 224 25static struct platform_device *twl4030_audio_dev;26 27struct twl4030_audio_resource {28 int request_count;29 u8 reg;30 u8 mask;31};32 33struct twl4030_audio {34 unsigned int audio_mclk;35 struct mutex mutex;36 struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];37 struct mfd_cell cells[TWL4030_AUDIO_CELLS];38};39 40/*41 * Modify the resource, the function returns the content of the register42 * after the modification.43 */44static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)45{46 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);47 u8 val;48 49 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,50 audio->resource[id].reg);51 52 if (enable)53 val |= audio->resource[id].mask;54 else55 val &= ~audio->resource[id].mask;56 57 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,58 val, audio->resource[id].reg);59 60 return val;61}62 63static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)64{65 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);66 u8 val;67 68 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,69 audio->resource[id].reg);70 71 return val;72}73 74/*75 * Enable the resource.76 * The function returns with error or the content of the register77 */78int twl4030_audio_enable_resource(enum twl4030_audio_res id)79{80 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);81 int val;82 83 if (id >= TWL4030_AUDIO_RES_MAX) {84 dev_err(&twl4030_audio_dev->dev,85 "Invalid resource ID (%u)\n", id);86 return -EINVAL;87 }88 89 mutex_lock(&audio->mutex);90 if (!audio->resource[id].request_count)91 /* Resource was disabled, enable it */92 val = twl4030_audio_set_resource(id, 1);93 else94 val = twl4030_audio_get_resource(id);95 96 audio->resource[id].request_count++;97 mutex_unlock(&audio->mutex);98 99 return val;100}101EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);102 103/*104 * Disable the resource.105 * The function returns with error or the content of the register106 */107int twl4030_audio_disable_resource(enum twl4030_audio_res id)108{109 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);110 int val;111 112 if (id >= TWL4030_AUDIO_RES_MAX) {113 dev_err(&twl4030_audio_dev->dev,114 "Invalid resource ID (%u)\n", id);115 return -EINVAL;116 }117 118 mutex_lock(&audio->mutex);119 if (!audio->resource[id].request_count) {120 dev_err(&twl4030_audio_dev->dev,121 "Resource has been disabled already (%u)\n", id);122 mutex_unlock(&audio->mutex);123 return -EPERM;124 }125 audio->resource[id].request_count--;126 127 if (!audio->resource[id].request_count)128 /* Resource can be disabled now */129 val = twl4030_audio_set_resource(id, 0);130 else131 val = twl4030_audio_get_resource(id);132 133 mutex_unlock(&audio->mutex);134 135 return val;136}137EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);138 139unsigned int twl4030_audio_get_mclk(void)140{141 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);142 143 return audio->audio_mclk;144}145EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);146 147static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata,148 struct device_node *parent)149{150 struct device_node *node;151 152 if (pdata && pdata->codec)153 return true;154 155 node = of_get_child_by_name(parent, "codec");156 if (node) {157 of_node_put(node);158 return true;159 }160 161 return false;162}163 164static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata,165 struct device_node *node)166{167 int vibra;168 169 if (pdata && pdata->vibra)170 return true;171 172 if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra)173 return true;174 175 return false;176}177 178static int twl4030_audio_probe(struct platform_device *pdev)179{180 struct twl4030_audio *audio;181 struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev);182 struct device_node *node = pdev->dev.of_node;183 struct mfd_cell *cell = NULL;184 int ret, childs = 0;185 u8 val;186 187 if (!pdata && !node) {188 dev_err(&pdev->dev, "Platform data is missing\n");189 return -EINVAL;190 }191 192 audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio),193 GFP_KERNEL);194 if (!audio)195 return -ENOMEM;196 197 mutex_init(&audio->mutex);198 audio->audio_mclk = twl_get_hfclk_rate();199 200 /* Configure APLL_INFREQ and disable APLL if enabled */201 switch (audio->audio_mclk) {202 case 19200000:203 val = TWL4030_APLL_INFREQ_19200KHZ;204 break;205 case 26000000:206 val = TWL4030_APLL_INFREQ_26000KHZ;207 break;208 case 38400000:209 val = TWL4030_APLL_INFREQ_38400KHZ;210 break;211 default:212 dev_err(&pdev->dev, "Invalid audio_mclk\n");213 return -EINVAL;214 }215 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL);216 217 /* Codec power */218 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;219 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;220 221 /* PLL */222 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;223 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;224 225 if (twl4030_audio_has_codec(pdata, node)) {226 cell = &audio->cells[childs];227 cell->name = "twl4030-codec";228 if (pdata) {229 cell->platform_data = pdata->codec;230 cell->pdata_size = sizeof(*pdata->codec);231 }232 childs++;233 }234 if (twl4030_audio_has_vibra(pdata, node)) {235 cell = &audio->cells[childs];236 cell->name = "twl4030-vibra";237 if (pdata) {238 cell->platform_data = pdata->vibra;239 cell->pdata_size = sizeof(*pdata->vibra);240 }241 childs++;242 }243 244 platform_set_drvdata(pdev, audio);245 twl4030_audio_dev = pdev;246 247 if (childs)248 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,249 childs, NULL, 0, NULL);250 else {251 dev_err(&pdev->dev, "No platform data found for childs\n");252 ret = -ENODEV;253 }254 255 if (ret)256 twl4030_audio_dev = NULL;257 258 return ret;259}260 261static void twl4030_audio_remove(struct platform_device *pdev)262{263 mfd_remove_devices(&pdev->dev);264 twl4030_audio_dev = NULL;265}266 267static const struct of_device_id twl4030_audio_of_match[] = {268 {.compatible = "ti,twl4030-audio", },269 { },270};271MODULE_DEVICE_TABLE(of, twl4030_audio_of_match);272 273static struct platform_driver twl4030_audio_driver = {274 .driver = {275 .name = "twl4030-audio",276 .of_match_table = twl4030_audio_of_match,277 },278 .probe = twl4030_audio_probe,279 .remove_new = twl4030_audio_remove,280};281 282module_platform_driver(twl4030_audio_driver);283 284MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");285MODULE_DESCRIPTION("TWL4030 audio block MFD driver");286MODULE_ALIAS("platform:twl4030-audio");287