xref: /openbmc/linux/sound/soc/codecs/si476x.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
18e8e69d6SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
206d7c133SAndrey Smirnov /*
306d7c133SAndrey Smirnov  * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips
406d7c133SAndrey Smirnov  *
506d7c133SAndrey Smirnov  * Copyright (C) 2012 Innovative Converged Devices(ICD)
606d7c133SAndrey Smirnov  * Copyright (C) 2013 Andrey Smirnov
706d7c133SAndrey Smirnov  *
806d7c133SAndrey Smirnov  * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
906d7c133SAndrey Smirnov  */
1006d7c133SAndrey Smirnov 
11330345ebSAndrey Smirnov #include <linux/module.h>
12330345ebSAndrey Smirnov #include <linux/slab.h>
13330345ebSAndrey Smirnov #include <sound/pcm.h>
14330345ebSAndrey Smirnov #include <sound/pcm_params.h>
15092eba93SXiubo Li #include <linux/regmap.h>
16330345ebSAndrey Smirnov #include <sound/soc.h>
17330345ebSAndrey Smirnov #include <sound/initval.h>
18330345ebSAndrey Smirnov 
19330345ebSAndrey Smirnov #include <linux/i2c.h>
20330345ebSAndrey Smirnov 
21330345ebSAndrey Smirnov #include <linux/mfd/si476x-core.h>
22330345ebSAndrey Smirnov 
23330345ebSAndrey Smirnov enum si476x_audio_registers {
24330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_OUTPUT_FORMAT		= 0x0203,
25330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE	= 0x0202,
26330345ebSAndrey Smirnov };
27330345ebSAndrey Smirnov 
28330345ebSAndrey Smirnov enum si476x_digital_io_output_format {
29330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT	= 11,
30330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT	= 8,
31330345ebSAndrey Smirnov };
32330345ebSAndrey Smirnov 
333b1f9f53SAndrew Morton #define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK	((0x7 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \
343b1f9f53SAndrew Morton 						  (0x7 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT))
353b1f9f53SAndrew Morton #define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK	(0x7e)
36330345ebSAndrey Smirnov 
37330345ebSAndrey Smirnov enum si476x_daudio_formats {
38330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_I2S		= (0x0 << 1),
39330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_DSP_A	= (0x6 << 1),
40330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_DSP_B	= (0x7 << 1),
41330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_LEFT_J	= (0x8 << 1),
42330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_RIGHT_J	= (0x9 << 1),
43330345ebSAndrey Smirnov 
44330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_IB		= (1 << 5),
45330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_IF		= (1 << 6),
46330345ebSAndrey Smirnov };
47330345ebSAndrey Smirnov 
48330345ebSAndrey Smirnov enum si476x_pcm_format {
49330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S8		= 2,
50330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S16_LE	= 4,
51330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S20_3LE	= 5,
52330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S24_LE	= 6,
53330345ebSAndrey Smirnov };
54330345ebSAndrey Smirnov 
55ac0b82b1SMark Brown static const struct snd_soc_dapm_widget si476x_dapm_widgets[] = {
56ac0b82b1SMark Brown SND_SOC_DAPM_OUTPUT("LOUT"),
57ac0b82b1SMark Brown SND_SOC_DAPM_OUTPUT("ROUT"),
58ac0b82b1SMark Brown };
59ac0b82b1SMark Brown 
60ac0b82b1SMark Brown static const struct snd_soc_dapm_route si476x_dapm_routes[] = {
61ac0b82b1SMark Brown 	{ "Capture", NULL, "LOUT" },
62ac0b82b1SMark Brown 	{ "Capture", NULL, "ROUT" },
63ac0b82b1SMark Brown };
64ac0b82b1SMark Brown 
si476x_codec_set_dai_fmt(struct snd_soc_dai * codec_dai,unsigned int fmt)65330345ebSAndrey Smirnov static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai,
66330345ebSAndrey Smirnov 				    unsigned int fmt)
67330345ebSAndrey Smirnov {
68cfcff69aSMark Brown 	struct si476x_core *core = i2c_mfd_cell_to_core(codec_dai->dev);
69330345ebSAndrey Smirnov 	int err;
70330345ebSAndrey Smirnov 	u16 format = 0;
71330345ebSAndrey Smirnov 
72*10daafb0SMark Brown 	if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC)
73330345ebSAndrey Smirnov 		return -EINVAL;
74330345ebSAndrey Smirnov 
75330345ebSAndrey Smirnov 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
76330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_A:
77330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_DSP_A;
78330345ebSAndrey Smirnov 		break;
79330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_B:
80330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_DSP_B;
81330345ebSAndrey Smirnov 		break;
82330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_I2S:
83330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_I2S;
84330345ebSAndrey Smirnov 		break;
85330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_RIGHT_J:
86330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_RIGHT_J;
87330345ebSAndrey Smirnov 		break;
88330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_LEFT_J:
89330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_LEFT_J;
90330345ebSAndrey Smirnov 		break;
91330345ebSAndrey Smirnov 	default:
92330345ebSAndrey Smirnov 		return -EINVAL;
93330345ebSAndrey Smirnov 	}
94330345ebSAndrey Smirnov 
95330345ebSAndrey Smirnov 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
96330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_A:
97330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_B:
98330345ebSAndrey Smirnov 		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
99330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_NB_NF:
100330345ebSAndrey Smirnov 			break;
101330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_IB_NF:
102330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IB;
103330345ebSAndrey Smirnov 			break;
104330345ebSAndrey Smirnov 		default:
105330345ebSAndrey Smirnov 			return -EINVAL;
106330345ebSAndrey Smirnov 		}
107330345ebSAndrey Smirnov 		break;
108330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_I2S:
109330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_RIGHT_J:
110330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_LEFT_J:
111330345ebSAndrey Smirnov 		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
112330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_NB_NF:
113330345ebSAndrey Smirnov 			break;
114330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_IB_IF:
115330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IB |
116330345ebSAndrey Smirnov 				SI476X_DAUDIO_MODE_IF;
117330345ebSAndrey Smirnov 			break;
118330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_IB_NF:
119330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IB;
120330345ebSAndrey Smirnov 			break;
121330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_NB_IF:
122330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IF;
123330345ebSAndrey Smirnov 			break;
124330345ebSAndrey Smirnov 		default:
125330345ebSAndrey Smirnov 			return -EINVAL;
126330345ebSAndrey Smirnov 		}
127330345ebSAndrey Smirnov 		break;
128330345ebSAndrey Smirnov 	default:
129330345ebSAndrey Smirnov 		return -EINVAL;
130330345ebSAndrey Smirnov 	}
131330345ebSAndrey Smirnov 
132cfcff69aSMark Brown 	si476x_core_lock(core);
133cfcff69aSMark Brown 
1348fcfe24cSKuninori Morimoto 	err = snd_soc_component_update_bits(codec_dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
135330345ebSAndrey Smirnov 				  SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK,
136330345ebSAndrey Smirnov 				  format);
137cfcff69aSMark Brown 
138cfcff69aSMark Brown 	si476x_core_unlock(core);
139cfcff69aSMark Brown 
140330345ebSAndrey Smirnov 	if (err < 0) {
1418fcfe24cSKuninori Morimoto 		dev_err(codec_dai->component->dev, "Failed to set output format\n");
142330345ebSAndrey Smirnov 		return err;
143330345ebSAndrey Smirnov 	}
144330345ebSAndrey Smirnov 
145330345ebSAndrey Smirnov 	return 0;
146330345ebSAndrey Smirnov }
147330345ebSAndrey Smirnov 
si476x_codec_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params,struct snd_soc_dai * dai)148330345ebSAndrey Smirnov static int si476x_codec_hw_params(struct snd_pcm_substream *substream,
149330345ebSAndrey Smirnov 				  struct snd_pcm_hw_params *params,
150330345ebSAndrey Smirnov 				  struct snd_soc_dai *dai)
151330345ebSAndrey Smirnov {
152cfcff69aSMark Brown 	struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
153330345ebSAndrey Smirnov 	int rate, width, err;
154330345ebSAndrey Smirnov 
155330345ebSAndrey Smirnov 	rate = params_rate(params);
156330345ebSAndrey Smirnov 	if (rate < 32000 || rate > 48000) {
1578fcfe24cSKuninori Morimoto 		dev_err(dai->component->dev, "Rate: %d is not supported\n", rate);
158330345ebSAndrey Smirnov 		return -EINVAL;
159330345ebSAndrey Smirnov 	}
160330345ebSAndrey Smirnov 
1610a49f706SMark Brown 	switch (params_width(params)) {
1620a49f706SMark Brown 	case 8:
163330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S8;
1644e38b745SAxel Lin 		break;
1650a49f706SMark Brown 	case 16:
166330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S16_LE;
167330345ebSAndrey Smirnov 		break;
1680a49f706SMark Brown 	case 20:
169330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S20_3LE;
170330345ebSAndrey Smirnov 		break;
1710a49f706SMark Brown 	case 24:
172330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S24_LE;
173330345ebSAndrey Smirnov 		break;
174330345ebSAndrey Smirnov 	default:
175330345ebSAndrey Smirnov 		return -EINVAL;
176330345ebSAndrey Smirnov 	}
177330345ebSAndrey Smirnov 
178cfcff69aSMark Brown 	si476x_core_lock(core);
179cfcff69aSMark Brown 
1808fcfe24cSKuninori Morimoto 	err = snd_soc_component_write(dai->component, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE,
181330345ebSAndrey Smirnov 			    rate);
182330345ebSAndrey Smirnov 	if (err < 0) {
1838fcfe24cSKuninori Morimoto 		dev_err(dai->component->dev, "Failed to set sample rate\n");
184cfcff69aSMark Brown 		goto out;
185330345ebSAndrey Smirnov 	}
186330345ebSAndrey Smirnov 
1878fcfe24cSKuninori Morimoto 	err = snd_soc_component_update_bits(dai->component, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
188330345ebSAndrey Smirnov 				  SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK,
189330345ebSAndrey Smirnov 				  (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) |
190330345ebSAndrey Smirnov 				  (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT));
191330345ebSAndrey Smirnov 	if (err < 0) {
1928fcfe24cSKuninori Morimoto 		dev_err(dai->component->dev, "Failed to set output width\n");
193cfcff69aSMark Brown 		goto out;
194330345ebSAndrey Smirnov 	}
195330345ebSAndrey Smirnov 
196cfcff69aSMark Brown out:
197cfcff69aSMark Brown 	si476x_core_unlock(core);
198cfcff69aSMark Brown 
199cfcff69aSMark Brown 	return err;
200330345ebSAndrey Smirnov }
201330345ebSAndrey Smirnov 
20264793047SAxel Lin static const struct snd_soc_dai_ops si476x_dai_ops = {
203330345ebSAndrey Smirnov 	.hw_params	= si476x_codec_hw_params,
204330345ebSAndrey Smirnov 	.set_fmt	= si476x_codec_set_dai_fmt,
205330345ebSAndrey Smirnov };
206330345ebSAndrey Smirnov 
207330345ebSAndrey Smirnov static struct snd_soc_dai_driver si476x_dai = {
208330345ebSAndrey Smirnov 	.name		= "si476x-codec",
209330345ebSAndrey Smirnov 	.capture	= {
210330345ebSAndrey Smirnov 		.stream_name	= "Capture",
211330345ebSAndrey Smirnov 		.channels_min	= 2,
212330345ebSAndrey Smirnov 		.channels_max	= 2,
213330345ebSAndrey Smirnov 
214330345ebSAndrey Smirnov 		.rates = SNDRV_PCM_RATE_32000 |
215330345ebSAndrey Smirnov 		SNDRV_PCM_RATE_44100 |
216330345ebSAndrey Smirnov 		SNDRV_PCM_RATE_48000,
217330345ebSAndrey Smirnov 		.formats = SNDRV_PCM_FMTBIT_S8 |
218330345ebSAndrey Smirnov 		SNDRV_PCM_FMTBIT_S16_LE |
219330345ebSAndrey Smirnov 		SNDRV_PCM_FMTBIT_S20_3LE |
220330345ebSAndrey Smirnov 		SNDRV_PCM_FMTBIT_S24_LE
221330345ebSAndrey Smirnov 	},
222330345ebSAndrey Smirnov 	.ops		= &si476x_dai_ops,
223330345ebSAndrey Smirnov };
224330345ebSAndrey Smirnov 
si476x_probe(struct snd_soc_component * component)2253047ec50SKuninori Morimoto static int si476x_probe(struct snd_soc_component *component)
22683905ef3SXiubo Li {
2273047ec50SKuninori Morimoto 	snd_soc_component_init_regmap(component,
2283047ec50SKuninori Morimoto 				dev_get_regmap(component->dev->parent, NULL));
2293047ec50SKuninori Morimoto 
2303047ec50SKuninori Morimoto 	return 0;
23183905ef3SXiubo Li }
23283905ef3SXiubo Li 
2338fcfe24cSKuninori Morimoto static const struct snd_soc_component_driver soc_component_dev_si476x = {
2343047ec50SKuninori Morimoto 	.probe			= si476x_probe,
235ac0b82b1SMark Brown 	.dapm_widgets		= si476x_dapm_widgets,
236ac0b82b1SMark Brown 	.num_dapm_widgets	= ARRAY_SIZE(si476x_dapm_widgets),
237ac0b82b1SMark Brown 	.dapm_routes		= si476x_dapm_routes,
238ac0b82b1SMark Brown 	.num_dapm_routes	= ARRAY_SIZE(si476x_dapm_routes),
2398fcfe24cSKuninori Morimoto 	.idle_bias_on		= 1,
2408fcfe24cSKuninori Morimoto 	.use_pmdown_time	= 1,
2418fcfe24cSKuninori Morimoto 	.endianness		= 1,
242330345ebSAndrey Smirnov };
243330345ebSAndrey Smirnov 
si476x_platform_probe(struct platform_device * pdev)2447a79e94eSBill Pemberton static int si476x_platform_probe(struct platform_device *pdev)
245330345ebSAndrey Smirnov {
2468fcfe24cSKuninori Morimoto 	return devm_snd_soc_register_component(&pdev->dev,
2478fcfe24cSKuninori Morimoto 				      &soc_component_dev_si476x,
248330345ebSAndrey Smirnov 				      &si476x_dai, 1);
249330345ebSAndrey Smirnov }
250330345ebSAndrey Smirnov 
251330345ebSAndrey Smirnov MODULE_ALIAS("platform:si476x-codec");
252330345ebSAndrey Smirnov 
253330345ebSAndrey Smirnov static struct platform_driver si476x_platform_driver = {
254330345ebSAndrey Smirnov 	.driver		= {
255330345ebSAndrey Smirnov 		.name	= "si476x-codec",
256330345ebSAndrey Smirnov 	},
257330345ebSAndrey Smirnov 	.probe		= si476x_platform_probe,
258330345ebSAndrey Smirnov };
259330345ebSAndrey Smirnov module_platform_driver(si476x_platform_driver);
260330345ebSAndrey Smirnov 
26106d7c133SAndrey Smirnov MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
262330345ebSAndrey Smirnov MODULE_DESCRIPTION("ASoC Si4761/64 codec driver");
263330345ebSAndrey Smirnov MODULE_LICENSE("GPL");
264