Root/
Source at commit 4ee9ce158310b789a1d7719c478c7c79c8f7ad15 created 10 years 10 months ago. By Lars-Peter Clausen, ASoC: jz4740-codec: Add device tree support | |
---|---|
1 | /* |
2 | * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License version 2 as |
6 | * published by the Free Software Foundation. |
7 | * |
8 | * You should have received a copy of the GNU General Public License along |
9 | * with this program; if not, write to the Free Software Foundation, Inc., |
10 | * 675 Mass Ave, Cambridge, MA 02139, USA. |
11 | * |
12 | */ |
13 | |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/io.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | #include <linux/delay.h> |
22 | |
23 | #include <sound/core.h> |
24 | #include <sound/pcm.h> |
25 | #include <sound/pcm_params.h> |
26 | #include <sound/initval.h> |
27 | #include <sound/soc.h> |
28 | #include <sound/tlv.h> |
29 | |
30 | #define JZ4740_REG_CODEC_1 0x0 |
31 | #define JZ4740_REG_CODEC_2 0x4 |
32 | |
33 | #define JZ4740_CODEC_1_LINE_ENABLE BIT(29) |
34 | #define JZ4740_CODEC_1_MIC_ENABLE BIT(28) |
35 | #define JZ4740_CODEC_1_SW1_ENABLE BIT(27) |
36 | #define JZ4740_CODEC_1_ADC_ENABLE BIT(26) |
37 | #define JZ4740_CODEC_1_SW2_ENABLE BIT(25) |
38 | #define JZ4740_CODEC_1_DAC_ENABLE BIT(24) |
39 | #define JZ4740_CODEC_1_VREF_DISABLE BIT(20) |
40 | #define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19) |
41 | #define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18) |
42 | #define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17) |
43 | #define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16) |
44 | #define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14) |
45 | #define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13) |
46 | #define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12) |
47 | #define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10)) |
48 | #define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9) |
49 | #define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8) |
50 | #define JZ4740_CODEC_1_SUSPEND BIT(1) |
51 | #define JZ4740_CODEC_1_RESET BIT(0) |
52 | |
53 | #define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29 |
54 | #define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28 |
55 | #define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27 |
56 | #define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26 |
57 | #define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25 |
58 | #define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24 |
59 | #define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14 |
60 | #define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8 |
61 | |
62 | #define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000 |
63 | #define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00 |
64 | #define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030 |
65 | #define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003 |
66 | |
67 | #define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16 |
68 | #define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8 |
69 | #define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4 |
70 | #define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0 |
71 | |
72 | static const struct reg_default jz4740_codec_reg_defaults[] = { |
73 | { JZ4740_REG_CODEC_1, 0x021b2302 }, |
74 | { JZ4740_REG_CODEC_2, 0x00170803 }, |
75 | }; |
76 | |
77 | struct jz4740_codec { |
78 | struct regmap *regmap; |
79 | }; |
80 | |
81 | static const unsigned int jz4740_mic_tlv[] = { |
82 | TLV_DB_RANGE_HEAD(2), |
83 | 0, 2, TLV_DB_SCALE_ITEM(0, 600, 0), |
84 | 3, 3, TLV_DB_SCALE_ITEM(2000, 0, 0), |
85 | }; |
86 | |
87 | static const DECLARE_TLV_DB_SCALE(jz4740_out_tlv, 0, 200, 0); |
88 | static const DECLARE_TLV_DB_SCALE(jz4740_in_tlv, -3450, 150, 0); |
89 | |
90 | static const struct snd_kcontrol_new jz4740_codec_controls[] = { |
91 | SOC_SINGLE_TLV("Master Playback Volume", JZ4740_REG_CODEC_2, |
92 | JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0, |
93 | jz4740_out_tlv), |
94 | SOC_SINGLE_TLV("Master Capture Volume", JZ4740_REG_CODEC_2, |
95 | JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0, |
96 | jz4740_in_tlv), |
97 | SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1, |
98 | JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1), |
99 | SOC_SINGLE_TLV("Mic Capture Volume", JZ4740_REG_CODEC_2, |
100 | JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0, |
101 | jz4740_mic_tlv), |
102 | }; |
103 | |
104 | static const struct snd_kcontrol_new jz4740_codec_output_controls[] = { |
105 | SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1, |
106 | JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0), |
107 | SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1, |
108 | JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0), |
109 | }; |
110 | |
111 | static const struct snd_kcontrol_new jz4740_codec_input_controls[] = { |
112 | SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1, |
113 | JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0), |
114 | SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1, |
115 | JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0), |
116 | }; |
117 | |
118 | static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = { |
119 | SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1, |
120 | JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0), |
121 | SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1, |
122 | JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0), |
123 | |
124 | SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1, |
125 | JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1, |
126 | jz4740_codec_output_controls, |
127 | ARRAY_SIZE(jz4740_codec_output_controls)), |
128 | |
129 | SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0, |
130 | jz4740_codec_input_controls, |
131 | ARRAY_SIZE(jz4740_codec_input_controls)), |
132 | SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0), |
133 | |
134 | SND_SOC_DAPM_OUTPUT("LOUT"), |
135 | SND_SOC_DAPM_OUTPUT("ROUT"), |
136 | |
137 | SND_SOC_DAPM_INPUT("MIC"), |
138 | SND_SOC_DAPM_INPUT("LIN"), |
139 | SND_SOC_DAPM_INPUT("RIN"), |
140 | }; |
141 | |
142 | static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = { |
143 | {"Line Input", NULL, "LIN"}, |
144 | {"Line Input", NULL, "RIN"}, |
145 | |
146 | {"Input Mixer", "Line Capture Switch", "Line Input"}, |
147 | {"Input Mixer", "Mic Capture Switch", "MIC"}, |
148 | |
149 | {"ADC", NULL, "Input Mixer"}, |
150 | |
151 | {"Output Mixer", "Bypass Switch", "Input Mixer"}, |
152 | {"Output Mixer", "DAC Switch", "DAC"}, |
153 | |
154 | {"LOUT", NULL, "Output Mixer"}, |
155 | {"ROUT", NULL, "Output Mixer"}, |
156 | }; |
157 | |
158 | static int jz4740_codec_hw_params(struct snd_pcm_substream *substream, |
159 | struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) |
160 | { |
161 | struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(dai->codec); |
162 | uint32_t val; |
163 | |
164 | switch (params_rate(params)) { |
165 | case 8000: |
166 | val = 0; |
167 | break; |
168 | case 11025: |
169 | val = 1; |
170 | break; |
171 | case 12000: |
172 | val = 2; |
173 | break; |
174 | case 16000: |
175 | val = 3; |
176 | break; |
177 | case 22050: |
178 | val = 4; |
179 | break; |
180 | case 24000: |
181 | val = 5; |
182 | break; |
183 | case 32000: |
184 | val = 6; |
185 | break; |
186 | case 44100: |
187 | val = 7; |
188 | break; |
189 | case 48000: |
190 | val = 8; |
191 | break; |
192 | default: |
193 | return -EINVAL; |
194 | } |
195 | |
196 | val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET; |
197 | |
198 | regmap_update_bits(jz4740_codec->regmap, JZ4740_REG_CODEC_2, |
199 | JZ4740_CODEC_2_SAMPLE_RATE_MASK, val); |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static const struct snd_soc_dai_ops jz4740_codec_dai_ops = { |
205 | .hw_params = jz4740_codec_hw_params, |
206 | }; |
207 | |
208 | static struct snd_soc_dai_driver jz4740_codec_dai = { |
209 | .name = "jz4740-hifi", |
210 | .playback = { |
211 | .stream_name = "Playback", |
212 | .channels_min = 2, |
213 | .channels_max = 2, |
214 | .rates = SNDRV_PCM_RATE_8000_48000, |
215 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, |
216 | }, |
217 | .capture = { |
218 | .stream_name = "Capture", |
219 | .channels_min = 2, |
220 | .channels_max = 2, |
221 | .rates = SNDRV_PCM_RATE_8000_48000, |
222 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8, |
223 | }, |
224 | .ops = &jz4740_codec_dai_ops, |
225 | .symmetric_rates = 1, |
226 | }; |
227 | |
228 | static void jz4740_codec_wakeup(struct regmap *regmap) |
229 | { |
230 | regmap_update_bits(regmap, JZ4740_REG_CODEC_1, |
231 | JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET); |
232 | udelay(2); |
233 | |
234 | regmap_update_bits(regmap, JZ4740_REG_CODEC_1, |
235 | JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0); |
236 | |
237 | regcache_sync(regmap); |
238 | } |
239 | |
240 | static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec, |
241 | enum snd_soc_bias_level level) |
242 | { |
243 | struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec); |
244 | struct regmap *regmap = jz4740_codec->regmap; |
245 | unsigned int mask; |
246 | unsigned int value; |
247 | |
248 | switch (level) { |
249 | case SND_SOC_BIAS_ON: |
250 | break; |
251 | case SND_SOC_BIAS_PREPARE: |
252 | mask = JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; |
253 | value = 0; |
254 | |
255 | regmap_update_bits(regmap, JZ4740_REG_CODEC_1, mask, value); |
256 | |
257 | msleep(500); |
258 | mask = JZ4740_CODEC_1_VREF_DISABLE | |
259 | JZ4740_CODEC_1_VREF_AMP_DISABLE; |
260 | regmap_update_bits(regmap, JZ4740_REG_CODEC_1, mask, 0); |
261 | break; |
262 | case SND_SOC_BIAS_STANDBY: |
263 | /* The only way to clear the suspend flag is to reset the codec */ |
264 | if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) |
265 | jz4740_codec_wakeup(regmap); |
266 | |
267 | mask = JZ4740_CODEC_1_VREF_DISABLE | |
268 | JZ4740_CODEC_1_VREF_AMP_DISABLE | |
269 | JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; |
270 | value = JZ4740_CODEC_1_VREF_DISABLE | |
271 | JZ4740_CODEC_1_VREF_AMP_DISABLE | |
272 | JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M; |
273 | |
274 | regmap_update_bits(regmap, JZ4740_REG_CODEC_1, mask, value); |
275 | break; |
276 | case SND_SOC_BIAS_OFF: |
277 | mask = JZ4740_CODEC_1_SUSPEND; |
278 | value = JZ4740_CODEC_1_SUSPEND; |
279 | |
280 | regmap_update_bits(regmap, JZ4740_REG_CODEC_1, mask, value); |
281 | regcache_mark_dirty(regmap); |
282 | break; |
283 | default: |
284 | break; |
285 | } |
286 | |
287 | codec->dapm.bias_level = level; |
288 | |
289 | return 0; |
290 | } |
291 | |
292 | static int jz4740_codec_dev_probe(struct snd_soc_codec *codec) |
293 | { |
294 | struct jz4740_codec *jz4740_codec = snd_soc_codec_get_drvdata(codec); |
295 | |
296 | regmap_update_bits(jz4740_codec->regmap, JZ4740_REG_CODEC_1, |
297 | JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE); |
298 | |
299 | jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
300 | |
301 | return 0; |
302 | } |
303 | |
304 | static int jz4740_codec_dev_remove(struct snd_soc_codec *codec) |
305 | { |
306 | jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF); |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | #ifdef CONFIG_PM_SLEEP |
312 | |
313 | static int jz4740_codec_suspend(struct snd_soc_codec *codec) |
314 | { |
315 | return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF); |
316 | } |
317 | |
318 | static int jz4740_codec_resume(struct snd_soc_codec *codec) |
319 | { |
320 | return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
321 | } |
322 | |
323 | #else |
324 | #define jz4740_codec_suspend NULL |
325 | #define jz4740_codec_resume NULL |
326 | #endif |
327 | |
328 | static struct snd_soc_codec_driver soc_codec_dev_jz4740_codec = { |
329 | .probe = jz4740_codec_dev_probe, |
330 | .remove = jz4740_codec_dev_remove, |
331 | .suspend = jz4740_codec_suspend, |
332 | .resume = jz4740_codec_resume, |
333 | .set_bias_level = jz4740_codec_set_bias_level, |
334 | |
335 | .controls = jz4740_codec_controls, |
336 | .num_controls = ARRAY_SIZE(jz4740_codec_controls), |
337 | .dapm_widgets = jz4740_codec_dapm_widgets, |
338 | .num_dapm_widgets = ARRAY_SIZE(jz4740_codec_dapm_widgets), |
339 | .dapm_routes = jz4740_codec_dapm_routes, |
340 | .num_dapm_routes = ARRAY_SIZE(jz4740_codec_dapm_routes), |
341 | }; |
342 | |
343 | static const struct regmap_config jz4740_codec_regmap_config = { |
344 | .reg_bits = 32, |
345 | .reg_stride = 4, |
346 | .val_bits = 32, |
347 | .max_register = JZ4740_REG_CODEC_2, |
348 | |
349 | .reg_defaults = jz4740_codec_reg_defaults, |
350 | .num_reg_defaults = ARRAY_SIZE(jz4740_codec_reg_defaults), |
351 | .cache_type = REGCACHE_RBTREE, |
352 | }; |
353 | |
354 | static int jz4740_codec_probe(struct platform_device *pdev) |
355 | { |
356 | int ret; |
357 | struct jz4740_codec *jz4740_codec; |
358 | struct resource *mem; |
359 | void __iomem *base; |
360 | |
361 | jz4740_codec = devm_kzalloc(&pdev->dev, sizeof(*jz4740_codec), |
362 | GFP_KERNEL); |
363 | if (!jz4740_codec) |
364 | return -ENOMEM; |
365 | |
366 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
367 | base = devm_ioremap_resource(&pdev->dev, mem); |
368 | if (IS_ERR(base)) |
369 | return PTR_ERR(base); |
370 | |
371 | jz4740_codec->regmap = devm_regmap_init_mmio(&pdev->dev, base, |
372 | &jz4740_codec_regmap_config); |
373 | if (IS_ERR(jz4740_codec->regmap)) |
374 | return PTR_ERR(jz4740_codec->regmap); |
375 | |
376 | platform_set_drvdata(pdev, jz4740_codec); |
377 | |
378 | ret = snd_soc_register_codec(&pdev->dev, |
379 | &soc_codec_dev_jz4740_codec, &jz4740_codec_dai, 1); |
380 | if (ret) |
381 | dev_err(&pdev->dev, "Failed to register codec\n"); |
382 | |
383 | return ret; |
384 | } |
385 | |
386 | static int jz4740_codec_remove(struct platform_device *pdev) |
387 | { |
388 | snd_soc_unregister_codec(&pdev->dev); |
389 | |
390 | return 0; |
391 | } |
392 | |
393 | static const struct of_device_id jz4740_codec_of_match[] = { |
394 | { .compatible = "ingenic,jz4740-codec" }, |
395 | {}, |
396 | }; |
397 | MODULE_DEVICE_TABLE(of, jz4740_codec_of_match); |
398 | |
399 | static struct platform_driver jz4740_codec_driver = { |
400 | .probe = jz4740_codec_probe, |
401 | .remove = jz4740_codec_remove, |
402 | .driver = { |
403 | .name = "jz4740-codec", |
404 | .owner = THIS_MODULE, |
405 | .of_match_table = jz4740_codec_of_match, |
406 | }, |
407 | }; |
408 | |
409 | module_platform_driver(jz4740_codec_driver); |
410 | |
411 | MODULE_DESCRIPTION("JZ4740 SoC internal codec driver"); |
412 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); |
413 | MODULE_LICENSE("GPL v2"); |
414 | MODULE_ALIAS("platform:jz4740-codec"); |
415 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9