]>
Commit | Line | Data |
---|---|---|
330345eb AS |
1 | #include <linux/module.h> |
2 | #include <linux/slab.h> | |
3 | #include <sound/pcm.h> | |
4 | #include <sound/pcm_params.h> | |
5 | #include <sound/soc.h> | |
6 | #include <sound/initval.h> | |
7 | ||
8 | #include <linux/i2c.h> | |
9 | ||
10 | #include <linux/mfd/si476x-core.h> | |
11 | ||
12 | enum si476x_audio_registers { | |
13 | SI476X_DIGITAL_IO_OUTPUT_FORMAT = 0x0203, | |
14 | SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202, | |
15 | }; | |
16 | ||
17 | enum si476x_digital_io_output_format { | |
18 | SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT = 11, | |
19 | SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT = 8, | |
20 | }; | |
21 | ||
22 | #define SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK ((0b111 << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | \ | |
23 | (0b111 << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)) | |
24 | #define SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK (0b1111110) | |
25 | ||
26 | enum si476x_daudio_formats { | |
27 | SI476X_DAUDIO_MODE_I2S = (0x0 << 1), | |
28 | SI476X_DAUDIO_MODE_DSP_A = (0x6 << 1), | |
29 | SI476X_DAUDIO_MODE_DSP_B = (0x7 << 1), | |
30 | SI476X_DAUDIO_MODE_LEFT_J = (0x8 << 1), | |
31 | SI476X_DAUDIO_MODE_RIGHT_J = (0x9 << 1), | |
32 | ||
33 | SI476X_DAUDIO_MODE_IB = (1 << 5), | |
34 | SI476X_DAUDIO_MODE_IF = (1 << 6), | |
35 | }; | |
36 | ||
37 | enum si476x_pcm_format { | |
38 | SI476X_PCM_FORMAT_S8 = 2, | |
39 | SI476X_PCM_FORMAT_S16_LE = 4, | |
40 | SI476X_PCM_FORMAT_S20_3LE = 5, | |
41 | SI476X_PCM_FORMAT_S24_LE = 6, | |
42 | }; | |
43 | ||
44 | static unsigned int si476x_codec_read(struct snd_soc_codec *codec, | |
45 | unsigned int reg) | |
46 | { | |
47 | int err; | |
48 | struct si476x_core *core = codec->control_data; | |
49 | ||
50 | si476x_core_lock(core); | |
51 | err = si476x_core_cmd_get_property(core, reg); | |
52 | si476x_core_unlock(core); | |
53 | ||
54 | return err; | |
55 | } | |
56 | ||
57 | static int si476x_codec_write(struct snd_soc_codec *codec, | |
58 | unsigned int reg, unsigned int val) | |
59 | { | |
60 | int err; | |
61 | struct si476x_core *core = codec->control_data; | |
62 | ||
63 | si476x_core_lock(core); | |
64 | err = si476x_core_cmd_set_property(core, reg, val); | |
65 | si476x_core_unlock(core); | |
66 | ||
67 | return err; | |
68 | } | |
69 | ||
70 | static int si476x_codec_set_dai_fmt(struct snd_soc_dai *codec_dai, | |
71 | unsigned int fmt) | |
72 | { | |
73 | int err; | |
74 | u16 format = 0; | |
75 | ||
76 | if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) | |
77 | return -EINVAL; | |
78 | ||
79 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
80 | case SND_SOC_DAIFMT_DSP_A: | |
81 | format |= SI476X_DAUDIO_MODE_DSP_A; | |
82 | break; | |
83 | case SND_SOC_DAIFMT_DSP_B: | |
84 | format |= SI476X_DAUDIO_MODE_DSP_B; | |
85 | break; | |
86 | case SND_SOC_DAIFMT_I2S: | |
87 | format |= SI476X_DAUDIO_MODE_I2S; | |
88 | break; | |
89 | case SND_SOC_DAIFMT_RIGHT_J: | |
90 | format |= SI476X_DAUDIO_MODE_RIGHT_J; | |
91 | break; | |
92 | case SND_SOC_DAIFMT_LEFT_J: | |
93 | format |= SI476X_DAUDIO_MODE_LEFT_J; | |
94 | break; | |
95 | default: | |
96 | return -EINVAL; | |
97 | } | |
98 | ||
99 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
100 | case SND_SOC_DAIFMT_DSP_A: | |
101 | case SND_SOC_DAIFMT_DSP_B: | |
102 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | |
103 | case SND_SOC_DAIFMT_NB_NF: | |
104 | break; | |
105 | case SND_SOC_DAIFMT_IB_NF: | |
106 | format |= SI476X_DAUDIO_MODE_IB; | |
107 | break; | |
108 | default: | |
109 | return -EINVAL; | |
110 | } | |
111 | break; | |
112 | case SND_SOC_DAIFMT_I2S: | |
113 | case SND_SOC_DAIFMT_RIGHT_J: | |
114 | case SND_SOC_DAIFMT_LEFT_J: | |
115 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | |
116 | case SND_SOC_DAIFMT_NB_NF: | |
117 | break; | |
118 | case SND_SOC_DAIFMT_IB_IF: | |
119 | format |= SI476X_DAUDIO_MODE_IB | | |
120 | SI476X_DAUDIO_MODE_IF; | |
121 | break; | |
122 | case SND_SOC_DAIFMT_IB_NF: | |
123 | format |= SI476X_DAUDIO_MODE_IB; | |
124 | break; | |
125 | case SND_SOC_DAIFMT_NB_IF: | |
126 | format |= SI476X_DAUDIO_MODE_IF; | |
127 | break; | |
128 | default: | |
129 | return -EINVAL; | |
130 | } | |
131 | break; | |
132 | default: | |
133 | return -EINVAL; | |
134 | } | |
135 | ||
136 | err = snd_soc_update_bits(codec_dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT, | |
137 | SI476X_DIGITAL_IO_OUTPUT_FORMAT_MASK, | |
138 | format); | |
139 | if (err < 0) { | |
140 | dev_err(codec_dai->codec->dev, "Failed to set output format\n"); | |
141 | return err; | |
142 | } | |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
147 | static int si476x_codec_hw_params(struct snd_pcm_substream *substream, | |
148 | struct snd_pcm_hw_params *params, | |
149 | struct snd_soc_dai *dai) | |
150 | { | |
151 | int rate, width, err; | |
152 | ||
153 | rate = params_rate(params); | |
154 | if (rate < 32000 || rate > 48000) { | |
155 | dev_err(dai->codec->dev, "Rate: %d is not supported\n", rate); | |
156 | return -EINVAL; | |
157 | } | |
158 | ||
159 | switch (params_format(params)) { | |
160 | case SNDRV_PCM_FORMAT_S8: | |
161 | width = SI476X_PCM_FORMAT_S8; | |
162 | case SNDRV_PCM_FORMAT_S16_LE: | |
163 | width = SI476X_PCM_FORMAT_S16_LE; | |
164 | break; | |
165 | case SNDRV_PCM_FORMAT_S20_3LE: | |
166 | width = SI476X_PCM_FORMAT_S20_3LE; | |
167 | break; | |
168 | case SNDRV_PCM_FORMAT_S24_LE: | |
169 | width = SI476X_PCM_FORMAT_S24_LE; | |
170 | break; | |
171 | default: | |
172 | return -EINVAL; | |
173 | } | |
174 | ||
175 | err = snd_soc_write(dai->codec, SI476X_DIGITAL_IO_OUTPUT_SAMPLE_RATE, | |
176 | rate); | |
177 | if (err < 0) { | |
178 | dev_err(dai->codec->dev, "Failed to set sample rate\n"); | |
179 | return err; | |
180 | } | |
181 | ||
182 | err = snd_soc_update_bits(dai->codec, SI476X_DIGITAL_IO_OUTPUT_FORMAT, | |
183 | SI476X_DIGITAL_IO_OUTPUT_WIDTH_MASK, | |
184 | (width << SI476X_DIGITAL_IO_SLOT_SIZE_SHIFT) | | |
185 | (width << SI476X_DIGITAL_IO_SAMPLE_SIZE_SHIFT)); | |
186 | if (err < 0) { | |
187 | dev_err(dai->codec->dev, "Failed to set output width\n"); | |
188 | return err; | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | static int si476x_codec_probe(struct snd_soc_codec *codec) | |
195 | { | |
196 | codec->control_data = i2c_mfd_cell_to_core(codec->dev); | |
197 | return 0; | |
198 | } | |
199 | ||
200 | static struct snd_soc_dai_ops si476x_dai_ops = { | |
201 | .hw_params = si476x_codec_hw_params, | |
202 | .set_fmt = si476x_codec_set_dai_fmt, | |
203 | }; | |
204 | ||
205 | static struct snd_soc_dai_driver si476x_dai = { | |
206 | .name = "si476x-codec", | |
207 | .capture = { | |
208 | .stream_name = "Capture", | |
209 | .channels_min = 2, | |
210 | .channels_max = 2, | |
211 | ||
212 | .rates = SNDRV_PCM_RATE_32000 | | |
213 | SNDRV_PCM_RATE_44100 | | |
214 | SNDRV_PCM_RATE_48000, | |
215 | .formats = SNDRV_PCM_FMTBIT_S8 | | |
216 | SNDRV_PCM_FMTBIT_S16_LE | | |
217 | SNDRV_PCM_FMTBIT_S20_3LE | | |
218 | SNDRV_PCM_FMTBIT_S24_LE | |
219 | }, | |
220 | .ops = &si476x_dai_ops, | |
221 | }; | |
222 | ||
223 | static struct snd_soc_codec_driver soc_codec_dev_si476x = { | |
224 | .probe = si476x_codec_probe, | |
225 | .read = si476x_codec_read, | |
226 | .write = si476x_codec_write, | |
227 | }; | |
228 | ||
7a79e94e | 229 | static int si476x_platform_probe(struct platform_device *pdev) |
330345eb AS |
230 | { |
231 | return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_si476x, | |
232 | &si476x_dai, 1); | |
233 | } | |
234 | ||
7a79e94e | 235 | static int si476x_platform_remove(struct platform_device *pdev) |
330345eb AS |
236 | { |
237 | snd_soc_unregister_codec(&pdev->dev); | |
238 | return 0; | |
239 | } | |
240 | ||
241 | MODULE_ALIAS("platform:si476x-codec"); | |
242 | ||
243 | static struct platform_driver si476x_platform_driver = { | |
244 | .driver = { | |
245 | .name = "si476x-codec", | |
246 | .owner = THIS_MODULE, | |
247 | }, | |
248 | .probe = si476x_platform_probe, | |
7a79e94e | 249 | .remove = si476x_platform_remove, |
330345eb AS |
250 | }; |
251 | module_platform_driver(si476x_platform_driver); | |
252 | ||
253 | MODULE_AUTHOR("Andrey Smirnov <andrey.smirnov@convergeddevices.net>"); | |
254 | MODULE_DESCRIPTION("ASoC Si4761/64 codec driver"); | |
255 | MODULE_LICENSE("GPL"); |