diff options
Diffstat (limited to 'drivers/mfd/twl4030-audio.c')
-rw-r--r-- | drivers/mfd/twl4030-audio.c | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/drivers/mfd/twl4030-audio.c b/drivers/mfd/twl4030-audio.c new file mode 100644 index 000000000000..ae51ab5d0e5d --- /dev/null +++ b/drivers/mfd/twl4030-audio.c | |||
@@ -0,0 +1,277 @@ | |||
1 | /* | ||
2 | * MFD driver for twl4030 audio submodule, which contains an audio codec, and | ||
3 | * the vibra control. | ||
4 | * | ||
5 | * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> | ||
6 | * | ||
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> | ||
27 | #include <linux/slab.h> | ||
28 | #include <linux/kernel.h> | ||
29 | #include <linux/fs.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/i2c/twl.h> | ||
32 | #include <linux/mfd/core.h> | ||
33 | #include <linux/mfd/twl4030-audio.h> | ||
34 | |||
35 | #define TWL4030_AUDIO_CELLS 2 | ||
36 | |||
37 | static struct platform_device *twl4030_audio_dev; | ||
38 | |||
39 | struct twl4030_audio_resource { | ||
40 | int request_count; | ||
41 | u8 reg; | ||
42 | u8 mask; | ||
43 | }; | ||
44 | |||
45 | struct twl4030_audio { | ||
46 | unsigned int audio_mclk; | ||
47 | struct mutex mutex; | ||
48 | struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; | ||
49 | struct mfd_cell cells[TWL4030_AUDIO_CELLS]; | ||
50 | }; | ||
51 | |||
52 | /* | ||
53 | * Modify the resource, the function returns the content of the register | ||
54 | * after the modification. | ||
55 | */ | ||
56 | static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) | ||
57 | { | ||
58 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); | ||
59 | u8 val; | ||
60 | |||
61 | twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, | ||
62 | audio->resource[id].reg); | ||
63 | |||
64 | if (enable) | ||
65 | val |= audio->resource[id].mask; | ||
66 | else | ||
67 | val &= ~audio->resource[id].mask; | ||
68 | |||
69 | twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, | ||
70 | val, audio->resource[id].reg); | ||
71 | |||
72 | return val; | ||
73 | } | ||
74 | |||
75 | static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) | ||
76 | { | ||
77 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); | ||
78 | u8 val; | ||
79 | |||
80 | twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, | ||
81 | audio->resource[id].reg); | ||
82 | |||
83 | return val; | ||
84 | } | ||
85 | |||
86 | /* | ||
87 | * Enable the resource. | ||
88 | * The function returns with error or the content of the register | ||
89 | */ | ||
90 | int twl4030_audio_enable_resource(enum twl4030_audio_res id) | ||
91 | { | ||
92 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); | ||
93 | int val; | ||
94 | |||
95 | if (id >= TWL4030_AUDIO_RES_MAX) { | ||
96 | dev_err(&twl4030_audio_dev->dev, | ||
97 | "Invalid resource ID (%u)\n", id); | ||
98 | return -EINVAL; | ||
99 | } | ||
100 | |||
101 | mutex_lock(&audio->mutex); | ||
102 | if (!audio->resource[id].request_count) | ||
103 | /* Resource was disabled, enable it */ | ||
104 | val = twl4030_audio_set_resource(id, 1); | ||
105 | else | ||
106 | val = twl4030_audio_get_resource(id); | ||
107 | |||
108 | audio->resource[id].request_count++; | ||
109 | mutex_unlock(&audio->mutex); | ||
110 | |||
111 | return val; | ||
112 | } | ||
113 | EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); | ||
114 | |||
115 | /* | ||
116 | * Disable the resource. | ||
117 | * The function returns with error or the content of the register | ||
118 | */ | ||
119 | int twl4030_audio_disable_resource(unsigned id) | ||
120 | { | ||
121 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); | ||
122 | int val; | ||
123 | |||
124 | if (id >= TWL4030_AUDIO_RES_MAX) { | ||
125 | dev_err(&twl4030_audio_dev->dev, | ||
126 | "Invalid resource ID (%u)\n", id); | ||
127 | return -EINVAL; | ||
128 | } | ||
129 | |||
130 | mutex_lock(&audio->mutex); | ||
131 | if (!audio->resource[id].request_count) { | ||
132 | dev_err(&twl4030_audio_dev->dev, | ||
133 | "Resource has been disabled already (%u)\n", id); | ||
134 | mutex_unlock(&audio->mutex); | ||
135 | return -EPERM; | ||
136 | } | ||
137 | audio->resource[id].request_count--; | ||
138 | |||
139 | if (!audio->resource[id].request_count) | ||
140 | /* Resource can be disabled now */ | ||
141 | val = twl4030_audio_set_resource(id, 0); | ||
142 | else | ||
143 | val = twl4030_audio_get_resource(id); | ||
144 | |||
145 | mutex_unlock(&audio->mutex); | ||
146 | |||
147 | return val; | ||
148 | } | ||
149 | EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); | ||
150 | |||
151 | unsigned int twl4030_audio_get_mclk(void) | ||
152 | { | ||
153 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); | ||
154 | |||
155 | return audio->audio_mclk; | ||
156 | } | ||
157 | EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); | ||
158 | |||
159 | static int __devinit twl4030_audio_probe(struct platform_device *pdev) | ||
160 | { | ||
161 | struct twl4030_audio *audio; | ||
162 | struct twl4030_audio_data *pdata = pdev->dev.platform_data; | ||
163 | struct mfd_cell *cell = NULL; | ||
164 | int ret, childs = 0; | ||
165 | 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 | } | ||
188 | twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, | ||
189 | val, TWL4030_REG_APLL_CTL); | ||
190 | |||
191 | audio = kzalloc(sizeof(struct twl4030_audio), GFP_KERNEL); | ||
192 | if (!audio) | ||
193 | return -ENOMEM; | ||
194 | |||
195 | platform_set_drvdata(pdev, audio); | ||
196 | |||
197 | twl4030_audio_dev = pdev; | ||
198 | mutex_init(&audio->mutex); | ||
199 | audio->audio_mclk = pdata->audio_mclk; | ||
200 | |||
201 | /* Codec power */ | ||
202 | audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; | ||
203 | audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; | ||
204 | |||
205 | /* PLL */ | ||
206 | audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; | ||
207 | audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; | ||
208 | |||
209 | if (pdata->codec) { | ||
210 | cell = &audio->cells[childs]; | ||
211 | cell->name = "twl4030-codec"; | ||
212 | cell->platform_data = pdata->codec; | ||
213 | cell->pdata_size = sizeof(*pdata->codec); | ||
214 | childs++; | ||
215 | } | ||
216 | if (pdata->vibra) { | ||
217 | cell = &audio->cells[childs]; | ||
218 | cell->name = "twl4030-vibra"; | ||
219 | cell->platform_data = pdata->vibra; | ||
220 | cell->pdata_size = sizeof(*pdata->vibra); | ||
221 | childs++; | ||
222 | } | ||
223 | |||
224 | if (childs) | ||
225 | ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, | ||
226 | 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); | ||
236 | kfree(audio); | ||
237 | twl4030_audio_dev = NULL; | ||
238 | return ret; | ||
239 | } | ||
240 | |||
241 | static int __devexit twl4030_audio_remove(struct platform_device *pdev) | ||
242 | { | ||
243 | struct twl4030_audio *audio = platform_get_drvdata(pdev); | ||
244 | |||
245 | mfd_remove_devices(&pdev->dev); | ||
246 | platform_set_drvdata(pdev, NULL); | ||
247 | kfree(audio); | ||
248 | twl4030_audio_dev = NULL; | ||
249 | |||
250 | return 0; | ||
251 | } | ||
252 | |||
253 | MODULE_ALIAS("platform:twl4030-audio"); | ||
254 | |||
255 | static struct platform_driver twl4030_audio_driver = { | ||
256 | .probe = twl4030_audio_probe, | ||
257 | .remove = __devexit_p(twl4030_audio_remove), | ||
258 | .driver = { | ||
259 | .owner = THIS_MODULE, | ||
260 | .name = "twl4030-audio", | ||
261 | }, | ||
262 | }; | ||
263 | |||
264 | static int __devinit twl4030_audio_init(void) | ||
265 | { | ||
266 | return platform_driver_register(&twl4030_audio_driver); | ||
267 | } | ||
268 | module_init(twl4030_audio_init); | ||
269 | |||
270 | static void __devexit twl4030_audio_exit(void) | ||
271 | { | ||
272 | platform_driver_unregister(&twl4030_audio_driver); | ||
273 | } | ||
274 | module_exit(twl4030_audio_exit); | ||
275 | |||
276 | MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>"); | ||
277 | MODULE_LICENSE("GPL"); | ||