xref: /openbmc/linux/drivers/mfd/twl4030-audio.c (revision 4ae6df5e)
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>
3157fe7251SPeter Ujfalusi #include <linux/i2c/twl.h>
3257fe7251SPeter Ujfalusi #include <linux/mfd/core.h>
3357fe7251SPeter Ujfalusi #include <linux/mfd/twl4030-audio.h>
3457fe7251SPeter Ujfalusi 
3557fe7251SPeter Ujfalusi #define TWL4030_AUDIO_CELLS	2
3657fe7251SPeter Ujfalusi 
3757fe7251SPeter Ujfalusi static struct platform_device *twl4030_audio_dev;
3857fe7251SPeter Ujfalusi 
3957fe7251SPeter Ujfalusi struct twl4030_audio_resource {
4057fe7251SPeter Ujfalusi 	int request_count;
4157fe7251SPeter Ujfalusi 	u8 reg;
4257fe7251SPeter Ujfalusi 	u8 mask;
4357fe7251SPeter Ujfalusi };
4457fe7251SPeter Ujfalusi 
4557fe7251SPeter Ujfalusi struct twl4030_audio {
4657fe7251SPeter Ujfalusi 	unsigned int audio_mclk;
4757fe7251SPeter Ujfalusi 	struct mutex mutex;
4857fe7251SPeter Ujfalusi 	struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];
4957fe7251SPeter Ujfalusi 	struct mfd_cell cells[TWL4030_AUDIO_CELLS];
5057fe7251SPeter Ujfalusi };
5157fe7251SPeter Ujfalusi 
5257fe7251SPeter Ujfalusi /*
5357fe7251SPeter Ujfalusi  * Modify the resource, the function returns the content of the register
5457fe7251SPeter Ujfalusi  * after the modification.
5557fe7251SPeter Ujfalusi  */
5657fe7251SPeter Ujfalusi static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)
5757fe7251SPeter Ujfalusi {
5857fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
5957fe7251SPeter Ujfalusi 	u8 val;
6057fe7251SPeter Ujfalusi 
6157fe7251SPeter Ujfalusi 	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
6257fe7251SPeter Ujfalusi 			audio->resource[id].reg);
6357fe7251SPeter Ujfalusi 
6457fe7251SPeter Ujfalusi 	if (enable)
6557fe7251SPeter Ujfalusi 		val |= audio->resource[id].mask;
6657fe7251SPeter Ujfalusi 	else
6757fe7251SPeter Ujfalusi 		val &= ~audio->resource[id].mask;
6857fe7251SPeter Ujfalusi 
6957fe7251SPeter Ujfalusi 	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
7057fe7251SPeter Ujfalusi 					val, audio->resource[id].reg);
7157fe7251SPeter Ujfalusi 
7257fe7251SPeter Ujfalusi 	return val;
7357fe7251SPeter Ujfalusi }
7457fe7251SPeter Ujfalusi 
7557fe7251SPeter Ujfalusi static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)
7657fe7251SPeter Ujfalusi {
7757fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
7857fe7251SPeter Ujfalusi 	u8 val;
7957fe7251SPeter Ujfalusi 
8057fe7251SPeter Ujfalusi 	twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
8157fe7251SPeter Ujfalusi 			audio->resource[id].reg);
8257fe7251SPeter Ujfalusi 
8357fe7251SPeter Ujfalusi 	return val;
8457fe7251SPeter Ujfalusi }
8557fe7251SPeter Ujfalusi 
8657fe7251SPeter Ujfalusi /*
8757fe7251SPeter Ujfalusi  * Enable the resource.
8857fe7251SPeter Ujfalusi  * The function returns with error or the content of the register
8957fe7251SPeter Ujfalusi  */
9057fe7251SPeter Ujfalusi int twl4030_audio_enable_resource(enum twl4030_audio_res id)
9157fe7251SPeter Ujfalusi {
9257fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
9357fe7251SPeter Ujfalusi 	int val;
9457fe7251SPeter Ujfalusi 
9557fe7251SPeter Ujfalusi 	if (id >= TWL4030_AUDIO_RES_MAX) {
9657fe7251SPeter Ujfalusi 		dev_err(&twl4030_audio_dev->dev,
9757fe7251SPeter Ujfalusi 				"Invalid resource ID (%u)\n", id);
9857fe7251SPeter Ujfalusi 		return -EINVAL;
9957fe7251SPeter Ujfalusi 	}
10057fe7251SPeter Ujfalusi 
10157fe7251SPeter Ujfalusi 	mutex_lock(&audio->mutex);
10257fe7251SPeter Ujfalusi 	if (!audio->resource[id].request_count)
10357fe7251SPeter Ujfalusi 		/* Resource was disabled, enable it */
10457fe7251SPeter Ujfalusi 		val = twl4030_audio_set_resource(id, 1);
10557fe7251SPeter Ujfalusi 	else
10657fe7251SPeter Ujfalusi 		val = twl4030_audio_get_resource(id);
10757fe7251SPeter Ujfalusi 
10857fe7251SPeter Ujfalusi 	audio->resource[id].request_count++;
10957fe7251SPeter Ujfalusi 	mutex_unlock(&audio->mutex);
11057fe7251SPeter Ujfalusi 
11157fe7251SPeter Ujfalusi 	return val;
11257fe7251SPeter Ujfalusi }
11357fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);
11457fe7251SPeter Ujfalusi 
11557fe7251SPeter Ujfalusi /*
11657fe7251SPeter Ujfalusi  * Disable the resource.
11757fe7251SPeter Ujfalusi  * The function returns with error or the content of the register
11857fe7251SPeter Ujfalusi  */
11957fe7251SPeter Ujfalusi int twl4030_audio_disable_resource(unsigned id)
12057fe7251SPeter Ujfalusi {
12157fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
12257fe7251SPeter Ujfalusi 	int val;
12357fe7251SPeter Ujfalusi 
12457fe7251SPeter Ujfalusi 	if (id >= TWL4030_AUDIO_RES_MAX) {
12557fe7251SPeter Ujfalusi 		dev_err(&twl4030_audio_dev->dev,
12657fe7251SPeter Ujfalusi 				"Invalid resource ID (%u)\n", id);
12757fe7251SPeter Ujfalusi 		return -EINVAL;
12857fe7251SPeter Ujfalusi 	}
12957fe7251SPeter Ujfalusi 
13057fe7251SPeter Ujfalusi 	mutex_lock(&audio->mutex);
13157fe7251SPeter Ujfalusi 	if (!audio->resource[id].request_count) {
13257fe7251SPeter Ujfalusi 		dev_err(&twl4030_audio_dev->dev,
13357fe7251SPeter Ujfalusi 			"Resource has been disabled already (%u)\n", id);
13457fe7251SPeter Ujfalusi 		mutex_unlock(&audio->mutex);
13557fe7251SPeter Ujfalusi 		return -EPERM;
13657fe7251SPeter Ujfalusi 	}
13757fe7251SPeter Ujfalusi 	audio->resource[id].request_count--;
13857fe7251SPeter Ujfalusi 
13957fe7251SPeter Ujfalusi 	if (!audio->resource[id].request_count)
14057fe7251SPeter Ujfalusi 		/* Resource can be disabled now */
14157fe7251SPeter Ujfalusi 		val = twl4030_audio_set_resource(id, 0);
14257fe7251SPeter Ujfalusi 	else
14357fe7251SPeter Ujfalusi 		val = twl4030_audio_get_resource(id);
14457fe7251SPeter Ujfalusi 
14557fe7251SPeter Ujfalusi 	mutex_unlock(&audio->mutex);
14657fe7251SPeter Ujfalusi 
14757fe7251SPeter Ujfalusi 	return val;
14857fe7251SPeter Ujfalusi }
14957fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);
15057fe7251SPeter Ujfalusi 
15157fe7251SPeter Ujfalusi unsigned int twl4030_audio_get_mclk(void)
15257fe7251SPeter Ujfalusi {
15357fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
15457fe7251SPeter Ujfalusi 
15557fe7251SPeter Ujfalusi 	return audio->audio_mclk;
15657fe7251SPeter Ujfalusi }
15757fe7251SPeter Ujfalusi EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
15857fe7251SPeter Ujfalusi 
15957fe7251SPeter Ujfalusi static int __devinit twl4030_audio_probe(struct platform_device *pdev)
16057fe7251SPeter Ujfalusi {
16157fe7251SPeter Ujfalusi 	struct twl4030_audio *audio;
1624ae6df5eSPeter Ujfalusi 	struct twl4030_audio_data *pdata = pdev->dev.platform_data;
16357fe7251SPeter Ujfalusi 	struct mfd_cell *cell = NULL;
16457fe7251SPeter Ujfalusi 	int ret, childs = 0;
16557fe7251SPeter Ujfalusi 	u8 val;
16657fe7251SPeter Ujfalusi 
16757fe7251SPeter Ujfalusi 	if (!pdata) {
16857fe7251SPeter Ujfalusi 		dev_err(&pdev->dev, "Platform data is missing\n");
16957fe7251SPeter Ujfalusi 		return -EINVAL;
17057fe7251SPeter Ujfalusi 	}
17157fe7251SPeter Ujfalusi 
17257fe7251SPeter Ujfalusi 	/* Configure APLL_INFREQ and disable APLL if enabled */
17357fe7251SPeter Ujfalusi 	val = 0;
17457fe7251SPeter Ujfalusi 	switch (pdata->audio_mclk) {
17557fe7251SPeter Ujfalusi 	case 19200000:
17657fe7251SPeter Ujfalusi 		val |= TWL4030_APLL_INFREQ_19200KHZ;
17757fe7251SPeter Ujfalusi 		break;
17857fe7251SPeter Ujfalusi 	case 26000000:
17957fe7251SPeter Ujfalusi 		val |= TWL4030_APLL_INFREQ_26000KHZ;
18057fe7251SPeter Ujfalusi 		break;
18157fe7251SPeter Ujfalusi 	case 38400000:
18257fe7251SPeter Ujfalusi 		val |= TWL4030_APLL_INFREQ_38400KHZ;
18357fe7251SPeter Ujfalusi 		break;
18457fe7251SPeter Ujfalusi 	default:
18557fe7251SPeter Ujfalusi 		dev_err(&pdev->dev, "Invalid audio_mclk\n");
18657fe7251SPeter Ujfalusi 		return -EINVAL;
18757fe7251SPeter Ujfalusi 	}
18857fe7251SPeter Ujfalusi 	twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
18957fe7251SPeter Ujfalusi 					val, TWL4030_REG_APLL_CTL);
19057fe7251SPeter Ujfalusi 
19157fe7251SPeter Ujfalusi 	audio = kzalloc(sizeof(struct twl4030_audio), GFP_KERNEL);
19257fe7251SPeter Ujfalusi 	if (!audio)
19357fe7251SPeter Ujfalusi 		return -ENOMEM;
19457fe7251SPeter Ujfalusi 
19557fe7251SPeter Ujfalusi 	platform_set_drvdata(pdev, audio);
19657fe7251SPeter Ujfalusi 
19757fe7251SPeter Ujfalusi 	twl4030_audio_dev = pdev;
19857fe7251SPeter Ujfalusi 	mutex_init(&audio->mutex);
19957fe7251SPeter Ujfalusi 	audio->audio_mclk = pdata->audio_mclk;
20057fe7251SPeter Ujfalusi 
20157fe7251SPeter Ujfalusi 	/* Codec power */
20257fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
20357fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
20457fe7251SPeter Ujfalusi 
20557fe7251SPeter Ujfalusi 	/* PLL */
20657fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
20757fe7251SPeter Ujfalusi 	audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
20857fe7251SPeter Ujfalusi 
2094ae6df5eSPeter Ujfalusi 	if (pdata->codec) {
21057fe7251SPeter Ujfalusi 		cell = &audio->cells[childs];
21157fe7251SPeter Ujfalusi 		cell->name = "twl4030-codec";
2124ae6df5eSPeter Ujfalusi 		cell->platform_data = pdata->codec;
2134ae6df5eSPeter Ujfalusi 		cell->pdata_size = sizeof(*pdata->codec);
21457fe7251SPeter Ujfalusi 		childs++;
21557fe7251SPeter Ujfalusi 	}
21657fe7251SPeter Ujfalusi 	if (pdata->vibra) {
21757fe7251SPeter Ujfalusi 		cell = &audio->cells[childs];
21857fe7251SPeter Ujfalusi 		cell->name = "twl4030-vibra";
21957fe7251SPeter Ujfalusi 		cell->platform_data = pdata->vibra;
22057fe7251SPeter Ujfalusi 		cell->pdata_size = sizeof(*pdata->vibra);
22157fe7251SPeter Ujfalusi 		childs++;
22257fe7251SPeter Ujfalusi 	}
22357fe7251SPeter Ujfalusi 
22457fe7251SPeter Ujfalusi 	if (childs)
22557fe7251SPeter Ujfalusi 		ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
22657fe7251SPeter Ujfalusi 				      childs, NULL, 0);
22757fe7251SPeter Ujfalusi 	else {
22857fe7251SPeter Ujfalusi 		dev_err(&pdev->dev, "No platform data found for childs\n");
22957fe7251SPeter Ujfalusi 		ret = -ENODEV;
23057fe7251SPeter Ujfalusi 	}
23157fe7251SPeter Ujfalusi 
23257fe7251SPeter Ujfalusi 	if (!ret)
23357fe7251SPeter Ujfalusi 		return 0;
23457fe7251SPeter Ujfalusi 
23557fe7251SPeter Ujfalusi 	platform_set_drvdata(pdev, NULL);
23657fe7251SPeter Ujfalusi 	kfree(audio);
23757fe7251SPeter Ujfalusi 	twl4030_audio_dev = NULL;
23857fe7251SPeter Ujfalusi 	return ret;
23957fe7251SPeter Ujfalusi }
24057fe7251SPeter Ujfalusi 
24157fe7251SPeter Ujfalusi static int __devexit twl4030_audio_remove(struct platform_device *pdev)
24257fe7251SPeter Ujfalusi {
24357fe7251SPeter Ujfalusi 	struct twl4030_audio *audio = platform_get_drvdata(pdev);
24457fe7251SPeter Ujfalusi 
24557fe7251SPeter Ujfalusi 	mfd_remove_devices(&pdev->dev);
24657fe7251SPeter Ujfalusi 	platform_set_drvdata(pdev, NULL);
24757fe7251SPeter Ujfalusi 	kfree(audio);
24857fe7251SPeter Ujfalusi 	twl4030_audio_dev = NULL;
24957fe7251SPeter Ujfalusi 
25057fe7251SPeter Ujfalusi 	return 0;
25157fe7251SPeter Ujfalusi }
25257fe7251SPeter Ujfalusi 
25357fe7251SPeter Ujfalusi MODULE_ALIAS("platform:twl4030-audio");
25457fe7251SPeter Ujfalusi 
25557fe7251SPeter Ujfalusi static struct platform_driver twl4030_audio_driver = {
25657fe7251SPeter Ujfalusi 	.probe		= twl4030_audio_probe,
25757fe7251SPeter Ujfalusi 	.remove		= __devexit_p(twl4030_audio_remove),
25857fe7251SPeter Ujfalusi 	.driver		= {
25957fe7251SPeter Ujfalusi 		.owner	= THIS_MODULE,
26057fe7251SPeter Ujfalusi 		.name	= "twl4030-audio",
26157fe7251SPeter Ujfalusi 	},
26257fe7251SPeter Ujfalusi };
26357fe7251SPeter Ujfalusi 
26457fe7251SPeter Ujfalusi static int __devinit twl4030_audio_init(void)
26557fe7251SPeter Ujfalusi {
26657fe7251SPeter Ujfalusi 	return platform_driver_register(&twl4030_audio_driver);
26757fe7251SPeter Ujfalusi }
26857fe7251SPeter Ujfalusi module_init(twl4030_audio_init);
26957fe7251SPeter Ujfalusi 
27057fe7251SPeter Ujfalusi static void __devexit twl4030_audio_exit(void)
27157fe7251SPeter Ujfalusi {
27257fe7251SPeter Ujfalusi 	platform_driver_unregister(&twl4030_audio_driver);
27357fe7251SPeter Ujfalusi }
27457fe7251SPeter Ujfalusi module_exit(twl4030_audio_exit);
27557fe7251SPeter Ujfalusi 
27657fe7251SPeter Ujfalusi MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
27757fe7251SPeter Ujfalusi MODULE_LICENSE("GPL");
278