1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver of Inno codec for rk3036 by Rockchip Inc. |
4 | * |
5 | * Author: Rockchip Inc. |
6 | * Author: Zheng ShunQian<zhengsq@rock-chips.com> |
7 | */ |
8 | |
9 | #include <sound/soc.h> |
10 | #include <sound/tlv.h> |
11 | #include <sound/soc-dapm.h> |
12 | #include <sound/soc-dai.h> |
13 | #include <sound/pcm.h> |
14 | #include <sound/pcm_params.h> |
15 | |
16 | #include <linux/platform_device.h> |
17 | #include <linux/of.h> |
18 | #include <linux/clk.h> |
19 | #include <linux/regmap.h> |
20 | #include <linux/device.h> |
21 | #include <linux/mfd/syscon.h> |
22 | #include <linux/module.h> |
23 | #include <linux/io.h> |
24 | |
25 | #include "inno_rk3036.h" |
26 | |
27 | struct rk3036_codec_priv { |
28 | void __iomem *base; |
29 | struct clk *pclk; |
30 | struct regmap *regmap; |
31 | struct device *dev; |
32 | }; |
33 | |
34 | static const DECLARE_TLV_DB_MINMAX(rk3036_codec_hp_tlv, -39, 0); |
35 | |
36 | static int rk3036_codec_antipop_info(struct snd_kcontrol *kcontrol, |
37 | struct snd_ctl_elem_info *uinfo) |
38 | { |
39 | uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; |
40 | uinfo->count = 2; |
41 | uinfo->value.integer.min = 0; |
42 | uinfo->value.integer.max = 1; |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | static int rk3036_codec_antipop_get(struct snd_kcontrol *kcontrol, |
48 | struct snd_ctl_elem_value *ucontrol) |
49 | { |
50 | struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
51 | int val, regval; |
52 | |
53 | regval = snd_soc_component_read(component, INNO_R09); |
54 | val = ((regval >> INNO_R09_HPL_ANITPOP_SHIFT) & |
55 | INNO_R09_HP_ANTIPOP_MSK) == INNO_R09_HP_ANTIPOP_ON; |
56 | ucontrol->value.integer.value[0] = val; |
57 | |
58 | val = ((regval >> INNO_R09_HPR_ANITPOP_SHIFT) & |
59 | INNO_R09_HP_ANTIPOP_MSK) == INNO_R09_HP_ANTIPOP_ON; |
60 | ucontrol->value.integer.value[1] = val; |
61 | |
62 | return 0; |
63 | } |
64 | |
65 | static int rk3036_codec_antipop_put(struct snd_kcontrol *kcontrol, |
66 | struct snd_ctl_elem_value *ucontrol) |
67 | { |
68 | struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
69 | int val, ret, regmsk; |
70 | |
71 | val = (ucontrol->value.integer.value[0] ? |
72 | INNO_R09_HP_ANTIPOP_ON : INNO_R09_HP_ANTIPOP_OFF) << |
73 | INNO_R09_HPL_ANITPOP_SHIFT; |
74 | val |= (ucontrol->value.integer.value[1] ? |
75 | INNO_R09_HP_ANTIPOP_ON : INNO_R09_HP_ANTIPOP_OFF) << |
76 | INNO_R09_HPR_ANITPOP_SHIFT; |
77 | |
78 | regmsk = INNO_R09_HP_ANTIPOP_MSK << INNO_R09_HPL_ANITPOP_SHIFT | |
79 | INNO_R09_HP_ANTIPOP_MSK << INNO_R09_HPR_ANITPOP_SHIFT; |
80 | |
81 | ret = snd_soc_component_update_bits(component, INNO_R09, |
82 | mask: regmsk, val); |
83 | if (ret < 0) |
84 | return ret; |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | #define SOC_RK3036_CODEC_ANTIPOP_DECL(xname) \ |
90 | { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ |
91 | .info = rk3036_codec_antipop_info, .get = rk3036_codec_antipop_get, \ |
92 | .put = rk3036_codec_antipop_put, } |
93 | |
94 | static const struct snd_kcontrol_new rk3036_codec_dapm_controls[] = { |
95 | SOC_DOUBLE_R_RANGE_TLV("Headphone Volume" , INNO_R07, INNO_R08, |
96 | INNO_HP_GAIN_SHIFT, INNO_HP_GAIN_N39DB, |
97 | INNO_HP_GAIN_0DB, 0, rk3036_codec_hp_tlv), |
98 | SOC_DOUBLE("Zero Cross Switch" , INNO_R06, INNO_R06_VOUTL_CZ_SHIFT, |
99 | INNO_R06_VOUTR_CZ_SHIFT, 1, 0), |
100 | SOC_DOUBLE("Headphone Switch" , INNO_R09, INNO_R09_HPL_MUTE_SHIFT, |
101 | INNO_R09_HPR_MUTE_SHIFT, 1, 0), |
102 | SOC_RK3036_CODEC_ANTIPOP_DECL("Anti-pop Switch" ), |
103 | }; |
104 | |
105 | static const struct snd_kcontrol_new rk3036_codec_hpl_mixer_controls[] = { |
106 | SOC_DAPM_SINGLE("DAC Left Out Switch" , INNO_R09, |
107 | INNO_R09_DACL_SWITCH_SHIFT, 1, 0), |
108 | }; |
109 | |
110 | static const struct snd_kcontrol_new rk3036_codec_hpr_mixer_controls[] = { |
111 | SOC_DAPM_SINGLE("DAC Right Out Switch" , INNO_R09, |
112 | INNO_R09_DACR_SWITCH_SHIFT, 1, 0), |
113 | }; |
114 | |
115 | static const struct snd_kcontrol_new rk3036_codec_hpl_switch_controls[] = { |
116 | SOC_DAPM_SINGLE("HP Left Out Switch" , INNO_R05, |
117 | INNO_R05_HPL_WORK_SHIFT, 1, 0), |
118 | }; |
119 | |
120 | static const struct snd_kcontrol_new rk3036_codec_hpr_switch_controls[] = { |
121 | SOC_DAPM_SINGLE("HP Right Out Switch" , INNO_R05, |
122 | INNO_R05_HPR_WORK_SHIFT, 1, 0), |
123 | }; |
124 | |
125 | static const struct snd_soc_dapm_widget rk3036_codec_dapm_widgets[] = { |
126 | SND_SOC_DAPM_SUPPLY_S("DAC PWR" , 1, INNO_R06, |
127 | INNO_R06_DAC_EN_SHIFT, 0, NULL, 0), |
128 | SND_SOC_DAPM_SUPPLY_S("DACL VREF" , 2, INNO_R04, |
129 | INNO_R04_DACL_VREF_SHIFT, 0, NULL, 0), |
130 | SND_SOC_DAPM_SUPPLY_S("DACR VREF" , 2, INNO_R04, |
131 | INNO_R04_DACR_VREF_SHIFT, 0, NULL, 0), |
132 | SND_SOC_DAPM_SUPPLY_S("DACL HiLo VREF" , 3, INNO_R06, |
133 | INNO_R06_DACL_HILO_VREF_SHIFT, 0, NULL, 0), |
134 | SND_SOC_DAPM_SUPPLY_S("DACR HiLo VREF" , 3, INNO_R06, |
135 | INNO_R06_DACR_HILO_VREF_SHIFT, 0, NULL, 0), |
136 | SND_SOC_DAPM_SUPPLY_S("DACR CLK" , 3, INNO_R04, |
137 | INNO_R04_DACR_CLK_SHIFT, 0, NULL, 0), |
138 | SND_SOC_DAPM_SUPPLY_S("DACL CLK" , 3, INNO_R04, |
139 | INNO_R04_DACL_CLK_SHIFT, 0, NULL, 0), |
140 | |
141 | SND_SOC_DAPM_DAC("DACL" , "Left Playback" , INNO_R04, |
142 | INNO_R04_DACL_SW_SHIFT, 0), |
143 | SND_SOC_DAPM_DAC("DACR" , "Right Playback" , INNO_R04, |
144 | INNO_R04_DACR_SW_SHIFT, 0), |
145 | |
146 | SND_SOC_DAPM_MIXER("Left Headphone Mixer" , SND_SOC_NOPM, 0, 0, |
147 | rk3036_codec_hpl_mixer_controls, |
148 | ARRAY_SIZE(rk3036_codec_hpl_mixer_controls)), |
149 | SND_SOC_DAPM_MIXER("Right Headphone Mixer" , SND_SOC_NOPM, 0, 0, |
150 | rk3036_codec_hpr_mixer_controls, |
151 | ARRAY_SIZE(rk3036_codec_hpr_mixer_controls)), |
152 | |
153 | SND_SOC_DAPM_PGA("HP Left Out" , INNO_R05, |
154 | INNO_R05_HPL_EN_SHIFT, 0, NULL, 0), |
155 | SND_SOC_DAPM_PGA("HP Right Out" , INNO_R05, |
156 | INNO_R05_HPR_EN_SHIFT, 0, NULL, 0), |
157 | |
158 | SND_SOC_DAPM_MIXER("HP Left Switch" , SND_SOC_NOPM, 0, 0, |
159 | rk3036_codec_hpl_switch_controls, |
160 | ARRAY_SIZE(rk3036_codec_hpl_switch_controls)), |
161 | SND_SOC_DAPM_MIXER("HP Right Switch" , SND_SOC_NOPM, 0, 0, |
162 | rk3036_codec_hpr_switch_controls, |
163 | ARRAY_SIZE(rk3036_codec_hpr_switch_controls)), |
164 | |
165 | SND_SOC_DAPM_OUTPUT("HPL" ), |
166 | SND_SOC_DAPM_OUTPUT("HPR" ), |
167 | }; |
168 | |
169 | static const struct snd_soc_dapm_route rk3036_codec_dapm_routes[] = { |
170 | {"DACL VREF" , NULL, "DAC PWR" }, |
171 | {"DACR VREF" , NULL, "DAC PWR" }, |
172 | {"DACL HiLo VREF" , NULL, "DAC PWR" }, |
173 | {"DACR HiLo VREF" , NULL, "DAC PWR" }, |
174 | {"DACL CLK" , NULL, "DAC PWR" }, |
175 | {"DACR CLK" , NULL, "DAC PWR" }, |
176 | |
177 | {"DACL" , NULL, "DACL VREF" }, |
178 | {"DACL" , NULL, "DACL HiLo VREF" }, |
179 | {"DACL" , NULL, "DACL CLK" }, |
180 | {"DACR" , NULL, "DACR VREF" }, |
181 | {"DACR" , NULL, "DACR HiLo VREF" }, |
182 | {"DACR" , NULL, "DACR CLK" }, |
183 | |
184 | {"Left Headphone Mixer" , "DAC Left Out Switch" , "DACL" }, |
185 | {"Right Headphone Mixer" , "DAC Right Out Switch" , "DACR" }, |
186 | {"HP Left Out" , NULL, "Left Headphone Mixer" }, |
187 | {"HP Right Out" , NULL, "Right Headphone Mixer" }, |
188 | |
189 | {"HP Left Switch" , "HP Left Out Switch" , "HP Left Out" }, |
190 | {"HP Right Switch" , "HP Right Out Switch" , "HP Right Out" }, |
191 | |
192 | {"HPL" , NULL, "HP Left Switch" }, |
193 | {"HPR" , NULL, "HP Right Switch" }, |
194 | }; |
195 | |
196 | static int rk3036_codec_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
197 | { |
198 | struct snd_soc_component *component = dai->component; |
199 | unsigned int reg01_val = 0, reg02_val = 0, reg03_val = 0; |
200 | |
201 | dev_dbg(component->dev, "rk3036_codec dai set fmt : %08x\n" , fmt); |
202 | |
203 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
204 | case SND_SOC_DAIFMT_CBC_CFC: |
205 | reg01_val |= INNO_R01_PINDIR_IN_SLAVE | |
206 | INNO_R01_I2SMODE_SLAVE; |
207 | break; |
208 | case SND_SOC_DAIFMT_CBP_CFP: |
209 | reg01_val |= INNO_R01_PINDIR_OUT_MASTER | |
210 | INNO_R01_I2SMODE_MASTER; |
211 | break; |
212 | default: |
213 | dev_err(component->dev, "invalid fmt\n" ); |
214 | return -EINVAL; |
215 | } |
216 | |
217 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
218 | case SND_SOC_DAIFMT_DSP_A: |
219 | reg02_val |= INNO_R02_DACM_PCM; |
220 | break; |
221 | case SND_SOC_DAIFMT_I2S: |
222 | reg02_val |= INNO_R02_DACM_I2S; |
223 | break; |
224 | case SND_SOC_DAIFMT_RIGHT_J: |
225 | reg02_val |= INNO_R02_DACM_RJM; |
226 | break; |
227 | case SND_SOC_DAIFMT_LEFT_J: |
228 | reg02_val |= INNO_R02_DACM_LJM; |
229 | break; |
230 | default: |
231 | dev_err(component->dev, "set dai format failed\n" ); |
232 | return -EINVAL; |
233 | } |
234 | |
235 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
236 | case SND_SOC_DAIFMT_NB_NF: |
237 | reg02_val |= INNO_R02_LRCP_NORMAL; |
238 | reg03_val |= INNO_R03_BCP_NORMAL; |
239 | break; |
240 | case SND_SOC_DAIFMT_IB_IF: |
241 | reg02_val |= INNO_R02_LRCP_REVERSAL; |
242 | reg03_val |= INNO_R03_BCP_REVERSAL; |
243 | break; |
244 | case SND_SOC_DAIFMT_IB_NF: |
245 | reg02_val |= INNO_R02_LRCP_REVERSAL; |
246 | reg03_val |= INNO_R03_BCP_NORMAL; |
247 | break; |
248 | case SND_SOC_DAIFMT_NB_IF: |
249 | reg02_val |= INNO_R02_LRCP_NORMAL; |
250 | reg03_val |= INNO_R03_BCP_REVERSAL; |
251 | break; |
252 | default: |
253 | dev_err(component->dev, "set dai format failed\n" ); |
254 | return -EINVAL; |
255 | } |
256 | |
257 | snd_soc_component_update_bits(component, INNO_R01, INNO_R01_I2SMODE_MSK | |
258 | INNO_R01_PINDIR_MSK, val: reg01_val); |
259 | snd_soc_component_update_bits(component, INNO_R02, INNO_R02_LRCP_MSK | |
260 | INNO_R02_DACM_MSK, val: reg02_val); |
261 | snd_soc_component_update_bits(component, INNO_R03, INNO_R03_BCP_MSK, val: reg03_val); |
262 | |
263 | return 0; |
264 | } |
265 | |
266 | static int rk3036_codec_dai_hw_params(struct snd_pcm_substream *substream, |
267 | struct snd_pcm_hw_params *hw_params, |
268 | struct snd_soc_dai *dai) |
269 | { |
270 | struct snd_soc_component *component = dai->component; |
271 | unsigned int reg02_val = 0, reg03_val = 0; |
272 | |
273 | switch (params_format(p: hw_params)) { |
274 | case SNDRV_PCM_FORMAT_S16_LE: |
275 | reg02_val |= INNO_R02_VWL_16BIT; |
276 | break; |
277 | case SNDRV_PCM_FORMAT_S20_3LE: |
278 | reg02_val |= INNO_R02_VWL_20BIT; |
279 | break; |
280 | case SNDRV_PCM_FORMAT_S24_LE: |
281 | reg02_val |= INNO_R02_VWL_24BIT; |
282 | break; |
283 | case SNDRV_PCM_FORMAT_S32_LE: |
284 | reg02_val |= INNO_R02_VWL_32BIT; |
285 | break; |
286 | default: |
287 | return -EINVAL; |
288 | } |
289 | |
290 | reg02_val |= INNO_R02_LRCP_NORMAL; |
291 | reg03_val |= INNO_R03_FWL_32BIT | INNO_R03_DACR_WORK; |
292 | |
293 | snd_soc_component_update_bits(component, INNO_R02, INNO_R02_LRCP_MSK | |
294 | INNO_R02_VWL_MSK, val: reg02_val); |
295 | snd_soc_component_update_bits(component, INNO_R03, INNO_R03_DACR_MSK | |
296 | INNO_R03_FWL_MSK, val: reg03_val); |
297 | return 0; |
298 | } |
299 | |
300 | #define RK3036_CODEC_RATES (SNDRV_PCM_RATE_8000 | \ |
301 | SNDRV_PCM_RATE_16000 | \ |
302 | SNDRV_PCM_RATE_32000 | \ |
303 | SNDRV_PCM_RATE_44100 | \ |
304 | SNDRV_PCM_RATE_48000 | \ |
305 | SNDRV_PCM_RATE_96000) |
306 | |
307 | #define RK3036_CODEC_FMTS (SNDRV_PCM_FMTBIT_S16_LE | \ |
308 | SNDRV_PCM_FMTBIT_S20_3LE | \ |
309 | SNDRV_PCM_FMTBIT_S24_LE | \ |
310 | SNDRV_PCM_FMTBIT_S32_LE) |
311 | |
312 | static const struct snd_soc_dai_ops rk3036_codec_dai_ops = { |
313 | .set_fmt = rk3036_codec_dai_set_fmt, |
314 | .hw_params = rk3036_codec_dai_hw_params, |
315 | }; |
316 | |
317 | static struct snd_soc_dai_driver rk3036_codec_dai_driver[] = { |
318 | { |
319 | .name = "rk3036-codec-dai" , |
320 | .playback = { |
321 | .stream_name = "Playback" , |
322 | .channels_min = 1, |
323 | .channels_max = 2, |
324 | .rates = RK3036_CODEC_RATES, |
325 | .formats = RK3036_CODEC_FMTS, |
326 | }, |
327 | .ops = &rk3036_codec_dai_ops, |
328 | .symmetric_rate = 1, |
329 | }, |
330 | }; |
331 | |
332 | static void rk3036_codec_reset(struct snd_soc_component *component) |
333 | { |
334 | snd_soc_component_write(component, INNO_R00, |
335 | INNO_R00_CSR_RESET | INNO_R00_CDCR_RESET); |
336 | snd_soc_component_write(component, INNO_R00, |
337 | INNO_R00_CSR_WORK | INNO_R00_CDCR_WORK); |
338 | } |
339 | |
340 | static int rk3036_codec_probe(struct snd_soc_component *component) |
341 | { |
342 | rk3036_codec_reset(component); |
343 | return 0; |
344 | } |
345 | |
346 | static void rk3036_codec_remove(struct snd_soc_component *component) |
347 | { |
348 | rk3036_codec_reset(component); |
349 | } |
350 | |
351 | static int rk3036_codec_set_bias_level(struct snd_soc_component *component, |
352 | enum snd_soc_bias_level level) |
353 | { |
354 | switch (level) { |
355 | case SND_SOC_BIAS_STANDBY: |
356 | /* set a big current for capacitor charging. */ |
357 | snd_soc_component_write(component, INNO_R10, INNO_R10_MAX_CUR); |
358 | /* start precharge */ |
359 | snd_soc_component_write(component, INNO_R06, INNO_R06_DAC_PRECHARGE); |
360 | |
361 | break; |
362 | |
363 | case SND_SOC_BIAS_OFF: |
364 | /* set a big current for capacitor discharging. */ |
365 | snd_soc_component_write(component, INNO_R10, INNO_R10_MAX_CUR); |
366 | /* start discharge. */ |
367 | snd_soc_component_write(component, INNO_R06, INNO_R06_DAC_DISCHARGE); |
368 | |
369 | break; |
370 | default: |
371 | break; |
372 | } |
373 | |
374 | return 0; |
375 | } |
376 | |
377 | static const struct snd_soc_component_driver rk3036_codec_driver = { |
378 | .probe = rk3036_codec_probe, |
379 | .remove = rk3036_codec_remove, |
380 | .set_bias_level = rk3036_codec_set_bias_level, |
381 | .controls = rk3036_codec_dapm_controls, |
382 | .num_controls = ARRAY_SIZE(rk3036_codec_dapm_controls), |
383 | .dapm_routes = rk3036_codec_dapm_routes, |
384 | .num_dapm_routes = ARRAY_SIZE(rk3036_codec_dapm_routes), |
385 | .dapm_widgets = rk3036_codec_dapm_widgets, |
386 | .num_dapm_widgets = ARRAY_SIZE(rk3036_codec_dapm_widgets), |
387 | .idle_bias_on = 1, |
388 | .use_pmdown_time = 1, |
389 | .endianness = 1, |
390 | }; |
391 | |
392 | static const struct regmap_config rk3036_codec_regmap_config = { |
393 | .reg_bits = 32, |
394 | .reg_stride = 4, |
395 | .val_bits = 32, |
396 | }; |
397 | |
398 | #define GRF_SOC_CON0 0x00140 |
399 | #define GRF_ACODEC_SEL (BIT(10) | BIT(16 + 10)) |
400 | |
401 | static int rk3036_codec_platform_probe(struct platform_device *pdev) |
402 | { |
403 | struct rk3036_codec_priv *priv; |
404 | struct device_node *of_node = pdev->dev.of_node; |
405 | void __iomem *base; |
406 | struct regmap *grf; |
407 | int ret; |
408 | |
409 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
410 | if (!priv) |
411 | return -ENOMEM; |
412 | |
413 | base = devm_platform_ioremap_resource(pdev, index: 0); |
414 | if (IS_ERR(ptr: base)) |
415 | return PTR_ERR(ptr: base); |
416 | |
417 | priv->base = base; |
418 | priv->regmap = devm_regmap_init_mmio(&pdev->dev, priv->base, |
419 | &rk3036_codec_regmap_config); |
420 | if (IS_ERR(ptr: priv->regmap)) { |
421 | dev_err(&pdev->dev, "init regmap failed\n" ); |
422 | return PTR_ERR(ptr: priv->regmap); |
423 | } |
424 | |
425 | grf = syscon_regmap_lookup_by_phandle(np: of_node, property: "rockchip,grf" ); |
426 | if (IS_ERR(ptr: grf)) { |
427 | dev_err(&pdev->dev, "needs 'rockchip,grf' property\n" ); |
428 | return PTR_ERR(ptr: grf); |
429 | } |
430 | ret = regmap_write(map: grf, GRF_SOC_CON0, GRF_ACODEC_SEL); |
431 | if (ret) { |
432 | dev_err(&pdev->dev, "Could not write to GRF: %d\n" , ret); |
433 | return ret; |
434 | } |
435 | |
436 | priv->pclk = devm_clk_get(dev: &pdev->dev, id: "acodec_pclk" ); |
437 | if (IS_ERR(ptr: priv->pclk)) |
438 | return PTR_ERR(ptr: priv->pclk); |
439 | |
440 | ret = clk_prepare_enable(clk: priv->pclk); |
441 | if (ret < 0) { |
442 | dev_err(&pdev->dev, "failed to enable clk\n" ); |
443 | return ret; |
444 | } |
445 | |
446 | priv->dev = &pdev->dev; |
447 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
448 | |
449 | ret = devm_snd_soc_register_component(dev: &pdev->dev, component_driver: &rk3036_codec_driver, |
450 | dai_drv: rk3036_codec_dai_driver, |
451 | ARRAY_SIZE(rk3036_codec_dai_driver)); |
452 | if (ret) { |
453 | clk_disable_unprepare(clk: priv->pclk); |
454 | dev_set_drvdata(dev: &pdev->dev, NULL); |
455 | } |
456 | |
457 | return ret; |
458 | } |
459 | |
460 | static void rk3036_codec_platform_remove(struct platform_device *pdev) |
461 | { |
462 | struct rk3036_codec_priv *priv = dev_get_drvdata(dev: &pdev->dev); |
463 | |
464 | clk_disable_unprepare(clk: priv->pclk); |
465 | } |
466 | |
467 | static const struct of_device_id rk3036_codec_of_match[] __maybe_unused = { |
468 | { .compatible = "rockchip,rk3036-codec" , }, |
469 | {} |
470 | }; |
471 | MODULE_DEVICE_TABLE(of, rk3036_codec_of_match); |
472 | |
473 | static struct platform_driver rk3036_codec_platform_driver = { |
474 | .driver = { |
475 | .name = "rk3036-codec-platform" , |
476 | .of_match_table = of_match_ptr(rk3036_codec_of_match), |
477 | }, |
478 | .probe = rk3036_codec_platform_probe, |
479 | .remove_new = rk3036_codec_platform_remove, |
480 | }; |
481 | |
482 | module_platform_driver(rk3036_codec_platform_driver); |
483 | |
484 | MODULE_AUTHOR("Rockchip Inc." ); |
485 | MODULE_DESCRIPTION("Rockchip rk3036 codec driver" ); |
486 | MODULE_LICENSE("GPL" ); |
487 | |