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