xref: /openbmc/linux/sound/soc/codecs/wm8728.c (revision 9a2abf70)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
271cfc902SMark Brown /*
371cfc902SMark Brown  * wm8728.c  --  WM8728 ALSA SoC Audio driver
471cfc902SMark Brown  *
571cfc902SMark Brown  * Copyright 2008 Wolfson Microelectronics plc
671cfc902SMark Brown  *
771cfc902SMark Brown  * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
871cfc902SMark Brown  */
971cfc902SMark Brown 
1071cfc902SMark Brown #include <linux/module.h>
1171cfc902SMark Brown #include <linux/moduleparam.h>
1271cfc902SMark Brown #include <linux/init.h>
1371cfc902SMark Brown #include <linux/delay.h>
1471cfc902SMark Brown #include <linux/pm.h>
1571cfc902SMark Brown #include <linux/i2c.h>
1671cfc902SMark Brown #include <linux/platform_device.h>
17d16383efSMark Brown #include <linux/regmap.h>
1871cfc902SMark Brown #include <linux/spi/spi.h>
195a0e3ad6STejun Heo #include <linux/slab.h>
2045b4d043SMark Brown #include <linux/of_device.h>
2171cfc902SMark Brown #include <sound/core.h>
2271cfc902SMark Brown #include <sound/pcm.h>
2371cfc902SMark Brown #include <sound/pcm_params.h>
2471cfc902SMark Brown #include <sound/soc.h>
2571cfc902SMark Brown #include <sound/initval.h>
2671cfc902SMark Brown #include <sound/tlv.h>
2771cfc902SMark Brown 
2871cfc902SMark Brown #include "wm8728.h"
2971cfc902SMark Brown 
3071cfc902SMark Brown /*
3171cfc902SMark Brown  * We can't read the WM8728 register space so we cache them instead.
3271cfc902SMark Brown  * Note that the defaults here aren't the physical defaults, we latch
3371cfc902SMark Brown  * the volume update bits, mute the output and enable infinite zero
3471cfc902SMark Brown  * detect.
3571cfc902SMark Brown  */
36d16383efSMark Brown static const struct reg_default wm8728_reg_defaults[] = {
37d16383efSMark Brown 	{ 0, 0x1ff },
38d16383efSMark Brown 	{ 1, 0x1ff },
39d16383efSMark Brown 	{ 2, 0x001 },
40d16383efSMark Brown 	{ 3, 0x100 },
4171cfc902SMark Brown };
4271cfc902SMark Brown 
43f0fba2adSLiam Girdwood /* codec private data */
44f0fba2adSLiam Girdwood struct wm8728_priv {
45d16383efSMark Brown 	struct regmap *regmap;
46f0fba2adSLiam Girdwood };
47f0fba2adSLiam Girdwood 
4871cfc902SMark Brown static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1);
4971cfc902SMark Brown 
5071cfc902SMark Brown static const struct snd_kcontrol_new wm8728_snd_controls[] = {
5171cfc902SMark Brown 
5271cfc902SMark Brown SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL,
5371cfc902SMark Brown 		 0, 255, 0, wm8728_tlv),
5471cfc902SMark Brown 
5571cfc902SMark Brown SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0),
5671cfc902SMark Brown };
5771cfc902SMark Brown 
5871cfc902SMark Brown /*
5971cfc902SMark Brown  * DAPM controls.
6071cfc902SMark Brown  */
6171cfc902SMark Brown static const struct snd_soc_dapm_widget wm8728_dapm_widgets[] = {
6271cfc902SMark Brown SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0),
6371cfc902SMark Brown SND_SOC_DAPM_OUTPUT("VOUTL"),
6471cfc902SMark Brown SND_SOC_DAPM_OUTPUT("VOUTR"),
6571cfc902SMark Brown };
6671cfc902SMark Brown 
678428edf9SLu Guanqun static const struct snd_soc_dapm_route wm8728_intercon[] = {
6871cfc902SMark Brown 	{"VOUTL", NULL, "DAC"},
6971cfc902SMark Brown 	{"VOUTR", NULL, "DAC"},
7071cfc902SMark Brown };
7171cfc902SMark Brown 
wm8728_mute(struct snd_soc_dai * dai,int mute,int direction)7226d3c16eSKuninori Morimoto static int wm8728_mute(struct snd_soc_dai *dai, int mute, int direction)
7371cfc902SMark Brown {
746b413080SKuninori Morimoto 	struct snd_soc_component *component = dai->component;
756d75dfc3SKuninori Morimoto 	u16 mute_reg = snd_soc_component_read(component, WM8728_DACCTL);
7671cfc902SMark Brown 
7771cfc902SMark Brown 	if (mute)
786b413080SKuninori Morimoto 		snd_soc_component_write(component, WM8728_DACCTL, mute_reg | 1);
7971cfc902SMark Brown 	else
806b413080SKuninori Morimoto 		snd_soc_component_write(component, WM8728_DACCTL, mute_reg & ~1);
8171cfc902SMark Brown 
8271cfc902SMark Brown 	return 0;
8371cfc902SMark Brown }
8471cfc902SMark Brown 
wm8728_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params,struct snd_soc_dai * dai)8571cfc902SMark Brown static int wm8728_hw_params(struct snd_pcm_substream *substream,
86dee89c4dSMark Brown 	struct snd_pcm_hw_params *params,
87dee89c4dSMark Brown 	struct snd_soc_dai *dai)
8871cfc902SMark Brown {
896b413080SKuninori Morimoto 	struct snd_soc_component *component = dai->component;
906d75dfc3SKuninori Morimoto 	u16 dac = snd_soc_component_read(component, WM8728_DACCTL);
9171cfc902SMark Brown 
9271cfc902SMark Brown 	dac &= ~0x18;
9371cfc902SMark Brown 
949fbad31aSMark Brown 	switch (params_width(params)) {
959fbad31aSMark Brown 	case 16:
9671cfc902SMark Brown 		break;
979fbad31aSMark Brown 	case 20:
9871cfc902SMark Brown 		dac |= 0x10;
9971cfc902SMark Brown 		break;
1009fbad31aSMark Brown 	case 24:
10171cfc902SMark Brown 		dac |= 0x08;
10271cfc902SMark Brown 		break;
10371cfc902SMark Brown 	default:
10471cfc902SMark Brown 		return -EINVAL;
10571cfc902SMark Brown 	}
10671cfc902SMark Brown 
1076b413080SKuninori Morimoto 	snd_soc_component_write(component, WM8728_DACCTL, dac);
10871cfc902SMark Brown 
10971cfc902SMark Brown 	return 0;
11071cfc902SMark Brown }
11171cfc902SMark Brown 
wm8728_set_dai_fmt(struct snd_soc_dai * codec_dai,unsigned int fmt)11271cfc902SMark Brown static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai,
11371cfc902SMark Brown 		unsigned int fmt)
11471cfc902SMark Brown {
1156b413080SKuninori Morimoto 	struct snd_soc_component *component = codec_dai->component;
1166d75dfc3SKuninori Morimoto 	u16 iface = snd_soc_component_read(component, WM8728_IFCTL);
11771cfc902SMark Brown 
11871cfc902SMark Brown 	/* Currently only I2S is supported by the driver, though the
11971cfc902SMark Brown 	 * hardware is more flexible.
12071cfc902SMark Brown 	 */
12171cfc902SMark Brown 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
12271cfc902SMark Brown 	case SND_SOC_DAIFMT_I2S:
12371cfc902SMark Brown 		iface |= 1;
12471cfc902SMark Brown 		break;
12571cfc902SMark Brown 	default:
12671cfc902SMark Brown 		return -EINVAL;
12771cfc902SMark Brown 	}
12871cfc902SMark Brown 
12971cfc902SMark Brown 	/* The hardware only support full slave mode */
13071cfc902SMark Brown 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
13171cfc902SMark Brown 	case SND_SOC_DAIFMT_CBS_CFS:
13271cfc902SMark Brown 		break;
13371cfc902SMark Brown 	default:
13471cfc902SMark Brown 		return -EINVAL;
13571cfc902SMark Brown 	}
13671cfc902SMark Brown 
13771cfc902SMark Brown 	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
13871cfc902SMark Brown 	case SND_SOC_DAIFMT_NB_NF:
13971cfc902SMark Brown 		iface &= ~0x22;
14071cfc902SMark Brown 		break;
14171cfc902SMark Brown 	case SND_SOC_DAIFMT_IB_NF:
14271cfc902SMark Brown 		iface |=  0x20;
14371cfc902SMark Brown 		iface &= ~0x02;
14471cfc902SMark Brown 		break;
14571cfc902SMark Brown 	case SND_SOC_DAIFMT_NB_IF:
14671cfc902SMark Brown 		iface |= 0x02;
14771cfc902SMark Brown 		iface &= ~0x20;
14871cfc902SMark Brown 		break;
14971cfc902SMark Brown 	case SND_SOC_DAIFMT_IB_IF:
15071cfc902SMark Brown 		iface |= 0x22;
15171cfc902SMark Brown 		break;
15271cfc902SMark Brown 	default:
15371cfc902SMark Brown 		return -EINVAL;
15471cfc902SMark Brown 	}
15571cfc902SMark Brown 
1566b413080SKuninori Morimoto 	snd_soc_component_write(component, WM8728_IFCTL, iface);
15771cfc902SMark Brown 	return 0;
15871cfc902SMark Brown }
15971cfc902SMark Brown 
wm8728_set_bias_level(struct snd_soc_component * component,enum snd_soc_bias_level level)1606b413080SKuninori Morimoto static int wm8728_set_bias_level(struct snd_soc_component *component,
16171cfc902SMark Brown 				 enum snd_soc_bias_level level)
16271cfc902SMark Brown {
1636b413080SKuninori Morimoto 	struct wm8728_priv *wm8728 = snd_soc_component_get_drvdata(component);
16471cfc902SMark Brown 	u16 reg;
16571cfc902SMark Brown 
16671cfc902SMark Brown 	switch (level) {
16771cfc902SMark Brown 	case SND_SOC_BIAS_ON:
16871cfc902SMark Brown 	case SND_SOC_BIAS_PREPARE:
16971cfc902SMark Brown 	case SND_SOC_BIAS_STANDBY:
1706b413080SKuninori Morimoto 		if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
17171cfc902SMark Brown 			/* Power everything up... */
1726d75dfc3SKuninori Morimoto 			reg = snd_soc_component_read(component, WM8728_DACCTL);
1736b413080SKuninori Morimoto 			snd_soc_component_write(component, WM8728_DACCTL, reg & ~0x4);
17471cfc902SMark Brown 
17571cfc902SMark Brown 			/* ..then sync in the register cache. */
176d16383efSMark Brown 			regcache_sync(wm8728->regmap);
17771cfc902SMark Brown 		}
17871cfc902SMark Brown 		break;
17971cfc902SMark Brown 
18071cfc902SMark Brown 	case SND_SOC_BIAS_OFF:
1816d75dfc3SKuninori Morimoto 		reg = snd_soc_component_read(component, WM8728_DACCTL);
1826b413080SKuninori Morimoto 		snd_soc_component_write(component, WM8728_DACCTL, reg | 0x4);
18371cfc902SMark Brown 		break;
18471cfc902SMark Brown 	}
18571cfc902SMark Brown 	return 0;
18671cfc902SMark Brown }
18771cfc902SMark Brown 
18871cfc902SMark Brown #define WM8728_RATES (SNDRV_PCM_RATE_8000_192000)
18971cfc902SMark Brown 
19071cfc902SMark Brown #define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
19171cfc902SMark Brown 	SNDRV_PCM_FMTBIT_S24_LE)
19271cfc902SMark Brown 
19385e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops wm8728_dai_ops = {
1946335d055SEric Miao 	.hw_params	= wm8728_hw_params,
19526d3c16eSKuninori Morimoto 	.mute_stream	= wm8728_mute,
1966335d055SEric Miao 	.set_fmt	= wm8728_set_dai_fmt,
19726d3c16eSKuninori Morimoto 	.no_capture_mute = 1,
1986335d055SEric Miao };
1996335d055SEric Miao 
200f0fba2adSLiam Girdwood static struct snd_soc_dai_driver wm8728_dai = {
201f0fba2adSLiam Girdwood 	.name = "wm8728-hifi",
20271cfc902SMark Brown 	.playback = {
20371cfc902SMark Brown 		.stream_name = "Playback",
20471cfc902SMark Brown 		.channels_min = 2,
20571cfc902SMark Brown 		.channels_max = 2,
20671cfc902SMark Brown 		.rates = WM8728_RATES,
20771cfc902SMark Brown 		.formats = WM8728_FORMATS,
20871cfc902SMark Brown 	},
2096335d055SEric Miao 	.ops = &wm8728_dai_ops,
21071cfc902SMark Brown };
21171cfc902SMark Brown 
2126b413080SKuninori Morimoto static const struct snd_soc_component_driver soc_component_dev_wm8728 = {
213f0fba2adSLiam Girdwood 	.set_bias_level		= wm8728_set_bias_level,
214e41d5a3bSMark Brown 	.controls		= wm8728_snd_controls,
215e41d5a3bSMark Brown 	.num_controls		= ARRAY_SIZE(wm8728_snd_controls),
2168428edf9SLu Guanqun 	.dapm_widgets		= wm8728_dapm_widgets,
2178428edf9SLu Guanqun 	.num_dapm_widgets	= ARRAY_SIZE(wm8728_dapm_widgets),
2188428edf9SLu Guanqun 	.dapm_routes		= wm8728_intercon,
2198428edf9SLu Guanqun 	.num_dapm_routes	= ARRAY_SIZE(wm8728_intercon),
2206b413080SKuninori Morimoto 	.suspend_bias_off	= 1,
2216b413080SKuninori Morimoto 	.idle_bias_on		= 1,
2226b413080SKuninori Morimoto 	.use_pmdown_time	= 1,
2236b413080SKuninori Morimoto 	.endianness		= 1,
224f0fba2adSLiam Girdwood };
225f0fba2adSLiam Girdwood 
22645b4d043SMark Brown static const struct of_device_id wm8728_of_match[] = {
22745b4d043SMark Brown 	{ .compatible = "wlf,wm8728", },
22845b4d043SMark Brown 	{ }
22945b4d043SMark Brown };
23045b4d043SMark Brown MODULE_DEVICE_TABLE(of, wm8728_of_match);
23145b4d043SMark Brown 
232d16383efSMark Brown static const struct regmap_config wm8728_regmap = {
233d16383efSMark Brown 	.reg_bits = 7,
234d16383efSMark Brown 	.val_bits = 9,
235d16383efSMark Brown 	.max_register = WM8728_IFCTL,
236d16383efSMark Brown 
237d16383efSMark Brown 	.reg_defaults = wm8728_reg_defaults,
238d16383efSMark Brown 	.num_reg_defaults = ARRAY_SIZE(wm8728_reg_defaults),
239*9a2abf70SMark Brown 	.cache_type = REGCACHE_MAPLE,
240d16383efSMark Brown };
241d16383efSMark Brown 
242f0fba2adSLiam Girdwood #if defined(CONFIG_SPI_MASTER)
wm8728_spi_probe(struct spi_device * spi)2437a79e94eSBill Pemberton static int wm8728_spi_probe(struct spi_device *spi)
244f0fba2adSLiam Girdwood {
245f0fba2adSLiam Girdwood 	struct wm8728_priv *wm8728;
24671cfc902SMark Brown 	int ret;
24771cfc902SMark Brown 
2481a9585b0SMark Brown 	wm8728 = devm_kzalloc(&spi->dev, sizeof(struct wm8728_priv),
2491a9585b0SMark Brown 			      GFP_KERNEL);
250f0fba2adSLiam Girdwood 	if (wm8728 == NULL)
251f0fba2adSLiam Girdwood 		return -ENOMEM;
25271cfc902SMark Brown 
253d16383efSMark Brown 	wm8728->regmap = devm_regmap_init_spi(spi, &wm8728_regmap);
254d16383efSMark Brown 	if (IS_ERR(wm8728->regmap))
255d16383efSMark Brown 		return PTR_ERR(wm8728->regmap);
256d16383efSMark Brown 
257f0fba2adSLiam Girdwood 	spi_set_drvdata(spi, wm8728);
258f0fba2adSLiam Girdwood 
2596b413080SKuninori Morimoto 	ret = devm_snd_soc_register_component(&spi->dev,
2606b413080SKuninori Morimoto 			&soc_component_dev_wm8728, &wm8728_dai, 1);
2611a9585b0SMark Brown 
26271cfc902SMark Brown 	return ret;
26371cfc902SMark Brown }
26471cfc902SMark Brown 
265f0fba2adSLiam Girdwood static struct spi_driver wm8728_spi_driver = {
266f0fba2adSLiam Girdwood 	.driver = {
2670473e61bSMark Brown 		.name	= "wm8728",
26845b4d043SMark Brown 		.of_match_table = wm8728_of_match,
269f0fba2adSLiam Girdwood 	},
270f0fba2adSLiam Girdwood 	.probe		= wm8728_spi_probe,
271f0fba2adSLiam Girdwood };
272f0fba2adSLiam Girdwood #endif /* CONFIG_SPI_MASTER */
273f0fba2adSLiam Girdwood 
2745c153716SFabio Estevam #if IS_ENABLED(CONFIG_I2C)
wm8728_i2c_probe(struct i2c_client * i2c)27597b0b6e3SStephen Kitt static int wm8728_i2c_probe(struct i2c_client *i2c)
276f0fba2adSLiam Girdwood {
277f0fba2adSLiam Girdwood 	struct wm8728_priv *wm8728;
278f0fba2adSLiam Girdwood 	int ret;
279f0fba2adSLiam Girdwood 
2801a9585b0SMark Brown 	wm8728 = devm_kzalloc(&i2c->dev, sizeof(struct wm8728_priv),
2811a9585b0SMark Brown 			      GFP_KERNEL);
282f0fba2adSLiam Girdwood 	if (wm8728 == NULL)
283f0fba2adSLiam Girdwood 		return -ENOMEM;
284f0fba2adSLiam Girdwood 
285d16383efSMark Brown 	wm8728->regmap = devm_regmap_init_i2c(i2c, &wm8728_regmap);
286d16383efSMark Brown 	if (IS_ERR(wm8728->regmap))
287d16383efSMark Brown 		return PTR_ERR(wm8728->regmap);
288d16383efSMark Brown 
289f0fba2adSLiam Girdwood 	i2c_set_clientdata(i2c, wm8728);
290f0fba2adSLiam Girdwood 
2916b413080SKuninori Morimoto 	ret =  devm_snd_soc_register_component(&i2c->dev,
2926b413080SKuninori Morimoto 			&soc_component_dev_wm8728, &wm8728_dai, 1);
2931a9585b0SMark Brown 
294f0fba2adSLiam Girdwood 	return ret;
295f0fba2adSLiam Girdwood }
296f0fba2adSLiam Girdwood 
29771cfc902SMark Brown static const struct i2c_device_id wm8728_i2c_id[] = {
29871cfc902SMark Brown 	{ "wm8728", 0 },
29971cfc902SMark Brown 	{ }
30071cfc902SMark Brown };
30171cfc902SMark Brown MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id);
30271cfc902SMark Brown 
30371cfc902SMark Brown static struct i2c_driver wm8728_i2c_driver = {
30471cfc902SMark Brown 	.driver = {
3050473e61bSMark Brown 		.name = "wm8728",
30645b4d043SMark Brown 		.of_match_table = wm8728_of_match,
30771cfc902SMark Brown 	},
3089abcd240SUwe Kleine-König 	.probe = wm8728_i2c_probe,
30971cfc902SMark Brown 	.id_table = wm8728_i2c_id,
31071cfc902SMark Brown };
31171cfc902SMark Brown #endif
31271cfc902SMark Brown 
wm8728_modinit(void)313c9b3a40fSTakashi Iwai static int __init wm8728_modinit(void)
31464089b84SMark Brown {
315f0fba2adSLiam Girdwood 	int ret = 0;
3165c153716SFabio Estevam #if IS_ENABLED(CONFIG_I2C)
317f0fba2adSLiam Girdwood 	ret = i2c_add_driver(&wm8728_i2c_driver);
318f0fba2adSLiam Girdwood 	if (ret != 0) {
319f0fba2adSLiam Girdwood 		printk(KERN_ERR "Failed to register wm8728 I2C driver: %d\n",
320f0fba2adSLiam Girdwood 		       ret);
321f0fba2adSLiam Girdwood 	}
322f0fba2adSLiam Girdwood #endif
323f0fba2adSLiam Girdwood #if defined(CONFIG_SPI_MASTER)
324f0fba2adSLiam Girdwood 	ret = spi_register_driver(&wm8728_spi_driver);
325f0fba2adSLiam Girdwood 	if (ret != 0) {
326f0fba2adSLiam Girdwood 		printk(KERN_ERR "Failed to register wm8728 SPI driver: %d\n",
327f0fba2adSLiam Girdwood 		       ret);
328f0fba2adSLiam Girdwood 	}
329f0fba2adSLiam Girdwood #endif
330f0fba2adSLiam Girdwood 	return ret;
33164089b84SMark Brown }
33264089b84SMark Brown module_init(wm8728_modinit);
33364089b84SMark Brown 
wm8728_exit(void)33464089b84SMark Brown static void __exit wm8728_exit(void)
33564089b84SMark Brown {
3365c153716SFabio Estevam #if IS_ENABLED(CONFIG_I2C)
337f0fba2adSLiam Girdwood 	i2c_del_driver(&wm8728_i2c_driver);
338f0fba2adSLiam Girdwood #endif
339f0fba2adSLiam Girdwood #if defined(CONFIG_SPI_MASTER)
340f0fba2adSLiam Girdwood 	spi_unregister_driver(&wm8728_spi_driver);
341f0fba2adSLiam Girdwood #endif
34264089b84SMark Brown }
34364089b84SMark Brown module_exit(wm8728_exit);
34464089b84SMark Brown 
34571cfc902SMark Brown MODULE_DESCRIPTION("ASoC WM8728 driver");
34671cfc902SMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
34771cfc902SMark Brown MODULE_LICENSE("GPL");
348