1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * sound/soc/codecs/si476x.c -- Codec driver for SI476X chips |
4 | * |
5 | * Copyright (C) 2012 Innovative Converged Devices(ICD) |
6 | * Copyright (C) 2013 Andrey Smirnov |
7 | * |
8 | * Author: Andrey Smirnov <andrew.smirnov@gmail.com> |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/slab.h> |
13 | #include <sound/pcm.h> |
14 | #include <sound/pcm_params.h> |
15 | #include <linux/regmap.h> |
16 | #include <sound/soc.h> |
17 | #include <sound/initval.h> |
18 | |
19 | #include <linux/i2c.h> |
20 | |
21 | #include <linux/mfd/si476x-core.h> |
22 | |
23 | enum si476x_audio_registers { |
24 | SI476X_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, |
25 | SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, |
26 | }; |
27 | |
28 | enum si476x_digital_io_output_format { |
29 | SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT = 11, |
30 | SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT = 8, |
31 | }; |
32 | |
33 | #define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK ((0x7 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \ |
34 | (0x7 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)) |
35 | #define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK (0x7e) |
36 | |
37 | enum si476x_daudio_formats { |
38 | SI476X_DAUDIO_MODE_I2S = (0x0 << 1), |
39 | SI476X_DAUDIO_MODE_DSP_A = (0x6 << 1), |
40 | SI476X_DAUDIO_MODE_DSP_B = (0x7 << 1), |
41 | SI476X_DAUDIO_MODE_LEFT_J = (0x8 << 1), |
42 | SI476X_DAUDIO_MODE_RIGHT_J = (0x9 << 1), |
43 | |
44 | SI476X_DAUDIO_MODE_IB = (1 << 5), |
45 | SI476X_DAUDIO_MODE_IF = (1 << 6), |
46 | }; |
47 | |
48 | enum si476x_pcm_format { |
49 | SI476X_PCM_FORMAT_S8 = 2, |
50 | SI476X_PCM_FORMAT_S16_LE = 4, |
51 | SI476X_PCM_FORMAT_S20_3LE = 5, |
52 | SI476X_PCM_FORMAT_S24_LE = 6, |
53 | }; |
54 | |
55 | static const struct snd_soc_dapm_widget si476x_dapm_widgets[] = { |
56 | SND_SOC_DAPM_OUTPUT("LOUT" ), |
57 | SND_SOC_DAPM_OUTPUT("ROUT" ), |
58 | }; |
59 | |
60 | static const struct snd_soc_dapm_route si476x_dapm_routes[] = { |
61 | { "Capture" , NULL, "LOUT" }, |
62 | { "Capture" , NULL, "ROUT" }, |
63 | }; |
64 | |
65 | static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, |
66 | unsigned int fmt) |
67 | { |
68 | struct si476x_core *core = i2c_mfd_cell_to_core(dev: codec_dai->dev); |
69 | int err; |
70 | u16 format = 0; |
71 | |
72 | if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) |
73 | return -EINVAL; |
74 | |
75 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
76 | case SND_SOC_DAIFMT_DSP_A: |
77 | format |= SI476X_DAUDIO_MODE_DSP_A; |
78 | break; |
79 | case SND_SOC_DAIFMT_DSP_B: |
80 | format |= SI476X_DAUDIO_MODE_DSP_B; |
81 | break; |
82 | case SND_SOC_DAIFMT_I2S: |
83 | format |= SI476X_DAUDIO_MODE_I2S; |
84 | break; |
85 | case SND_SOC_DAIFMT_RIGHT_J: |
86 | format |= SI476X_DAUDIO_MODE_RIGHT_J; |
87 | break; |
88 | case SND_SOC_DAIFMT_LEFT_J: |
89 | format |= SI476X_DAUDIO_MODE_LEFT_J; |
90 | break; |
91 | default: |
92 | return -EINVAL; |
93 | } |
94 | |
95 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
96 | case SND_SOC_DAIFMT_DSP_A: |
97 | case SND_SOC_DAIFMT_DSP_B: |
98 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
99 | case SND_SOC_DAIFMT_NB_NF: |
100 | break; |
101 | case SND_SOC_DAIFMT_IB_NF: |
102 | format |= SI476X_DAUDIO_MODE_IB; |
103 | break; |
104 | default: |
105 | return -EINVAL; |
106 | } |
107 | break; |
108 | case SND_SOC_DAIFMT_I2S: |
109 | case SND_SOC_DAIFMT_RIGHT_J: |
110 | case SND_SOC_DAIFMT_LEFT_J: |
111 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
112 | case SND_SOC_DAIFMT_NB_NF: |
113 | break; |
114 | case SND_SOC_DAIFMT_IB_IF: |
115 | format |= SI476X_DAUDIO_MODE_IB | |
116 | SI476X_DAUDIO_MODE_IF; |
117 | break; |
118 | case SND_SOC_DAIFMT_IB_NF: |
119 | format |= SI476X_DAUDIO_MODE_IB; |
120 | break; |
121 | case SND_SOC_DAIFMT_NB_IF: |
122 | format |= SI476X_DAUDIO_MODE_IF; |
123 | break; |
124 | default: |
125 | return -EINVAL; |
126 | } |
127 | break; |
128 | default: |
129 | return -EINVAL; |
130 | } |
131 | |
132 | si476x_core_lock(core); |
133 | |
134 | err = snd_soc_component_update_bits(component: codec_dai->component, reg: SI476X_DIGITAL_IO_OUTPUT_FORMAT, |
135 | SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK, |
136 | val: format); |
137 | |
138 | si476x_core_unlock(core); |
139 | |
140 | if (err < 0) { |
141 | dev_err(codec_dai->component->dev, "Failed to set output format\n" ); |
142 | return err; |
143 | } |
144 | |
145 | return 0; |
146 | } |
147 | |
148 | static int si476x_codec_hw_params(struct snd_pcm_substream *substream, |
149 | struct snd_pcm_hw_params *params, |
150 | struct snd_soc_dai *dai) |
151 | { |
152 | struct si476x_core *core = i2c_mfd_cell_to_core(dev: dai->dev); |
153 | int rate, width, err; |
154 | |
155 | rate = params_rate(p: params); |
156 | if (rate < 32000 || rate > 48000) { |
157 | dev_err(dai->component->dev, "Rate: %d is not supported\n" , rate); |
158 | return -EINVAL; |
159 | } |
160 | |
161 | switch (params_width(p: params)) { |
162 | case 8: |
163 | width = SI476X_PCM_FORMAT_S8; |
164 | break; |
165 | case 16: |
166 | width = SI476X_PCM_FORMAT_S16_LE; |
167 | break; |
168 | case 20: |
169 | width = SI476X_PCM_FORMAT_S20_3LE; |
170 | break; |
171 | case 24: |
172 | width = SI476X_PCM_FORMAT_S24_LE; |
173 | break; |
174 | default: |
175 | return -EINVAL; |
176 | } |
177 | |
178 | si476x_core_lock(core); |
179 | |
180 | err = snd_soc_component_write(component: dai->component, reg: SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE, |
181 | val: rate); |
182 | if (err < 0) { |
183 | dev_err(dai->component->dev, "Failed to set sample rate\n" ); |
184 | goto out; |
185 | } |
186 | |
187 | err = snd_soc_component_update_bits(component: dai->component, reg: SI476X_DIGITAL_IO_OUTPUT_FORMAT, |
188 | SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK, |
189 | val: (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | |
190 | (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)); |
191 | if (err < 0) { |
192 | dev_err(dai->component->dev, "Failed to set output width\n" ); |
193 | goto out; |
194 | } |
195 | |
196 | out: |
197 | si476x_core_unlock(core); |
198 | |
199 | return err; |
200 | } |
201 | |
202 | static const struct snd_soc_dai_ops si476x_dai_ops = { |
203 | .hw_params = si476x_codec_hw_params, |
204 | .set_fmt = si476x_codec_set_dai_fmt, |
205 | }; |
206 | |
207 | static struct snd_soc_dai_driver si476x_dai = { |
208 | .name = "si476x-codec" , |
209 | .capture = { |
210 | .stream_name = "Capture" , |
211 | .channels_min = 2, |
212 | .channels_max = 2, |
213 | |
214 | .rates = SNDRV_PCM_RATE_32000 | |
215 | SNDRV_PCM_RATE_44100 | |
216 | SNDRV_PCM_RATE_48000, |
217 | .formats = SNDRV_PCM_FMTBIT_S8 | |
218 | SNDRV_PCM_FMTBIT_S16_LE | |
219 | SNDRV_PCM_FMTBIT_S20_3LE | |
220 | SNDRV_PCM_FMTBIT_S24_LE |
221 | }, |
222 | .ops = &si476x_dai_ops, |
223 | }; |
224 | |
225 | static int si476x_probe(struct snd_soc_component *component) |
226 | { |
227 | snd_soc_component_init_regmap(component, |
228 | regmap: dev_get_regmap(dev: component->dev->parent, NULL)); |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static const struct snd_soc_component_driver soc_component_dev_si476x = { |
234 | .probe = si476x_probe, |
235 | .dapm_widgets = si476x_dapm_widgets, |
236 | .num_dapm_widgets = ARRAY_SIZE(si476x_dapm_widgets), |
237 | .dapm_routes = si476x_dapm_routes, |
238 | .num_dapm_routes = ARRAY_SIZE(si476x_dapm_routes), |
239 | .idle_bias_on = 1, |
240 | .use_pmdown_time = 1, |
241 | .endianness = 1, |
242 | }; |
243 | |
244 | static int si476x_platform_probe(struct platform_device *pdev) |
245 | { |
246 | return devm_snd_soc_register_component(dev: &pdev->dev, |
247 | component_driver: &soc_component_dev_si476x, |
248 | dai_drv: &si476x_dai, num_dai: 1); |
249 | } |
250 | |
251 | MODULE_ALIAS("platform:si476x-codec" ); |
252 | |
253 | static struct platform_driver si476x_platform_driver = { |
254 | .driver = { |
255 | .name = "si476x-codec" , |
256 | }, |
257 | .probe = si476x_platform_probe, |
258 | }; |
259 | module_platform_driver(si476x_platform_driver); |
260 | |
261 | MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>" ); |
262 | MODULE_DESCRIPTION("ASoC Si4761/64 codec driver" ); |
263 | MODULE_LICENSE("GPL" ); |
264 | |