diff options
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/codecs/Kconfig | 8 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/codecs/tas5720.c | 620 | ||||
-rw-r--r-- | sound/soc/codecs/tas5720.h | 90 |
4 files changed, 720 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c011f076d58b..06d298b79b9b 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig | |||
@@ -124,6 +124,7 @@ config SND_SOC_ALL_CODECS | |||
124 | select SND_SOC_TAS2552 if I2C | 124 | select SND_SOC_TAS2552 if I2C |
125 | select SND_SOC_TAS5086 if I2C | 125 | select SND_SOC_TAS5086 if I2C |
126 | select SND_SOC_TAS571X if I2C | 126 | select SND_SOC_TAS571X if I2C |
127 | select SND_SOC_TAS5720 if I2C | ||
127 | select SND_SOC_TFA9879 if I2C | 128 | select SND_SOC_TFA9879 if I2C |
128 | select SND_SOC_TLV320AIC23_I2C if I2C | 129 | select SND_SOC_TLV320AIC23_I2C if I2C |
129 | select SND_SOC_TLV320AIC23_SPI if SPI_MASTER | 130 | select SND_SOC_TLV320AIC23_SPI if SPI_MASTER |
@@ -740,6 +741,13 @@ config SND_SOC_TAS571X | |||
740 | tristate "Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 power amplifiers" | 741 | tristate "Texas Instruments TAS5711/TAS5717/TAS5719/TAS5721 power amplifiers" |
741 | depends on I2C | 742 | depends on I2C |
742 | 743 | ||
744 | config SND_SOC_TAS5720 | ||
745 | tristate "Texas Instruments TAS5720 Mono Audio amplifier" | ||
746 | depends on I2C | ||
747 | help | ||
748 | Enable support for Texas Instruments TAS5720L/M high-efficiency mono | ||
749 | Class-D audio power amplifiers. | ||
750 | |||
743 | config SND_SOC_TFA9879 | 751 | config SND_SOC_TFA9879 |
744 | tristate "NXP Semiconductors TFA9879 amplifier" | 752 | tristate "NXP Semiconductors TFA9879 amplifier" |
745 | depends on I2C | 753 | depends on I2C |
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 185a712a7fe7..83d352ede6fd 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile | |||
@@ -129,6 +129,7 @@ snd-soc-stac9766-objs := stac9766.o | |||
129 | snd-soc-sti-sas-objs := sti-sas.o | 129 | snd-soc-sti-sas-objs := sti-sas.o |
130 | snd-soc-tas5086-objs := tas5086.o | 130 | snd-soc-tas5086-objs := tas5086.o |
131 | snd-soc-tas571x-objs := tas571x.o | 131 | snd-soc-tas571x-objs := tas571x.o |
132 | snd-soc-tas5720-objs := tas5720.o | ||
132 | snd-soc-tfa9879-objs := tfa9879.o | 133 | snd-soc-tfa9879-objs := tfa9879.o |
133 | snd-soc-tlv320aic23-objs := tlv320aic23.o | 134 | snd-soc-tlv320aic23-objs := tlv320aic23.o |
134 | snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o | 135 | snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o |
@@ -335,6 +336,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o | |||
335 | obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o | 336 | obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o |
336 | obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o | 337 | obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o |
337 | obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o | 338 | obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o |
339 | obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o | ||
338 | obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o | 340 | obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o |
339 | obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o | 341 | obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o |
340 | obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o | 342 | obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o |
diff --git a/sound/soc/codecs/tas5720.c b/sound/soc/codecs/tas5720.c new file mode 100644 index 000000000000..f54fb46b77c2 --- /dev/null +++ b/sound/soc/codecs/tas5720.c | |||
@@ -0,0 +1,620 @@ | |||
1 | /* | ||
2 | * tas5720.c - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier | ||
3 | * | ||
4 | * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com | ||
5 | * | ||
6 | * Author: Andreas Dannenberg <dannenberg@ti.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * version 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/errno.h> | ||
20 | #include <linux/device.h> | ||
21 | #include <linux/i2c.h> | ||
22 | #include <linux/pm_runtime.h> | ||
23 | #include <linux/regmap.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/regulator/consumer.h> | ||
26 | #include <linux/delay.h> | ||
27 | |||
28 | #include <sound/pcm.h> | ||
29 | #include <sound/pcm_params.h> | ||
30 | #include <sound/soc.h> | ||
31 | #include <sound/soc-dapm.h> | ||
32 | #include <sound/tlv.h> | ||
33 | |||
34 | #include "tas5720.h" | ||
35 | |||
36 | /* Define how often to check (and clear) the fault status register (in ms) */ | ||
37 | #define TAS5720_FAULT_CHECK_INTERVAL 200 | ||
38 | |||
39 | static const char * const tas5720_supply_names[] = { | ||
40 | "dvdd", /* Digital power supply. Connect to 3.3-V supply. */ | ||
41 | "pvdd", /* Class-D amp and analog power supply (connected). */ | ||
42 | }; | ||
43 | |||
44 | #define TAS5720_NUM_SUPPLIES ARRAY_SIZE(tas5720_supply_names) | ||
45 | |||
46 | struct tas5720_data { | ||
47 | struct snd_soc_codec *codec; | ||
48 | struct regmap *regmap; | ||
49 | struct i2c_client *tas5720_client; | ||
50 | struct regulator_bulk_data supplies[TAS5720_NUM_SUPPLIES]; | ||
51 | struct delayed_work fault_check_work; | ||
52 | unsigned int last_fault; | ||
53 | }; | ||
54 | |||
55 | static int tas5720_hw_params(struct snd_pcm_substream *substream, | ||
56 | struct snd_pcm_hw_params *params, | ||
57 | struct snd_soc_dai *dai) | ||
58 | { | ||
59 | struct snd_soc_codec *codec = dai->codec; | ||
60 | unsigned int rate = params_rate(params); | ||
61 | bool ssz_ds; | ||
62 | int ret; | ||
63 | |||
64 | switch (rate) { | ||
65 | case 44100: | ||
66 | case 48000: | ||
67 | ssz_ds = false; | ||
68 | break; | ||
69 | case 88200: | ||
70 | case 96000: | ||
71 | ssz_ds = true; | ||
72 | break; | ||
73 | default: | ||
74 | dev_err(codec->dev, "unsupported sample rate: %u\n", rate); | ||
75 | return -EINVAL; | ||
76 | } | ||
77 | |||
78 | ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, | ||
79 | TAS5720_SSZ_DS, ssz_ds); | ||
80 | if (ret < 0) { | ||
81 | dev_err(codec->dev, "error setting sample rate: %d\n", ret); | ||
82 | return ret; | ||
83 | } | ||
84 | |||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | static int tas5720_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) | ||
89 | { | ||
90 | struct snd_soc_codec *codec = dai->codec; | ||
91 | u8 serial_format; | ||
92 | int ret; | ||
93 | |||
94 | if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { | ||
95 | dev_vdbg(codec->dev, "DAI Format master is not found\n"); | ||
96 | return -EINVAL; | ||
97 | } | ||
98 | |||
99 | switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | | ||
100 | SND_SOC_DAIFMT_INV_MASK)) { | ||
101 | case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): | ||
102 | /* 1st data bit occur one BCLK cycle after the frame sync */ | ||
103 | serial_format = TAS5720_SAIF_I2S; | ||
104 | break; | ||
105 | case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF): | ||
106 | /* | ||
107 | * Note that although the TAS5720 does not have a dedicated DSP | ||
108 | * mode it doesn't care about the LRCLK duty cycle during TDM | ||
109 | * operation. Therefore we can use the device's I2S mode with | ||
110 | * its delaying of the 1st data bit to receive DSP_A formatted | ||
111 | * data. See device datasheet for additional details. | ||
112 | */ | ||
113 | serial_format = TAS5720_SAIF_I2S; | ||
114 | break; | ||
115 | case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF): | ||
116 | /* | ||
117 | * Similar to DSP_A, we can use the fact that the TAS5720 does | ||
118 | * not care about the LRCLK duty cycle during TDM to receive | ||
119 | * DSP_B formatted data in LEFTJ mode (no delaying of the 1st | ||
120 | * data bit). | ||
121 | */ | ||
122 | serial_format = TAS5720_SAIF_LEFTJ; | ||
123 | break; | ||
124 | case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): | ||
125 | /* No delay after the frame sync */ | ||
126 | serial_format = TAS5720_SAIF_LEFTJ; | ||
127 | break; | ||
128 | default: | ||
129 | dev_vdbg(codec->dev, "DAI Format is not found\n"); | ||
130 | return -EINVAL; | ||
131 | } | ||
132 | |||
133 | ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, | ||
134 | TAS5720_SAIF_FORMAT_MASK, | ||
135 | serial_format); | ||
136 | if (ret < 0) { | ||
137 | dev_err(codec->dev, "error setting SAIF format: %d\n", ret); | ||
138 | return ret; | ||
139 | } | ||
140 | |||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static int tas5720_set_dai_tdm_slot(struct snd_soc_dai *dai, | ||
145 | unsigned int tx_mask, unsigned int rx_mask, | ||
146 | int slots, int slot_width) | ||
147 | { | ||
148 | struct snd_soc_codec *codec = dai->codec; | ||
149 | unsigned int first_slot; | ||
150 | int ret; | ||
151 | |||
152 | if (!tx_mask) { | ||
153 | dev_err(codec->dev, "tx masks must not be 0\n"); | ||
154 | return -EINVAL; | ||
155 | } | ||
156 | |||
157 | /* | ||
158 | * Determine the first slot that is being requested. We will only | ||
159 | * use the first slot that is found since the TAS5720 is a mono | ||
160 | * amplifier. | ||
161 | */ | ||
162 | first_slot = __ffs(tx_mask); | ||
163 | |||
164 | if (first_slot > 7) { | ||
165 | dev_err(codec->dev, "slot selection out of bounds (%u)\n", | ||
166 | first_slot); | ||
167 | return -EINVAL; | ||
168 | } | ||
169 | |||
170 | /* Enable manual TDM slot selection (instead of I2C ID based) */ | ||
171 | ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL1_REG, | ||
172 | TAS5720_TDM_CFG_SRC, TAS5720_TDM_CFG_SRC); | ||
173 | if (ret < 0) | ||
174 | goto error_snd_soc_update_bits; | ||
175 | |||
176 | /* Configure the TDM slot to process audio from */ | ||
177 | ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, | ||
178 | TAS5720_TDM_SLOT_SEL_MASK, first_slot); | ||
179 | if (ret < 0) | ||
180 | goto error_snd_soc_update_bits; | ||
181 | |||
182 | return 0; | ||
183 | |||
184 | error_snd_soc_update_bits: | ||
185 | dev_err(codec->dev, "error configuring TDM mode: %d\n", ret); | ||
186 | return ret; | ||
187 | } | ||
188 | |||
189 | static int tas5720_mute(struct snd_soc_dai *dai, int mute) | ||
190 | { | ||
191 | struct snd_soc_codec *codec = dai->codec; | ||
192 | int ret; | ||
193 | |||
194 | ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, | ||
195 | TAS5720_MUTE, mute ? TAS5720_MUTE : 0); | ||
196 | if (ret < 0) { | ||
197 | dev_err(codec->dev, "error (un-)muting device: %d\n", ret); | ||
198 | return ret; | ||
199 | } | ||
200 | |||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static void tas5720_fault_check_work(struct work_struct *work) | ||
205 | { | ||
206 | struct tas5720_data *tas5720 = container_of(work, struct tas5720_data, | ||
207 | fault_check_work.work); | ||
208 | struct device *dev = tas5720->codec->dev; | ||
209 | unsigned int curr_fault; | ||
210 | int ret; | ||
211 | |||
212 | ret = regmap_read(tas5720->regmap, TAS5720_FAULT_REG, &curr_fault); | ||
213 | if (ret < 0) { | ||
214 | dev_err(dev, "failed to read FAULT register: %d\n", ret); | ||
215 | goto out; | ||
216 | } | ||
217 | |||
218 | /* Check/handle all errors except SAIF clock errors */ | ||
219 | curr_fault &= TAS5720_OCE | TAS5720_DCE | TAS5720_OTE; | ||
220 | |||
221 | /* | ||
222 | * Only flag errors once for a given occurrence. This is needed as | ||
223 | * the TAS5720 will take time clearing the fault condition internally | ||
224 | * during which we don't want to bombard the system with the same | ||
225 | * error message over and over. | ||
226 | */ | ||
227 | if ((curr_fault & TAS5720_OCE) && !(tas5720->last_fault & TAS5720_OCE)) | ||
228 | dev_crit(dev, "experienced an over current hardware fault\n"); | ||
229 | |||
230 | if ((curr_fault & TAS5720_DCE) && !(tas5720->last_fault & TAS5720_DCE)) | ||
231 | dev_crit(dev, "experienced a DC detection fault\n"); | ||
232 | |||
233 | if ((curr_fault & TAS5720_OTE) && !(tas5720->last_fault & TAS5720_OTE)) | ||
234 | dev_crit(dev, "experienced an over temperature fault\n"); | ||
235 | |||
236 | /* Store current fault value so we can detect any changes next time */ | ||
237 | tas5720->last_fault = curr_fault; | ||
238 | |||
239 | if (!curr_fault) | ||
240 | goto out; | ||
241 | |||
242 | /* | ||
243 | * Periodically toggle SDZ (shutdown bit) H->L->H to clear any latching | ||
244 | * faults as long as a fault condition persists. Always going through | ||
245 | * the full sequence no matter the first return value to minimizes | ||
246 | * chances for the device to end up in shutdown mode. | ||
247 | */ | ||
248 | ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, | ||
249 | TAS5720_SDZ, 0); | ||
250 | if (ret < 0) | ||
251 | dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); | ||
252 | |||
253 | ret = regmap_write_bits(tas5720->regmap, TAS5720_POWER_CTRL_REG, | ||
254 | TAS5720_SDZ, TAS5720_SDZ); | ||
255 | if (ret < 0) | ||
256 | dev_err(dev, "failed to write POWER_CTRL register: %d\n", ret); | ||
257 | |||
258 | out: | ||
259 | /* Schedule the next fault check at the specified interval */ | ||
260 | schedule_delayed_work(&tas5720->fault_check_work, | ||
261 | msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); | ||
262 | } | ||
263 | |||
264 | static int tas5720_codec_probe(struct snd_soc_codec *codec) | ||
265 | { | ||
266 | struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); | ||
267 | unsigned int device_id; | ||
268 | int ret; | ||
269 | |||
270 | tas5720->codec = codec; | ||
271 | |||
272 | ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), | ||
273 | tas5720->supplies); | ||
274 | if (ret != 0) { | ||
275 | dev_err(codec->dev, "failed to enable supplies: %d\n", ret); | ||
276 | return ret; | ||
277 | } | ||
278 | |||
279 | ret = regmap_read(tas5720->regmap, TAS5720_DEVICE_ID_REG, &device_id); | ||
280 | if (ret < 0) { | ||
281 | dev_err(codec->dev, "failed to read device ID register: %d\n", | ||
282 | ret); | ||
283 | goto probe_fail; | ||
284 | } | ||
285 | |||
286 | if (device_id != TAS5720_DEVICE_ID) { | ||
287 | dev_err(codec->dev, "wrong device ID. expected: %u read: %u\n", | ||
288 | TAS5720_DEVICE_ID, device_id); | ||
289 | ret = -ENODEV; | ||
290 | goto probe_fail; | ||
291 | } | ||
292 | |||
293 | /* Set device to mute */ | ||
294 | ret = snd_soc_update_bits(codec, TAS5720_DIGITAL_CTRL2_REG, | ||
295 | TAS5720_MUTE, TAS5720_MUTE); | ||
296 | if (ret < 0) | ||
297 | goto error_snd_soc_update_bits; | ||
298 | |||
299 | /* | ||
300 | * Enter shutdown mode - our default when not playing audio - to | ||
301 | * minimize current consumption. On the TAS5720 there is no real down | ||
302 | * side doing so as all device registers are preserved and the wakeup | ||
303 | * of the codec is rather quick which we do using a dapm widget. | ||
304 | */ | ||
305 | ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, | ||
306 | TAS5720_SDZ, 0); | ||
307 | if (ret < 0) | ||
308 | goto error_snd_soc_update_bits; | ||
309 | |||
310 | INIT_DELAYED_WORK(&tas5720->fault_check_work, tas5720_fault_check_work); | ||
311 | |||
312 | return 0; | ||
313 | |||
314 | error_snd_soc_update_bits: | ||
315 | dev_err(codec->dev, "error configuring device registers: %d\n", ret); | ||
316 | |||
317 | probe_fail: | ||
318 | regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), | ||
319 | tas5720->supplies); | ||
320 | return ret; | ||
321 | } | ||
322 | |||
323 | static int tas5720_codec_remove(struct snd_soc_codec *codec) | ||
324 | { | ||
325 | struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); | ||
326 | int ret; | ||
327 | |||
328 | cancel_delayed_work_sync(&tas5720->fault_check_work); | ||
329 | |||
330 | ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), | ||
331 | tas5720->supplies); | ||
332 | if (ret < 0) | ||
333 | dev_err(codec->dev, "failed to disable supplies: %d\n", ret); | ||
334 | |||
335 | return ret; | ||
336 | }; | ||
337 | |||
338 | static int tas5720_dac_event(struct snd_soc_dapm_widget *w, | ||
339 | struct snd_kcontrol *kcontrol, int event) | ||
340 | { | ||
341 | struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); | ||
342 | struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); | ||
343 | int ret; | ||
344 | |||
345 | if (event & SND_SOC_DAPM_POST_PMU) { | ||
346 | /* Take TAS5720 out of shutdown mode */ | ||
347 | ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, | ||
348 | TAS5720_SDZ, TAS5720_SDZ); | ||
349 | if (ret < 0) { | ||
350 | dev_err(codec->dev, "error waking codec: %d\n", ret); | ||
351 | return ret; | ||
352 | } | ||
353 | |||
354 | /* | ||
355 | * Observe codec shutdown-to-active time. The datasheet only | ||
356 | * lists a nominal value however just use-it as-is without | ||
357 | * additional padding to minimize the delay introduced in | ||
358 | * starting to play audio (actually there is other setup done | ||
359 | * by the ASoC framework that will provide additional delays, | ||
360 | * so we should always be safe). | ||
361 | */ | ||
362 | msleep(25); | ||
363 | |||
364 | /* Turn on TAS5720 periodic fault checking/handling */ | ||
365 | tas5720->last_fault = 0; | ||
366 | schedule_delayed_work(&tas5720->fault_check_work, | ||
367 | msecs_to_jiffies(TAS5720_FAULT_CHECK_INTERVAL)); | ||
368 | } else if (event & SND_SOC_DAPM_PRE_PMD) { | ||
369 | /* Disable TAS5720 periodic fault checking/handling */ | ||
370 | cancel_delayed_work_sync(&tas5720->fault_check_work); | ||
371 | |||
372 | /* Place TAS5720 in shutdown mode to minimize current draw */ | ||
373 | ret = snd_soc_update_bits(codec, TAS5720_POWER_CTRL_REG, | ||
374 | TAS5720_SDZ, 0); | ||
375 | if (ret < 0) { | ||
376 | dev_err(codec->dev, "error shutting down codec: %d\n", | ||
377 | ret); | ||
378 | return ret; | ||
379 | } | ||
380 | } | ||
381 | |||
382 | return 0; | ||
383 | } | ||
384 | |||
385 | #ifdef CONFIG_PM | ||
386 | static int tas5720_suspend(struct snd_soc_codec *codec) | ||
387 | { | ||
388 | struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); | ||
389 | int ret; | ||
390 | |||
391 | regcache_cache_only(tas5720->regmap, true); | ||
392 | regcache_mark_dirty(tas5720->regmap); | ||
393 | |||
394 | ret = regulator_bulk_disable(ARRAY_SIZE(tas5720->supplies), | ||
395 | tas5720->supplies); | ||
396 | if (ret < 0) | ||
397 | dev_err(codec->dev, "failed to disable supplies: %d\n", ret); | ||
398 | |||
399 | return ret; | ||
400 | } | ||
401 | |||
402 | static int tas5720_resume(struct snd_soc_codec *codec) | ||
403 | { | ||
404 | struct tas5720_data *tas5720 = snd_soc_codec_get_drvdata(codec); | ||
405 | int ret; | ||
406 | |||
407 | ret = regulator_bulk_enable(ARRAY_SIZE(tas5720->supplies), | ||
408 | tas5720->supplies); | ||
409 | if (ret < 0) { | ||
410 | dev_err(codec->dev, "failed to enable supplies: %d\n", ret); | ||
411 | return ret; | ||
412 | } | ||
413 | |||
414 | regcache_cache_only(tas5720->regmap, false); | ||
415 | |||
416 | ret = regcache_sync(tas5720->regmap); | ||
417 | if (ret < 0) { | ||
418 | dev_err(codec->dev, "failed to sync regcache: %d\n", ret); | ||
419 | return ret; | ||
420 | } | ||
421 | |||
422 | return 0; | ||
423 | } | ||
424 | #else | ||
425 | #define tas5720_suspend NULL | ||
426 | #define tas5720_resume NULL | ||
427 | #endif | ||
428 | |||
429 | static bool tas5720_is_volatile_reg(struct device *dev, unsigned int reg) | ||
430 | { | ||
431 | switch (reg) { | ||
432 | case TAS5720_DEVICE_ID_REG: | ||
433 | case TAS5720_FAULT_REG: | ||
434 | return true; | ||
435 | default: | ||
436 | return false; | ||
437 | } | ||
438 | } | ||
439 | |||
440 | static const struct regmap_config tas5720_regmap_config = { | ||
441 | .reg_bits = 8, | ||
442 | .val_bits = 8, | ||
443 | |||
444 | .max_register = TAS5720_MAX_REG, | ||
445 | .cache_type = REGCACHE_RBTREE, | ||
446 | .volatile_reg = tas5720_is_volatile_reg, | ||
447 | }; | ||
448 | |||
449 | /* | ||
450 | * DAC analog gain. There are four discrete values to select from, ranging | ||
451 | * from 19.2 dB to 26.3dB. | ||
452 | */ | ||
453 | static const DECLARE_TLV_DB_RANGE(dac_analog_tlv, | ||
454 | 0x0, 0x0, TLV_DB_SCALE_ITEM(1920, 0, 0), | ||
455 | 0x1, 0x1, TLV_DB_SCALE_ITEM(2070, 0, 0), | ||
456 | 0x2, 0x2, TLV_DB_SCALE_ITEM(2350, 0, 0), | ||
457 | 0x3, 0x3, TLV_DB_SCALE_ITEM(2630, 0, 0), | ||
458 | ); | ||
459 | |||
460 | /* | ||
461 | * DAC digital volumes. From -103.5 to 24 dB in 0.5 dB steps. Note that | ||
462 | * setting the gain below -100 dB (register value <0x7) is effectively a MUTE | ||
463 | * as per device datasheet. | ||
464 | */ | ||
465 | static DECLARE_TLV_DB_SCALE(dac_tlv, -10350, 50, 0); | ||
466 | |||
467 | static const struct snd_kcontrol_new tas5720_snd_controls[] = { | ||
468 | SOC_SINGLE_TLV("Speaker Driver Playback Volume", | ||
469 | TAS5720_VOLUME_CTRL_REG, 0, 0xff, 0, dac_tlv), | ||
470 | SOC_SINGLE_TLV("Speaker Driver Analog Gain", TAS5720_ANALOG_CTRL_REG, | ||
471 | TAS5720_ANALOG_GAIN_SHIFT, 3, 0, dac_analog_tlv), | ||
472 | }; | ||
473 | |||
474 | static const struct snd_soc_dapm_widget tas5720_dapm_widgets[] = { | ||
475 | SND_SOC_DAPM_AIF_IN("DAC IN", "Playback", 0, SND_SOC_NOPM, 0, 0), | ||
476 | SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas5720_dac_event, | ||
477 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), | ||
478 | SND_SOC_DAPM_OUTPUT("OUT") | ||
479 | }; | ||
480 | |||
481 | static const struct snd_soc_dapm_route tas5720_audio_map[] = { | ||
482 | { "DAC", NULL, "DAC IN" }, | ||
483 | { "OUT", NULL, "DAC" }, | ||
484 | }; | ||
485 | |||
486 | static struct snd_soc_codec_driver soc_codec_dev_tas5720 = { | ||
487 | .probe = tas5720_codec_probe, | ||
488 | .remove = tas5720_codec_remove, | ||
489 | .suspend = tas5720_suspend, | ||
490 | .resume = tas5720_resume, | ||
491 | |||
492 | .controls = tas5720_snd_controls, | ||
493 | .num_controls = ARRAY_SIZE(tas5720_snd_controls), | ||
494 | .dapm_widgets = tas5720_dapm_widgets, | ||
495 | .num_dapm_widgets = ARRAY_SIZE(tas5720_dapm_widgets), | ||
496 | .dapm_routes = tas5720_audio_map, | ||
497 | .num_dapm_routes = ARRAY_SIZE(tas5720_audio_map), | ||
498 | }; | ||
499 | |||
500 | /* PCM rates supported by the TAS5720 driver */ | ||
501 | #define TAS5720_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ | ||
502 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) | ||
503 | |||
504 | /* Formats supported by TAS5720 driver */ | ||
505 | #define TAS5720_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ | ||
506 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) | ||
507 | |||
508 | static struct snd_soc_dai_ops tas5720_speaker_dai_ops = { | ||
509 | .hw_params = tas5720_hw_params, | ||
510 | .set_fmt = tas5720_set_dai_fmt, | ||
511 | .set_tdm_slot = tas5720_set_dai_tdm_slot, | ||
512 | .digital_mute = tas5720_mute, | ||
513 | }; | ||
514 | |||
515 | /* | ||
516 | * TAS5720 DAI structure | ||
517 | * | ||
518 | * Note that were are advertising .playback.channels_max = 2 despite this being | ||
519 | * a mono amplifier. The reason for that is that some serial ports such as TI's | ||
520 | * McASP module have a minimum number of channels (2) that they can output. | ||
521 | * Advertising more channels than we have will allow us to interface with such | ||
522 | * a serial port without really any negative side effects as the TAS5720 will | ||
523 | * simply ignore any extra channel(s) asides from the one channel that is | ||
524 | * configured to be played back. | ||
525 | */ | ||
526 | static struct snd_soc_dai_driver tas5720_dai[] = { | ||
527 | { | ||
528 | .name = "tas5720-amplifier", | ||
529 | .playback = { | ||
530 | .stream_name = "Playback", | ||
531 | .channels_min = 1, | ||
532 | .channels_max = 2, | ||
533 | .rates = TAS5720_RATES, | ||
534 | .formats = TAS5720_FORMATS, | ||
535 | }, | ||
536 | .ops = &tas5720_speaker_dai_ops, | ||
537 | }, | ||
538 | }; | ||
539 | |||
540 | static int tas5720_probe(struct i2c_client *client, | ||
541 | const struct i2c_device_id *id) | ||
542 | { | ||
543 | struct device *dev = &client->dev; | ||
544 | struct tas5720_data *data; | ||
545 | int ret; | ||
546 | int i; | ||
547 | |||
548 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | ||
549 | if (!data) | ||
550 | return -ENOMEM; | ||
551 | |||
552 | data->tas5720_client = client; | ||
553 | data->regmap = devm_regmap_init_i2c(client, &tas5720_regmap_config); | ||
554 | if (IS_ERR(data->regmap)) { | ||
555 | ret = PTR_ERR(data->regmap); | ||
556 | dev_err(dev, "failed to allocate register map: %d\n", ret); | ||
557 | return ret; | ||
558 | } | ||
559 | |||
560 | for (i = 0; i < ARRAY_SIZE(data->supplies); i++) | ||
561 | data->supplies[i].supply = tas5720_supply_names[i]; | ||
562 | |||
563 | ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), | ||
564 | data->supplies); | ||
565 | if (ret != 0) { | ||
566 | dev_err(dev, "failed to request supplies: %d\n", ret); | ||
567 | return ret; | ||
568 | } | ||
569 | |||
570 | dev_set_drvdata(dev, data); | ||
571 | |||
572 | ret = snd_soc_register_codec(&client->dev, | ||
573 | &soc_codec_dev_tas5720, | ||
574 | tas5720_dai, ARRAY_SIZE(tas5720_dai)); | ||
575 | if (ret < 0) { | ||
576 | dev_err(dev, "failed to register codec: %d\n", ret); | ||
577 | return ret; | ||
578 | } | ||
579 | |||
580 | return 0; | ||
581 | } | ||
582 | |||
583 | static int tas5720_remove(struct i2c_client *client) | ||
584 | { | ||
585 | struct device *dev = &client->dev; | ||
586 | |||
587 | snd_soc_unregister_codec(dev); | ||
588 | |||
589 | return 0; | ||
590 | } | ||
591 | |||
592 | static const struct i2c_device_id tas5720_id[] = { | ||
593 | { "tas5720", 0 }, | ||
594 | { } | ||
595 | }; | ||
596 | MODULE_DEVICE_TABLE(i2c, tas5720_id); | ||
597 | |||
598 | #if IS_ENABLED(CONFIG_OF) | ||
599 | static const struct of_device_id tas5720_of_match[] = { | ||
600 | { .compatible = "ti,tas5720", }, | ||
601 | { }, | ||
602 | }; | ||
603 | MODULE_DEVICE_TABLE(of, tas5720_of_match); | ||
604 | #endif | ||
605 | |||
606 | static struct i2c_driver tas5720_i2c_driver = { | ||
607 | .driver = { | ||
608 | .name = "tas5720", | ||
609 | .of_match_table = of_match_ptr(tas5720_of_match), | ||
610 | }, | ||
611 | .probe = tas5720_probe, | ||
612 | .remove = tas5720_remove, | ||
613 | .id_table = tas5720_id, | ||
614 | }; | ||
615 | |||
616 | module_i2c_driver(tas5720_i2c_driver); | ||
617 | |||
618 | MODULE_AUTHOR("Andreas Dannenberg <dannenberg@ti.com>"); | ||
619 | MODULE_DESCRIPTION("TAS5720 Audio amplifier driver"); | ||
620 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/codecs/tas5720.h b/sound/soc/codecs/tas5720.h new file mode 100644 index 000000000000..3d077c779b12 --- /dev/null +++ b/sound/soc/codecs/tas5720.h | |||
@@ -0,0 +1,90 @@ | |||
1 | /* | ||
2 | * tas5720.h - ALSA SoC Texas Instruments TAS5720 Mono Audio Amplifier | ||
3 | * | ||
4 | * Copyright (C)2015-2016 Texas Instruments Incorporated - http://www.ti.com | ||
5 | * | ||
6 | * Author: Andreas Dannenberg <dannenberg@ti.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * version 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | */ | ||
17 | |||
18 | #ifndef __TAS5720_H__ | ||
19 | #define __TAS5720_H__ | ||
20 | |||
21 | /* Register Address Map */ | ||
22 | #define TAS5720_DEVICE_ID_REG 0x00 | ||
23 | #define TAS5720_POWER_CTRL_REG 0x01 | ||
24 | #define TAS5720_DIGITAL_CTRL1_REG 0x02 | ||
25 | #define TAS5720_DIGITAL_CTRL2_REG 0x03 | ||
26 | #define TAS5720_VOLUME_CTRL_REG 0x04 | ||
27 | #define TAS5720_ANALOG_CTRL_REG 0x06 | ||
28 | #define TAS5720_FAULT_REG 0x08 | ||
29 | #define TAS5720_DIGITAL_CLIP2_REG 0x10 | ||
30 | #define TAS5720_DIGITAL_CLIP1_REG 0x11 | ||
31 | #define TAS5720_MAX_REG TAS5720_DIGITAL_CLIP1_REG | ||
32 | |||
33 | /* TAS5720_DEVICE_ID_REG */ | ||
34 | #define TAS5720_DEVICE_ID 0x01 | ||
35 | |||
36 | /* TAS5720_POWER_CTRL_REG */ | ||
37 | #define TAS5720_DIG_CLIP_MASK GENMASK(7, 2) | ||
38 | #define TAS5720_SLEEP BIT(1) | ||
39 | #define TAS5720_SDZ BIT(0) | ||
40 | |||
41 | /* TAS5720_DIGITAL_CTRL1_REG */ | ||
42 | #define TAS5720_HPF_BYPASS BIT(7) | ||
43 | #define TAS5720_TDM_CFG_SRC BIT(6) | ||
44 | #define TAS5720_SSZ_DS BIT(3) | ||
45 | #define TAS5720_SAIF_RIGHTJ_24BIT (0x0) | ||
46 | #define TAS5720_SAIF_RIGHTJ_20BIT (0x1) | ||
47 | #define TAS5720_SAIF_RIGHTJ_18BIT (0x2) | ||
48 | #define TAS5720_SAIF_RIGHTJ_16BIT (0x3) | ||
49 | #define TAS5720_SAIF_I2S (0x4) | ||
50 | #define TAS5720_SAIF_LEFTJ (0x5) | ||
51 | #define TAS5720_SAIF_FORMAT_MASK GENMASK(2, 0) | ||
52 | |||
53 | /* TAS5720_DIGITAL_CTRL2_REG */ | ||
54 | #define TAS5720_MUTE BIT(4) | ||
55 | #define TAS5720_TDM_SLOT_SEL_MASK GENMASK(2, 0) | ||
56 | |||
57 | /* TAS5720_ANALOG_CTRL_REG */ | ||
58 | #define TAS5720_PWM_RATE_6_3_FSYNC (0x0 << 4) | ||
59 | #define TAS5720_PWM_RATE_8_4_FSYNC (0x1 << 4) | ||
60 | #define TAS5720_PWM_RATE_10_5_FSYNC (0x2 << 4) | ||
61 | #define TAS5720_PWM_RATE_12_6_FSYNC (0x3 << 4) | ||
62 | #define TAS5720_PWM_RATE_14_7_FSYNC (0x4 << 4) | ||
63 | #define TAS5720_PWM_RATE_16_8_FSYNC (0x5 << 4) | ||
64 | #define TAS5720_PWM_RATE_20_10_FSYNC (0x6 << 4) | ||
65 | #define TAS5720_PWM_RATE_24_12_FSYNC (0x7 << 4) | ||
66 | #define TAS5720_PWM_RATE_MASK GENMASK(6, 4) | ||
67 | #define TAS5720_ANALOG_GAIN_19_2DBV (0x0 << 2) | ||
68 | #define TAS5720_ANALOG_GAIN_20_7DBV (0x1 << 2) | ||
69 | #define TAS5720_ANALOG_GAIN_23_5DBV (0x2 << 2) | ||
70 | #define TAS5720_ANALOG_GAIN_26_3DBV (0x3 << 2) | ||
71 | #define TAS5720_ANALOG_GAIN_MASK GENMASK(3, 2) | ||
72 | #define TAS5720_ANALOG_GAIN_SHIFT (0x2) | ||
73 | |||
74 | /* TAS5720_FAULT_REG */ | ||
75 | #define TAS5720_OC_THRESH_100PCT (0x0 << 4) | ||
76 | #define TAS5720_OC_THRESH_75PCT (0x1 << 4) | ||
77 | #define TAS5720_OC_THRESH_50PCT (0x2 << 4) | ||
78 | #define TAS5720_OC_THRESH_25PCT (0x3 << 4) | ||
79 | #define TAS5720_OC_THRESH_MASK GENMASK(5, 4) | ||
80 | #define TAS5720_CLKE BIT(3) | ||
81 | #define TAS5720_OCE BIT(2) | ||
82 | #define TAS5720_DCE BIT(1) | ||
83 | #define TAS5720_OTE BIT(0) | ||
84 | #define TAS5720_FAULT_MASK GENMASK(3, 0) | ||
85 | |||
86 | /* TAS5720_DIGITAL_CLIP1_REG */ | ||
87 | #define TAS5720_CLIP1_MASK GENMASK(7, 2) | ||
88 | #define TAS5720_CLIP1_SHIFT (0x2) | ||
89 | |||
90 | #endif /* __TAS5720_H__ */ | ||