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