brintos

brintos / linux-shallow public Read only

0
0
Text · 6.6 KiB · d436ddf Raw
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