blob: 838ce4eb444e24ce44bd3b120fe21a9f977d6e75 [file] [log] [blame]
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +03001/*
Peter Ujfalusi4fe56682011-05-03 18:03:43 +03002 * MFD driver for twl4030 audio submodule, which contains an audio codec, and
3 * the vibra control.
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +03004 *
Peter Ujfalusi4a7c00c2011-05-10 08:59:23 +03005 * Author: Peter Ujfalusi <peter.ujfalusi@ti.com>
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +03006 *
7 * Copyright: (C) 2009 Nokia Corporation
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 *
23 */
24
25#include <linux/module.h>
26#include <linux/types.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090027#include <linux/slab.h>
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030028#include <linux/kernel.h>
29#include <linux/fs.h>
30#include <linux/platform_device.h>
Stephen Rothwell8bea8672009-12-15 16:33:10 +110031#include <linux/i2c/twl.h>
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030032#include <linux/mfd/core.h>
Peter Ujfalusi57fe7252011-05-31 12:02:49 +030033#include <linux/mfd/twl4030-audio.h>
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030034
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030035#define TWL4030_AUDIO_CELLS 2
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030036
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030037static struct platform_device *twl4030_audio_dev;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030038
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030039struct twl4030_audio_resource {
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030040 int request_count;
41 u8 reg;
42 u8 mask;
43};
44
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030045struct twl4030_audio {
Peter Ujfalusif9b4639e2009-11-04 09:58:19 +020046 unsigned int audio_mclk;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030047 struct mutex mutex;
Peter Ujfalusi57fe7252011-05-31 12:02:49 +030048 struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX];
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030049 struct mfd_cell cells[TWL4030_AUDIO_CELLS];
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030050};
51
52/*
53 * Modify the resource, the function returns the content of the register
54 * after the modification.
55 */
Peter Ujfalusi57fe7252011-05-31 12:02:49 +030056static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030057{
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030058 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030059 u8 val;
60
Stephen Rothwell8bea8672009-12-15 16:33:10 +110061 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030062 audio->resource[id].reg);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030063
64 if (enable)
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030065 val |= audio->resource[id].mask;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030066 else
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030067 val &= ~audio->resource[id].mask;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030068
Stephen Rothwell8bea8672009-12-15 16:33:10 +110069 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030070 val, audio->resource[id].reg);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030071
72 return val;
73}
74
Peter Ujfalusi57fe7252011-05-31 12:02:49 +030075static inline int twl4030_audio_get_resource(enum twl4030_audio_res id)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030076{
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030077 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030078 u8 val;
79
Stephen Rothwell8bea8672009-12-15 16:33:10 +110080 twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030081 audio->resource[id].reg);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030082
83 return val;
84}
85
86/*
87 * Enable the resource.
88 * The function returns with error or the content of the register
89 */
Peter Ujfalusi57fe7252011-05-31 12:02:49 +030090int twl4030_audio_enable_resource(enum twl4030_audio_res id)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030091{
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030092 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030093 int val;
94
Peter Ujfalusi57fe7252011-05-31 12:02:49 +030095 if (id >= TWL4030_AUDIO_RES_MAX) {
Peter Ujfalusi4fe56682011-05-03 18:03:43 +030096 dev_err(&twl4030_audio_dev->dev,
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +030097 "Invalid resource ID (%u)\n", id);
98 return -EINVAL;
99 }
100
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300101 mutex_lock(&audio->mutex);
102 if (!audio->resource[id].request_count)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300103 /* Resource was disabled, enable it */
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300104 val = twl4030_audio_set_resource(id, 1);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300105 else
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300106 val = twl4030_audio_get_resource(id);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300107
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300108 audio->resource[id].request_count++;
109 mutex_unlock(&audio->mutex);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300110
111 return val;
112}
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300113EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300114
115/*
116 * Disable the resource.
117 * The function returns with error or the content of the register
118 */
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300119int twl4030_audio_disable_resource(unsigned id)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300120{
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300121 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300122 int val;
123
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300124 if (id >= TWL4030_AUDIO_RES_MAX) {
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300125 dev_err(&twl4030_audio_dev->dev,
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300126 "Invalid resource ID (%u)\n", id);
127 return -EINVAL;
128 }
129
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300130 mutex_lock(&audio->mutex);
131 if (!audio->resource[id].request_count) {
132 dev_err(&twl4030_audio_dev->dev,
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300133 "Resource has been disabled already (%u)\n", id);
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300134 mutex_unlock(&audio->mutex);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300135 return -EPERM;
136 }
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300137 audio->resource[id].request_count--;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300138
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300139 if (!audio->resource[id].request_count)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300140 /* Resource can be disabled now */
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300141 val = twl4030_audio_set_resource(id, 0);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300142 else
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300143 val = twl4030_audio_get_resource(id);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300144
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300145 mutex_unlock(&audio->mutex);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300146
147 return val;
148}
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300149EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300150
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300151unsigned int twl4030_audio_get_mclk(void)
Peter Ujfalusif9b4639e2009-11-04 09:58:19 +0200152{
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300153 struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev);
Peter Ujfalusif9b4639e2009-11-04 09:58:19 +0200154
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300155 return audio->audio_mclk;
Peter Ujfalusif9b4639e2009-11-04 09:58:19 +0200156}
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300157EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
Peter Ujfalusif9b4639e2009-11-04 09:58:19 +0200158
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300159static int __devinit twl4030_audio_probe(struct platform_device *pdev)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300160{
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300161 struct twl4030_audio *audio;
Peter Ujfalusi4ae6df5e2011-05-31 15:21:13 +0300162 struct twl4030_audio_data *pdata = pdev->dev.platform_data;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300163 struct mfd_cell *cell = NULL;
164 int ret, childs = 0;
Peter Ujfalusif9b4639e2009-11-04 09:58:19 +0200165 u8 val;
166
167 if (!pdata) {
168 dev_err(&pdev->dev, "Platform data is missing\n");
169 return -EINVAL;
170 }
171
172 /* Configure APLL_INFREQ and disable APLL if enabled */
173 val = 0;
174 switch (pdata->audio_mclk) {
175 case 19200000:
176 val |= TWL4030_APLL_INFREQ_19200KHZ;
177 break;
178 case 26000000:
179 val |= TWL4030_APLL_INFREQ_26000KHZ;
180 break;
181 case 38400000:
182 val |= TWL4030_APLL_INFREQ_38400KHZ;
183 break;
184 default:
185 dev_err(&pdev->dev, "Invalid audio_mclk\n");
186 return -EINVAL;
187 }
Stephen Rothwell8bea8672009-12-15 16:33:10 +1100188 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
Peter Ujfalusif9b4639e2009-11-04 09:58:19 +0200189 val, TWL4030_REG_APLL_CTL);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300190
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300191 audio = kzalloc(sizeof(struct twl4030_audio), GFP_KERNEL);
192 if (!audio)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300193 return -ENOMEM;
194
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300195 platform_set_drvdata(pdev, audio);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300196
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300197 twl4030_audio_dev = pdev;
198 mutex_init(&audio->mutex);
199 audio->audio_mclk = pdata->audio_mclk;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300200
201 /* Codec power */
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300202 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
203 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300204
205 /* PLL */
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300206 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
207 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300208
Peter Ujfalusi4ae6df5e2011-05-31 15:21:13 +0300209 if (pdata->codec) {
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300210 cell = &audio->cells[childs];
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000211 cell->name = "twl4030-codec";
Peter Ujfalusi4ae6df5e2011-05-31 15:21:13 +0300212 cell->platform_data = pdata->codec;
213 cell->pdata_size = sizeof(*pdata->codec);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300214 childs++;
215 }
216 if (pdata->vibra) {
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300217 cell = &audio->cells[childs];
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000218 cell->name = "twl4030-vibra";
Samuel Ortiza4579ad2011-04-06 15:57:17 +0200219 cell->platform_data = pdata->vibra;
220 cell->pdata_size = sizeof(*pdata->vibra);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300221 childs++;
222 }
223
224 if (childs)
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300225 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300226 childs, NULL, 0);
227 else {
228 dev_err(&pdev->dev, "No platform data found for childs\n");
229 ret = -ENODEV;
230 }
231
232 if (!ret)
233 return 0;
234
235 platform_set_drvdata(pdev, NULL);
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300236 kfree(audio);
237 twl4030_audio_dev = NULL;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300238 return ret;
239}
240
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300241static int __devexit twl4030_audio_remove(struct platform_device *pdev)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300242{
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300243 struct twl4030_audio *audio = platform_get_drvdata(pdev);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300244
245 mfd_remove_devices(&pdev->dev);
246 platform_set_drvdata(pdev, NULL);
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300247 kfree(audio);
248 twl4030_audio_dev = NULL;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300249
250 return 0;
251}
252
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000253MODULE_ALIAS("platform:twl4030-audio");
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300254
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300255static struct platform_driver twl4030_audio_driver = {
256 .probe = twl4030_audio_probe,
257 .remove = __devexit_p(twl4030_audio_remove),
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300258 .driver = {
259 .owner = THIS_MODULE,
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000260 .name = "twl4030-audio",
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300261 },
262};
263
Mark Brown65349d62011-11-23 22:58:34 +0000264module_platform_driver(twl4030_audio_driver);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300265
Peter Ujfalusi4a7c00c2011-05-10 08:59:23 +0300266MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300267MODULE_LICENSE("GPL");