aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSylwester Nawrocki <s.nawrocki@samsung.com>2016-06-29 07:26:37 -0400
committerMark Brown <broonie@kernel.org>2016-06-29 15:21:50 -0400
commit4c5d1469297d14d59f5f673493bc02fc939293a4 (patch)
tree93f75c4a502630ccaea9298eb0ecee0c1cfddccd
parent1a695a905c18548062509178b98bc91e67510864 (diff)
ASoC: max98504: Add max98504 speaker amplifier driver
This patch adds driver for the MAX98504 speaker amplifier. The MAX98504 is a high efficiency mono class D amplifier that features an integrated boost converter with voltage and current sensing ADCs for Dynamic Speaker Management. This driver does not include support for the I2S DAI, as we wouldn't be able to test such code in a hardware configuration where the amplifier has only wired the analogue input. Signed-off-by: Inha Song <ideal.song@samsung.com> [k.kozlowski: rebased on 4.1] Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com> [s.nawrocki: removed unused macro definitions, rewrote regulator supply related parts, rewrote regmap configuration code, added support for speaker enable and global chip enable through DAPM, rewritten as component driver, added PDM DAI definition and TDM callbacks for PDM channels configuration] Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com> -- Changes since v2: - added parsing of the VBAT brownout DT properties, - removed MAX98504_REG_SPEAKER_SOURCE_SELECT register initialization, - removed unused macro definitions. Changes since v1: - none. Changes since initial version: - added regulator supply handling, - added DAPM widges for speaker source selection, - added PDM DAI definition and TDM callbacks for setting up active PDM Tx channels and I/V sense ADC data mapping, - removed all optional DT properties, added regulator supply properties in the DT binding. Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r--sound/soc/codecs/Kconfig4
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/max98504.c383
-rw-r--r--sound/soc/codecs/max98504.h59
4 files changed, 448 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4d82a58ff6b0..b141d7816878 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -535,6 +535,10 @@ config SND_SOC_MAX98357A
535config SND_SOC_MAX98371 535config SND_SOC_MAX98371
536 tristate 536 tristate
537 537
538config SND_SOC_MAX98504
539 tristate "Maxim MAX98504 speaker amplifier"
540 depends on I2C
541
538config SND_SOC_MAX9867 542config SND_SOC_MAX9867
539 tristate 543 tristate
540 544
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 0f548fd34ca3..fd8c477244e8 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -208,6 +208,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o
208 208
209# Amp 209# Amp
210snd-soc-max9877-objs := max9877.o 210snd-soc-max9877-objs := max9877.o
211snd-soc-max98504-objs := max98504.o
211snd-soc-tpa6130a2-objs := tpa6130a2.o 212snd-soc-tpa6130a2-objs := tpa6130a2.o
212snd-soc-tas2552-objs := tas2552.o 213snd-soc-tas2552-objs := tas2552.o
213 214
@@ -419,4 +420,5 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
419 420
420# Amp 421# Amp
421obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o 422obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
423obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o
422obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o 424obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
diff --git a/sound/soc/codecs/max98504.c b/sound/soc/codecs/max98504.c
new file mode 100644
index 000000000000..a7320e709890
--- /dev/null
+++ b/sound/soc/codecs/max98504.c
@@ -0,0 +1,383 @@
1/*
2 * MAX98504 ALSA SoC Audio driver
3 *
4 * Copyright 2013 - 2014 Maxim Integrated Products
5 * Copyright 2016 Samsung Electronics Co., Ltd.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#include <linux/delay.h>
13#include <linux/i2c.h>
14#include <linux/module.h>
15#include <linux/regulator/consumer.h>
16#include <linux/slab.h>
17#include <linux/types.h>
18#include <sound/soc.h>
19
20#include "max98504.h"
21
22static const char * const max98504_supply_names[] = {
23 "DVDD",
24 "DIOVDD",
25 "PVDD",
26};
27#define MAX98504_NUM_SUPPLIES ARRAY_SIZE(max98504_supply_names)
28
29struct max98504_priv {
30 struct regmap *regmap;
31 struct regulator_bulk_data supplies[MAX98504_NUM_SUPPLIES];
32 unsigned int pcm_rx_channels;
33 bool brownout_enable;
34 unsigned int brownout_threshold;
35 unsigned int brownout_attenuation;
36 unsigned int brownout_attack_hold;
37 unsigned int brownout_timed_hold;
38 unsigned int brownout_release_rate;
39};
40
41static struct reg_default max98504_reg_defaults[] = {
42 { 0x01, 0},
43 { 0x02, 0},
44 { 0x03, 0},
45 { 0x04, 0},
46 { 0x10, 0},
47 { 0x11, 0},
48 { 0x12, 0},
49 { 0x13, 0},
50 { 0x14, 0},
51 { 0x15, 0},
52 { 0x16, 0},
53 { 0x17, 0},
54 { 0x18, 0},
55 { 0x19, 0},
56 { 0x1A, 0},
57 { 0x20, 0},
58 { 0x21, 0},
59 { 0x22, 0},
60 { 0x23, 0},
61 { 0x24, 0},
62 { 0x25, 0},
63 { 0x26, 0},
64 { 0x27, 0},
65 { 0x28, 0},
66 { 0x30, 0},
67 { 0x31, 0},
68 { 0x32, 0},
69 { 0x33, 0},
70 { 0x34, 0},
71 { 0x35, 0},
72 { 0x36, 0},
73 { 0x37, 0},
74 { 0x38, 0},
75 { 0x39, 0},
76 { 0x40, 0},
77 { 0x41, 0},
78};
79
80static bool max98504_volatile_register(struct device *dev, unsigned int reg)
81{
82 switch (reg) {
83 case MAX98504_INTERRUPT_STATUS:
84 case MAX98504_INTERRUPT_FLAGS:
85 case MAX98504_INTERRUPT_FLAG_CLEARS:
86 case MAX98504_WATCHDOG_CLEAR:
87 case MAX98504_GLOBAL_ENABLE:
88 case MAX98504_SOFTWARE_RESET:
89 return true;
90 default:
91 return false;
92 }
93}
94
95static bool max98504_readable_register(struct device *dev, unsigned int reg)
96{
97 switch (reg) {
98 case MAX98504_SOFTWARE_RESET:
99 case MAX98504_WATCHDOG_CLEAR:
100 case MAX98504_INTERRUPT_FLAG_CLEARS:
101 return false;
102 default:
103 return true;
104 }
105}
106
107static int max98504_pcm_rx_ev(struct snd_soc_dapm_widget *w,
108 struct snd_kcontrol *kcontrol, int event)
109{
110 struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm);
111 struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
112
113 switch (event) {
114 case SND_SOC_DAPM_PRE_PMU:
115 regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE,
116 max98504->pcm_rx_channels);
117 break;
118 case SND_SOC_DAPM_POST_PMD:
119 regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, 0);
120 break;
121 }
122
123 return 0;
124}
125
126static int max98504_component_probe(struct snd_soc_component *c)
127{
128 struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
129 struct regmap *map = max98504->regmap;
130 int ret;
131
132 ret = regulator_bulk_enable(MAX98504_NUM_SUPPLIES, max98504->supplies);
133 if (ret < 0)
134 return ret;
135
136 regmap_write(map, MAX98504_SOFTWARE_RESET, 0x1);
137 msleep(20);
138
139 if (!max98504->brownout_enable)
140 return 0;
141
142 regmap_write(map, MAX98504_PVDD_BROWNOUT_ENABLE, 0x1);
143
144 regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_1,
145 (max98504->brownout_threshold & 0x1f) << 3 |
146 (max98504->brownout_attenuation & 0x3));
147
148 regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_2,
149 max98504->brownout_attack_hold & 0xff);
150
151 regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_3,
152 max98504->brownout_timed_hold & 0xff);
153
154 regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_4,
155 max98504->brownout_release_rate & 0xff);
156
157 return 0;
158}
159
160static void max98504_component_remove(struct snd_soc_component *c)
161{
162 struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c);
163
164 regulator_bulk_disable(MAX98504_NUM_SUPPLIES, max98504->supplies);
165}
166
167static const char *spk_source_mux_text[] = {
168 "PCM Monomix", "Analog In", "PDM Left", "PDM Right"
169};
170
171static const struct soc_enum spk_source_mux_enum =
172 SOC_ENUM_SINGLE(MAX98504_SPEAKER_SOURCE_SELECT,
173 0, ARRAY_SIZE(spk_source_mux_text),
174 spk_source_mux_text);
175
176static const struct snd_kcontrol_new spk_source_mux =
177 SOC_DAPM_ENUM("SPK Source", spk_source_mux_enum);
178
179static const struct snd_soc_dapm_route max98504_dapm_routes[] = {
180 { "SPKOUT", NULL, "Global Enable" },
181 { "SPK Source", "PCM Monomix", "DAC PCM" },
182 { "SPK Source", "Analog In", "AIN" },
183 { "SPK Source", "PDM Left", "DAC PDM" },
184 { "SPK Source", "PDM Right", "DAC PDM" },
185};
186
187static const struct snd_soc_dapm_widget max98504_dapm_widgets[] = {
188 SND_SOC_DAPM_SUPPLY("Global Enable", MAX98504_GLOBAL_ENABLE,
189 0, 0, NULL, 0),
190 SND_SOC_DAPM_INPUT("AIN"),
191 SND_SOC_DAPM_AIF_OUT("AIF2OUTL", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
192 SND_SOC_DAPM_AIF_OUT("AIF2OUTR", "AIF2 Capture", 1, SND_SOC_NOPM, 0, 0),
193 SND_SOC_DAPM_DAC_E("DAC PCM", NULL, SND_SOC_NOPM, 0, 0,
194 max98504_pcm_rx_ev,
195 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
196 SND_SOC_DAPM_DAC("DAC PDM", NULL, MAX98504_PDM_RX_ENABLE, 0, 0),
197 SND_SOC_DAPM_MUX("SPK Source", SND_SOC_NOPM, 0, 0, &spk_source_mux),
198 SND_SOC_DAPM_REG(snd_soc_dapm_spk, "SPKOUT",
199 MAX98504_SPEAKER_ENABLE, 0, 1, 1, 0),
200};
201
202static int max98504_set_tdm_slot(struct snd_soc_dai *dai,
203 unsigned int tx_mask, unsigned int rx_mask,
204 int slots, int slot_width)
205{
206 struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai);
207 struct regmap *map = max98504->regmap;
208
209
210 switch (dai->id) {
211 case MAX98504_DAI_ID_PCM:
212 regmap_write(map, MAX98504_PCM_TX_ENABLE, tx_mask);
213 max98504->pcm_rx_channels = rx_mask;
214 break;
215
216 case MAX98504_DAI_ID_PDM:
217 regmap_write(map, MAX98504_PDM_TX_ENABLE, tx_mask);
218 break;
219 default:
220 WARN_ON(1);
221 }
222
223 return 0;
224}
225static int max98504_set_channel_map(struct snd_soc_dai *dai,
226 unsigned int tx_num, unsigned int *tx_slot,
227 unsigned int rx_num, unsigned int *rx_slot)
228{
229 struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai);
230 struct regmap *map = max98504->regmap;
231 unsigned int i, sources = 0;
232
233 for (i = 0; i < tx_num; i++)
234 if (tx_slot[i])
235 sources |= (1 << i);
236
237 switch (dai->id) {
238 case MAX98504_DAI_ID_PCM:
239 regmap_write(map, MAX98504_PCM_TX_CHANNEL_SOURCES,
240 sources);
241 break;
242
243 case MAX98504_DAI_ID_PDM:
244 regmap_write(map, MAX98504_PDM_TX_CONTROL, sources);
245 break;
246 default:
247 WARN_ON(1);
248 }
249
250 regmap_write(map, MAX98504_MEASUREMENT_ENABLE, sources ? 0x3 : 0x01);
251
252 return 0;
253}
254
255static const struct snd_soc_dai_ops max98504_dai_ops = {
256 .set_tdm_slot = max98504_set_tdm_slot,
257 .set_channel_map = max98504_set_channel_map,
258};
259
260#define MAX98504_FORMATS (SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|\
261 SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE)
262#define MAX98504_PDM_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
263 SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|\
264 SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|\
265 SNDRV_PCM_RATE_96000)
266
267static struct snd_soc_dai_driver max98504_dai[] = {
268 /* TODO: Add the PCM interface definitions */
269 {
270 .name = "max98504-aif2",
271 .id = MAX98504_DAI_ID_PDM,
272 .playback = {
273 .stream_name = "AIF2 Playback",
274 .channels_min = 1,
275 .channels_max = 2,
276 .rates = MAX98504_PDM_RATES,
277 .formats = MAX98504_FORMATS,
278 },
279 .capture = {
280 .stream_name = "AIF2 Capture",
281 .channels_min = 1,
282 .channels_max = 2,
283 .rates = MAX98504_PDM_RATES,
284 .formats = MAX98504_FORMATS,
285 },
286 .ops = &max98504_dai_ops,
287 },
288};
289
290static const struct snd_soc_component_driver max98504_component_driver = {
291 .probe = max98504_component_probe,
292 .remove = max98504_component_remove,
293 .dapm_widgets = max98504_dapm_widgets,
294 .num_dapm_widgets = ARRAY_SIZE(max98504_dapm_widgets),
295 .dapm_routes = max98504_dapm_routes,
296 .num_dapm_routes = ARRAY_SIZE(max98504_dapm_routes),
297};
298
299static const struct regmap_config max98504_regmap = {
300 .reg_bits = 16,
301 .val_bits = 8,
302 .max_register = MAX98504_MAX_REGISTER,
303 .reg_defaults = max98504_reg_defaults,
304 .num_reg_defaults = ARRAY_SIZE(max98504_reg_defaults),
305 .volatile_reg = max98504_volatile_register,
306 .readable_reg = max98504_readable_register,
307 .cache_type = REGCACHE_RBTREE,
308};
309
310static int max98504_i2c_probe(struct i2c_client *client,
311 const struct i2c_device_id *id)
312{
313 struct device *dev = &client->dev;
314 struct device_node *node = dev->of_node;
315 struct max98504_priv *max98504;
316 int i, ret;
317
318 max98504 = devm_kzalloc(dev, sizeof(*max98504), GFP_KERNEL);
319 if (!max98504)
320 return -ENOMEM;
321
322 if (node) {
323 if (!of_property_read_u32(node, "maxim,brownout-threshold",
324 &max98504->brownout_threshold))
325 max98504->brownout_enable = true;
326
327 of_property_read_u32(node, "maxim,brownout-attenuation",
328 &max98504->brownout_attenuation);
329 of_property_read_u32(node, "maxim,brownout-attack-hold-ms",
330 &max98504->brownout_attack_hold);
331 of_property_read_u32(node, "maxim,brownout-timed-hold-ms",
332 &max98504->brownout_timed_hold);
333 of_property_read_u32(node, "maxim,brownout-release-rate-ms",
334 &max98504->brownout_release_rate);
335 }
336
337 max98504->regmap = devm_regmap_init_i2c(client, &max98504_regmap);
338 if (IS_ERR(max98504->regmap)) {
339 ret = PTR_ERR(max98504->regmap);
340 dev_err(&client->dev, "regmap initialization failed: %d\n", ret);
341 return ret;
342 }
343
344 for (i = 0; i < MAX98504_NUM_SUPPLIES; i++)
345 max98504->supplies[i].supply = max98504_supply_names[i];
346
347 ret = devm_regulator_bulk_get(dev, MAX98504_NUM_SUPPLIES,
348 max98504->supplies);
349 if (ret < 0)
350 return ret;
351
352 i2c_set_clientdata(client, max98504);
353
354 return devm_snd_soc_register_component(dev, &max98504_component_driver,
355 max98504_dai, ARRAY_SIZE(max98504_dai));
356}
357
358#ifdef CONFIG_OF
359static const struct of_device_id max98504_of_match[] = {
360 { .compatible = "maxim,max98504" },
361 { },
362};
363MODULE_DEVICE_TABLE(of, max98504_of_match);
364#endif
365
366static const struct i2c_device_id max98504_i2c_id[] = {
367 { "max98504" },
368 { }
369};
370MODULE_DEVICE_TABLE(i2c, max98504_i2c_id);
371
372static struct i2c_driver max98504_i2c_driver = {
373 .driver = {
374 .name = "max98504",
375 .of_match_table = of_match_ptr(max98504_of_match),
376 },
377 .probe = max98504_i2c_probe,
378 .id_table = max98504_i2c_id,
379};
380module_i2c_driver(max98504_i2c_driver);
381
382MODULE_DESCRIPTION("ASoC MAX98504 driver");
383MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98504.h b/sound/soc/codecs/max98504.h
new file mode 100644
index 000000000000..afbefad2d5ce
--- /dev/null
+++ b/sound/soc/codecs/max98504.h
@@ -0,0 +1,59 @@
1/*
2 * MAX98504 ALSA SoC Audio driver
3 *
4 * Copyright 2011 - 2012 Maxim Integrated Products
5 * Copyright 2016 Samsung Electronics Co., Ltd.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11#ifndef MAX98504_H_
12#define MAX98504_H_
13
14/*
15 * MAX98504 Register Definitions
16 */
17#define MAX98504_INTERRUPT_STATUS 0x01
18#define MAX98504_INTERRUPT_FLAGS 0x02
19#define MAX98504_INTERRUPT_ENABLE 0x03
20#define MAX98504_INTERRUPT_FLAG_CLEARS 0x04
21#define MAX98504_GPIO_ENABLE 0x10
22#define MAX98504_GPIO_CONFIG 0x11
23#define MAX98504_WATCHDOG_ENABLE 0x12
24#define MAX98504_WATCHDOG_CONFIG 0x13
25#define MAX98504_WATCHDOG_CLEAR 0x14
26#define MAX98504_CLOCK_MONITOR_ENABLE 0x15
27#define MAX98504_PVDD_BROWNOUT_ENABLE 0x16
28#define MAX98504_PVDD_BROWNOUT_CONFIG_1 0x17
29#define MAX98504_PVDD_BROWNOUT_CONFIG_2 0x18
30#define MAX98504_PVDD_BROWNOUT_CONFIG_3 0x19
31#define MAX98504_PVDD_BROWNOUT_CONFIG_4 0x1a
32#define MAX98504_PCM_RX_ENABLE 0x20
33#define MAX98504_PCM_TX_ENABLE 0x21
34#define MAX98504_PCM_TX_HIZ_CONTROL 0x22
35#define MAX98504_PCM_TX_CHANNEL_SOURCES 0x23
36#define MAX98504_PCM_MODE_CONFIG 0x24
37#define MAX98504_PCM_DSP_CONFIG 0x25
38#define MAX98504_PCM_CLOCK_SETUP 0x26
39#define MAX98504_PCM_SAMPLE_RATE_SETUP 0x27
40#define MAX98504_PCM_TO_SPEAKER_MONOMIX 0x28
41#define MAX98504_PDM_TX_ENABLE 0x30
42#define MAX98504_PDM_TX_HIZ_CONTROL 0x31
43#define MAX98504_PDM_TX_CONTROL 0x32
44#define MAX98504_PDM_RX_ENABLE 0x33
45#define MAX98504_SPEAKER_ENABLE 0x34
46#define MAX98504_SPEAKER_SOURCE_SELECT 0x35
47#define MAX98504_MEASUREMENT_ENABLE 0x36
48#define MAX98504_ANALOGUE_INPUT_GAIN 0x37
49#define MAX98504_TEMPERATURE_LIMIT_CONFIG 0x38
50#define MAX98504_GLOBAL_ENABLE 0x40
51#define MAX98504_SOFTWARE_RESET 0x41
52#define MAX98504_REV_ID 0x7fff
53
54#define MAX98504_MAX_REGISTER 0x7fff
55
56#define MAX98504_DAI_ID_PCM 1
57#define MAX98504_DAI_ID_PDM 2
58
59#endif /* MAX98504_H_ */