1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * tas5720.c - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier |
4 | * |
5 | * Copyright (C)2015-2016 Texas Instruments Incorporated - https://www.ti.com |
6 | * |
7 | * Author: Andreas Dannenberg <dannenberg@ti.com> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/device.h> |
13 | #include <linux/i2c.h> |
14 | #include <linux/regmap.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/regulator/consumer.h> |
17 | #include <linux/delay.h> |
18 | |
19 | #include <sound/pcm.h> |
20 | #include <sound/pcm_params.h> |
21 | #include <sound/soc.h> |
22 | #include <sound/soc-dapm.h> |
23 | #include <sound/tlv.h> |
24 | |
25 | #include "tas5720.h" |
26 | |
27 | /* Define how often to check (and clear) the fault status register (in ms) */ |
28 | #define TAS5720_FAULT_CHECK_INTERVAL 200 |
29 | |
30 | enum tas572x_type { |
31 | TAS5720, |
32 | TAS5720A_Q1, |
33 | TAS5722, |
34 | }; |
35 | |
36 | static const char * const tas5720_supply_names[] = { |
37 | "dvdd" , /* Digital power supply. Connect to 3.3-V supply. */ |
38 | "pvdd" , /* Class-D amp and analog power supply (connected). */ |
39 | }; |
40 | |
41 | #define TAS5720_NUM_SUPPLIES ARRAY_SIZE(tas5720_supply_names) |
42 | |
43 | struct tas5720_data { |
44 | struct snd_soc_component *component; |
45 | struct regmap *regmap; |
46 | struct i2c_client *tas5720_client; |
47 | enum tas572x_type devtype; |
48 | struct regulator_bulk_data supplies[TAS5720_NUM_SUPPLIES]; |
49 | struct delayed_work fault_check_work; |
50 | unsigned int last_fault; |
51 | }; |
52 | |
53 | static int tas5720_hw_params(struct snd_pcm_substream *substream, |
54 | struct snd_pcm_hw_params *params, |
55 | struct snd_soc_dai *dai) |
56 | { |
57 | struct snd_soc_component *component = dai->component; |
58 | unsigned int rate = params_rate(p: params); |
59 | bool ssz_ds; |
60 | int ret; |
61 | |
62 | switch (rate) { |
63 | case 44100: |
64 | case 48000: |
65 | ssz_ds = false; |
66 | break; |
67 | case 88200: |
68 | case 96000: |
69 | ssz_ds = true; |
70 | break; |
71 | default: |
72 | dev_err(component->dev, "unsupported sample rate: %u\n" , rate); |
73 | return -EINVAL; |
74 | } |
75 | |
76 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, |
77 | TAS5720_SSZ_DS, val: ssz_ds); |
78 | if (ret < 0) { |
79 | dev_err(component->dev, "error setting sample rate: %d\n" , ret); |
80 | return ret; |
81 | } |
82 | |
83 | return 0; |
84 | } |
85 | |
86 | static int tas5720_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
87 | { |
88 | struct snd_soc_component *component = dai->component; |
89 | u8 serial_format; |
90 | int ret; |
91 | |
92 | if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) { |
93 | dev_vdbg(component->dev, "DAI clocking invalid\n" ); |
94 | return -EINVAL; |
95 | } |
96 | |
97 | switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | |
98 | SND_SOC_DAIFMT_INV_MASK)) { |
99 | case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): |
100 | /* 1st data bit occur one BCLK cycle after the frame sync */ |
101 | serial_format = TAS5720_SAIF_I2S; |
102 | break; |
103 | case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF): |
104 | /* |
105 | * Note that although the TAS5720 does not have a dedicated DSP |
106 | * mode it doesn't care about the LRCLK duty cycle during TDM |
107 | * operation. Therefore we can use the device's I2S mode with |
108 | * its delaying of the 1st data bit to receive DSP_A formatted |
109 | * data. See device datasheet for additional details. |
110 | */ |
111 | serial_format = TAS5720_SAIF_I2S; |
112 | break; |
113 | case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF): |
114 | /* |
115 | * Similar to DSP_A, we can use the fact that the TAS5720 does |
116 | * not care about the LRCLK duty cycle during TDM to receive |
117 | * DSP_B formatted data in LEFTJ mode (no delaying of the 1st |
118 | * data bit). |
119 | */ |
120 | serial_format = TAS5720_SAIF_LEFTJ; |
121 | break; |
122 | case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): |
123 | /* No delay after the frame sync */ |
124 | serial_format = TAS5720_SAIF_LEFTJ; |
125 | break; |
126 | default: |
127 | dev_vdbg(component->dev, "DAI Format is not found\n" ); |
128 | return -EINVAL; |
129 | } |
130 | |
131 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, |
132 | TAS5720_SAIF_FORMAT_MASK, |
133 | val: serial_format); |
134 | if (ret < 0) { |
135 | dev_err(component->dev, "error setting SAIF format: %d\n" , ret); |
136 | return ret; |
137 | } |
138 | |
139 | return 0; |
140 | } |
141 | |
142 | static int tas5720_set_dai_tdm_slot(struct snd_soc_dai *dai, |
143 | unsigned int tx_mask, unsigned int rx_mask, |
144 | int slots, int slot_width) |
145 | { |
146 | struct snd_soc_component *component = dai->component; |
147 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(c: component); |
148 | unsigned int first_slot; |
149 | int ret; |
150 | |
151 | if (!tx_mask) { |
152 | dev_err(component->dev, "tx masks must not be 0\n" ); |
153 | return -EINVAL; |
154 | } |
155 | |
156 | /* |
157 | * Determine the first slot that is being requested. We will only |
158 | * use the first slot that is found since the TAS5720 is a mono |
159 | * amplifier. |
160 | */ |
161 | first_slot = __ffs(tx_mask); |
162 | |
163 | if (first_slot > 7) { |
164 | dev_err(component->dev, "slot selection out of bounds (%u)\n" , |
165 | first_slot); |
166 | return -EINVAL; |
167 | } |
168 | |
169 | /* |
170 | * Enable manual TDM slot selection (instead of I2C ID based). |
171 | * This is not applicable to TAS5720A-Q1. |
172 | */ |
173 | switch (tas5720->devtype) { |
174 | case TAS5720A_Q1: |
175 | break; |
176 | default: |
177 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL1_REG, |
178 | TAS5720_TDM_CFG_SRC, TAS5720_TDM_CFG_SRC); |
179 | if (ret < 0) |
180 | goto error_snd_soc_component_update_bits; |
181 | |
182 | /* Configure the TDM slot to process audio from */ |
183 | ret = snd_soc_component_update_bits(component, TAS5720_DIGITAL_CTRL2_REG, |
184 | TAS5720_TDM_SLOT_SEL_MASK, val: first_slot); |
185 | if (ret < 0) |
186 | goto error_snd_soc_component_update_bits; |
187 | break; |
188 | } |
189 | |
190 | /* Configure TDM slot width. This is only applicable to TAS5722. */ |
191 | switch (tas5720->devtype) { |
192 | case TAS5722: |
193 | ret = snd_soc_component_update_bits(component, TAS5722_DIGITAL_CTRL2_REG, |
194 | TAS5722_TDM_SLOT_16B, |
195 | val: slot_width == 16 ? |
196 | TAS5722_TDM_SLOT_16B : 0); |
197 | if (ret < 0) |
198 | goto error_snd_soc_component_update_bits; |
199 | break; |
200 | default: |
201 | break; |
202 | } |
203 | |
204 | return 0; |
205 | |
206 | error_snd_soc_component_update_bits: |
207 | dev_err(component->dev, "error configuring TDM mode: %d\n" , ret); |
208 | return ret; |
209 | } |
210 | |
211 | static int tas5720_mute_soc_component(struct snd_soc_component *component, int mute) |
212 | { |
213 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(c: component); |
214 | unsigned int reg, mask; |
215 | int ret; |
216 | |
217 | switch (tas5720->devtype) { |
218 | case TAS5720A_Q1: |
219 | reg = TAS5720_Q1_VOLUME_CTRL_CFG_REG; |
220 | mask = TAS5720_Q1_MUTE; |
221 | break; |
222 | default: |
223 | reg = TAS5720_DIGITAL_CTRL2_REG; |
224 | mask = TAS5720_MUTE; |
225 | break; |
226 | } |
227 | |
228 | ret = snd_soc_component_update_bits(component, reg, mask, val: mute ? mask : 0); |
229 | if (ret < 0) { |
230 | dev_err(component->dev, "error (un-)muting device: %d\n" , ret); |
231 | return ret; |
232 | } |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static int tas5720_mute(struct snd_soc_dai *dai, int mute, int direction) |
238 | { |
239 | return tas5720_mute_soc_component(component: dai->component, mute); |
240 | } |
241 | |
242 | static void tas5720_fault_check_work(struct work_struct *work) |
243 | { |
244 | struct tas5720_data *tas5720 = container_of(work, struct tas5720_data, |
245 | fault_check_work.work); |
246 | struct device *dev = tas5720->component->dev; |
247 | unsigned int curr_fault; |
248 | int ret; |
249 | |
250 | ret = regmap_read(map: tas5720->regmap, TAS5720_FAULT_REG, val: &curr_fault); |
251 | if (ret < 0) { |
252 | dev_err(dev, "failed to read FAULT register: %d\n" , ret); |
253 | goto out; |
254 | } |
255 | |
256 | /* Check/handle all errors except SAIF clock errors */ |
257 | curr_fault &= TAS5720_OCE | TAS5720_DCE | TAS5720_OTE; |
258 | |
259 | /* |
260 | * Only flag errors once for a given occurrence. This is needed as |
261 | * the TAS5720 will take time clearing the fault condition internally |
262 | * during which we don't want to bombard the system with the same |
263 | * error message over and over. |
264 | */ |
265 | if ((curr_fault & TAS5720_OCE) && !(tas5720->last_fault & TAS5720_OCE)) |
266 | dev_crit(dev, "experienced an over current hardware fault\n" ); |
267 | |
268 | if ((curr_fault & TAS5720_DCE) && !(tas5720->last_fault & TAS5720_DCE)) |
269 | dev_crit(dev, "experienced a DC detection fault\n" ); |
270 | |
271 | if ((curr_fault & TAS5720_OTE) && !(tas5720->last_fault & TAS5720_OTE)) |
272 | dev_crit(dev, "experienced an over temperature fault\n" ); |
273 | |
274 | /* Store current fault value so we can detect any changes next time */ |
275 | tas5720->last_fault = curr_fault; |
276 | |
277 | if (!curr_fault) |
278 | goto out; |
279 | |
280 | /* |
281 | * Periodically toggle SDZ (shutdown bit) H->L->H to clear any latching |
282 | * faults as long as a fault condition persists. Always going through |
283 | * the full sequence no matter the first return value to minimizes |
284 | * chances for the device to end up in shutdown mode. |
285 | */ |
286 | ret = regmap_write_bits(map: tas5720->regmap, TAS5720_POWER_CTRL_REG, |
287 | TAS5720_SDZ, val: 0); |
288 | if (ret < 0) |
289 | dev_err(dev, "failed to write POWER_CTRL register: %d\n" , ret); |
290 | |
291 | ret = regmap_write_bits(map: tas5720->regmap, TAS5720_POWER_CTRL_REG, |
292 | TAS5720_SDZ, TAS5720_SDZ); |
293 | if (ret < 0) |
294 | dev_err(dev, "failed to write POWER_CTRL register: %d\n" , ret); |
295 | |
296 | out: |
297 | /* Schedule the next fault check at the specified interval */ |
298 | schedule_delayed_work(dwork: &tas5720->fault_check_work, |
299 | delay: msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); |
300 | } |
301 | |
302 | static int tas5720_codec_probe(struct snd_soc_component *component) |
303 | { |
304 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(c: component); |
305 | unsigned int device_id, expected_device_id; |
306 | int ret; |
307 | |
308 | tas5720->component = component; |
309 | |
310 | ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), |
311 | consumers: tas5720->supplies); |
312 | if (ret != 0) { |
313 | dev_err(component->dev, "failed to enable supplies: %d\n" , ret); |
314 | return ret; |
315 | } |
316 | |
317 | /* |
318 | * Take a liberal approach to checking the device ID to allow the |
319 | * driver to be used even if the device ID does not match, however |
320 | * issue a warning if there is a mismatch. |
321 | */ |
322 | ret = regmap_read(map: tas5720->regmap, TAS5720_DEVICE_ID_REG, val: &device_id); |
323 | if (ret < 0) { |
324 | dev_err(component->dev, "failed to read device ID register: %d\n" , |
325 | ret); |
326 | goto probe_fail; |
327 | } |
328 | |
329 | switch (tas5720->devtype) { |
330 | case TAS5720: |
331 | expected_device_id = TAS5720_DEVICE_ID; |
332 | break; |
333 | case TAS5720A_Q1: |
334 | expected_device_id = TAS5720A_Q1_DEVICE_ID; |
335 | break; |
336 | case TAS5722: |
337 | expected_device_id = TAS5722_DEVICE_ID; |
338 | break; |
339 | default: |
340 | dev_err(component->dev, "unexpected private driver data\n" ); |
341 | ret = -EINVAL; |
342 | goto probe_fail; |
343 | } |
344 | |
345 | if (device_id != expected_device_id) |
346 | dev_warn(component->dev, "wrong device ID. expected: %u read: %u\n" , |
347 | expected_device_id, device_id); |
348 | |
349 | /* Set device to mute */ |
350 | ret = tas5720_mute_soc_component(component, mute: 1); |
351 | if (ret < 0) |
352 | goto error_snd_soc_component_update_bits; |
353 | |
354 | /* Set Bit 7 in TAS5720_ANALOG_CTRL_REG to 1 for TAS5720A_Q1 */ |
355 | switch (tas5720->devtype) { |
356 | case TAS5720A_Q1: |
357 | ret = snd_soc_component_update_bits(component, TAS5720_ANALOG_CTRL_REG, |
358 | TAS5720_Q1_RESERVED7_BIT, |
359 | TAS5720_Q1_RESERVED7_BIT); |
360 | break; |
361 | default: |
362 | break; |
363 | } |
364 | if (ret < 0) |
365 | goto error_snd_soc_component_update_bits; |
366 | |
367 | /* |
368 | * Enter shutdown mode - our default when not playing audio - to |
369 | * minimize current consumption. On the TAS5720 there is no real down |
370 | * side doing so as all device registers are preserved and the wakeup |
371 | * of the codec is rather quick which we do using a dapm widget. |
372 | */ |
373 | ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, |
374 | TAS5720_SDZ, val: 0); |
375 | if (ret < 0) |
376 | goto error_snd_soc_component_update_bits; |
377 | |
378 | INIT_DELAYED_WORK(&tas5720->fault_check_work, tas5720_fault_check_work); |
379 | |
380 | return 0; |
381 | |
382 | error_snd_soc_component_update_bits: |
383 | dev_err(component->dev, "error configuring device registers: %d\n" , ret); |
384 | |
385 | probe_fail: |
386 | regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), |
387 | consumers: tas5720->supplies); |
388 | return ret; |
389 | } |
390 | |
391 | static void tas5720_codec_remove(struct snd_soc_component *component) |
392 | { |
393 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(c: component); |
394 | int ret; |
395 | |
396 | cancel_delayed_work_sync(dwork: &tas5720->fault_check_work); |
397 | |
398 | ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), |
399 | consumers: tas5720->supplies); |
400 | if (ret < 0) |
401 | dev_err(component->dev, "failed to disable supplies: %d\n" , ret); |
402 | }; |
403 | |
404 | static int tas5720_dac_event(struct snd_soc_dapm_widget *w, |
405 | struct snd_kcontrol *kcontrol, int event) |
406 | { |
407 | struct snd_soc_component *component = snd_soc_dapm_to_component(dapm: w->dapm); |
408 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(c: component); |
409 | int ret; |
410 | |
411 | if (event & SND_SOC_DAPM_POST_PMU) { |
412 | /* Take TAS5720 out of shutdown mode */ |
413 | ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, |
414 | TAS5720_SDZ, TAS5720_SDZ); |
415 | if (ret < 0) { |
416 | dev_err(component->dev, "error waking component: %d\n" , ret); |
417 | return ret; |
418 | } |
419 | |
420 | /* |
421 | * Observe codec shutdown-to-active time. The datasheet only |
422 | * lists a nominal value however just use-it as-is without |
423 | * additional padding to minimize the delay introduced in |
424 | * starting to play audio (actually there is other setup done |
425 | * by the ASoC framework that will provide additional delays, |
426 | * so we should always be safe). |
427 | */ |
428 | msleep(msecs: 25); |
429 | |
430 | /* Turn on TAS5720 periodic fault checking/handling */ |
431 | tas5720->last_fault = 0; |
432 | schedule_delayed_work(dwork: &tas5720->fault_check_work, |
433 | delay: msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); |
434 | } else if (event & SND_SOC_DAPM_PRE_PMD) { |
435 | /* Disable TAS5720 periodic fault checking/handling */ |
436 | cancel_delayed_work_sync(dwork: &tas5720->fault_check_work); |
437 | |
438 | /* Place TAS5720 in shutdown mode to minimize current draw */ |
439 | ret = snd_soc_component_update_bits(component, TAS5720_POWER_CTRL_REG, |
440 | TAS5720_SDZ, val: 0); |
441 | if (ret < 0) { |
442 | dev_err(component->dev, "error shutting down component: %d\n" , |
443 | ret); |
444 | return ret; |
445 | } |
446 | } |
447 | |
448 | return 0; |
449 | } |
450 | |
451 | #ifdef CONFIG_PM |
452 | static int tas5720_suspend(struct snd_soc_component *component) |
453 | { |
454 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(c: component); |
455 | int ret; |
456 | |
457 | regcache_cache_only(map: tas5720->regmap, enable: true); |
458 | regcache_mark_dirty(map: tas5720->regmap); |
459 | |
460 | ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), |
461 | consumers: tas5720->supplies); |
462 | if (ret < 0) |
463 | dev_err(component->dev, "failed to disable supplies: %d\n" , ret); |
464 | |
465 | return ret; |
466 | } |
467 | |
468 | static int tas5720_resume(struct snd_soc_component *component) |
469 | { |
470 | struct tas5720_data *tas5720 = snd_soc_component_get_drvdata(c: component); |
471 | int ret; |
472 | |
473 | ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), |
474 | consumers: tas5720->supplies); |
475 | if (ret < 0) { |
476 | dev_err(component->dev, "failed to enable supplies: %d\n" , ret); |
477 | return ret; |
478 | } |
479 | |
480 | regcache_cache_only(map: tas5720->regmap, enable: false); |
481 | |
482 | ret = regcache_sync(map: tas5720->regmap); |
483 | if (ret < 0) { |
484 | dev_err(component->dev, "failed to sync regcache: %d\n" , ret); |
485 | return ret; |
486 | } |
487 | |
488 | return 0; |
489 | } |
490 | #else |
491 | #define tas5720_suspend NULL |
492 | #define tas5720_resume NULL |
493 | #endif |
494 | |
495 | static bool tas5720_is_volatile_reg(struct device *dev, unsigned int reg) |
496 | { |
497 | switch (reg) { |
498 | case TAS5720_DEVICE_ID_REG: |
499 | case TAS5720_FAULT_REG: |
500 | return true; |
501 | default: |
502 | return false; |
503 | } |
504 | } |
505 | |
506 | static const struct regmap_config tas5720_regmap_config = { |
507 | .reg_bits = 8, |
508 | .val_bits = 8, |
509 | |
510 | .max_register = TAS5720_MAX_REG, |
511 | .cache_type = REGCACHE_RBTREE, |
512 | .volatile_reg = tas5720_is_volatile_reg, |
513 | }; |
514 | |
515 | static const struct regmap_config tas5720a_q1_regmap_config = { |
516 | .reg_bits = 8, |
517 | .val_bits = 8, |
518 | |
519 | .max_register = TAS5720_MAX_REG, |
520 | .cache_type = REGCACHE_RBTREE, |
521 | .volatile_reg = tas5720_is_volatile_reg, |
522 | }; |
523 | |
524 | static const struct regmap_config tas5722_regmap_config = { |
525 | .reg_bits = 8, |
526 | .val_bits = 8, |
527 | |
528 | .max_register = TAS5722_MAX_REG, |
529 | .cache_type = REGCACHE_RBTREE, |
530 | .volatile_reg = tas5720_is_volatile_reg, |
531 | }; |
532 | |
533 | /* |
534 | * DAC analog gain. There are four discrete values to select from, ranging |
535 | * from 19.2 dB to 26.3dB. |
536 | */ |
537 | static const DECLARE_TLV_DB_RANGE(dac_analog_tlv, |
538 | 0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0), |
539 | 0x1, 0x1, TLV_DB_SCALE_ITEM(2070, 0, 0), |
540 | 0x2, 0x2, TLV_DB_SCALE_ITEM(2350, 0, 0), |
541 | 0x3, 0x3, TLV_DB_SCALE_ITEM(2630, 0, 0), |
542 | ); |
543 | |
544 | /* |
545 | * DAC analog gain for TAS5720A-Q1. There are three discrete values to select from, ranging |
546 | * from 19.2 dB to 25.0dB. |
547 | */ |
548 | static const DECLARE_TLV_DB_RANGE(dac_analog_tlv_a_q1, |
549 | 0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0), |
550 | 0x1, 0x1, TLV_DB_SCALE_ITEM(2260, 0, 0), |
551 | 0x2, 0x2, TLV_DB_SCALE_ITEM(2500, 0, 0), |
552 | ); |
553 | |
554 | /* |
555 | * DAC digital volumes. From -103.5 to 24 dB in 0.5 dB or 0.25 dB steps |
556 | * depending on the device. Note that setting the gain below -100 dB |
557 | * (register value <0x7) is effectively a MUTE as per device datasheet. |
558 | * |
559 | * Note that for the TAS5722 the digital volume controls are actually split |
560 | * over two registers, so we need custom getters/setters for access. |
561 | */ |
562 | static DECLARE_TLV_DB_SCALE(tas5720_dac_tlv, -10350, 50, 0); |
563 | static DECLARE_TLV_DB_SCALE(tas5722_dac_tlv, -10350, 25, 0); |
564 | |
565 | static int tas5722_volume_get(struct snd_kcontrol *kcontrol, |
566 | struct snd_ctl_elem_value *ucontrol) |
567 | { |
568 | struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); |
569 | unsigned int val; |
570 | |
571 | val = snd_soc_component_read(component, TAS5720_VOLUME_CTRL_REG); |
572 | ucontrol->value.integer.value[0] = val << 1; |
573 | |
574 | val = snd_soc_component_read(component, TAS5722_DIGITAL_CTRL2_REG); |
575 | ucontrol->value.integer.value[0] |= val & TAS5722_VOL_CONTROL_LSB; |
576 | |
577 | return 0; |
578 | } |
579 | |
580 | static int tas5722_volume_set(struct snd_kcontrol *kcontrol, |
581 | struct snd_ctl_elem_value *ucontrol) |
582 | { |
583 | struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); |
584 | unsigned int sel = ucontrol->value.integer.value[0]; |
585 | |
586 | snd_soc_component_write(component, TAS5720_VOLUME_CTRL_REG, val: sel >> 1); |
587 | snd_soc_component_update_bits(component, TAS5722_DIGITAL_CTRL2_REG, |
588 | TAS5722_VOL_CONTROL_LSB, val: sel); |
589 | |
590 | return 0; |
591 | } |
592 | |
593 | static const struct snd_kcontrol_new tas5720_snd_controls[] = { |
594 | SOC_SINGLE_TLV("Speaker Driver Playback Volume" , |
595 | TAS5720_VOLUME_CTRL_REG, 0, 0xff, 0, tas5720_dac_tlv), |
596 | SOC_SINGLE_TLV("Speaker Driver Analog Gain" , TAS5720_ANALOG_CTRL_REG, |
597 | TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), |
598 | }; |
599 | |
600 | static const struct snd_kcontrol_new tas5720a_q1_snd_controls[] = { |
601 | SOC_DOUBLE_R_TLV("Speaker Driver Playback Volume" , |
602 | TAS5720_Q1_VOLUME_CTRL_LEFT_REG, |
603 | TAS5720_Q1_VOLUME_CTRL_RIGHT_REG, |
604 | 0, 0xff, 0, tas5720_dac_tlv), |
605 | SOC_SINGLE_TLV("Speaker Driver Analog Gain" , TAS5720_ANALOG_CTRL_REG, |
606 | TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv_a_q1), |
607 | }; |
608 | |
609 | static const struct snd_kcontrol_new tas5722_snd_controls[] = { |
610 | SOC_SINGLE_EXT_TLV("Speaker Driver Playback Volume" , |
611 | 0, 0, 511, 0, |
612 | tas5722_volume_get, tas5722_volume_set, |
613 | tas5722_dac_tlv), |
614 | SOC_SINGLE_TLV("Speaker Driver Analog Gain" , TAS5720_ANALOG_CTRL_REG, |
615 | TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), |
616 | }; |
617 | |
618 | static const struct snd_soc_dapm_widget tas5720_dapm_widgets[] = { |
619 | SND_SOC_DAPM_AIF_IN("DAC IN" , "Playback" , 0, SND_SOC_NOPM, 0, 0), |
620 | SND_SOC_DAPM_DAC_E("DAC" , NULL, SND_SOC_NOPM, 0, 0, tas5720_dac_event, |
621 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), |
622 | SND_SOC_DAPM_OUTPUT("OUT" ) |
623 | }; |
624 | |
625 | static const struct snd_soc_dapm_route tas5720_audio_map[] = { |
626 | { "DAC" , NULL, "DAC IN" }, |
627 | { "OUT" , NULL, "DAC" }, |
628 | }; |
629 | |
630 | static const struct snd_soc_component_driver soc_component_dev_tas5720 = { |
631 | .probe = tas5720_codec_probe, |
632 | .remove = tas5720_codec_remove, |
633 | .suspend = tas5720_suspend, |
634 | .resume = tas5720_resume, |
635 | .controls = tas5720_snd_controls, |
636 | .num_controls = ARRAY_SIZE(tas5720_snd_controls), |
637 | .dapm_widgets = tas5720_dapm_widgets, |
638 | .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), |
639 | .dapm_routes = tas5720_audio_map, |
640 | .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), |
641 | .idle_bias_on = 1, |
642 | .use_pmdown_time = 1, |
643 | .endianness = 1, |
644 | }; |
645 | |
646 | static const struct snd_soc_component_driver soc_component_dev_tas5720_a_q1 = { |
647 | .probe = tas5720_codec_probe, |
648 | .remove = tas5720_codec_remove, |
649 | .suspend = tas5720_suspend, |
650 | .resume = tas5720_resume, |
651 | .controls = tas5720a_q1_snd_controls, |
652 | .num_controls = ARRAY_SIZE(tas5720a_q1_snd_controls), |
653 | .dapm_widgets = tas5720_dapm_widgets, |
654 | .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), |
655 | .dapm_routes = tas5720_audio_map, |
656 | .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), |
657 | .idle_bias_on = 1, |
658 | .use_pmdown_time = 1, |
659 | .endianness = 1, |
660 | }; |
661 | |
662 | static const struct snd_soc_component_driver soc_component_dev_tas5722 = { |
663 | .probe = tas5720_codec_probe, |
664 | .remove = tas5720_codec_remove, |
665 | .suspend = tas5720_suspend, |
666 | .resume = tas5720_resume, |
667 | .controls = tas5722_snd_controls, |
668 | .num_controls = ARRAY_SIZE(tas5722_snd_controls), |
669 | .dapm_widgets = tas5720_dapm_widgets, |
670 | .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), |
671 | .dapm_routes = tas5720_audio_map, |
672 | .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), |
673 | .idle_bias_on = 1, |
674 | .use_pmdown_time = 1, |
675 | .endianness = 1, |
676 | }; |
677 | |
678 | /* PCM rates supported by the TAS5720 driver */ |
679 | #define TAS5720_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ |
680 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) |
681 | |
682 | /* Formats supported by TAS5720 driver */ |
683 | #define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ |
684 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) |
685 | |
686 | static const struct snd_soc_dai_ops tas5720_speaker_dai_ops = { |
687 | .hw_params = tas5720_hw_params, |
688 | .set_fmt = tas5720_set_dai_fmt, |
689 | .set_tdm_slot = tas5720_set_dai_tdm_slot, |
690 | .mute_stream = tas5720_mute, |
691 | .no_capture_mute = 1, |
692 | }; |
693 | |
694 | /* |
695 | * TAS5720 DAI structure |
696 | * |
697 | * Note that were are advertising .playback.channels_max = 2 despite this being |
698 | * a mono amplifier. The reason for that is that some serial ports such as TI's |
699 | * McASP module have a minimum number of channels (2) that they can output. |
700 | * Advertising more channels than we have will allow us to interface with such |
701 | * a serial port without really any negative side effects as the TAS5720 will |
702 | * simply ignore any extra channel(s) asides from the one channel that is |
703 | * configured to be played back. |
704 | */ |
705 | static struct snd_soc_dai_driver tas5720_dai[] = { |
706 | { |
707 | .name = "tas5720-amplifier" , |
708 | .playback = { |
709 | .stream_name = "Playback" , |
710 | .channels_min = 1, |
711 | .channels_max = 2, |
712 | .rates = TAS5720_RATES, |
713 | .formats = TAS5720_FORMATS, |
714 | }, |
715 | .ops = &tas5720_speaker_dai_ops, |
716 | }, |
717 | }; |
718 | |
719 | static const struct i2c_device_id tas5720_id[] = { |
720 | { "tas5720" , TAS5720 }, |
721 | { "tas5720a-q1" , TAS5720A_Q1 }, |
722 | { "tas5722" , TAS5722 }, |
723 | { } |
724 | }; |
725 | MODULE_DEVICE_TABLE(i2c, tas5720_id); |
726 | |
727 | static int tas5720_probe(struct i2c_client *client) |
728 | { |
729 | struct device *dev = &client->dev; |
730 | struct tas5720_data *data; |
731 | const struct regmap_config *regmap_config; |
732 | const struct i2c_device_id *id; |
733 | int ret; |
734 | int i; |
735 | |
736 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
737 | if (!data) |
738 | return -ENOMEM; |
739 | |
740 | id = i2c_match_id(id: tas5720_id, client); |
741 | data->tas5720_client = client; |
742 | data->devtype = id->driver_data; |
743 | |
744 | switch (id->driver_data) { |
745 | case TAS5720: |
746 | regmap_config = &tas5720_regmap_config; |
747 | break; |
748 | case TAS5720A_Q1: |
749 | regmap_config = &tas5720a_q1_regmap_config; |
750 | break; |
751 | case TAS5722: |
752 | regmap_config = &tas5722_regmap_config; |
753 | break; |
754 | default: |
755 | dev_err(dev, "unexpected private driver data\n" ); |
756 | return -EINVAL; |
757 | } |
758 | data->regmap = devm_regmap_init_i2c(client, regmap_config); |
759 | if (IS_ERR(ptr: data->regmap)) { |
760 | ret = PTR_ERR(ptr: data->regmap); |
761 | dev_err(dev, "failed to allocate register map: %d\n" , ret); |
762 | return ret; |
763 | } |
764 | |
765 | for (i = 0; i < ARRAY_SIZE(data->supplies); i++) |
766 | data->supplies[i].supply = tas5720_supply_names[i]; |
767 | |
768 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), |
769 | consumers: data->supplies); |
770 | if (ret != 0) { |
771 | dev_err(dev, "failed to request supplies: %d\n" , ret); |
772 | return ret; |
773 | } |
774 | |
775 | dev_set_drvdata(dev, data); |
776 | |
777 | switch (id->driver_data) { |
778 | case TAS5720: |
779 | ret = devm_snd_soc_register_component(dev: &client->dev, |
780 | component_driver: &soc_component_dev_tas5720, |
781 | dai_drv: tas5720_dai, |
782 | ARRAY_SIZE(tas5720_dai)); |
783 | break; |
784 | case TAS5720A_Q1: |
785 | ret = devm_snd_soc_register_component(dev: &client->dev, |
786 | component_driver: &soc_component_dev_tas5720_a_q1, |
787 | dai_drv: tas5720_dai, |
788 | ARRAY_SIZE(tas5720_dai)); |
789 | break; |
790 | case TAS5722: |
791 | ret = devm_snd_soc_register_component(dev: &client->dev, |
792 | component_driver: &soc_component_dev_tas5722, |
793 | dai_drv: tas5720_dai, |
794 | ARRAY_SIZE(tas5720_dai)); |
795 | break; |
796 | default: |
797 | dev_err(dev, "unexpected private driver data\n" ); |
798 | return -EINVAL; |
799 | } |
800 | if (ret < 0) { |
801 | dev_err(dev, "failed to register component: %d\n" , ret); |
802 | return ret; |
803 | } |
804 | |
805 | return 0; |
806 | } |
807 | |
808 | #if IS_ENABLED(CONFIG_OF) |
809 | static const struct of_device_id tas5720_of_match[] = { |
810 | { .compatible = "ti,tas5720" , }, |
811 | { .compatible = "ti,tas5720a-q1" , }, |
812 | { .compatible = "ti,tas5722" , }, |
813 | { }, |
814 | }; |
815 | MODULE_DEVICE_TABLE(of, tas5720_of_match); |
816 | #endif |
817 | |
818 | static struct i2c_driver tas5720_i2c_driver = { |
819 | .driver = { |
820 | .name = "tas5720" , |
821 | .of_match_table = of_match_ptr(tas5720_of_match), |
822 | }, |
823 | .probe = tas5720_probe, |
824 | .id_table = tas5720_id, |
825 | }; |
826 | |
827 | module_i2c_driver(tas5720_i2c_driver); |
828 | |
829 | MODULE_AUTHOR("Andreas Dannenberg <dannenberg@ti.com>" ); |
830 | MODULE_DESCRIPTION("TAS5720 Audio amplifier driver" ); |
831 | MODULE_LICENSE("GPL" ); |
832 | |