xref: /linux/sound/soc/codecs/si476x.c (revision 092eba937d948a76ff55825922eff4df010f6a17)
106d7c133SAndrey Smirnov /*
206d7c133SAndrey Smirnov  * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips
306d7c133SAndrey Smirnov  *
406d7c133SAndrey Smirnov  * Copyright (C) 2012 Innovative Converged Devices(ICD)
506d7c133SAndrey Smirnov  * Copyright (C) 2013 Andrey Smirnov
606d7c133SAndrey Smirnov  *
706d7c133SAndrey Smirnov  * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
806d7c133SAndrey Smirnov  *
906d7c133SAndrey Smirnov  * This program is free software; you can redistribute it and/or modify
1006d7c133SAndrey Smirnov  * it under the terms of the GNU General Public License as published by
1106d7c133SAndrey Smirnov  * the Free Software Foundation; version 2 of the License.
1206d7c133SAndrey Smirnov  *
1306d7c133SAndrey Smirnov  * This program is distributed in the hope that it will be useful, but
1406d7c133SAndrey Smirnov  * WITHOUT ANY WARRANTY; without even the implied warranty of
1506d7c133SAndrey Smirnov  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1606d7c133SAndrey Smirnov  * General Public License for more details.
1706d7c133SAndrey Smirnov  *
1806d7c133SAndrey Smirnov  */
1906d7c133SAndrey Smirnov 
20330345ebSAndrey Smirnov #include <linux/module.h>
21330345ebSAndrey Smirnov #include <linux/slab.h>
22330345ebSAndrey Smirnov #include <sound/pcm.h>
23330345ebSAndrey Smirnov #include <sound/pcm_params.h>
24*092eba93SXiubo Li #include <linux/regmap.h>
25330345ebSAndrey Smirnov #include <sound/soc.h>
26330345ebSAndrey Smirnov #include <sound/initval.h>
27330345ebSAndrey Smirnov 
28330345ebSAndrey Smirnov #include <linux/i2c.h>
29330345ebSAndrey Smirnov 
30330345ebSAndrey Smirnov #include <linux/mfd/si476x-core.h>
31330345ebSAndrey Smirnov 
32330345ebSAndrey Smirnov enum si476x_audio_registers {
33330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_OUTPUT_FORMAT		= 0x0203,
34330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE	= 0x0202,
35330345ebSAndrey Smirnov };
36330345ebSAndrey Smirnov 
37330345ebSAndrey Smirnov enum si476x_digital_io_output_format {
38330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT	= 11,
39330345ebSAndrey Smirnov 	SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT	= 8,
40330345ebSAndrey Smirnov };
41330345ebSAndrey Smirnov 
423b1f9f53SAndrew Morton #define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK	((0x7 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \
433b1f9f53SAndrew Morton 						  (0x7 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT))
443b1f9f53SAndrew Morton #define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK	(0x7e)
45330345ebSAndrey Smirnov 
46330345ebSAndrey Smirnov enum si476x_daudio_formats {
47330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_I2S		= (0x0 << 1),
48330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_DSP_A	= (0x6 << 1),
49330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_DSP_B	= (0x7 << 1),
50330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_LEFT_J	= (0x8 << 1),
51330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_RIGHT_J	= (0x9 << 1),
52330345ebSAndrey Smirnov 
53330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_IB		= (1 << 5),
54330345ebSAndrey Smirnov 	SI476X_DAUDIO_MODE_IF		= (1 << 6),
55330345ebSAndrey Smirnov };
56330345ebSAndrey Smirnov 
57330345ebSAndrey Smirnov enum si476x_pcm_format {
58330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S8		= 2,
59330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S16_LE	= 4,
60330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S20_3LE	= 5,
61330345ebSAndrey Smirnov 	SI476X_PCM_FORMAT_S24_LE	= 6,
62330345ebSAndrey Smirnov };
63330345ebSAndrey Smirnov 
64ac0b82b1SMark Brown static const struct snd_soc_dapm_widget si476x_dapm_widgets[] = {
65ac0b82b1SMark Brown SND_SOC_DAPM_OUTPUT("LOUT"),
66ac0b82b1SMark Brown SND_SOC_DAPM_OUTPUT("ROUT"),
67ac0b82b1SMark Brown };
68ac0b82b1SMark Brown 
69ac0b82b1SMark Brown static const struct snd_soc_dapm_route si476x_dapm_routes[] = {
70ac0b82b1SMark Brown 	{ "Capture", NULL, "LOUT" },
71ac0b82b1SMark Brown 	{ "Capture", NULL, "ROUT" },
72ac0b82b1SMark Brown };
73ac0b82b1SMark Brown 
74330345ebSAndrey Smirnov static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai,
75330345ebSAndrey Smirnov 				    unsigned int fmt)
76330345ebSAndrey Smirnov {
77cfcff69aSMark Brown 	struct si476x_core *core = i2c_mfd_cell_to_core(codec_dai->dev);
78330345ebSAndrey Smirnov 	int err;
79330345ebSAndrey Smirnov 	u16 format = 0;
80330345ebSAndrey Smirnov 
81330345ebSAndrey Smirnov 	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
82330345ebSAndrey Smirnov 		return -EINVAL;
83330345ebSAndrey Smirnov 
84330345ebSAndrey Smirnov 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
85330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_A:
86330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_DSP_A;
87330345ebSAndrey Smirnov 		break;
88330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_B:
89330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_DSP_B;
90330345ebSAndrey Smirnov 		break;
91330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_I2S:
92330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_I2S;
93330345ebSAndrey Smirnov 		break;
94330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_RIGHT_J:
95330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_RIGHT_J;
96330345ebSAndrey Smirnov 		break;
97330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_LEFT_J:
98330345ebSAndrey Smirnov 		format |= SI476X_DAUDIO_MODE_LEFT_J;
99330345ebSAndrey Smirnov 		break;
100330345ebSAndrey Smirnov 	default:
101330345ebSAndrey Smirnov 		return -EINVAL;
102330345ebSAndrey Smirnov 	}
103330345ebSAndrey Smirnov 
104330345ebSAndrey Smirnov 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
105330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_A:
106330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_DSP_B:
107330345ebSAndrey Smirnov 		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
108330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_NB_NF:
109330345ebSAndrey Smirnov 			break;
110330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_IB_NF:
111330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IB;
112330345ebSAndrey Smirnov 			break;
113330345ebSAndrey Smirnov 		default:
114330345ebSAndrey Smirnov 			return -EINVAL;
115330345ebSAndrey Smirnov 		}
116330345ebSAndrey Smirnov 		break;
117330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_I2S:
118330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_RIGHT_J:
119330345ebSAndrey Smirnov 	case SND_SOC_DAIFMT_LEFT_J:
120330345ebSAndrey Smirnov 		switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
121330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_NB_NF:
122330345ebSAndrey Smirnov 			break;
123330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_IB_IF:
124330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IB |
125330345ebSAndrey Smirnov 				SI476X_DAUDIO_MODE_IF;
126330345ebSAndrey Smirnov 			break;
127330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_IB_NF:
128330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IB;
129330345ebSAndrey Smirnov 			break;
130330345ebSAndrey Smirnov 		case SND_SOC_DAIFMT_NB_IF:
131330345ebSAndrey Smirnov 			format |= SI476X_DAUDIO_MODE_IF;
132330345ebSAndrey Smirnov 			break;
133330345ebSAndrey Smirnov 		default:
134330345ebSAndrey Smirnov 			return -EINVAL;
135330345ebSAndrey Smirnov 		}
136330345ebSAndrey Smirnov 		break;
137330345ebSAndrey Smirnov 	default:
138330345ebSAndrey Smirnov 		return -EINVAL;
139330345ebSAndrey Smirnov 	}
140330345ebSAndrey Smirnov 
141cfcff69aSMark Brown 	si476x_core_lock(core);
142cfcff69aSMark Brown 
143330345ebSAndrey Smirnov 	err = snd_soc_update_bits(codec_dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
144330345ebSAndrey Smirnov 				  SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK,
145330345ebSAndrey Smirnov 				  format);
146cfcff69aSMark Brown 
147cfcff69aSMark Brown 	si476x_core_unlock(core);
148cfcff69aSMark Brown 
149330345ebSAndrey Smirnov 	if (err < 0) {
150330345ebSAndrey Smirnov 		dev_err(codec_dai->codec->dev, "Failed to set output format\n");
151330345ebSAndrey Smirnov 		return err;
152330345ebSAndrey Smirnov 	}
153330345ebSAndrey Smirnov 
154330345ebSAndrey Smirnov 	return 0;
155330345ebSAndrey Smirnov }
156330345ebSAndrey Smirnov 
157330345ebSAndrey Smirnov static int si476x_codec_hw_params(struct snd_pcm_substream *substream,
158330345ebSAndrey Smirnov 				  struct snd_pcm_hw_params *params,
159330345ebSAndrey Smirnov 				  struct snd_soc_dai *dai)
160330345ebSAndrey Smirnov {
161cfcff69aSMark Brown 	struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
162330345ebSAndrey Smirnov 	int rate, width, err;
163330345ebSAndrey Smirnov 
164330345ebSAndrey Smirnov 	rate = params_rate(params);
165330345ebSAndrey Smirnov 	if (rate < 32000 || rate > 48000) {
166330345ebSAndrey Smirnov 		dev_err(dai->codec->dev, "Rate: %d is not supported\n", rate);
167330345ebSAndrey Smirnov 		return -EINVAL;
168330345ebSAndrey Smirnov 	}
169330345ebSAndrey Smirnov 
170330345ebSAndrey Smirnov 	switch (params_format(params)) {
171330345ebSAndrey Smirnov 	case SNDRV_PCM_FORMAT_S8:
172330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S8;
1734e38b745SAxel Lin 		break;
174330345ebSAndrey Smirnov 	case SNDRV_PCM_FORMAT_S16_LE:
175330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S16_LE;
176330345ebSAndrey Smirnov 		break;
177330345ebSAndrey Smirnov 	case SNDRV_PCM_FORMAT_S20_3LE:
178330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S20_3LE;
179330345ebSAndrey Smirnov 		break;
180330345ebSAndrey Smirnov 	case SNDRV_PCM_FORMAT_S24_LE:
181330345ebSAndrey Smirnov 		width = SI476X_PCM_FORMAT_S24_LE;
182330345ebSAndrey Smirnov 		break;
183330345ebSAndrey Smirnov 	default:
184330345ebSAndrey Smirnov 		return -EINVAL;
185330345ebSAndrey Smirnov 	}
186330345ebSAndrey Smirnov 
187cfcff69aSMark Brown 	si476x_core_lock(core);
188cfcff69aSMark Brown 
189330345ebSAndrey Smirnov 	err = snd_soc_write(dai->codec, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE,
190330345ebSAndrey Smirnov 			    rate);
191330345ebSAndrey Smirnov 	if (err < 0) {
192330345ebSAndrey Smirnov 		dev_err(dai->codec->dev, "Failed to set sample rate\n");
193cfcff69aSMark Brown 		goto out;
194330345ebSAndrey Smirnov 	}
195330345ebSAndrey Smirnov 
196330345ebSAndrey Smirnov 	err = snd_soc_update_bits(dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT,
197330345ebSAndrey Smirnov 				  SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK,
198330345ebSAndrey Smirnov 				  (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) |
199330345ebSAndrey Smirnov 				  (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT));
200330345ebSAndrey Smirnov 	if (err < 0) {
201330345ebSAndrey Smirnov 		dev_err(dai->codec->dev, "Failed to set output width\n");
202cfcff69aSMark Brown 		goto out;
203330345ebSAndrey Smirnov 	}
204330345ebSAndrey Smirnov 
205cfcff69aSMark Brown out:
206cfcff69aSMark Brown 	si476x_core_unlock(core);
207cfcff69aSMark Brown 
208cfcff69aSMark Brown 	return err;
209330345ebSAndrey Smirnov }
210330345ebSAndrey Smirnov 
211330345ebSAndrey Smirnov static int si476x_codec_probe(struct snd_soc_codec *codec)
212330345ebSAndrey Smirnov {
213*092eba93SXiubo Li 	struct regmap *regmap = dev_get_regmap(codec->dev->parent, NULL);
214*092eba93SXiubo Li 
215*092eba93SXiubo Li 	return snd_soc_codec_set_cache_io(codec, regmap);
216330345ebSAndrey Smirnov }
217330345ebSAndrey Smirnov 
218330345ebSAndrey Smirnov static struct snd_soc_dai_ops si476x_dai_ops = {
219330345ebSAndrey Smirnov 	.hw_params	= si476x_codec_hw_params,
220330345ebSAndrey Smirnov 	.set_fmt	= si476x_codec_set_dai_fmt,
221330345ebSAndrey Smirnov };
222330345ebSAndrey Smirnov 
223330345ebSAndrey Smirnov static struct snd_soc_dai_driver si476x_dai = {
224330345ebSAndrey Smirnov 	.name		= "si476x-codec",
225330345ebSAndrey Smirnov 	.capture	= {
226330345ebSAndrey Smirnov 		.stream_name	= "Capture",
227330345ebSAndrey Smirnov 		.channels_min	= 2,
228330345ebSAndrey Smirnov 		.channels_max	= 2,
229330345ebSAndrey Smirnov 
230330345ebSAndrey Smirnov 		.rates = SNDRV_PCM_RATE_32000 |
231330345ebSAndrey Smirnov 		SNDRV_PCM_RATE_44100 |
232330345ebSAndrey Smirnov 		SNDRV_PCM_RATE_48000,
233330345ebSAndrey Smirnov 		.formats = SNDRV_PCM_FMTBIT_S8 |
234330345ebSAndrey Smirnov 		SNDRV_PCM_FMTBIT_S16_LE |
235330345ebSAndrey Smirnov 		SNDRV_PCM_FMTBIT_S20_3LE |
236330345ebSAndrey Smirnov 		SNDRV_PCM_FMTBIT_S24_LE
237330345ebSAndrey Smirnov 	},
238330345ebSAndrey Smirnov 	.ops		= &si476x_dai_ops,
239330345ebSAndrey Smirnov };
240330345ebSAndrey Smirnov 
241330345ebSAndrey Smirnov static struct snd_soc_codec_driver soc_codec_dev_si476x = {
242330345ebSAndrey Smirnov 	.probe  = si476x_codec_probe,
243ac0b82b1SMark Brown 	.dapm_widgets = si476x_dapm_widgets,
244ac0b82b1SMark Brown 	.num_dapm_widgets = ARRAY_SIZE(si476x_dapm_widgets),
245ac0b82b1SMark Brown 	.dapm_routes = si476x_dapm_routes,
246ac0b82b1SMark Brown 	.num_dapm_routes = ARRAY_SIZE(si476x_dapm_routes),
247330345ebSAndrey Smirnov };
248330345ebSAndrey Smirnov 
2497a79e94eSBill Pemberton static int si476x_platform_probe(struct platform_device *pdev)
250330345ebSAndrey Smirnov {
251330345ebSAndrey Smirnov 	return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_si476x,
252330345ebSAndrey Smirnov 				      &si476x_dai, 1);
253330345ebSAndrey Smirnov }
254330345ebSAndrey Smirnov 
2557a79e94eSBill Pemberton static int si476x_platform_remove(struct platform_device *pdev)
256330345ebSAndrey Smirnov {
257330345ebSAndrey Smirnov 	snd_soc_unregister_codec(&pdev->dev);
258330345ebSAndrey Smirnov 	return 0;
259330345ebSAndrey Smirnov }
260330345ebSAndrey Smirnov 
261330345ebSAndrey Smirnov MODULE_ALIAS("platform:si476x-codec");
262330345ebSAndrey Smirnov 
263330345ebSAndrey Smirnov static struct platform_driver si476x_platform_driver = {
264330345ebSAndrey Smirnov 	.driver		= {
265330345ebSAndrey Smirnov 		.name	= "si476x-codec",
266330345ebSAndrey Smirnov 		.owner	= THIS_MODULE,
267330345ebSAndrey Smirnov 	},
268330345ebSAndrey Smirnov 	.probe		= si476x_platform_probe,
2697a79e94eSBill Pemberton 	.remove		= si476x_platform_remove,
270330345ebSAndrey Smirnov };
271330345ebSAndrey Smirnov module_platform_driver(si476x_platform_driver);
272330345ebSAndrey Smirnov 
27306d7c133SAndrey Smirnov MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
274330345ebSAndrey Smirnov MODULE_DESCRIPTION("ASoC Si4761/64 codec driver");
275330345ebSAndrey Smirnov MODULE_LICENSE("GPL");
276