xref: /openbmc/linux/drivers/mfd/twl4030-audio.c (revision 96ac2d1a)
157fe7251SPeter Ujfalusi /*
257fe7251SPeter Ujfalusi  * MFD driver for twl4030 audio submodule, which contains an audio codec, and
357fe7251SPeter Ujfalusi  * the vibra control.
457fe7251SPeter Ujfalusi  *
557fe7251SPeter Ujfalusi  * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
657fe7251SPeter Ujfalusi  *
757fe7251SPeter Ujfalusi  * Copyright:   (C) 2009 Nokia Corporation
857fe7251SPeter Ujfalusi  *
957fe7251SPeter Ujfalusi  * This program is free software; you can redistribute it and/or modify
1057fe7251SPeter Ujfalusi  * it under the terms of the GNU General Public License version 2 as
1157fe7251SPeter Ujfalusi  * published by the Free Software Foundation.
1257fe7251SPeter Ujfalusi  *
1357fe7251SPeter Ujfalusi  * This program is distributed in the hope that it will be useful, but
1457fe7251SPeter Ujfalusi  * WITHOUT ANY WARRANTY; without even the implied warranty of
1557fe7251SPeter Ujfalusi  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1657fe7251SPeter Ujfalusi  * General Public License for more details.
1757fe7251SPeter Ujfalusi  *
1857fe7251SPeter Ujfalusi  * You should have received a copy of the GNU General Public License
1957fe7251SPeter Ujfalusi  * along with this program; if not, write to the Free Software
2057fe7251SPeter Ujfalusi  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
2157fe7251SPeter Ujfalusi  * 02110-1301 USA
2257fe7251SPeter Ujfalusi  *
2357fe7251SPeter Ujfalusi  */
2457fe7251SPeter Ujfalusi 
2557fe7251SPeter Ujfalusi #include <linux/module.h>
2657fe7251SPeter Ujfalusi #include <linux/types.h>
2757fe7251SPeter Ujfalusi #include <linux/slab.h>
2857fe7251SPeter Ujfalusi #include <linux/kernel.h>
2957fe7251SPeter Ujfalusi #include <linux/fs.h>
3057fe7251SPeter Ujfalusi #include <linux/platform_device.h>
31019a7e6bSPeter Ujfalusi #include <linux/of.h>
32019a7e6bSPeter Ujfalusi #include <linux/of_platform.h>
3357fe7251SPeter Ujfalusi #include <linux/i2c/twl.h>
3457fe7251SPeter Ujfalusi #include <linux/mfd/core.h>
3557fe7251SPeter Ujfalusi #include <linux/mfd/twl4030-audio.h>
3657fe7251SPeter Ujfalusi 
3757fe7251SPeter Ujfalusi #define TWL4030_AUDIO_CELLS	2
3857fe7251SPeter Ujfalusi 
3957fe7251SPeter Ujfalusi static struct platform_device *twl4030_audio_dev;
4057fe7251SPeter Ujfalusi 
4157fe7251SPeter Ujfalusi struct twl4030_audio_resource {
4257fe7251SPeter Ujfalusi 	int request_count;
4357fe7251SPeter Ujfalusi 	u8 reg;
4457fe7251SPeter Ujfalusi 	u8 mask;
4557fe7251SPeter Ujfalusi };
4657fe7251SPeter Ujfalusi 
4757fe7251SPeter Ujfalusi struct twl4030_audio {
4857fe7251SPeter Ujfalusi 	unsigned int audio_mclk;
4957fe7251SPeter Ujfalusi 	struct mutex mutex;
5057fe7251SPeter Ujfalusi 	struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];
5157fe7251SPeter Ujfalusi 	struct mfd_cell cells[TWL4030_AUDIO_CELLS];
5257fe7251SPeter Ujfalusi };
5357fe7251SPeter Ujfalusi 
5457fe7251SPeter Ujfalusi /*
5557fe7251SPeter Ujfalusi  * Modify the resource, the function returns the content of the register
5657fe7251SPeter Ujfalusi  * after the modification.
5757fe7251SPeter Ujfalusi  */
5857fe7251SPeter Ujfalusi static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)
5957fe7251SPeter Ujfalusi {
6057fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
6157fe7251SPeter Ujfalusi 	u8 val;
6257fe7251SPeter Ujfalusi 
6357fe7251SPeter Ujfalusi 	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
6457fe7251SPeter Ujfalusi 			audio->resource[id].reg);
6557fe7251SPeter Ujfalusi 
6657fe7251SPeter Ujfalusi 	if (enable)
6757fe7251SPeter Ujfalusi 		val |= audio->resource[id].mask;
6857fe7251SPeter Ujfalusi 	else
6957fe7251SPeter Ujfalusi 		val &= ~audio->resource[id].mask;
7057fe7251SPeter Ujfalusi 
7157fe7251SPeter Ujfalusi 	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
7257fe7251SPeter Ujfalusi 					val, audio->resource[id].reg);
7357fe7251SPeter Ujfalusi 
7457fe7251SPeter Ujfalusi 	return val;
7557fe7251SPeter Ujfalusi }
7657fe7251SPeter Ujfalusi 
7757fe7251SPeter Ujfalusi static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)
7857fe7251SPeter Ujfalusi {
7957fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
8057fe7251SPeter Ujfalusi 	u8 val;
8157fe7251SPeter Ujfalusi 
8257fe7251SPeter Ujfalusi 	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
8357fe7251SPeter Ujfalusi 			audio->resource[id].reg);
8457fe7251SPeter Ujfalusi 
8557fe7251SPeter Ujfalusi 	return val;
8657fe7251SPeter Ujfalusi }
8757fe7251SPeter Ujfalusi 
8857fe7251SPeter Ujfalusi /*
8957fe7251SPeter Ujfalusi  * Enable the resource.
9057fe7251SPeter Ujfalusi  * The function returns with error or the content of the register
9157fe7251SPeter Ujfalusi  */
9257fe7251SPeter Ujfalusi int twl4030_audio_enable_resource(enum twl4030_audio_res id)
9357fe7251SPeter Ujfalusi {
9457fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
9557fe7251SPeter Ujfalusi 	int val;
9657fe7251SPeter Ujfalusi 
9757fe7251SPeter Ujfalusi 	if (id >= TWL4030_AUDIO_RES_MAX) {
9857fe7251SPeter Ujfalusi 		dev_err(&twl4030_audio_dev->dev,
9957fe7251SPeter Ujfalusi 				"Invalid resource ID (%u)\n", id);
10057fe7251SPeter Ujfalusi 		return -EINVAL;
10157fe7251SPeter Ujfalusi 	}
10257fe7251SPeter Ujfalusi 
10357fe7251SPeter Ujfalusi 	mutex_lock(&audio->mutex);
10457fe7251SPeter Ujfalusi 	if (!audio->resource[id].request_count)
10557fe7251SPeter Ujfalusi 		/* Resource was disabled, enable it */
10657fe7251SPeter Ujfalusi 		val = twl4030_audio_set_resource(id, 1);
10757fe7251SPeter Ujfalusi 	else
10857fe7251SPeter Ujfalusi 		val = twl4030_audio_get_resource(id);
10957fe7251SPeter Ujfalusi 
11057fe7251SPeter Ujfalusi 	audio->resource[id].request_count++;
11157fe7251SPeter Ujfalusi 	mutex_unlock(&audio->mutex);
11257fe7251SPeter Ujfalusi 
11357fe7251SPeter Ujfalusi 	return val;
11457fe7251SPeter Ujfalusi }
11557fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);
11657fe7251SPeter Ujfalusi 
11757fe7251SPeter Ujfalusi /*
11857fe7251SPeter Ujfalusi  * Disable the resource.
11957fe7251SPeter Ujfalusi  * The function returns with error or the content of the register
12057fe7251SPeter Ujfalusi  */
1216049bcefSMark Brown int twl4030_audio_disable_resource(enum twl4030_audio_res id)
12257fe7251SPeter Ujfalusi {
12357fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
12457fe7251SPeter Ujfalusi 	int val;
12557fe7251SPeter Ujfalusi 
12657fe7251SPeter Ujfalusi 	if (id >= TWL4030_AUDIO_RES_MAX) {
12757fe7251SPeter Ujfalusi 		dev_err(&twl4030_audio_dev->dev,
12857fe7251SPeter Ujfalusi 				"Invalid resource ID (%u)\n", id);
12957fe7251SPeter Ujfalusi 		return -EINVAL;
13057fe7251SPeter Ujfalusi 	}
13157fe7251SPeter Ujfalusi 
13257fe7251SPeter Ujfalusi 	mutex_lock(&audio->mutex);
13357fe7251SPeter Ujfalusi 	if (!audio->resource[id].request_count) {
13457fe7251SPeter Ujfalusi 		dev_err(&twl4030_audio_dev->dev,
13557fe7251SPeter Ujfalusi 			"Resource has been disabled already (%u)\n", id);
13657fe7251SPeter Ujfalusi 		mutex_unlock(&audio->mutex);
13757fe7251SPeter Ujfalusi 		return -EPERM;
13857fe7251SPeter Ujfalusi 	}
13957fe7251SPeter Ujfalusi 	audio->resource[id].request_count--;
14057fe7251SPeter Ujfalusi 
14157fe7251SPeter Ujfalusi 	if (!audio->resource[id].request_count)
14257fe7251SPeter Ujfalusi 		/* Resource can be disabled now */
14357fe7251SPeter Ujfalusi 		val = twl4030_audio_set_resource(id, 0);
14457fe7251SPeter Ujfalusi 	else
14557fe7251SPeter Ujfalusi 		val = twl4030_audio_get_resource(id);
14657fe7251SPeter Ujfalusi 
14757fe7251SPeter Ujfalusi 	mutex_unlock(&audio->mutex);
14857fe7251SPeter Ujfalusi 
14957fe7251SPeter Ujfalusi 	return val;
15057fe7251SPeter Ujfalusi }
15157fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);
15257fe7251SPeter Ujfalusi 
15357fe7251SPeter Ujfalusi unsigned int twl4030_audio_get_mclk(void)
15457fe7251SPeter Ujfalusi {
15557fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
15657fe7251SPeter Ujfalusi 
15757fe7251SPeter Ujfalusi 	return audio->audio_mclk;
15857fe7251SPeter Ujfalusi }
15957fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
16057fe7251SPeter Ujfalusi 
161019a7e6bSPeter Ujfalusi static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata,
162019a7e6bSPeter Ujfalusi 			      struct device_node *node)
163019a7e6bSPeter Ujfalusi {
164019a7e6bSPeter Ujfalusi 	if (pdata && pdata->codec)
165019a7e6bSPeter Ujfalusi 		return true;
166019a7e6bSPeter Ujfalusi 
167019a7e6bSPeter Ujfalusi 	if (of_find_node_by_name(node, "codec"))
168019a7e6bSPeter Ujfalusi 		return true;
169019a7e6bSPeter Ujfalusi 
170019a7e6bSPeter Ujfalusi 	return false;
171019a7e6bSPeter Ujfalusi }
172019a7e6bSPeter Ujfalusi 
173019a7e6bSPeter Ujfalusi static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata,
174019a7e6bSPeter Ujfalusi 			      struct device_node *node)
175019a7e6bSPeter Ujfalusi {
176019a7e6bSPeter Ujfalusi 	int vibra;
177019a7e6bSPeter Ujfalusi 
178019a7e6bSPeter Ujfalusi 	if (pdata && pdata->vibra)
179019a7e6bSPeter Ujfalusi 		return true;
180019a7e6bSPeter Ujfalusi 
181019a7e6bSPeter Ujfalusi 	if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra)
182019a7e6bSPeter Ujfalusi 		return true;
183019a7e6bSPeter Ujfalusi 
184019a7e6bSPeter Ujfalusi 	return false;
185019a7e6bSPeter Ujfalusi }
186019a7e6bSPeter Ujfalusi 
187f791be49SBill Pemberton static int twl4030_audio_probe(struct platform_device *pdev)
18857fe7251SPeter Ujfalusi {
18957fe7251SPeter Ujfalusi 	struct twl4030_audio *audio;
1904ae6df5eSPeter Ujfalusi 	struct twl4030_audio_data *pdata = pdev->dev.platform_data;
191019a7e6bSPeter Ujfalusi 	struct device_node *node = pdev->dev.of_node;
19257fe7251SPeter Ujfalusi 	struct mfd_cell *cell = NULL;
19357fe7251SPeter Ujfalusi 	int ret, childs = 0;
19457fe7251SPeter Ujfalusi 	u8 val;
19557fe7251SPeter Ujfalusi 
196019a7e6bSPeter Ujfalusi 	if (!pdata && !node) {
19757fe7251SPeter Ujfalusi 		dev_err(&pdev->dev, "Platform data is missing\n");
19857fe7251SPeter Ujfalusi 		return -EINVAL;
19957fe7251SPeter Ujfalusi 	}
20057fe7251SPeter Ujfalusi 
20139c1421dSPeter Ujfalusi 	audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio),
20239c1421dSPeter Ujfalusi 			     GFP_KERNEL);
20357fe7251SPeter Ujfalusi 	if (!audio)
20457fe7251SPeter Ujfalusi 		return -ENOMEM;
20557fe7251SPeter Ujfalusi 
20657fe7251SPeter Ujfalusi 	mutex_init(&audio->mutex);
207c531241dSPeter Ujfalusi 	audio->audio_mclk = twl_get_hfclk_rate();
20857fe7251SPeter Ujfalusi 
209cdf4b670SPeter Ujfalusi 	/* Configure APLL_INFREQ and disable APLL if enabled */
210cdf4b670SPeter Ujfalusi 	switch (audio->audio_mclk) {
211cdf4b670SPeter Ujfalusi 	case 19200000:
212cdf4b670SPeter Ujfalusi 		val = TWL4030_APLL_INFREQ_19200KHZ;
213cdf4b670SPeter Ujfalusi 		break;
214cdf4b670SPeter Ujfalusi 	case 26000000:
215cdf4b670SPeter Ujfalusi 		val = TWL4030_APLL_INFREQ_26000KHZ;
216cdf4b670SPeter Ujfalusi 		break;
217cdf4b670SPeter Ujfalusi 	case 38400000:
218cdf4b670SPeter Ujfalusi 		val = TWL4030_APLL_INFREQ_38400KHZ;
219cdf4b670SPeter Ujfalusi 		break;
220cdf4b670SPeter Ujfalusi 	default:
221cdf4b670SPeter Ujfalusi 		dev_err(&pdev->dev, "Invalid audio_mclk\n");
222cdf4b670SPeter Ujfalusi 		return -EINVAL;
223cdf4b670SPeter Ujfalusi 	}
224cdf4b670SPeter Ujfalusi 	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL);
225cdf4b670SPeter Ujfalusi 
22657fe7251SPeter Ujfalusi 	/* Codec power */
22757fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
22857fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
22957fe7251SPeter Ujfalusi 
23057fe7251SPeter Ujfalusi 	/* PLL */
23157fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
23257fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
23357fe7251SPeter Ujfalusi 
234019a7e6bSPeter Ujfalusi 	if (twl4030_audio_has_codec(pdata, node)) {
23557fe7251SPeter Ujfalusi 		cell = &audio->cells[childs];
23657fe7251SPeter Ujfalusi 		cell->name = "twl4030-codec";
237019a7e6bSPeter Ujfalusi 		if (pdata) {
2384ae6df5eSPeter Ujfalusi 			cell->platform_data = pdata->codec;
2394ae6df5eSPeter Ujfalusi 			cell->pdata_size = sizeof(*pdata->codec);
240019a7e6bSPeter Ujfalusi 		}
24157fe7251SPeter Ujfalusi 		childs++;
24257fe7251SPeter Ujfalusi 	}
243019a7e6bSPeter Ujfalusi 	if (twl4030_audio_has_vibra(pdata, node)) {
24457fe7251SPeter Ujfalusi 		cell = &audio->cells[childs];
24557fe7251SPeter Ujfalusi 		cell->name = "twl4030-vibra";
246019a7e6bSPeter Ujfalusi 		if (pdata) {
24757fe7251SPeter Ujfalusi 			cell->platform_data = pdata->vibra;
24857fe7251SPeter Ujfalusi 			cell->pdata_size = sizeof(*pdata->vibra);
249019a7e6bSPeter Ujfalusi 		}
25057fe7251SPeter Ujfalusi 		childs++;
25157fe7251SPeter Ujfalusi 	}
25257fe7251SPeter Ujfalusi 
253cdf4b670SPeter Ujfalusi 	platform_set_drvdata(pdev, audio);
254cdf4b670SPeter Ujfalusi 	twl4030_audio_dev = pdev;
255cdf4b670SPeter Ujfalusi 
25657fe7251SPeter Ujfalusi 	if (childs)
25757fe7251SPeter Ujfalusi 		ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
25855692af5SMark Brown 				      childs, NULL, 0, NULL);
25957fe7251SPeter Ujfalusi 	else {
26057fe7251SPeter Ujfalusi 		dev_err(&pdev->dev, "No platform data found for childs\n");
26157fe7251SPeter Ujfalusi 		ret = -ENODEV;
26257fe7251SPeter Ujfalusi 	}
26357fe7251SPeter Ujfalusi 
26496ac2d1aSJingoo Han 	if (ret)
26557fe7251SPeter Ujfalusi 		twl4030_audio_dev = NULL;
26639c1421dSPeter Ujfalusi 
26757fe7251SPeter Ujfalusi 	return ret;
26857fe7251SPeter Ujfalusi }
26957fe7251SPeter Ujfalusi 
2704740f73fSBill Pemberton static int twl4030_audio_remove(struct platform_device *pdev)
27157fe7251SPeter Ujfalusi {
27257fe7251SPeter Ujfalusi 	mfd_remove_devices(&pdev->dev);
27357fe7251SPeter Ujfalusi 	twl4030_audio_dev = NULL;
27457fe7251SPeter Ujfalusi 
27557fe7251SPeter Ujfalusi 	return 0;
27657fe7251SPeter Ujfalusi }
27757fe7251SPeter Ujfalusi 
278019a7e6bSPeter Ujfalusi static const struct of_device_id twl4030_audio_of_match[] = {
279019a7e6bSPeter Ujfalusi 	{.compatible = "ti,twl4030-audio", },
280019a7e6bSPeter Ujfalusi 	{ },
281019a7e6bSPeter Ujfalusi };
282019a7e6bSPeter Ujfalusi MODULE_DEVICE_TABLE(of, twl4030_audio_of_match);
283019a7e6bSPeter Ujfalusi 
28457fe7251SPeter Ujfalusi static struct platform_driver twl4030_audio_driver = {
28557fe7251SPeter Ujfalusi 	.driver		= {
28657fe7251SPeter Ujfalusi 		.owner	= THIS_MODULE,
28757fe7251SPeter Ujfalusi 		.name	= "twl4030-audio",
288019a7e6bSPeter Ujfalusi 		.of_match_table = twl4030_audio_of_match,
28957fe7251SPeter Ujfalusi 	},
29041569a16SPeter Ujfalusi 	.probe		= twl4030_audio_probe,
29184449216SBill Pemberton 	.remove		= twl4030_audio_remove,
29257fe7251SPeter Ujfalusi };
29357fe7251SPeter Ujfalusi 
29465349d60SMark Brown module_platform_driver(twl4030_audio_driver);
29557fe7251SPeter Ujfalusi 
29657fe7251SPeter Ujfalusi MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
29741569a16SPeter Ujfalusi MODULE_DESCRIPTION("TWL4030 audio block MFD driver");
29857fe7251SPeter Ujfalusi MODULE_LICENSE("GPL");
29941569a16SPeter Ujfalusi MODULE_ALIAS("platform:twl4030-audio");
300