97103078dc2aafbc2dab66770d5199e413a24011
[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/twl4030.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         struct mutex mutex;
45         struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX];
46         struct mfd_cell cells[TWL4030_CODEC_CELLS];
47 };
48
49 /*
50  * Modify the resource, the function returns the content of the register
51  * after the modification.
52  */
53 static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable)
54 {
55         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
56         u8 val;
57
58         twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
59                         codec->resource[id].reg);
60
61         if (enable)
62                 val |= codec->resource[id].mask;
63         else
64                 val &= ~codec->resource[id].mask;
65
66         twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
67                                         val, codec->resource[id].reg);
68
69         return val;
70 }
71
72 static inline int twl4030_codec_get_resource(enum twl4030_codec_res id)
73 {
74         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
75         u8 val;
76
77         twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val,
78                         codec->resource[id].reg);
79
80         return val;
81 }
82
83 /*
84  * Enable the resource.
85  * The function returns with error or the content of the register
86  */
87 int twl4030_codec_enable_resource(enum twl4030_codec_res id)
88 {
89         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
90         int val;
91
92         if (id >= TWL4030_CODEC_RES_MAX) {
93                 dev_err(&twl4030_codec_dev->dev,
94                                 "Invalid resource ID (%u)\n", id);
95                 return -EINVAL;
96         }
97
98         mutex_lock(&codec->mutex);
99         if (!codec->resource[id].request_count)
100                 /* Resource was disabled, enable it */
101                 val = twl4030_codec_set_resource(id, 1);
102         else
103                 val = twl4030_codec_get_resource(id);
104
105         codec->resource[id].request_count++;
106         mutex_unlock(&codec->mutex);
107
108         return val;
109 }
110 EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource);
111
112 /*
113  * Disable the resource.
114  * The function returns with error or the content of the register
115  */
116 int twl4030_codec_disable_resource(unsigned id)
117 {
118         struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev);
119         int val;
120
121         if (id >= TWL4030_CODEC_RES_MAX) {
122                 dev_err(&twl4030_codec_dev->dev,
123                                 "Invalid resource ID (%u)\n", id);
124                 return -EINVAL;
125         }
126
127         mutex_lock(&codec->mutex);
128         if (!codec->resource[id].request_count) {
129                 dev_err(&twl4030_codec_dev->dev,
130                         "Resource has been disabled already (%u)\n", id);
131                 mutex_unlock(&codec->mutex);
132                 return -EPERM;
133         }
134         codec->resource[id].request_count--;
135
136         if (!codec->resource[id].request_count)
137                 /* Resource can be disabled now */
138                 val = twl4030_codec_set_resource(id, 0);
139         else
140                 val = twl4030_codec_get_resource(id);
141
142         mutex_unlock(&codec->mutex);
143
144         return val;
145 }
146 EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource);
147
148 static int __devinit twl4030_codec_probe(struct platform_device *pdev)
149 {
150         struct twl4030_codec *codec;
151         struct twl4030_codec_data *pdata = pdev->dev.platform_data;
152         struct mfd_cell *cell = NULL;
153         int ret, childs = 0;
154
155         codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL);
156         if (!codec)
157                 return -ENOMEM;
158
159         platform_set_drvdata(pdev, codec);
160
161         twl4030_codec_dev = pdev;
162         mutex_init(&codec->mutex);
163
164         /* Codec power */
165         codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE;
166         codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ;
167
168         /* PLL */
169         codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL;
170         codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN;
171
172         if (pdata->audio) {
173                 cell = &codec->cells[childs];
174                 cell->name = "twl4030_codec_audio";
175                 cell->platform_data = pdata->audio;
176                 cell->data_size = sizeof(*pdata->audio);
177                 childs++;
178         }
179         if (pdata->vibra) {
180                 cell = &codec->cells[childs];
181                 cell->name = "twl4030_codec_vibra";
182                 cell->platform_data = pdata->vibra;
183                 cell->data_size = sizeof(*pdata->vibra);
184                 childs++;
185         }
186
187         if (childs)
188                 ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells,
189                                       childs, NULL, 0);
190         else {
191                 dev_err(&pdev->dev, "No platform data found for childs\n");
192                 ret = -ENODEV;
193         }
194
195         if (!ret)
196                 return 0;
197
198         platform_set_drvdata(pdev, NULL);
199         kfree(codec);
200         twl4030_codec_dev = NULL;
201         return ret;
202 }
203
204 static int __devexit twl4030_codec_remove(struct platform_device *pdev)
205 {
206         struct twl4030_codec *codec = platform_get_drvdata(pdev);
207
208         mfd_remove_devices(&pdev->dev);
209         platform_set_drvdata(pdev, NULL);
210         kfree(codec);
211         twl4030_codec_dev = NULL;
212
213         return 0;
214 }
215
216 MODULE_ALIAS("platform:twl4030_codec");
217
218 static struct platform_driver twl4030_codec_driver = {
219         .probe          = twl4030_codec_probe,
220         .remove         = __devexit_p(twl4030_codec_remove),
221         .driver         = {
222                 .owner  = THIS_MODULE,
223                 .name   = "twl4030_codec",
224         },
225 };
226
227 static int __devinit twl4030_codec_init(void)
228 {
229         return platform_driver_register(&twl4030_codec_driver);
230 }
231 module_init(twl4030_codec_init);
232
233 static void __devexit twl4030_codec_exit(void)
234 {
235         platform_driver_unregister(&twl4030_codec_driver);
236 }
237 module_exit(twl4030_codec_exit);
238
239 MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>");
240 MODULE_LICENSE("GPL");
241