1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Ingenic JZ4770 CODEC driver |
4 | // |
5 | // Copyright (C) 2012, Maarten ter Huurne <maarten@treewalker.org> |
6 | // Copyright (C) 2019, Paul Cercueil <paul@crapouillou.net> |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/iopoll.h> |
11 | #include <linux/module.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/time64.h> |
14 | |
15 | #include <sound/pcm_params.h> |
16 | #include <sound/soc.h> |
17 | #include <sound/soc-dai.h> |
18 | #include <sound/soc-dapm.h> |
19 | #include <sound/tlv.h> |
20 | |
21 | #define ICDC_RGADW_OFFSET 0x00 |
22 | #define ICDC_RGDATA_OFFSET 0x04 |
23 | |
24 | /* ICDC internal register access control register(RGADW) */ |
25 | #define ICDC_RGADW_RGWR BIT(16) |
26 | |
27 | #define ICDC_RGADW_RGADDR_OFFSET 8 |
28 | #define ICDC_RGADW_RGADDR_MASK GENMASK(14, ICDC_RGADW_RGADDR_OFFSET) |
29 | |
30 | #define ICDC_RGADW_RGDIN_OFFSET 0 |
31 | #define ICDC_RGADW_RGDIN_MASK GENMASK(7, ICDC_RGADW_RGDIN_OFFSET) |
32 | |
33 | /* ICDC internal register data output register (RGDATA)*/ |
34 | #define ICDC_RGDATA_IRQ BIT(8) |
35 | |
36 | #define ICDC_RGDATA_RGDOUT_OFFSET 0 |
37 | #define ICDC_RGDATA_RGDOUT_MASK GENMASK(7, ICDC_RGDATA_RGDOUT_OFFSET) |
38 | |
39 | /* Internal register space, accessed through regmap */ |
40 | enum { |
41 | JZ4770_CODEC_REG_SR, |
42 | JZ4770_CODEC_REG_AICR_DAC, |
43 | JZ4770_CODEC_REG_AICR_ADC, |
44 | JZ4770_CODEC_REG_CR_LO, |
45 | JZ4770_CODEC_REG_CR_HP, |
46 | |
47 | JZ4770_CODEC_REG_MISSING_REG1, |
48 | |
49 | JZ4770_CODEC_REG_CR_DAC, |
50 | JZ4770_CODEC_REG_CR_MIC, |
51 | JZ4770_CODEC_REG_CR_LI, |
52 | JZ4770_CODEC_REG_CR_ADC, |
53 | JZ4770_CODEC_REG_CR_MIX, |
54 | JZ4770_CODEC_REG_CR_VIC, |
55 | JZ4770_CODEC_REG_CCR, |
56 | JZ4770_CODEC_REG_FCR_DAC, |
57 | JZ4770_CODEC_REG_FCR_ADC, |
58 | JZ4770_CODEC_REG_ICR, |
59 | JZ4770_CODEC_REG_IMR, |
60 | JZ4770_CODEC_REG_IFR, |
61 | JZ4770_CODEC_REG_GCR_HPL, |
62 | JZ4770_CODEC_REG_GCR_HPR, |
63 | JZ4770_CODEC_REG_GCR_LIBYL, |
64 | JZ4770_CODEC_REG_GCR_LIBYR, |
65 | JZ4770_CODEC_REG_GCR_DACL, |
66 | JZ4770_CODEC_REG_GCR_DACR, |
67 | JZ4770_CODEC_REG_GCR_MIC1, |
68 | JZ4770_CODEC_REG_GCR_MIC2, |
69 | JZ4770_CODEC_REG_GCR_ADCL, |
70 | JZ4770_CODEC_REG_GCR_ADCR, |
71 | |
72 | JZ4770_CODEC_REG_MISSING_REG2, |
73 | |
74 | JZ4770_CODEC_REG_GCR_MIXADC, |
75 | JZ4770_CODEC_REG_GCR_MIXDAC, |
76 | JZ4770_CODEC_REG_AGC1, |
77 | JZ4770_CODEC_REG_AGC2, |
78 | JZ4770_CODEC_REG_AGC3, |
79 | JZ4770_CODEC_REG_AGC4, |
80 | JZ4770_CODEC_REG_AGC5, |
81 | }; |
82 | |
83 | #define REG_AICR_DAC_ADWL_OFFSET 6 |
84 | #define REG_AICR_DAC_ADWL_MASK (0x3 << REG_AICR_DAC_ADWL_OFFSET) |
85 | #define REG_AICR_DAC_SERIAL BIT(1) |
86 | #define REG_AICR_DAC_I2S BIT(0) |
87 | |
88 | #define REG_AICR_ADC_ADWL_OFFSET 6 |
89 | #define REG_AICR_ADC_ADWL_MASK (0x3 << REG_AICR_ADC_ADWL_OFFSET) |
90 | #define REG_AICR_ADC_SERIAL BIT(1) |
91 | #define REG_AICR_ADC_I2S BIT(0) |
92 | |
93 | #define REG_CR_LO_MUTE_OFFSET 7 |
94 | #define REG_CR_LO_SB_OFFSET 4 |
95 | #define REG_CR_LO_SEL_OFFSET 0 |
96 | #define REG_CR_LO_SEL_MASK (0x3 << REG_CR_LO_SEL_OFFSET) |
97 | |
98 | #define REG_CR_HP_MUTE BIT(7) |
99 | #define REG_CR_HP_LOAD BIT(6) |
100 | #define REG_CR_HP_SB_OFFSET 4 |
101 | #define REG_CR_HP_SB_HPCM_OFFSET 3 |
102 | #define REG_CR_HP_SEL_OFFSET 0 |
103 | #define REG_CR_HP_SEL_MASK (0x3 << REG_CR_HP_SEL_OFFSET) |
104 | |
105 | #define REG_CR_DAC_MUTE BIT(7) |
106 | #define REG_CR_DAC_MONO BIT(6) |
107 | #define REG_CR_DAC_LEFT_ONLY BIT(5) |
108 | #define REG_CR_DAC_SB_OFFSET 4 |
109 | #define REG_CR_DAC_LRSWAP BIT(3) |
110 | |
111 | #define REG_CR_MIC_STEREO_OFFSET 7 |
112 | #define REG_CR_MIC_IDIFF_OFFSET 6 |
113 | #define REG_CR_MIC_SB_MIC2_OFFSET 5 |
114 | #define REG_CR_MIC_SB_MIC1_OFFSET 4 |
115 | #define REG_CR_MIC_BIAS_V0_OFFSET 1 |
116 | #define REG_CR_MIC_BIAS_SB_OFFSET 0 |
117 | |
118 | #define REG_CR_LI_LIBY_OFFSET 4 |
119 | #define REG_CR_LI_SB_OFFSET 0 |
120 | |
121 | #define REG_CR_ADC_DMIC_SEL BIT(7) |
122 | #define REG_CR_ADC_MONO BIT(6) |
123 | #define REG_CR_ADC_LEFT_ONLY BIT(5) |
124 | #define REG_CR_ADC_SB_OFFSET 4 |
125 | #define REG_CR_ADC_LRSWAP BIT(3) |
126 | #define REG_CR_ADC_IN_SEL_OFFSET 0 |
127 | #define REG_CR_ADC_IN_SEL_MASK (0x3 << REG_CR_ADC_IN_SEL_OFFSET) |
128 | |
129 | #define REG_CR_VIC_SB_SLEEP BIT(1) |
130 | #define REG_CR_VIC_SB BIT(0) |
131 | |
132 | #define REG_CCR_CRYSTAL_OFFSET 0 |
133 | #define REG_CCR_CRYSTAL_MASK (0xf << REG_CCR_CRYSTAL_OFFSET) |
134 | |
135 | #define REG_FCR_DAC_FREQ_OFFSET 0 |
136 | #define REG_FCR_DAC_FREQ_MASK (0xf << REG_FCR_DAC_FREQ_OFFSET) |
137 | |
138 | #define REG_FCR_ADC_FREQ_OFFSET 0 |
139 | #define REG_FCR_ADC_FREQ_MASK (0xf << REG_FCR_ADC_FREQ_OFFSET) |
140 | |
141 | #define REG_ICR_INT_FORM_OFFSET 6 |
142 | #define REG_ICR_INT_FORM_MASK (0x3 << REG_ICR_INT_FORM_OFFSET) |
143 | |
144 | #define REG_IMR_ALL_MASK (0x7f) |
145 | #define REG_IMR_SCLR_MASK BIT(6) |
146 | #define REG_IMR_JACK_MASK BIT(5) |
147 | #define REG_IMR_SCMC_MASK BIT(4) |
148 | #define REG_IMR_RUP_MASK BIT(3) |
149 | #define REG_IMR_RDO_MASK BIT(2) |
150 | #define REG_IMR_GUP_MASK BIT(1) |
151 | #define REG_IMR_GDO_MASK BIT(0) |
152 | |
153 | #define REG_IFR_ALL_MASK (0x7f) |
154 | #define REG_IFR_SCLR BIT(6) |
155 | #define REG_IFR_JACK BIT(5) |
156 | #define REG_IFR_SCMC BIT(4) |
157 | #define REG_IFR_RUP BIT(3) |
158 | #define REG_IFR_RDO BIT(2) |
159 | #define REG_IFR_GUP BIT(1) |
160 | #define REG_IFR_GDO BIT(0) |
161 | |
162 | #define REG_GCR_HPL_LRGO BIT(7) |
163 | |
164 | #define REG_GCR_DACL_RLGOD BIT(7) |
165 | |
166 | #define REG_GCR_GAIN_OFFSET 0 |
167 | #define REG_GCR_GAIN_MAX 0x1f |
168 | |
169 | #define REG_GCR_MIC_GAIN_OFFSET 0 |
170 | #define REG_GCR_MIC_GAIN_MAX 5 |
171 | |
172 | #define REG_GCR_ADC_GAIN_OFFSET 0 |
173 | #define REG_GCR_ADC_GAIN_MAX 23 |
174 | |
175 | #define REG_AGC1_EN BIT(7) |
176 | |
177 | /* codec private data */ |
178 | struct jz_codec { |
179 | struct device *dev; |
180 | struct regmap *regmap; |
181 | void __iomem *base; |
182 | struct clk *clk; |
183 | }; |
184 | |
185 | static int jz4770_codec_set_bias_level(struct snd_soc_component *codec, |
186 | enum snd_soc_bias_level level) |
187 | { |
188 | struct jz_codec *jz_codec = snd_soc_component_get_drvdata(c: codec); |
189 | struct regmap *regmap = jz_codec->regmap; |
190 | |
191 | switch (level) { |
192 | case SND_SOC_BIAS_PREPARE: |
193 | /* Reset all interrupt flags. */ |
194 | regmap_write(map: regmap, reg: JZ4770_CODEC_REG_IFR, REG_IFR_ALL_MASK); |
195 | |
196 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_VIC, |
197 | REG_CR_VIC_SB); |
198 | msleep(msecs: 250); |
199 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_VIC, |
200 | REG_CR_VIC_SB_SLEEP); |
201 | msleep(msecs: 400); |
202 | break; |
203 | case SND_SOC_BIAS_STANDBY: |
204 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_VIC, |
205 | REG_CR_VIC_SB_SLEEP); |
206 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_VIC, |
207 | REG_CR_VIC_SB); |
208 | fallthrough; |
209 | default: |
210 | break; |
211 | } |
212 | |
213 | return 0; |
214 | } |
215 | |
216 | static int jz4770_codec_startup(struct snd_pcm_substream *substream, |
217 | struct snd_soc_dai *dai) |
218 | { |
219 | struct snd_soc_component *codec = dai->component; |
220 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: codec); |
221 | |
222 | /* |
223 | * SYSCLK output from the codec to the AIC is required to keep the |
224 | * DMA transfer going during playback when all audible outputs have |
225 | * been disabled. |
226 | */ |
227 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
228 | snd_soc_dapm_force_enable_pin(dapm, pin: "SYSCLK" ); |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static void jz4770_codec_shutdown(struct snd_pcm_substream *substream, |
234 | struct snd_soc_dai *dai) |
235 | { |
236 | struct snd_soc_component *codec = dai->component; |
237 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: codec); |
238 | |
239 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
240 | snd_soc_dapm_disable_pin(dapm, pin: "SYSCLK" ); |
241 | } |
242 | |
243 | |
244 | static int jz4770_codec_pcm_trigger(struct snd_pcm_substream *substream, |
245 | int cmd, struct snd_soc_dai *dai) |
246 | { |
247 | struct snd_soc_component *codec = dai->component; |
248 | int ret = 0; |
249 | |
250 | switch (cmd) { |
251 | case SNDRV_PCM_TRIGGER_START: |
252 | case SNDRV_PCM_TRIGGER_RESUME: |
253 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
254 | if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) |
255 | snd_soc_component_force_bias_level(component: codec, |
256 | level: SND_SOC_BIAS_ON); |
257 | break; |
258 | case SNDRV_PCM_TRIGGER_STOP: |
259 | case SNDRV_PCM_TRIGGER_SUSPEND: |
260 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
261 | /* do nothing */ |
262 | break; |
263 | default: |
264 | ret = -EINVAL; |
265 | } |
266 | |
267 | return ret; |
268 | } |
269 | |
270 | static int jz4770_codec_mute_stream(struct snd_soc_dai *dai, int mute, int direction) |
271 | { |
272 | struct snd_soc_component *codec = dai->component; |
273 | struct jz_codec *jz_codec = snd_soc_component_get_drvdata(c: codec); |
274 | unsigned int gain_bit = mute ? REG_IFR_GDO : REG_IFR_GUP; |
275 | unsigned int val; |
276 | int change, err; |
277 | |
278 | change = snd_soc_component_update_bits(component: codec, reg: JZ4770_CODEC_REG_CR_DAC, |
279 | REG_CR_DAC_MUTE, |
280 | val: mute ? REG_CR_DAC_MUTE : 0); |
281 | if (change == 1) { |
282 | regmap_read(map: jz_codec->regmap, reg: JZ4770_CODEC_REG_CR_DAC, val: &val); |
283 | |
284 | if (val & BIT(REG_CR_DAC_SB_OFFSET)) |
285 | return 1; |
286 | |
287 | err = regmap_read_poll_timeout(jz_codec->regmap, |
288 | JZ4770_CODEC_REG_IFR, |
289 | val, val & gain_bit, |
290 | 1000, 1 * USEC_PER_SEC); |
291 | if (err) { |
292 | dev_err(jz_codec->dev, |
293 | "Timeout while setting digital mute: %d" , err); |
294 | return err; |
295 | } |
296 | |
297 | /* clear GUP/GDO flag */ |
298 | regmap_set_bits(map: jz_codec->regmap, reg: JZ4770_CODEC_REG_IFR, |
299 | bits: gain_bit); |
300 | } |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | /* unit: 0.01dB */ |
306 | static const DECLARE_TLV_DB_MINMAX_MUTE(dac_tlv, -3100, 0); |
307 | static const DECLARE_TLV_DB_SCALE(adc_tlv, 0, 100, 0); |
308 | static const DECLARE_TLV_DB_MINMAX(out_tlv, -2500, 600); |
309 | static const DECLARE_TLV_DB_SCALE(linein_tlv, -2500, 100, 0); |
310 | static const DECLARE_TLV_DB_MINMAX(mixer_tlv, -3100, 0); |
311 | |
312 | /* Unconditional controls. */ |
313 | static const struct snd_kcontrol_new jz4770_codec_snd_controls[] = { |
314 | /* record gain control */ |
315 | SOC_DOUBLE_R_TLV("PCM Capture Volume" , |
316 | JZ4770_CODEC_REG_GCR_ADCL, JZ4770_CODEC_REG_GCR_ADCR, |
317 | REG_GCR_ADC_GAIN_OFFSET, REG_GCR_ADC_GAIN_MAX, |
318 | 0, adc_tlv), |
319 | |
320 | SOC_DOUBLE_R_TLV("Line In Bypass Playback Volume" , |
321 | JZ4770_CODEC_REG_GCR_LIBYL, JZ4770_CODEC_REG_GCR_LIBYR, |
322 | REG_GCR_GAIN_OFFSET, REG_GCR_GAIN_MAX, 1, linein_tlv), |
323 | |
324 | SOC_SINGLE_TLV("Mixer Capture Volume" , |
325 | JZ4770_CODEC_REG_GCR_MIXADC, |
326 | REG_GCR_GAIN_OFFSET, REG_GCR_GAIN_MAX, 1, mixer_tlv), |
327 | |
328 | SOC_SINGLE_TLV("Mixer Playback Volume" , |
329 | JZ4770_CODEC_REG_GCR_MIXDAC, |
330 | REG_GCR_GAIN_OFFSET, REG_GCR_GAIN_MAX, 1, mixer_tlv), |
331 | }; |
332 | |
333 | static const struct snd_kcontrol_new jz4770_codec_pcm_playback_controls[] = { |
334 | { |
335 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
336 | .name = "Volume" , |
337 | .info = snd_soc_info_volsw, |
338 | .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
339 | | SNDRV_CTL_ELEM_ACCESS_READWRITE, |
340 | .tlv.p = dac_tlv, |
341 | .get = snd_soc_dapm_get_volsw, |
342 | .put = snd_soc_dapm_put_volsw, |
343 | /* |
344 | * NOTE: DACR/DACL are inversed; the gain value written to DACR |
345 | * seems to affect the left channel, and the gain value written |
346 | * to DACL seems to affect the right channel. |
347 | */ |
348 | .private_value = SOC_DOUBLE_R_VALUE(JZ4770_CODEC_REG_GCR_DACR, |
349 | JZ4770_CODEC_REG_GCR_DACL, |
350 | REG_GCR_GAIN_OFFSET, |
351 | REG_GCR_GAIN_MAX, 1), |
352 | }, |
353 | }; |
354 | |
355 | static const struct snd_kcontrol_new jz4770_codec_hp_playback_controls[] = { |
356 | { |
357 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
358 | .name = "Volume" , |
359 | .info = snd_soc_info_volsw, |
360 | .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
361 | | SNDRV_CTL_ELEM_ACCESS_READWRITE, |
362 | .tlv.p = out_tlv, |
363 | .get = snd_soc_dapm_get_volsw, |
364 | .put = snd_soc_dapm_put_volsw, |
365 | /* HPR/HPL inversed for the same reason as above */ |
366 | .private_value = SOC_DOUBLE_R_VALUE(JZ4770_CODEC_REG_GCR_HPR, |
367 | JZ4770_CODEC_REG_GCR_HPL, |
368 | REG_GCR_GAIN_OFFSET, |
369 | REG_GCR_GAIN_MAX, 1), |
370 | }, |
371 | }; |
372 | |
373 | static int hpout_event(struct snd_soc_dapm_widget *w, |
374 | struct snd_kcontrol *kcontrol, int event) |
375 | { |
376 | struct snd_soc_component *codec = snd_soc_dapm_to_component(dapm: w->dapm); |
377 | struct jz_codec *jz_codec = snd_soc_component_get_drvdata(c: codec); |
378 | unsigned int val; |
379 | int err; |
380 | |
381 | switch (event) { |
382 | case SND_SOC_DAPM_PRE_PMU: |
383 | /* unmute HP */ |
384 | regmap_clear_bits(map: jz_codec->regmap, reg: JZ4770_CODEC_REG_CR_HP, |
385 | REG_CR_HP_MUTE); |
386 | break; |
387 | |
388 | case SND_SOC_DAPM_POST_PMU: |
389 | /* wait for ramp-up complete (RUP) */ |
390 | err = regmap_read_poll_timeout(jz_codec->regmap, |
391 | JZ4770_CODEC_REG_IFR, |
392 | val, val & REG_IFR_RUP, |
393 | 1000, 1 * USEC_PER_SEC); |
394 | if (err) { |
395 | dev_err(jz_codec->dev, "RUP timeout: %d" , err); |
396 | return err; |
397 | } |
398 | |
399 | /* clear RUP flag */ |
400 | regmap_set_bits(map: jz_codec->regmap, reg: JZ4770_CODEC_REG_IFR, |
401 | REG_IFR_RUP); |
402 | |
403 | break; |
404 | |
405 | case SND_SOC_DAPM_POST_PMD: |
406 | /* mute HP */ |
407 | regmap_set_bits(map: jz_codec->regmap, reg: JZ4770_CODEC_REG_CR_HP, |
408 | REG_CR_HP_MUTE); |
409 | |
410 | err = regmap_read_poll_timeout(jz_codec->regmap, |
411 | JZ4770_CODEC_REG_IFR, |
412 | val, val & REG_IFR_RDO, |
413 | 1000, 1 * USEC_PER_SEC); |
414 | if (err) { |
415 | dev_err(jz_codec->dev, "RDO timeout: %d" , err); |
416 | return err; |
417 | } |
418 | |
419 | /* clear RDO flag */ |
420 | regmap_set_bits(map: jz_codec->regmap, reg: JZ4770_CODEC_REG_IFR, |
421 | REG_IFR_RDO); |
422 | |
423 | break; |
424 | } |
425 | |
426 | return 0; |
427 | } |
428 | |
429 | static int adc_poweron_event(struct snd_soc_dapm_widget *w, |
430 | struct snd_kcontrol *kcontrol, int event) |
431 | { |
432 | if (event == SND_SOC_DAPM_POST_PMU) |
433 | msleep(msecs: 1000); |
434 | |
435 | return 0; |
436 | } |
437 | |
438 | static const char * const jz4770_codec_hp_texts[] = { |
439 | "PCM" , "Line In" , "Mic 1" , "Mic 2" |
440 | }; |
441 | static const unsigned int jz4770_codec_hp_values[] = { 3, 2, 0, 1 }; |
442 | static SOC_VALUE_ENUM_SINGLE_DECL(jz4770_codec_hp_enum, |
443 | JZ4770_CODEC_REG_CR_HP, |
444 | REG_CR_HP_SEL_OFFSET, |
445 | REG_CR_HP_SEL_MASK, |
446 | jz4770_codec_hp_texts, |
447 | jz4770_codec_hp_values); |
448 | static const struct snd_kcontrol_new jz4770_codec_hp_source = |
449 | SOC_DAPM_ENUM("Route" , jz4770_codec_hp_enum); |
450 | |
451 | static SOC_VALUE_ENUM_SINGLE_DECL(jz4770_codec_lo_enum, |
452 | JZ4770_CODEC_REG_CR_LO, |
453 | REG_CR_LO_SEL_OFFSET, |
454 | REG_CR_LO_SEL_MASK, |
455 | jz4770_codec_hp_texts, |
456 | jz4770_codec_hp_values); |
457 | static const struct snd_kcontrol_new jz4770_codec_lo_source = |
458 | SOC_DAPM_ENUM("Route" , jz4770_codec_lo_enum); |
459 | |
460 | static const char * const jz4770_codec_cap_texts[] = { |
461 | "Line In" , "Mic 1" , "Mic 2" |
462 | }; |
463 | static const unsigned int jz4770_codec_cap_values[] = { 2, 0, 1 }; |
464 | static SOC_VALUE_ENUM_SINGLE_DECL(jz4770_codec_cap_enum, |
465 | JZ4770_CODEC_REG_CR_ADC, |
466 | REG_CR_ADC_IN_SEL_OFFSET, |
467 | REG_CR_ADC_IN_SEL_MASK, |
468 | jz4770_codec_cap_texts, |
469 | jz4770_codec_cap_values); |
470 | static const struct snd_kcontrol_new jz4770_codec_cap_source = |
471 | SOC_DAPM_ENUM("Route" , jz4770_codec_cap_enum); |
472 | |
473 | static const struct snd_kcontrol_new jz4770_codec_mic_controls[] = { |
474 | SOC_DAPM_SINGLE("Stereo Capture Switch" , JZ4770_CODEC_REG_CR_MIC, |
475 | REG_CR_MIC_STEREO_OFFSET, 1, 0), |
476 | }; |
477 | |
478 | static const struct snd_soc_dapm_widget jz4770_codec_dapm_widgets[] = { |
479 | SND_SOC_DAPM_PGA_E("HP Out" , JZ4770_CODEC_REG_CR_HP, |
480 | REG_CR_HP_SB_OFFSET, 1, NULL, 0, hpout_event, |
481 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | |
482 | SND_SOC_DAPM_POST_PMD), |
483 | |
484 | SND_SOC_DAPM_PGA("Line Out" , JZ4770_CODEC_REG_CR_LO, |
485 | REG_CR_LO_SB_OFFSET, 1, NULL, 0), |
486 | |
487 | SND_SOC_DAPM_PGA("Line Out Switch 2" , JZ4770_CODEC_REG_CR_LO, |
488 | REG_CR_LO_MUTE_OFFSET, 1, NULL, 0), |
489 | |
490 | SND_SOC_DAPM_PGA("Line In" , JZ4770_CODEC_REG_CR_LI, |
491 | REG_CR_LI_SB_OFFSET, 1, NULL, 0), |
492 | |
493 | SND_SOC_DAPM_MUX("Headphones Source" , SND_SOC_NOPM, 0, 0, |
494 | &jz4770_codec_hp_source), |
495 | SND_SOC_DAPM_MUX("Capture Source" , SND_SOC_NOPM, 0, 0, |
496 | &jz4770_codec_cap_source), |
497 | SND_SOC_DAPM_MUX("Line Out Source" , SND_SOC_NOPM, 0, 0, |
498 | &jz4770_codec_lo_source), |
499 | |
500 | SND_SOC_DAPM_PGA("Mic 1" , JZ4770_CODEC_REG_CR_MIC, |
501 | REG_CR_MIC_SB_MIC1_OFFSET, 1, NULL, 0), |
502 | SND_SOC_DAPM_PGA("Mic 2" , JZ4770_CODEC_REG_CR_MIC, |
503 | REG_CR_MIC_SB_MIC2_OFFSET, 1, NULL, 0), |
504 | |
505 | SND_SOC_DAPM_PGA("Mic Diff" , JZ4770_CODEC_REG_CR_MIC, |
506 | REG_CR_MIC_IDIFF_OFFSET, 0, NULL, 0), |
507 | |
508 | SND_SOC_DAPM_MIXER("Mic" , SND_SOC_NOPM, 0, 0, |
509 | jz4770_codec_mic_controls, |
510 | ARRAY_SIZE(jz4770_codec_mic_controls)), |
511 | |
512 | SND_SOC_DAPM_PGA("Line In Bypass" , JZ4770_CODEC_REG_CR_LI, |
513 | REG_CR_LI_LIBY_OFFSET, 1, NULL, 0), |
514 | |
515 | SND_SOC_DAPM_ADC_E("ADC" , "HiFi Capture" , JZ4770_CODEC_REG_CR_ADC, |
516 | REG_CR_ADC_SB_OFFSET, 1, adc_poweron_event, |
517 | SND_SOC_DAPM_POST_PMU), |
518 | SND_SOC_DAPM_DAC("DAC" , "HiFi Playback" , JZ4770_CODEC_REG_CR_DAC, |
519 | REG_CR_DAC_SB_OFFSET, 1), |
520 | |
521 | SND_SOC_DAPM_MIXER("PCM Playback" , SND_SOC_NOPM, 0, 0, |
522 | jz4770_codec_pcm_playback_controls, |
523 | ARRAY_SIZE(jz4770_codec_pcm_playback_controls)), |
524 | SND_SOC_DAPM_MIXER("Headphones Playback" , SND_SOC_NOPM, 0, 0, |
525 | jz4770_codec_hp_playback_controls, |
526 | ARRAY_SIZE(jz4770_codec_hp_playback_controls)), |
527 | |
528 | SND_SOC_DAPM_SUPPLY("MICBIAS" , JZ4770_CODEC_REG_CR_MIC, |
529 | REG_CR_MIC_BIAS_SB_OFFSET, 1, NULL, 0), |
530 | |
531 | SND_SOC_DAPM_SUPPLY("Cap-less" , JZ4770_CODEC_REG_CR_HP, |
532 | REG_CR_HP_SB_HPCM_OFFSET, 1, NULL, 0), |
533 | |
534 | SND_SOC_DAPM_INPUT("MIC1P" ), |
535 | SND_SOC_DAPM_INPUT("MIC1N" ), |
536 | SND_SOC_DAPM_INPUT("MIC2P" ), |
537 | SND_SOC_DAPM_INPUT("MIC2N" ), |
538 | |
539 | SND_SOC_DAPM_OUTPUT("LOUT" ), |
540 | SND_SOC_DAPM_OUTPUT("ROUT" ), |
541 | |
542 | SND_SOC_DAPM_OUTPUT("LHPOUT" ), |
543 | SND_SOC_DAPM_OUTPUT("RHPOUT" ), |
544 | |
545 | SND_SOC_DAPM_INPUT("LLINEIN" ), |
546 | SND_SOC_DAPM_INPUT("RLINEIN" ), |
547 | |
548 | SND_SOC_DAPM_OUTPUT("SYSCLK" ), |
549 | }; |
550 | |
551 | /* Unconditional routes. */ |
552 | static const struct snd_soc_dapm_route jz4770_codec_dapm_routes[] = { |
553 | { "Mic 1" , NULL, "MIC1P" }, |
554 | { "Mic Diff" , NULL, "MIC1N" }, |
555 | { "Mic 1" , NULL, "Mic Diff" }, |
556 | { "Mic 2" , NULL, "MIC2P" }, |
557 | { "Mic Diff" , NULL, "MIC2N" }, |
558 | { "Mic 2" , NULL, "Mic Diff" }, |
559 | |
560 | { "Line In" , NULL, "LLINEIN" }, |
561 | { "Line In" , NULL, "RLINEIN" }, |
562 | |
563 | { "Mic" , "Stereo Capture Switch" , "Mic 1" }, |
564 | { "Mic" , "Stereo Capture Switch" , "Mic 2" }, |
565 | { "Headphones Source" , "Mic 1" , "Mic" }, |
566 | { "Headphones Source" , "Mic 2" , "Mic" }, |
567 | { "Capture Source" , "Mic 1" , "Mic" }, |
568 | { "Capture Source" , "Mic 2" , "Mic" }, |
569 | |
570 | { "Headphones Source" , "Mic 1" , "Mic 1" }, |
571 | { "Headphones Source" , "Mic 2" , "Mic 2" }, |
572 | { "Headphones Source" , "Line In" , "Line In Bypass" }, |
573 | { "Headphones Source" , "PCM" , "Headphones Playback" }, |
574 | { "HP Out" , NULL, "Headphones Source" }, |
575 | |
576 | { "Capture Source" , "Line In" , "Line In" }, |
577 | { "Capture Source" , "Mic 1" , "Mic 1" }, |
578 | { "Capture Source" , "Mic 2" , "Mic 2" }, |
579 | { "ADC" , NULL, "Capture Source" }, |
580 | |
581 | { "Line In Bypass" , NULL, "Line In" }, |
582 | { "Line Out Source" , "Line In" , "Line In Bypass" }, |
583 | { "Line Out Source" , "PCM" , "PCM Playback" }, |
584 | |
585 | { "LHPOUT" , NULL, "HP Out" }, |
586 | { "RHPOUT" , NULL, "HP Out" }, |
587 | |
588 | { "Line Out" , NULL, "Line Out Source" }, |
589 | { "Line Out Switch 2" , NULL, "Line Out" }, |
590 | |
591 | { "LOUT" , NULL, "Line Out Switch 2" }, |
592 | { "ROUT" , NULL, "Line Out Switch 2" }, |
593 | |
594 | { "PCM Playback" , "Volume" , "DAC" }, |
595 | { "Headphones Playback" , "Volume" , "PCM Playback" }, |
596 | |
597 | { "SYSCLK" , NULL, "DAC" }, |
598 | }; |
599 | |
600 | static void jz4770_codec_codec_init_regs(struct snd_soc_component *codec) |
601 | { |
602 | struct jz_codec *jz_codec = snd_soc_component_get_drvdata(c: codec); |
603 | struct regmap *regmap = jz_codec->regmap; |
604 | |
605 | /* Collect updates for later sending. */ |
606 | regcache_cache_only(map: regmap, enable: true); |
607 | |
608 | /* default HP output to PCM */ |
609 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_HP, REG_CR_HP_SEL_MASK); |
610 | |
611 | /* default line output to PCM */ |
612 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_LO, REG_CR_LO_SEL_MASK); |
613 | |
614 | /* Disable stereo mic */ |
615 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_MIC, |
616 | BIT(REG_CR_MIC_STEREO_OFFSET)); |
617 | |
618 | /* Set mic 1 as default source for ADC */ |
619 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_ADC, |
620 | REG_CR_ADC_IN_SEL_MASK); |
621 | |
622 | /* ADC/DAC: serial + i2s */ |
623 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_AICR_ADC, |
624 | REG_AICR_ADC_SERIAL | REG_AICR_ADC_I2S); |
625 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_AICR_DAC, |
626 | REG_AICR_DAC_SERIAL | REG_AICR_DAC_I2S); |
627 | |
628 | /* The generated IRQ is a high level */ |
629 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_ICR, REG_ICR_INT_FORM_MASK); |
630 | regmap_update_bits(map: regmap, reg: JZ4770_CODEC_REG_IMR, REG_IMR_ALL_MASK, |
631 | REG_IMR_JACK_MASK | REG_IMR_RUP_MASK | |
632 | REG_IMR_RDO_MASK | REG_IMR_GUP_MASK | |
633 | REG_IMR_GDO_MASK); |
634 | |
635 | /* 12M oscillator */ |
636 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_CCR, REG_CCR_CRYSTAL_MASK); |
637 | |
638 | /* 0: 16ohm/220uF, 1: 10kohm/1uF */ |
639 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_HP, REG_CR_HP_LOAD); |
640 | |
641 | /* disable automatic gain */ |
642 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_AGC1, REG_AGC1_EN); |
643 | |
644 | /* Disable DAC lrswap */ |
645 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_DAC, REG_CR_DAC_LRSWAP); |
646 | |
647 | /* Independent L/R DAC gain control */ |
648 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_GCR_DACL, |
649 | REG_GCR_DACL_RLGOD); |
650 | |
651 | /* Disable ADC lrswap */ |
652 | regmap_set_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_ADC, REG_CR_ADC_LRSWAP); |
653 | |
654 | /* default to cap-less mode(0) */ |
655 | regmap_clear_bits(map: regmap, reg: JZ4770_CODEC_REG_CR_HP, |
656 | BIT(REG_CR_HP_SB_HPCM_OFFSET)); |
657 | |
658 | /* Send collected updates. */ |
659 | regcache_cache_only(map: regmap, enable: false); |
660 | regcache_sync(map: regmap); |
661 | } |
662 | |
663 | static int jz4770_codec_codec_probe(struct snd_soc_component *codec) |
664 | { |
665 | struct jz_codec *jz_codec = snd_soc_component_get_drvdata(c: codec); |
666 | |
667 | clk_prepare_enable(clk: jz_codec->clk); |
668 | |
669 | jz4770_codec_codec_init_regs(codec); |
670 | |
671 | return 0; |
672 | } |
673 | |
674 | static void jz4770_codec_codec_remove(struct snd_soc_component *codec) |
675 | { |
676 | struct jz_codec *jz_codec = snd_soc_component_get_drvdata(c: codec); |
677 | |
678 | clk_disable_unprepare(clk: jz_codec->clk); |
679 | } |
680 | |
681 | static const struct snd_soc_component_driver jz4770_codec_soc_codec_dev = { |
682 | .probe = jz4770_codec_codec_probe, |
683 | .remove = jz4770_codec_codec_remove, |
684 | .set_bias_level = jz4770_codec_set_bias_level, |
685 | .controls = jz4770_codec_snd_controls, |
686 | .num_controls = ARRAY_SIZE(jz4770_codec_snd_controls), |
687 | .dapm_widgets = jz4770_codec_dapm_widgets, |
688 | .num_dapm_widgets = ARRAY_SIZE(jz4770_codec_dapm_widgets), |
689 | .dapm_routes = jz4770_codec_dapm_routes, |
690 | .num_dapm_routes = ARRAY_SIZE(jz4770_codec_dapm_routes), |
691 | .suspend_bias_off = 1, |
692 | .use_pmdown_time = 1, |
693 | }; |
694 | |
695 | static const unsigned int jz4770_codec_sample_rates[] = { |
696 | 96000, 48000, 44100, 32000, |
697 | 24000, 22050, 16000, 12000, |
698 | 11025, 9600, 8000, |
699 | }; |
700 | |
701 | static int jz4770_codec_hw_params(struct snd_pcm_substream *substream, |
702 | struct snd_pcm_hw_params *params, |
703 | struct snd_soc_dai *dai) |
704 | { |
705 | struct jz_codec *codec = snd_soc_component_get_drvdata(c: dai->component); |
706 | unsigned int rate, bit_width; |
707 | |
708 | switch (params_format(p: params)) { |
709 | case SNDRV_PCM_FORMAT_S16_LE: |
710 | bit_width = 0; |
711 | break; |
712 | case SNDRV_PCM_FORMAT_S18_3LE: |
713 | bit_width = 1; |
714 | break; |
715 | case SNDRV_PCM_FORMAT_S20_3LE: |
716 | bit_width = 2; |
717 | break; |
718 | case SNDRV_PCM_FORMAT_S24_3LE: |
719 | bit_width = 3; |
720 | break; |
721 | default: |
722 | return -EINVAL; |
723 | } |
724 | |
725 | for (rate = 0; rate < ARRAY_SIZE(jz4770_codec_sample_rates); rate++) { |
726 | if (jz4770_codec_sample_rates[rate] == params_rate(p: params)) |
727 | break; |
728 | } |
729 | |
730 | if (rate == ARRAY_SIZE(jz4770_codec_sample_rates)) |
731 | return -EINVAL; |
732 | |
733 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
734 | regmap_update_bits(map: codec->regmap, reg: JZ4770_CODEC_REG_AICR_DAC, |
735 | REG_AICR_DAC_ADWL_MASK, |
736 | val: bit_width << REG_AICR_DAC_ADWL_OFFSET); |
737 | regmap_update_bits(map: codec->regmap, reg: JZ4770_CODEC_REG_FCR_DAC, |
738 | REG_FCR_DAC_FREQ_MASK, |
739 | val: rate << REG_FCR_DAC_FREQ_OFFSET); |
740 | } else { |
741 | regmap_update_bits(map: codec->regmap, reg: JZ4770_CODEC_REG_AICR_ADC, |
742 | REG_AICR_ADC_ADWL_MASK, |
743 | val: bit_width << REG_AICR_ADC_ADWL_OFFSET); |
744 | regmap_update_bits(map: codec->regmap, reg: JZ4770_CODEC_REG_FCR_ADC, |
745 | REG_FCR_ADC_FREQ_MASK, |
746 | val: rate << REG_FCR_ADC_FREQ_OFFSET); |
747 | } |
748 | |
749 | return 0; |
750 | } |
751 | |
752 | static const struct snd_soc_dai_ops jz4770_codec_dai_ops = { |
753 | .startup = jz4770_codec_startup, |
754 | .shutdown = jz4770_codec_shutdown, |
755 | .hw_params = jz4770_codec_hw_params, |
756 | .trigger = jz4770_codec_pcm_trigger, |
757 | .mute_stream = jz4770_codec_mute_stream, |
758 | .no_capture_mute = 1, |
759 | }; |
760 | |
761 | #define JZ_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ |
762 | SNDRV_PCM_FMTBIT_S18_3LE | \ |
763 | SNDRV_PCM_FMTBIT_S20_3LE | \ |
764 | SNDRV_PCM_FMTBIT_S24_3LE) |
765 | |
766 | static struct snd_soc_dai_driver jz4770_codec_dai = { |
767 | .name = "jz4770-hifi" , |
768 | .playback = { |
769 | .stream_name = "Playback" , |
770 | .channels_min = 2, |
771 | .channels_max = 2, |
772 | .rates = SNDRV_PCM_RATE_8000_96000, |
773 | .formats = JZ_CODEC_FORMATS, |
774 | }, |
775 | .capture = { |
776 | .stream_name = "Capture" , |
777 | .channels_min = 2, |
778 | .channels_max = 2, |
779 | .rates = SNDRV_PCM_RATE_8000_96000, |
780 | .formats = JZ_CODEC_FORMATS, |
781 | }, |
782 | .ops = &jz4770_codec_dai_ops, |
783 | }; |
784 | |
785 | static bool jz4770_codec_volatile(struct device *dev, unsigned int reg) |
786 | { |
787 | return reg == JZ4770_CODEC_REG_SR || reg == JZ4770_CODEC_REG_IFR; |
788 | } |
789 | |
790 | static bool jz4770_codec_readable(struct device *dev, unsigned int reg) |
791 | { |
792 | switch (reg) { |
793 | case JZ4770_CODEC_REG_MISSING_REG1: |
794 | case JZ4770_CODEC_REG_MISSING_REG2: |
795 | return false; |
796 | default: |
797 | return true; |
798 | } |
799 | } |
800 | |
801 | static bool jz4770_codec_writeable(struct device *dev, unsigned int reg) |
802 | { |
803 | switch (reg) { |
804 | case JZ4770_CODEC_REG_SR: |
805 | case JZ4770_CODEC_REG_MISSING_REG1: |
806 | case JZ4770_CODEC_REG_MISSING_REG2: |
807 | return false; |
808 | default: |
809 | return true; |
810 | } |
811 | } |
812 | |
813 | static int jz4770_codec_io_wait(struct jz_codec *codec) |
814 | { |
815 | u32 reg; |
816 | |
817 | return readl_poll_timeout(codec->base + ICDC_RGADW_OFFSET, reg, |
818 | !(reg & ICDC_RGADW_RGWR), |
819 | 1000, 1 * USEC_PER_SEC); |
820 | } |
821 | |
822 | static int jz4770_codec_reg_read(void *context, unsigned int reg, |
823 | unsigned int *val) |
824 | { |
825 | struct jz_codec *codec = context; |
826 | unsigned int i; |
827 | u32 tmp; |
828 | int ret; |
829 | |
830 | ret = jz4770_codec_io_wait(codec); |
831 | if (ret) |
832 | return ret; |
833 | |
834 | tmp = readl(addr: codec->base + ICDC_RGADW_OFFSET); |
835 | tmp = (tmp & ~ICDC_RGADW_RGADDR_MASK) |
836 | | (reg << ICDC_RGADW_RGADDR_OFFSET); |
837 | writel(val: tmp, addr: codec->base + ICDC_RGADW_OFFSET); |
838 | |
839 | /* wait 6+ cycles */ |
840 | for (i = 0; i < 6; i++) |
841 | *val = readl(addr: codec->base + ICDC_RGDATA_OFFSET) & |
842 | ICDC_RGDATA_RGDOUT_MASK; |
843 | |
844 | return 0; |
845 | } |
846 | |
847 | static int jz4770_codec_reg_write(void *context, unsigned int reg, |
848 | unsigned int val) |
849 | { |
850 | struct jz_codec *codec = context; |
851 | int ret; |
852 | |
853 | ret = jz4770_codec_io_wait(codec); |
854 | if (ret) |
855 | return ret; |
856 | |
857 | writel(ICDC_RGADW_RGWR | (reg << ICDC_RGADW_RGADDR_OFFSET) | val, |
858 | addr: codec->base + ICDC_RGADW_OFFSET); |
859 | |
860 | ret = jz4770_codec_io_wait(codec); |
861 | if (ret) |
862 | return ret; |
863 | |
864 | return 0; |
865 | } |
866 | |
867 | static const u8 jz4770_codec_reg_defaults[] = { |
868 | 0x00, 0xC3, 0xC3, 0x90, 0x98, 0xFF, 0x90, 0xB1, |
869 | 0x11, 0x10, 0x00, 0x03, 0x00, 0x00, 0x40, 0x00, |
870 | 0xFF, 0x00, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, |
871 | 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x34, |
872 | 0x07, 0x44, 0x1F, 0x00 |
873 | }; |
874 | |
875 | static struct regmap_config jz4770_codec_regmap_config = { |
876 | .reg_bits = 7, |
877 | .val_bits = 8, |
878 | |
879 | .max_register = JZ4770_CODEC_REG_AGC5, |
880 | .volatile_reg = jz4770_codec_volatile, |
881 | .readable_reg = jz4770_codec_readable, |
882 | .writeable_reg = jz4770_codec_writeable, |
883 | |
884 | .reg_read = jz4770_codec_reg_read, |
885 | .reg_write = jz4770_codec_reg_write, |
886 | |
887 | .reg_defaults_raw = jz4770_codec_reg_defaults, |
888 | .num_reg_defaults_raw = ARRAY_SIZE(jz4770_codec_reg_defaults), |
889 | .cache_type = REGCACHE_FLAT, |
890 | }; |
891 | |
892 | static int jz4770_codec_probe(struct platform_device *pdev) |
893 | { |
894 | struct device *dev = &pdev->dev; |
895 | struct jz_codec *codec; |
896 | int ret; |
897 | |
898 | codec = devm_kzalloc(dev, size: sizeof(*codec), GFP_KERNEL); |
899 | if (!codec) |
900 | return -ENOMEM; |
901 | |
902 | codec->dev = dev; |
903 | |
904 | codec->base = devm_platform_ioremap_resource(pdev, index: 0); |
905 | if (IS_ERR(ptr: codec->base)) |
906 | return PTR_ERR(ptr: codec->base); |
907 | |
908 | codec->regmap = devm_regmap_init(dev, NULL, codec, |
909 | &jz4770_codec_regmap_config); |
910 | if (IS_ERR(ptr: codec->regmap)) |
911 | return PTR_ERR(ptr: codec->regmap); |
912 | |
913 | codec->clk = devm_clk_get(dev, id: "aic" ); |
914 | if (IS_ERR(ptr: codec->clk)) |
915 | return PTR_ERR(ptr: codec->clk); |
916 | |
917 | platform_set_drvdata(pdev, data: codec); |
918 | |
919 | ret = devm_snd_soc_register_component(dev, component_driver: &jz4770_codec_soc_codec_dev, |
920 | dai_drv: &jz4770_codec_dai, num_dai: 1); |
921 | if (ret) { |
922 | dev_err(dev, "Failed to register codec: %d\n" , ret); |
923 | return ret; |
924 | } |
925 | |
926 | return 0; |
927 | } |
928 | |
929 | static const struct of_device_id jz4770_codec_of_matches[] = { |
930 | { .compatible = "ingenic,jz4770-codec" , }, |
931 | { /* sentinel */ } |
932 | }; |
933 | MODULE_DEVICE_TABLE(of, jz4770_codec_of_matches); |
934 | |
935 | static struct platform_driver jz4770_codec_driver = { |
936 | .probe = jz4770_codec_probe, |
937 | .driver = { |
938 | .name = "jz4770-codec" , |
939 | .of_match_table = jz4770_codec_of_matches, |
940 | }, |
941 | }; |
942 | module_platform_driver(jz4770_codec_driver); |
943 | |
944 | MODULE_DESCRIPTION("JZ4770 SoC internal codec driver" ); |
945 | MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>" ); |
946 | MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>" ); |
947 | MODULE_LICENSE("GPL v2" ); |
948 | |