blob: 42e69ae63c44e9efaee30185ad01548b47331992 [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 Ujfalusif9b46392009-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 Ujfalusif9b46392009-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 Ujfalusif9b46392009-11-04 09:58:19 +0200154
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300155 return audio->audio_mclk;
Peter Ujfalusif9b46392009-11-04 09:58:19 +0200156}
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300157EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk);
Peter Ujfalusif9b46392009-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 Ujfalusi4ae6df52011-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 Ujfalusif9b46392009-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
Peter Ujfalusi39c14212012-09-10 13:46:20 +0300172 audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio),
173 GFP_KERNEL);
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300174 if (!audio)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300175 return -ENOMEM;
176
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300177 mutex_init(&audio->mutex);
178 audio->audio_mclk = pdata->audio_mclk;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300179
Peter Ujfalusicdf4b672012-09-10 13:46:21 +0300180 /* Configure APLL_INFREQ and disable APLL if enabled */
181 switch (audio->audio_mclk) {
182 case 19200000:
183 val = TWL4030_APLL_INFREQ_19200KHZ;
184 break;
185 case 26000000:
186 val = TWL4030_APLL_INFREQ_26000KHZ;
187 break;
188 case 38400000:
189 val = TWL4030_APLL_INFREQ_38400KHZ;
190 break;
191 default:
192 dev_err(&pdev->dev, "Invalid audio_mclk\n");
193 return -EINVAL;
194 }
195 twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL);
196
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300197 /* Codec power */
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300198 audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
199 audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300200
201 /* PLL */
Peter Ujfalusi57fe7252011-05-31 12:02:49 +0300202 audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL;
203 audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300204
Peter Ujfalusi4ae6df52011-05-31 15:21:13 +0300205 if (pdata->codec) {
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300206 cell = &audio->cells[childs];
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000207 cell->name = "twl4030-codec";
Peter Ujfalusi4ae6df52011-05-31 15:21:13 +0300208 cell->platform_data = pdata->codec;
209 cell->pdata_size = sizeof(*pdata->codec);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300210 childs++;
211 }
212 if (pdata->vibra) {
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300213 cell = &audio->cells[childs];
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000214 cell->name = "twl4030-vibra";
Samuel Ortiza4579ad2011-04-06 15:57:17 +0200215 cell->platform_data = pdata->vibra;
216 cell->pdata_size = sizeof(*pdata->vibra);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300217 childs++;
218 }
219
Peter Ujfalusicdf4b672012-09-10 13:46:21 +0300220 platform_set_drvdata(pdev, audio);
221 twl4030_audio_dev = pdev;
222
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300223 if (childs)
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300224 ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells,
Mark Brown55692af2012-09-11 15:16:36 +0800225 childs, NULL, 0, NULL);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300226 else {
227 dev_err(&pdev->dev, "No platform data found for childs\n");
228 ret = -ENODEV;
229 }
230
Peter Ujfalusi39c14212012-09-10 13:46:20 +0300231 if (ret) {
232 platform_set_drvdata(pdev, NULL);
233 twl4030_audio_dev = NULL;
234 }
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300235
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300236 return ret;
237}
238
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300239static int __devexit twl4030_audio_remove(struct platform_device *pdev)
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300240{
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300241 mfd_remove_devices(&pdev->dev);
242 platform_set_drvdata(pdev, NULL);
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300243 twl4030_audio_dev = NULL;
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300244
245 return 0;
246}
247
Peter Ujfalusi4fe56682011-05-03 18:03:43 +0300248static struct platform_driver twl4030_audio_driver = {
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300249 .driver = {
250 .owner = THIS_MODULE,
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000251 .name = "twl4030-audio",
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300252 },
Peter Ujfalusi41569a12012-09-10 13:46:19 +0300253 .probe = twl4030_audio_probe,
254 .remove = __devexit_p(twl4030_audio_remove),
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300255};
256
Mark Brown65349d62011-11-23 22:58:34 +0000257module_platform_driver(twl4030_audio_driver);
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300258
Peter Ujfalusi4a7c00c2011-05-10 08:59:23 +0300259MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>");
Peter Ujfalusi41569a12012-09-10 13:46:19 +0300260MODULE_DESCRIPTION("TWL4030 audio block MFD driver");
Peter Ujfalusi0b83dde2009-10-22 13:26:45 +0300261MODULE_LICENSE("GPL");
Peter Ujfalusi41569a12012-09-10 13:46:19 +0300262MODULE_ALIAS("platform:twl4030-audio");