1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * ak4118.c -- Asahi Kasei ALSA Soc Audio driver |
4 | * |
5 | * Copyright 2018 DEVIALET |
6 | */ |
7 | |
8 | #include <linux/i2c.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/of_gpio.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/slab.h> |
15 | |
16 | #include <sound/asoundef.h> |
17 | #include <sound/core.h> |
18 | #include <sound/initval.h> |
19 | #include <sound/soc.h> |
20 | |
21 | #define AK4118_REG_CLK_PWR_CTL 0x00 |
22 | #define AK4118_REG_FORMAT_CTL 0x01 |
23 | #define AK4118_REG_IO_CTL0 0x02 |
24 | #define AK4118_REG_IO_CTL1 0x03 |
25 | #define AK4118_REG_INT0_MASK 0x04 |
26 | #define AK4118_REG_INT1_MASK 0x05 |
27 | #define AK4118_REG_RCV_STATUS0 0x06 |
28 | #define AK4118_REG_RCV_STATUS1 0x07 |
29 | #define AK4118_REG_RXCHAN_STATUS0 0x08 |
30 | #define AK4118_REG_RXCHAN_STATUS1 0x09 |
31 | #define AK4118_REG_RXCHAN_STATUS2 0x0a |
32 | #define AK4118_REG_RXCHAN_STATUS3 0x0b |
33 | #define AK4118_REG_RXCHAN_STATUS4 0x0c |
34 | #define AK4118_REG_TXCHAN_STATUS0 0x0d |
35 | #define AK4118_REG_TXCHAN_STATUS1 0x0e |
36 | #define AK4118_REG_TXCHAN_STATUS2 0x0f |
37 | #define AK4118_REG_TXCHAN_STATUS3 0x10 |
38 | #define AK4118_REG_TXCHAN_STATUS4 0x11 |
39 | #define AK4118_REG_BURST_PREAMB_PC0 0x12 |
40 | #define AK4118_REG_BURST_PREAMB_PC1 0x13 |
41 | #define AK4118_REG_BURST_PREAMB_PD0 0x14 |
42 | #define AK4118_REG_BURST_PREAMB_PD1 0x15 |
43 | #define AK4118_REG_QSUB_CTL 0x16 |
44 | #define AK4118_REG_QSUB_TRACK 0x17 |
45 | #define AK4118_REG_QSUB_INDEX 0x18 |
46 | #define AK4118_REG_QSUB_MIN 0x19 |
47 | #define AK4118_REG_QSUB_SEC 0x1a |
48 | #define AK4118_REG_QSUB_FRAME 0x1b |
49 | #define AK4118_REG_QSUB_ZERO 0x1c |
50 | #define AK4118_REG_QSUB_ABS_MIN 0x1d |
51 | #define AK4118_REG_QSUB_ABS_SEC 0x1e |
52 | #define AK4118_REG_QSUB_ABS_FRAME 0x1f |
53 | #define AK4118_REG_GPE 0x20 |
54 | #define AK4118_REG_GPDR 0x21 |
55 | #define AK4118_REG_GPSCR 0x22 |
56 | #define AK4118_REG_GPLR 0x23 |
57 | #define AK4118_REG_DAT_MASK_DTS 0x24 |
58 | #define AK4118_REG_RX_DETECT 0x25 |
59 | #define AK4118_REG_STC_DAT_DETECT 0x26 |
60 | #define AK4118_REG_RXCHAN_STATUS5 0x27 |
61 | #define AK4118_REG_TXCHAN_STATUS5 0x28 |
62 | #define AK4118_REG_MAX 0x29 |
63 | |
64 | #define AK4118_REG_FORMAT_CTL_DIF0 (1 << 4) |
65 | #define AK4118_REG_FORMAT_CTL_DIF1 (1 << 5) |
66 | #define AK4118_REG_FORMAT_CTL_DIF2 (1 << 6) |
67 | |
68 | struct ak4118_priv { |
69 | struct regmap *regmap; |
70 | struct gpio_desc *reset; |
71 | struct gpio_desc *irq; |
72 | struct snd_soc_component *component; |
73 | }; |
74 | |
75 | static const struct reg_default ak4118_reg_defaults[] = { |
76 | {AK4118_REG_CLK_PWR_CTL, 0x43}, |
77 | {AK4118_REG_FORMAT_CTL, 0x6a}, |
78 | {AK4118_REG_IO_CTL0, 0x88}, |
79 | {AK4118_REG_IO_CTL1, 0x48}, |
80 | {AK4118_REG_INT0_MASK, 0xee}, |
81 | {AK4118_REG_INT1_MASK, 0xb5}, |
82 | {AK4118_REG_RCV_STATUS0, 0x00}, |
83 | {AK4118_REG_RCV_STATUS1, 0x10}, |
84 | {AK4118_REG_TXCHAN_STATUS0, 0x00}, |
85 | {AK4118_REG_TXCHAN_STATUS1, 0x00}, |
86 | {AK4118_REG_TXCHAN_STATUS2, 0x00}, |
87 | {AK4118_REG_TXCHAN_STATUS3, 0x00}, |
88 | {AK4118_REG_TXCHAN_STATUS4, 0x00}, |
89 | {AK4118_REG_GPE, 0x77}, |
90 | {AK4118_REG_GPDR, 0x00}, |
91 | {AK4118_REG_GPSCR, 0x00}, |
92 | {AK4118_REG_GPLR, 0x00}, |
93 | {AK4118_REG_DAT_MASK_DTS, 0x3f}, |
94 | {AK4118_REG_RX_DETECT, 0x00}, |
95 | {AK4118_REG_STC_DAT_DETECT, 0x00}, |
96 | {AK4118_REG_TXCHAN_STATUS5, 0x00}, |
97 | }; |
98 | |
99 | static const char * const ak4118_input_select_txt[] = { |
100 | "RX0" , "RX1" , "RX2" , "RX3" , "RX4" , "RX5" , "RX6" , "RX7" , |
101 | }; |
102 | static SOC_ENUM_SINGLE_DECL(ak4118_insel_enum, AK4118_REG_IO_CTL1, 0x0, |
103 | ak4118_input_select_txt); |
104 | |
105 | static const struct snd_kcontrol_new ak4118_input_mux_controls = |
106 | SOC_DAPM_ENUM("Input Select" , ak4118_insel_enum); |
107 | |
108 | static const char * const ak4118_iec958_fs_txt[] = { |
109 | "44100" , "48000" , "32000" , "22050" , "11025" , "24000" , "16000" , "88200" , |
110 | "8000" , "96000" , "64000" , "176400" , "192000" , |
111 | }; |
112 | |
113 | static const int ak4118_iec958_fs_val[] = { |
114 | 0x0, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xE, |
115 | }; |
116 | |
117 | static SOC_VALUE_ENUM_SINGLE_DECL(ak4118_iec958_fs_enum, AK4118_REG_RCV_STATUS1, |
118 | 0x4, 0x4, ak4118_iec958_fs_txt, |
119 | ak4118_iec958_fs_val); |
120 | |
121 | static struct snd_kcontrol_new ak4118_iec958_controls[] = { |
122 | SOC_SINGLE("IEC958 Parity Errors" , AK4118_REG_RCV_STATUS0, 0, 1, 0), |
123 | SOC_SINGLE("IEC958 No Audio" , AK4118_REG_RCV_STATUS0, 1, 1, 0), |
124 | SOC_SINGLE("IEC958 PLL Lock" , AK4118_REG_RCV_STATUS0, 4, 1, 1), |
125 | SOC_SINGLE("IEC958 Non PCM" , AK4118_REG_RCV_STATUS0, 6, 1, 0), |
126 | SOC_ENUM("IEC958 Sampling Freq" , ak4118_iec958_fs_enum), |
127 | }; |
128 | |
129 | static const struct snd_soc_dapm_widget ak4118_dapm_widgets[] = { |
130 | SND_SOC_DAPM_INPUT("INRX0" ), |
131 | SND_SOC_DAPM_INPUT("INRX1" ), |
132 | SND_SOC_DAPM_INPUT("INRX2" ), |
133 | SND_SOC_DAPM_INPUT("INRX3" ), |
134 | SND_SOC_DAPM_INPUT("INRX4" ), |
135 | SND_SOC_DAPM_INPUT("INRX5" ), |
136 | SND_SOC_DAPM_INPUT("INRX6" ), |
137 | SND_SOC_DAPM_INPUT("INRX7" ), |
138 | SND_SOC_DAPM_MUX("Input Mux" , SND_SOC_NOPM, 0, 0, |
139 | &ak4118_input_mux_controls), |
140 | }; |
141 | |
142 | static const struct snd_soc_dapm_route ak4118_dapm_routes[] = { |
143 | {"Input Mux" , "RX0" , "INRX0" }, |
144 | {"Input Mux" , "RX1" , "INRX1" }, |
145 | {"Input Mux" , "RX2" , "INRX2" }, |
146 | {"Input Mux" , "RX3" , "INRX3" }, |
147 | {"Input Mux" , "RX4" , "INRX4" }, |
148 | {"Input Mux" , "RX5" , "INRX5" }, |
149 | {"Input Mux" , "RX6" , "INRX6" }, |
150 | {"Input Mux" , "RX7" , "INRX7" }, |
151 | }; |
152 | |
153 | |
154 | static int ak4118_set_dai_fmt_provider(struct ak4118_priv *ak4118, |
155 | unsigned int format) |
156 | { |
157 | int dif; |
158 | |
159 | switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { |
160 | case SND_SOC_DAIFMT_I2S: |
161 | dif = AK4118_REG_FORMAT_CTL_DIF0 | AK4118_REG_FORMAT_CTL_DIF2; |
162 | break; |
163 | case SND_SOC_DAIFMT_RIGHT_J: |
164 | dif = AK4118_REG_FORMAT_CTL_DIF0 | AK4118_REG_FORMAT_CTL_DIF1; |
165 | break; |
166 | case SND_SOC_DAIFMT_LEFT_J: |
167 | dif = AK4118_REG_FORMAT_CTL_DIF2; |
168 | break; |
169 | default: |
170 | return -ENOTSUPP; |
171 | } |
172 | |
173 | return dif; |
174 | } |
175 | |
176 | static int ak4118_set_dai_fmt_consumer(struct ak4118_priv *ak4118, |
177 | unsigned int format) |
178 | { |
179 | int dif; |
180 | |
181 | switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { |
182 | case SND_SOC_DAIFMT_I2S: |
183 | dif = AK4118_REG_FORMAT_CTL_DIF0 | AK4118_REG_FORMAT_CTL_DIF1 | |
184 | AK4118_REG_FORMAT_CTL_DIF2; |
185 | break; |
186 | case SND_SOC_DAIFMT_LEFT_J: |
187 | dif = AK4118_REG_FORMAT_CTL_DIF1 | AK4118_REG_FORMAT_CTL_DIF2; |
188 | break; |
189 | default: |
190 | return -ENOTSUPP; |
191 | } |
192 | |
193 | return dif; |
194 | } |
195 | |
196 | static int ak4118_set_dai_fmt(struct snd_soc_dai *dai, |
197 | unsigned int format) |
198 | { |
199 | struct snd_soc_component *component = dai->component; |
200 | struct ak4118_priv *ak4118 = snd_soc_component_get_drvdata(c: component); |
201 | int dif; |
202 | int ret = 0; |
203 | |
204 | switch (format & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
205 | case SND_SOC_DAIFMT_CBP_CFP: |
206 | dif = ak4118_set_dai_fmt_provider(ak4118, format); |
207 | break; |
208 | case SND_SOC_DAIFMT_CBC_CFC: |
209 | dif = ak4118_set_dai_fmt_consumer(ak4118, format); |
210 | break; |
211 | default: |
212 | ret = -ENOTSUPP; |
213 | goto exit; |
214 | } |
215 | |
216 | /* format not supported */ |
217 | if (dif < 0) { |
218 | ret = dif; |
219 | goto exit; |
220 | } |
221 | |
222 | ret = regmap_update_bits(map: ak4118->regmap, AK4118_REG_FORMAT_CTL, |
223 | AK4118_REG_FORMAT_CTL_DIF0 | |
224 | AK4118_REG_FORMAT_CTL_DIF1 | |
225 | AK4118_REG_FORMAT_CTL_DIF2, val: dif); |
226 | if (ret < 0) |
227 | goto exit; |
228 | |
229 | exit: |
230 | return ret; |
231 | } |
232 | |
233 | static int ak4118_hw_params(struct snd_pcm_substream *substream, |
234 | struct snd_pcm_hw_params *params, |
235 | struct snd_soc_dai *dai) |
236 | { |
237 | return 0; |
238 | } |
239 | |
240 | static const struct snd_soc_dai_ops ak4118_dai_ops = { |
241 | .hw_params = ak4118_hw_params, |
242 | .set_fmt = ak4118_set_dai_fmt, |
243 | }; |
244 | |
245 | static struct snd_soc_dai_driver ak4118_dai = { |
246 | .name = "ak4118-hifi" , |
247 | .capture = { |
248 | .stream_name = "Capture" , |
249 | .channels_min = 2, |
250 | .channels_max = 2, |
251 | .rates = SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | |
252 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | |
253 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | |
254 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, |
255 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
256 | SNDRV_PCM_FMTBIT_S24_3LE | |
257 | SNDRV_PCM_FMTBIT_S24_LE |
258 | }, |
259 | .ops = &ak4118_dai_ops, |
260 | }; |
261 | |
262 | static irqreturn_t ak4118_irq_handler(int irq, void *data) |
263 | { |
264 | struct ak4118_priv *ak4118 = data; |
265 | struct snd_soc_component *component = ak4118->component; |
266 | struct snd_kcontrol_new *kctl_new; |
267 | unsigned int i; |
268 | |
269 | if (!component) |
270 | return IRQ_NONE; |
271 | |
272 | for (i = 0; i < ARRAY_SIZE(ak4118_iec958_controls); i++) { |
273 | kctl_new = &ak4118_iec958_controls[i]; |
274 | |
275 | snd_soc_component_notify_control(component, ctl: kctl_new->name); |
276 | } |
277 | |
278 | return IRQ_HANDLED; |
279 | } |
280 | |
281 | static int ak4118_probe(struct snd_soc_component *component) |
282 | { |
283 | struct ak4118_priv *ak4118 = snd_soc_component_get_drvdata(c: component); |
284 | int ret = 0; |
285 | |
286 | ak4118->component = component; |
287 | |
288 | /* release reset */ |
289 | gpiod_set_value(desc: ak4118->reset, value: 0); |
290 | |
291 | /* unmask all int1 sources */ |
292 | ret = regmap_write(map: ak4118->regmap, AK4118_REG_INT1_MASK, val: 0x00); |
293 | if (ret < 0) { |
294 | dev_err(component->dev, |
295 | "failed to write regmap 0x%x 0x%x: %d\n" , |
296 | AK4118_REG_INT1_MASK, 0x00, ret); |
297 | return ret; |
298 | } |
299 | |
300 | /* rx detect enable on all channels */ |
301 | ret = regmap_write(map: ak4118->regmap, AK4118_REG_RX_DETECT, val: 0xff); |
302 | if (ret < 0) { |
303 | dev_err(component->dev, |
304 | "failed to write regmap 0x%x 0x%x: %d\n" , |
305 | AK4118_REG_RX_DETECT, 0xff, ret); |
306 | return ret; |
307 | } |
308 | |
309 | ret = snd_soc_add_component_controls(component, controls: ak4118_iec958_controls, |
310 | ARRAY_SIZE(ak4118_iec958_controls)); |
311 | if (ret) { |
312 | dev_err(component->dev, |
313 | "failed to add component kcontrols: %d\n" , ret); |
314 | return ret; |
315 | } |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | static void ak4118_remove(struct snd_soc_component *component) |
321 | { |
322 | struct ak4118_priv *ak4118 = snd_soc_component_get_drvdata(c: component); |
323 | |
324 | /* hold reset */ |
325 | gpiod_set_value(desc: ak4118->reset, value: 1); |
326 | } |
327 | |
328 | static const struct snd_soc_component_driver soc_component_drv_ak4118 = { |
329 | .probe = ak4118_probe, |
330 | .remove = ak4118_remove, |
331 | .dapm_widgets = ak4118_dapm_widgets, |
332 | .num_dapm_widgets = ARRAY_SIZE(ak4118_dapm_widgets), |
333 | .dapm_routes = ak4118_dapm_routes, |
334 | .num_dapm_routes = ARRAY_SIZE(ak4118_dapm_routes), |
335 | .idle_bias_on = 1, |
336 | .use_pmdown_time = 1, |
337 | .endianness = 1, |
338 | }; |
339 | |
340 | static const struct regmap_config ak4118_regmap = { |
341 | .reg_bits = 8, |
342 | .val_bits = 8, |
343 | |
344 | .reg_defaults = ak4118_reg_defaults, |
345 | .num_reg_defaults = ARRAY_SIZE(ak4118_reg_defaults), |
346 | |
347 | .cache_type = REGCACHE_NONE, |
348 | .max_register = AK4118_REG_MAX - 1, |
349 | }; |
350 | |
351 | static int ak4118_i2c_probe(struct i2c_client *i2c) |
352 | { |
353 | struct ak4118_priv *ak4118; |
354 | int ret; |
355 | |
356 | ak4118 = devm_kzalloc(dev: &i2c->dev, size: sizeof(struct ak4118_priv), |
357 | GFP_KERNEL); |
358 | if (ak4118 == NULL) |
359 | return -ENOMEM; |
360 | |
361 | ak4118->regmap = devm_regmap_init_i2c(i2c, &ak4118_regmap); |
362 | if (IS_ERR(ptr: ak4118->regmap)) |
363 | return PTR_ERR(ptr: ak4118->regmap); |
364 | |
365 | i2c_set_clientdata(client: i2c, data: ak4118); |
366 | |
367 | ak4118->reset = devm_gpiod_get(dev: &i2c->dev, con_id: "reset" , flags: GPIOD_OUT_HIGH); |
368 | if (IS_ERR(ptr: ak4118->reset)) |
369 | return dev_err_probe(dev: &i2c->dev, err: PTR_ERR(ptr: ak4118->reset), |
370 | fmt: "Failed to get reset\n" ); |
371 | |
372 | ak4118->irq = devm_gpiod_get(dev: &i2c->dev, con_id: "irq" , flags: GPIOD_IN); |
373 | if (IS_ERR(ptr: ak4118->irq)) |
374 | return dev_err_probe(dev: &i2c->dev, err: PTR_ERR(ptr: ak4118->irq), |
375 | fmt: "Failed to get IRQ\n" ); |
376 | |
377 | ret = devm_request_threaded_irq(dev: &i2c->dev, irq: gpiod_to_irq(desc: ak4118->irq), |
378 | NULL, thread_fn: ak4118_irq_handler, |
379 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, |
380 | devname: "ak4118-irq" , dev_id: ak4118); |
381 | if (ret < 0) { |
382 | dev_err(&i2c->dev, "Fail to request_irq: %d\n" , ret); |
383 | return ret; |
384 | } |
385 | |
386 | return devm_snd_soc_register_component(dev: &i2c->dev, |
387 | component_driver: &soc_component_drv_ak4118, dai_drv: &ak4118_dai, num_dai: 1); |
388 | } |
389 | |
390 | #ifdef CONFIG_OF |
391 | static const struct of_device_id ak4118_of_match[] = { |
392 | { .compatible = "asahi-kasei,ak4118" , }, |
393 | {} |
394 | }; |
395 | MODULE_DEVICE_TABLE(of, ak4118_of_match); |
396 | #endif |
397 | |
398 | static const struct i2c_device_id ak4118_id_table[] = { |
399 | { "ak4118" , 0 }, |
400 | {} |
401 | }; |
402 | MODULE_DEVICE_TABLE(i2c, ak4118_id_table); |
403 | |
404 | static struct i2c_driver ak4118_i2c_driver = { |
405 | .driver = { |
406 | .name = "ak4118" , |
407 | .of_match_table = of_match_ptr(ak4118_of_match), |
408 | }, |
409 | .id_table = ak4118_id_table, |
410 | .probe = ak4118_i2c_probe, |
411 | }; |
412 | |
413 | module_i2c_driver(ak4118_i2c_driver); |
414 | |
415 | MODULE_DESCRIPTION("Asahi Kasei AK4118 ALSA SoC driver" ); |
416 | MODULE_AUTHOR("Adrien Charruel <adrien.charruel@devialet.com>" ); |
417 | MODULE_LICENSE("GPL" ); |
418 | |