]>
Commit | Line | Data |
---|---|---|
2b27bdcc | 1 | // SPDX-License-Identifier: GPL-2.0-only |
0b83ddeb | 2 | /* |
4fe5668b PU |
3 | * MFD driver for twl4030 audio submodule, which contains an audio codec, and |
4 | * the vibra control. | |
0b83ddeb | 5 | * |
4a7c00cd | 6 | * Author: Peter Ujfalusi <[email protected]> |
0b83ddeb PU |
7 | * |
8 | * Copyright: (C) 2009 Nokia Corporation | |
0b83ddeb PU |
9 | */ |
10 | ||
11 | #include <linux/module.h> | |
12 | #include <linux/types.h> | |
5a0e3ad6 | 13 | #include <linux/slab.h> |
0b83ddeb PU |
14 | #include <linux/kernel.h> |
15 | #include <linux/fs.h> | |
16 | #include <linux/platform_device.h> | |
019a7e6b PU |
17 | #include <linux/of.h> |
18 | #include <linux/of_platform.h> | |
a2054256 | 19 | #include <linux/mfd/twl.h> |
0b83ddeb | 20 | #include <linux/mfd/core.h> |
57fe7251 | 21 | #include <linux/mfd/twl4030-audio.h> |
0b83ddeb | 22 | |
4fe5668b | 23 | #define TWL4030_AUDIO_CELLS 2 |
0b83ddeb | 24 | |
4fe5668b | 25 | static struct platform_device *twl4030_audio_dev; |
0b83ddeb | 26 | |
4fe5668b | 27 | struct twl4030_audio_resource { |
0b83ddeb PU |
28 | int request_count; |
29 | u8 reg; | |
30 | u8 mask; | |
31 | }; | |
32 | ||
4fe5668b | 33 | struct twl4030_audio { |
f9b4639e | 34 | unsigned int audio_mclk; |
0b83ddeb | 35 | struct mutex mutex; |
57fe7251 | 36 | struct twl4030_audio_resource resource[TWL4030_AUDIO_RES_MAX]; |
4fe5668b | 37 | struct mfd_cell cells[TWL4030_AUDIO_CELLS]; |
0b83ddeb PU |
38 | }; |
39 | ||
40 | /* | |
41 | * Modify the resource, the function returns the content of the register | |
42 | * after the modification. | |
43 | */ | |
57fe7251 | 44 | static int twl4030_audio_set_resource(enum twl4030_audio_res id, int enable) |
0b83ddeb | 45 | { |
4fe5668b | 46 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); |
0b83ddeb PU |
47 | u8 val; |
48 | ||
8bea8672 | 49 | twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, |
4fe5668b | 50 | audio->resource[id].reg); |
0b83ddeb PU |
51 | |
52 | if (enable) | |
4fe5668b | 53 | val |= audio->resource[id].mask; |
0b83ddeb | 54 | else |
4fe5668b | 55 | val &= ~audio->resource[id].mask; |
0b83ddeb | 56 | |
8bea8672 | 57 | twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, |
4fe5668b | 58 | val, audio->resource[id].reg); |
0b83ddeb PU |
59 | |
60 | return val; | |
61 | } | |
62 | ||
57fe7251 | 63 | static inline int twl4030_audio_get_resource(enum twl4030_audio_res id) |
0b83ddeb | 64 | { |
4fe5668b | 65 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); |
0b83ddeb PU |
66 | u8 val; |
67 | ||
8bea8672 | 68 | twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, |
4fe5668b | 69 | audio->resource[id].reg); |
0b83ddeb PU |
70 | |
71 | return val; | |
72 | } | |
73 | ||
74 | /* | |
75 | * Enable the resource. | |
76 | * The function returns with error or the content of the register | |
77 | */ | |
57fe7251 | 78 | int twl4030_audio_enable_resource(enum twl4030_audio_res id) |
0b83ddeb | 79 | { |
4fe5668b | 80 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); |
0b83ddeb PU |
81 | int val; |
82 | ||
57fe7251 | 83 | if (id >= TWL4030_AUDIO_RES_MAX) { |
4fe5668b | 84 | dev_err(&twl4030_audio_dev->dev, |
0b83ddeb PU |
85 | "Invalid resource ID (%u)\n", id); |
86 | return -EINVAL; | |
87 | } | |
88 | ||
4fe5668b PU |
89 | mutex_lock(&audio->mutex); |
90 | if (!audio->resource[id].request_count) | |
0b83ddeb | 91 | /* Resource was disabled, enable it */ |
4fe5668b | 92 | val = twl4030_audio_set_resource(id, 1); |
0b83ddeb | 93 | else |
4fe5668b | 94 | val = twl4030_audio_get_resource(id); |
0b83ddeb | 95 | |
4fe5668b PU |
96 | audio->resource[id].request_count++; |
97 | mutex_unlock(&audio->mutex); | |
0b83ddeb PU |
98 | |
99 | return val; | |
100 | } | |
57fe7251 | 101 | EXPORT_SYMBOL_GPL(twl4030_audio_enable_resource); |
0b83ddeb PU |
102 | |
103 | /* | |
104 | * Disable the resource. | |
105 | * The function returns with error or the content of the register | |
106 | */ | |
6049bcef | 107 | int twl4030_audio_disable_resource(enum twl4030_audio_res id) |
0b83ddeb | 108 | { |
4fe5668b | 109 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); |
0b83ddeb PU |
110 | int val; |
111 | ||
57fe7251 | 112 | if (id >= TWL4030_AUDIO_RES_MAX) { |
4fe5668b | 113 | dev_err(&twl4030_audio_dev->dev, |
0b83ddeb PU |
114 | "Invalid resource ID (%u)\n", id); |
115 | return -EINVAL; | |
116 | } | |
117 | ||
4fe5668b PU |
118 | mutex_lock(&audio->mutex); |
119 | if (!audio->resource[id].request_count) { | |
120 | dev_err(&twl4030_audio_dev->dev, | |
0b83ddeb | 121 | "Resource has been disabled already (%u)\n", id); |
4fe5668b | 122 | mutex_unlock(&audio->mutex); |
0b83ddeb PU |
123 | return -EPERM; |
124 | } | |
4fe5668b | 125 | audio->resource[id].request_count--; |
0b83ddeb | 126 | |
4fe5668b | 127 | if (!audio->resource[id].request_count) |
0b83ddeb | 128 | /* Resource can be disabled now */ |
4fe5668b | 129 | val = twl4030_audio_set_resource(id, 0); |
0b83ddeb | 130 | else |
4fe5668b | 131 | val = twl4030_audio_get_resource(id); |
0b83ddeb | 132 | |
4fe5668b | 133 | mutex_unlock(&audio->mutex); |
0b83ddeb PU |
134 | |
135 | return val; | |
136 | } | |
57fe7251 | 137 | EXPORT_SYMBOL_GPL(twl4030_audio_disable_resource); |
0b83ddeb | 138 | |
57fe7251 | 139 | unsigned int twl4030_audio_get_mclk(void) |
f9b4639e | 140 | { |
4fe5668b | 141 | struct twl4030_audio *audio = platform_get_drvdata(twl4030_audio_dev); |
f9b4639e | 142 | |
4fe5668b | 143 | return audio->audio_mclk; |
f9b4639e | 144 | } |
57fe7251 | 145 | EXPORT_SYMBOL_GPL(twl4030_audio_get_mclk); |
f9b4639e | 146 | |
019a7e6b | 147 | static bool twl4030_audio_has_codec(struct twl4030_audio_data *pdata, |
0a423772 | 148 | struct device_node *parent) |
019a7e6b | 149 | { |
0a423772 JH |
150 | struct device_node *node; |
151 | ||
019a7e6b PU |
152 | if (pdata && pdata->codec) |
153 | return true; | |
154 | ||
0a423772 JH |
155 | node = of_get_child_by_name(parent, "codec"); |
156 | if (node) { | |
157 | of_node_put(node); | |
019a7e6b | 158 | return true; |
0a423772 | 159 | } |
019a7e6b PU |
160 | |
161 | return false; | |
162 | } | |
163 | ||
164 | static bool twl4030_audio_has_vibra(struct twl4030_audio_data *pdata, | |
165 | struct device_node *node) | |
166 | { | |
167 | int vibra; | |
168 | ||
169 | if (pdata && pdata->vibra) | |
170 | return true; | |
171 | ||
172 | if (!of_property_read_u32(node, "ti,enable-vibra", &vibra) && vibra) | |
173 | return true; | |
174 | ||
175 | return false; | |
176 | } | |
177 | ||
f791be49 | 178 | static int twl4030_audio_probe(struct platform_device *pdev) |
0b83ddeb | 179 | { |
4fe5668b | 180 | struct twl4030_audio *audio; |
334a41ce | 181 | struct twl4030_audio_data *pdata = dev_get_platdata(&pdev->dev); |
019a7e6b | 182 | struct device_node *node = pdev->dev.of_node; |
0b83ddeb PU |
183 | struct mfd_cell *cell = NULL; |
184 | int ret, childs = 0; | |
f9b4639e PU |
185 | u8 val; |
186 | ||
019a7e6b | 187 | if (!pdata && !node) { |
f9b4639e PU |
188 | dev_err(&pdev->dev, "Platform data is missing\n"); |
189 | return -EINVAL; | |
190 | } | |
191 | ||
cdf4b670 PU |
192 | audio = devm_kzalloc(&pdev->dev, sizeof(struct twl4030_audio), |
193 | GFP_KERNEL); | |
194 | if (!audio) | |
195 | return -ENOMEM; | |
196 | ||
197 | mutex_init(&audio->mutex); | |
c531241d | 198 | audio->audio_mclk = twl_get_hfclk_rate(); |
cdf4b670 | 199 | |
f9b4639e | 200 | /* Configure APLL_INFREQ and disable APLL if enabled */ |
cdf4b670 | 201 | switch (audio->audio_mclk) { |
f9b4639e | 202 | case 19200000: |
cdf4b670 | 203 | val = TWL4030_APLL_INFREQ_19200KHZ; |
f9b4639e PU |
204 | break; |
205 | case 26000000: | |
cdf4b670 | 206 | val = TWL4030_APLL_INFREQ_26000KHZ; |
f9b4639e PU |
207 | break; |
208 | case 38400000: | |
cdf4b670 | 209 | val = TWL4030_APLL_INFREQ_38400KHZ; |
f9b4639e PU |
210 | break; |
211 | default: | |
212 | dev_err(&pdev->dev, "Invalid audio_mclk\n"); | |
213 | return -EINVAL; | |
214 | } | |
cdf4b670 | 215 | twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, val, TWL4030_REG_APLL_CTL); |
0b83ddeb PU |
216 | |
217 | /* Codec power */ | |
57fe7251 PU |
218 | audio->resource[TWL4030_AUDIO_RES_POWER].reg = TWL4030_REG_CODEC_MODE; |
219 | audio->resource[TWL4030_AUDIO_RES_POWER].mask = TWL4030_CODECPDZ; | |
0b83ddeb PU |
220 | |
221 | /* PLL */ | |
57fe7251 PU |
222 | audio->resource[TWL4030_AUDIO_RES_APLL].reg = TWL4030_REG_APLL_CTL; |
223 | audio->resource[TWL4030_AUDIO_RES_APLL].mask = TWL4030_APLL_EN; | |
0b83ddeb | 224 | |
019a7e6b | 225 | if (twl4030_audio_has_codec(pdata, node)) { |
4fe5668b | 226 | cell = &audio->cells[childs]; |
f0fba2ad | 227 | cell->name = "twl4030-codec"; |
019a7e6b PU |
228 | if (pdata) { |
229 | cell->platform_data = pdata->codec; | |
230 | cell->pdata_size = sizeof(*pdata->codec); | |
231 | } | |
0b83ddeb PU |
232 | childs++; |
233 | } | |
019a7e6b | 234 | if (twl4030_audio_has_vibra(pdata, node)) { |
4fe5668b | 235 | cell = &audio->cells[childs]; |
f0fba2ad | 236 | cell->name = "twl4030-vibra"; |
019a7e6b PU |
237 | if (pdata) { |
238 | cell->platform_data = pdata->vibra; | |
239 | cell->pdata_size = sizeof(*pdata->vibra); | |
240 | } | |
0b83ddeb PU |
241 | childs++; |
242 | } | |
243 | ||
cdf4b670 PU |
244 | platform_set_drvdata(pdev, audio); |
245 | twl4030_audio_dev = pdev; | |
246 | ||
0b83ddeb | 247 | if (childs) |
4fe5668b | 248 | ret = mfd_add_devices(&pdev->dev, pdev->id, audio->cells, |
55692af5 | 249 | childs, NULL, 0, NULL); |
0b83ddeb PU |
250 | else { |
251 | dev_err(&pdev->dev, "No platform data found for childs\n"); | |
252 | ret = -ENODEV; | |
253 | } | |
254 | ||
96ac2d1a | 255 | if (ret) |
39c1421d | 256 | twl4030_audio_dev = NULL; |
0b83ddeb | 257 | |
0b83ddeb PU |
258 | return ret; |
259 | } | |
260 | ||
e0191f30 | 261 | static void twl4030_audio_remove(struct platform_device *pdev) |
0b83ddeb | 262 | { |
0b83ddeb | 263 | mfd_remove_devices(&pdev->dev); |
4fe5668b | 264 | twl4030_audio_dev = NULL; |
0b83ddeb PU |
265 | } |
266 | ||
019a7e6b PU |
267 | static const struct of_device_id twl4030_audio_of_match[] = { |
268 | {.compatible = "ti,twl4030-audio", }, | |
269 | { }, | |
270 | }; | |
271 | MODULE_DEVICE_TABLE(of, twl4030_audio_of_match); | |
272 | ||
4fe5668b | 273 | static struct platform_driver twl4030_audio_driver = { |
0b83ddeb | 274 | .driver = { |
f0fba2ad | 275 | .name = "twl4030-audio", |
019a7e6b | 276 | .of_match_table = twl4030_audio_of_match, |
0b83ddeb | 277 | }, |
41569a16 | 278 | .probe = twl4030_audio_probe, |
e0191f30 | 279 | .remove_new = twl4030_audio_remove, |
0b83ddeb PU |
280 | }; |
281 | ||
65349d60 | 282 | module_platform_driver(twl4030_audio_driver); |
0b83ddeb | 283 | |
4a7c00cd | 284 | MODULE_AUTHOR("Peter Ujfalusi <[email protected]>"); |
41569a16 | 285 | MODULE_DESCRIPTION("TWL4030 audio block MFD driver"); |
41569a16 | 286 | MODULE_ALIAS("platform:twl4030-audio"); |