Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6
[pandora-kernel.git] / drivers / mfd / twl4030-codec.c
1 /*
2  * MFD driver for twl4030 codec submodule
3  *
4  * Author:      Peter Ujfalusi <peter.ujfalusi@nokia.com>
5  *
6  * Copyright:   (C) 2009 Nokia Corporation
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 as
10  * published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  *
22  */
23
24 #include <linux/module.h>
25 #include <linux/types.h>
26 #include <linux/kernel.h>
27 #include <linux/fs.h>
28 #include <linux/platform_device.h>
29 #include <linux/i2c/twl.h>
30 #include <linux/mfd/core.h>
31 #include <linux/mfd/twl4030-codec.h>
32
33 #define TWL4030_CODEC_CELLS     2
34
35 static struct platform_device *twl4030_codec_dev;
36
37 struct twl4030_codec_resource {
38         int request_count;
39         u8 reg;
40         u8 mask;
41 };
42
43 struct twl4030_codec {
44         unsigned int audio_mclk;
45         struct mutex mutex;
46         struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX];
47         struct mfd_cell cells[TWL4030_CODEC_CELLS];
48 };
49
50 /*
51  * Modify the resource, the function returns the content of the register
52  * after the modification.
53  */
54 static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable)
55 {
56         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
57         u8 val;
58
59         twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
60                         codec->resource[id].reg);
61
62         if (enable)
63                 val |= codec->resource[id].mask;
64         else
65                 val &= ~codec->resource[id].mask;
66
67         twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
68                                         val, codec->resource[id].reg);
69
70         return val;
71 }
72
73 static inline int twl4030_codec_get_resource(enum twl4030_codec_res id)
74 {
75         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
76         u8 val;
77
78         twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
79                         codec->resource[id].reg);
80
81         return val;
82 }
83
84 /*
85  * Enable the resource.
86  * The function returns with error or the content of the register
87  */
88 int twl4030_codec_enable_resource(enum twl4030_codec_res id)
89 {
90         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
91         int val;
92
93         if (id >= TWL4030_CODEC_RES_MAX) {
94                 dev_err(&twl4030_codec_dev->dev,
95                                 "Invalid resource ID (%u)\n", id);
96                 return -EINVAL;
97         }
98
99         mutex_lock(&codec->mutex);
100         if (!codec->resource[id].request_count)
101                 /* Resource was disabled, enable it */
102                 val = twl4030_codec_set_resource(id, 1);
103         else
104                 val = twl4030_codec_get_resource(id);
105
106         codec->resource[id].request_count++;
107         mutex_unlock(&codec->mutex);
108
109         return val;
110 }
111 EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource);
112
113 /*
114  * Disable the resource.
115  * The function returns with error or the content of the register
116  */
117 int twl4030_codec_disable_resource(unsigned id)
118 {
119         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
120         int val;
121
122         if (id >= TWL4030_CODEC_RES_MAX) {
123                 dev_err(&twl4030_codec_dev->dev,
124                                 "Invalid resource ID (%u)\n", id);
125                 return -EINVAL;
126         }
127
128         mutex_lock(&codec->mutex);
129         if (!codec->resource[id].request_count) {
130                 dev_err(&twl4030_codec_dev->dev,
131                         "Resource has been disabled already (%u)\n", id);
132                 mutex_unlock(&codec->mutex);
133                 return -EPERM;
134         }
135         codec->resource[id].request_count--;
136
137         if (!codec->resource[id].request_count)
138                 /* Resource can be disabled now */
139                 val = twl4030_codec_set_resource(id, 0);
140         else
141                 val = twl4030_codec_get_resource(id);
142
143         mutex_unlock(&codec->mutex);
144
145         return val;
146 }
147 EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource);
148
149 unsigned int twl4030_codec_get_mclk(void)
150 {
151         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
152
153         return codec->audio_mclk;
154 }
155 EXPORT_SYMBOL_GPL(twl4030_codec_get_mclk);
156
157 static int __devinit twl4030_codec_probe(struct platform_device *pdev)
158 {
159         struct twl4030_codec *codec;
160         struct twl4030_codec_data *pdata = pdev->dev.platform_data;
161         struct mfd_cell *cell = NULL;
162         int ret, childs = 0;
163         u8 val;
164
165         if (!pdata) {
166                 dev_err(&pdev->dev, "Platform data is missing\n");
167                 return -EINVAL;
168         }
169
170         /* Configure APLL_INFREQ and disable APLL if enabled */
171         val = 0;
172         switch (pdata->audio_mclk) {
173         case 19200000:
174                 val |= TWL4030_APLL_INFREQ_19200KHZ;
175                 break;
176         case 26000000:
177                 val |= TWL4030_APLL_INFREQ_26000KHZ;
178                 break;
179         case 38400000:
180                 val |= TWL4030_APLL_INFREQ_38400KHZ;
181                 break;
182         default:
183                 dev_err(&pdev->dev, "Invalid audio_mclk\n");
184                 return -EINVAL;
185         }
186         twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
187                                         val, TWL4030_REG_APLL_CTL);
188
189         codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL);
190         if (!codec)
191                 return -ENOMEM;
192
193         platform_set_drvdata(pdev, codec);
194
195         twl4030_codec_dev = pdev;
196         mutex_init(&codec->mutex);
197         codec->audio_mclk = pdata->audio_mclk;
198
199         /* Codec power */
200         codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
201         codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ;
202
203         /* PLL */
204         codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL;
205         codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN;
206
207         if (pdata->audio) {
208                 cell = &codec->cells[childs];
209                 cell->name = "twl4030_codec_audio";
210                 cell->platform_data = pdata->audio;
211                 cell->data_size = sizeof(*pdata->audio);
212                 childs++;
213         }
214         if (pdata->vibra) {
215                 cell = &codec->cells[childs];
216                 cell->name = "twl4030_codec_vibra";
217                 cell->platform_data = pdata->vibra;
218                 cell->data_size = sizeof(*pdata->vibra);
219                 childs++;
220         }
221
222         if (childs)
223                 ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells,
224                                       childs, NULL, 0);
225         else {
226                 dev_err(&pdev->dev, "No platform data found for childs\n");
227                 ret = -ENODEV;
228         }
229
230         if (!ret)
231                 return 0;
232
233         platform_set_drvdata(pdev, NULL);
234         kfree(codec);
235         twl4030_codec_dev = NULL;
236         return ret;
237 }
238
239 static int __devexit twl4030_codec_remove(struct platform_device *pdev)
240 {
241         struct twl4030_codec *codec = platform_get_drvdata(pdev);
242
243         mfd_remove_devices(&pdev->dev);
244         platform_set_drvdata(pdev, NULL);
245         kfree(codec);
246         twl4030_codec_dev = NULL;
247
248         return 0;
249 }
250
251 MODULE_ALIAS("platform:twl4030_codec");
252
253 static struct platform_driver twl4030_codec_driver = {
254         .probe          = twl4030_codec_probe,
255         .remove         = __devexit_p(twl4030_codec_remove),
256         .driver         = {
257                 .owner  = THIS_MODULE,
258                 .name   = "twl4030_codec",
259         },
260 };
261
262 static int __devinit twl4030_codec_init(void)
263 {
264         return platform_driver_register(&twl4030_codec_driver);
265 }
266 module_init(twl4030_codec_init);
267
268 static void __devexit twl4030_codec_exit(void)
269 {
270         platform_driver_unregister(&twl4030_codec_driver);
271 }
272 module_exit(twl4030_codec_exit);
273
274 MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>");
275 MODULE_LICENSE("GPL");
276