Commit | Line | Data |
---|---|---|
b291f42a CYC |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
727f1c71 TBS |
3 | * Copyright 2019 Google, Inc. |
4 | * | |
5 | * ChromeOS Embedded Controller codec driver. | |
b291f42a CYC |
6 | * |
7 | * This driver uses the cros-ec interface to communicate with the ChromeOS | |
8 | * EC for audio function. | |
9 | */ | |
10 | ||
11 | #include <linux/delay.h> | |
12 | #include <linux/device.h> | |
13 | #include <linux/kernel.h> | |
b291f42a | 14 | #include <linux/module.h> |
840d9f13 EBS |
15 | #include <linux/platform_data/cros_ec_commands.h> |
16 | #include <linux/platform_data/cros_ec_proto.h> | |
b291f42a CYC |
17 | #include <linux/platform_device.h> |
18 | #include <sound/pcm.h> | |
19 | #include <sound/pcm_params.h> | |
20 | #include <sound/soc.h> | |
21 | #include <sound/tlv.h> | |
22 | ||
727f1c71 | 23 | struct cros_ec_codec_priv { |
b291f42a CYC |
24 | struct device *dev; |
25 | struct cros_ec_device *ec_device; | |
b291f42a CYC |
26 | }; |
27 | ||
727f1c71 TBS |
28 | static int send_ec_host_command(struct cros_ec_device *ec_dev, uint32_t cmd, |
29 | uint8_t *out, size_t outsize, | |
30 | uint8_t *in, size_t insize) | |
b291f42a | 31 | { |
b291f42a | 32 | int ret; |
727f1c71 TBS |
33 | struct cros_ec_command *msg; |
34 | ||
35 | msg = kmalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL); | |
36 | if (!msg) | |
37 | return -ENOMEM; | |
b291f42a CYC |
38 | |
39 | msg->version = 0; | |
727f1c71 TBS |
40 | msg->command = cmd; |
41 | msg->outsize = outsize; | |
42 | msg->insize = insize; | |
43 | ||
44 | if (outsize) | |
45 | memcpy(msg->data, out, outsize); | |
b291f42a | 46 | |
727f1c71 TBS |
47 | ret = cros_ec_cmd_xfer_status(ec_dev, msg); |
48 | if (ret < 0) | |
49 | goto error; | |
b291f42a | 50 | |
727f1c71 TBS |
51 | if (insize) |
52 | memcpy(in, msg->data, insize); | |
b291f42a | 53 | |
727f1c71 TBS |
54 | ret = 0; |
55 | error: | |
56 | kfree(msg); | |
b291f42a CYC |
57 | return ret; |
58 | } | |
59 | ||
727f1c71 TBS |
60 | static int dmic_get_gain(struct snd_kcontrol *kcontrol, |
61 | struct snd_ctl_elem_value *ucontrol) | |
b291f42a | 62 | { |
727f1c71 TBS |
63 | struct snd_soc_component *component = |
64 | snd_soc_kcontrol_component(kcontrol); | |
65 | struct cros_ec_codec_priv *priv = | |
b291f42a | 66 | snd_soc_component_get_drvdata(component); |
8f731d4c | 67 | struct ec_param_ec_codec_dmic p; |
f3e82ad4 | 68 | struct ec_response_ec_codec_dmic_get_gain_idx r; |
727f1c71 | 69 | int ret; |
b291f42a | 70 | |
f3e82ad4 TBS |
71 | p.cmd = EC_CODEC_DMIC_GET_GAIN_IDX; |
72 | p.get_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_0; | |
8f731d4c | 73 | ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, |
727f1c71 TBS |
74 | (uint8_t *)&p, sizeof(p), |
75 | (uint8_t *)&r, sizeof(r)); | |
76 | if (ret < 0) | |
77 | return ret; | |
f3e82ad4 | 78 | ucontrol->value.integer.value[0] = r.gain; |
b291f42a | 79 | |
f3e82ad4 TBS |
80 | p.cmd = EC_CODEC_DMIC_GET_GAIN_IDX; |
81 | p.get_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_1; | |
82 | ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, | |
83 | (uint8_t *)&p, sizeof(p), | |
84 | (uint8_t *)&r, sizeof(r)); | |
85 | if (ret < 0) | |
86 | return ret; | |
87 | ucontrol->value.integer.value[1] = r.gain; | |
b291f42a | 88 | |
727f1c71 | 89 | return 0; |
b291f42a CYC |
90 | } |
91 | ||
727f1c71 TBS |
92 | static int dmic_put_gain(struct snd_kcontrol *kcontrol, |
93 | struct snd_ctl_elem_value *ucontrol) | |
b291f42a | 94 | { |
727f1c71 TBS |
95 | struct snd_soc_component *component = |
96 | snd_soc_kcontrol_component(kcontrol); | |
97 | struct cros_ec_codec_priv *priv = | |
98 | snd_soc_component_get_drvdata(component); | |
99 | struct soc_mixer_control *control = | |
100 | (struct soc_mixer_control *)kcontrol->private_value; | |
101 | int max_dmic_gain = control->max; | |
102 | int left = ucontrol->value.integer.value[0]; | |
103 | int right = ucontrol->value.integer.value[1]; | |
8f731d4c | 104 | struct ec_param_ec_codec_dmic p; |
f3e82ad4 | 105 | int ret; |
b291f42a | 106 | |
727f1c71 | 107 | if (left > max_dmic_gain || right > max_dmic_gain) |
b291f42a | 108 | return -EINVAL; |
b291f42a | 109 | |
727f1c71 | 110 | dev_dbg(component->dev, "set mic gain to %u, %u\n", left, right); |
b291f42a | 111 | |
f3e82ad4 TBS |
112 | p.cmd = EC_CODEC_DMIC_SET_GAIN_IDX; |
113 | p.set_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_0; | |
114 | p.set_gain_idx_param.gain = left; | |
115 | ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, | |
116 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
117 | if (ret < 0) | |
118 | return ret; | |
119 | ||
120 | p.cmd = EC_CODEC_DMIC_SET_GAIN_IDX; | |
121 | p.set_gain_idx_param.channel = EC_CODEC_DMIC_CHANNEL_1; | |
122 | p.set_gain_idx_param.gain = right; | |
8f731d4c | 123 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, |
727f1c71 | 124 | (uint8_t *)&p, sizeof(p), NULL, 0); |
b291f42a CYC |
125 | } |
126 | ||
727f1c71 | 127 | static const DECLARE_TLV_DB_SCALE(dmic_gain_tlv, 0, 100, 0); |
b291f42a | 128 | |
727f1c71 TBS |
129 | enum { |
130 | DMIC_CTL_GAIN = 0, | |
131 | }; | |
b291f42a | 132 | |
727f1c71 TBS |
133 | static struct snd_kcontrol_new dmic_controls[] = { |
134 | [DMIC_CTL_GAIN] = | |
135 | SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM, | |
136 | 0, 0, 0, dmic_get_gain, dmic_put_gain, | |
137 | dmic_gain_tlv), | |
138 | }; | |
b291f42a | 139 | |
8f731d4c TBS |
140 | static int dmic_probe(struct snd_soc_component *component) |
141 | { | |
142 | struct cros_ec_codec_priv *priv = | |
143 | snd_soc_component_get_drvdata(component); | |
144 | struct device *dev = priv->dev; | |
8f731d4c | 145 | struct soc_mixer_control *control; |
f3e82ad4 TBS |
146 | struct ec_param_ec_codec_dmic p; |
147 | struct ec_response_ec_codec_dmic_get_max_gain r; | |
148 | int ret; | |
8f731d4c | 149 | |
f3e82ad4 TBS |
150 | p.cmd = EC_CODEC_DMIC_GET_MAX_GAIN; |
151 | ||
152 | ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC, | |
153 | (uint8_t *)&p, sizeof(p), | |
154 | (uint8_t *)&r, sizeof(r)); | |
155 | if (ret < 0) { | |
156 | dev_warn(dev, "get_max_gain() unsupported\n"); | |
157 | return 0; | |
8f731d4c TBS |
158 | } |
159 | ||
f3e82ad4 TBS |
160 | dev_dbg(dev, "max gain = %d\n", r.max_gain); |
161 | ||
8f731d4c TBS |
162 | control = (struct soc_mixer_control *) |
163 | dmic_controls[DMIC_CTL_GAIN].private_value; | |
f3e82ad4 TBS |
164 | control->max = r.max_gain; |
165 | control->platform_max = r.max_gain; | |
8f731d4c TBS |
166 | |
167 | return snd_soc_add_component_controls(component, | |
168 | &dmic_controls[DMIC_CTL_GAIN], 1); | |
169 | } | |
170 | ||
727f1c71 TBS |
171 | static int i2s_rx_hw_params(struct snd_pcm_substream *substream, |
172 | struct snd_pcm_hw_params *params, | |
173 | struct snd_soc_dai *dai) | |
b291f42a CYC |
174 | { |
175 | struct snd_soc_component *component = dai->component; | |
727f1c71 TBS |
176 | struct cros_ec_codec_priv *priv = |
177 | snd_soc_component_get_drvdata(component); | |
178 | struct ec_param_ec_codec_i2s_rx p; | |
179 | enum ec_codec_i2s_rx_sample_depth depth; | |
b291f42a CYC |
180 | int ret; |
181 | ||
727f1c71 | 182 | if (params_rate(params) != 48000) |
b291f42a CYC |
183 | return -EINVAL; |
184 | ||
185 | switch (params_format(params)) { | |
186 | case SNDRV_PCM_FORMAT_S16_LE: | |
727f1c71 | 187 | depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_16; |
b291f42a CYC |
188 | break; |
189 | case SNDRV_PCM_FORMAT_S24_LE: | |
727f1c71 | 190 | depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_24; |
b291f42a CYC |
191 | break; |
192 | default: | |
193 | return -EINVAL; | |
194 | } | |
b291f42a | 195 | |
727f1c71 | 196 | dev_dbg(component->dev, "set depth to %u\n", depth); |
b291f42a | 197 | |
727f1c71 TBS |
198 | p.cmd = EC_CODEC_I2S_RX_SET_SAMPLE_DEPTH; |
199 | p.set_sample_depth_param.depth = depth; | |
200 | ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
201 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
202 | if (ret < 0) |
203 | return ret; | |
204 | ||
727f1c71 TBS |
205 | dev_dbg(component->dev, "set bclk to %u\n", |
206 | snd_soc_params_to_bclk(params)); | |
b291f42a | 207 | |
727f1c71 TBS |
208 | p.cmd = EC_CODEC_I2S_RX_SET_BCLK; |
209 | p.set_bclk_param.bclk = snd_soc_params_to_bclk(params); | |
210 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
211 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
212 | } |
213 | ||
727f1c71 | 214 | static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
b291f42a | 215 | { |
727f1c71 TBS |
216 | struct snd_soc_component *component = dai->component; |
217 | struct cros_ec_codec_priv *priv = | |
b291f42a | 218 | snd_soc_component_get_drvdata(component); |
727f1c71 TBS |
219 | struct ec_param_ec_codec_i2s_rx p; |
220 | enum ec_codec_i2s_rx_daifmt daifmt; | |
b291f42a | 221 | |
727f1c71 TBS |
222 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
223 | case SND_SOC_DAIFMT_CBS_CFS: | |
224 | break; | |
225 | default: | |
b291f42a | 226 | return -EINVAL; |
727f1c71 | 227 | } |
b291f42a | 228 | |
727f1c71 TBS |
229 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
230 | case SND_SOC_DAIFMT_NB_NF: | |
231 | break; | |
232 | default: | |
233 | return -EINVAL; | |
234 | } | |
b291f42a | 235 | |
727f1c71 TBS |
236 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
237 | case SND_SOC_DAIFMT_I2S: | |
238 | daifmt = EC_CODEC_I2S_RX_DAIFMT_I2S; | |
239 | break; | |
240 | case SND_SOC_DAIFMT_RIGHT_J: | |
241 | daifmt = EC_CODEC_I2S_RX_DAIFMT_RIGHT_J; | |
242 | break; | |
243 | case SND_SOC_DAIFMT_LEFT_J: | |
244 | daifmt = EC_CODEC_I2S_RX_DAIFMT_LEFT_J; | |
245 | break; | |
246 | default: | |
247 | return -EINVAL; | |
248 | } | |
b291f42a | 249 | |
727f1c71 | 250 | dev_dbg(component->dev, "set format to %u\n", daifmt); |
b291f42a | 251 | |
727f1c71 TBS |
252 | p.cmd = EC_CODEC_I2S_RX_SET_DAIFMT; |
253 | p.set_daifmt_param.daifmt = daifmt; | |
254 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
255 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
256 | } |
257 | ||
727f1c71 TBS |
258 | static const struct snd_soc_dai_ops i2s_rx_dai_ops = { |
259 | .hw_params = i2s_rx_hw_params, | |
260 | .set_fmt = i2s_rx_set_fmt, | |
261 | }; | |
262 | ||
263 | static int i2s_rx_event(struct snd_soc_dapm_widget *w, | |
264 | struct snd_kcontrol *kcontrol, int event) | |
b291f42a CYC |
265 | { |
266 | struct snd_soc_component *component = | |
267 | snd_soc_dapm_to_component(w->dapm); | |
727f1c71 TBS |
268 | struct cros_ec_codec_priv *priv = |
269 | snd_soc_component_get_drvdata(component); | |
270 | struct ec_param_ec_codec_i2s_rx p; | |
b291f42a CYC |
271 | |
272 | switch (event) { | |
273 | case SND_SOC_DAPM_PRE_PMU: | |
727f1c71 TBS |
274 | dev_dbg(component->dev, "enable I2S RX\n"); |
275 | p.cmd = EC_CODEC_I2S_RX_ENABLE; | |
276 | break; | |
b291f42a | 277 | case SND_SOC_DAPM_PRE_PMD: |
727f1c71 TBS |
278 | dev_dbg(component->dev, "disable I2S RX\n"); |
279 | p.cmd = EC_CODEC_I2S_RX_DISABLE; | |
280 | break; | |
281 | default: | |
282 | return 0; | |
b291f42a CYC |
283 | } |
284 | ||
727f1c71 TBS |
285 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, |
286 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
287 | } |
288 | ||
727f1c71 | 289 | static struct snd_soc_dapm_widget i2s_rx_dapm_widgets[] = { |
b291f42a | 290 | SND_SOC_DAPM_INPUT("DMIC"), |
727f1c71 | 291 | SND_SOC_DAPM_SUPPLY("I2S RX Enable", SND_SOC_NOPM, 0, 0, i2s_rx_event, |
b291f42a | 292 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), |
727f1c71 TBS |
293 | SND_SOC_DAPM_AIF_OUT("I2S RX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0), |
294 | }; | |
b291f42a | 295 | |
727f1c71 TBS |
296 | static struct snd_soc_dapm_route i2s_rx_dapm_routes[] = { |
297 | {"I2S RX", NULL, "DMIC"}, | |
298 | {"I2S RX", NULL, "I2S RX Enable"}, | |
b291f42a CYC |
299 | }; |
300 | ||
727f1c71 TBS |
301 | static struct snd_soc_dai_driver i2s_rx_dai_driver = { |
302 | .name = "EC Codec I2S RX", | |
303 | .capture = { | |
304 | .stream_name = "I2S Capture", | |
305 | .channels_min = 2, | |
306 | .channels_max = 2, | |
307 | .rates = SNDRV_PCM_RATE_48000, | |
308 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | |
309 | SNDRV_PCM_FMTBIT_S24_LE, | |
310 | }, | |
311 | .ops = &i2s_rx_dai_ops, | |
b291f42a CYC |
312 | }; |
313 | ||
727f1c71 | 314 | static int i2s_rx_probe(struct snd_soc_component *component) |
b291f42a | 315 | { |
8f731d4c | 316 | return dmic_probe(component); |
b291f42a CYC |
317 | } |
318 | ||
727f1c71 TBS |
319 | static const struct snd_soc_component_driver i2s_rx_component_driver = { |
320 | .probe = i2s_rx_probe, | |
321 | .dapm_widgets = i2s_rx_dapm_widgets, | |
322 | .num_dapm_widgets = ARRAY_SIZE(i2s_rx_dapm_widgets), | |
323 | .dapm_routes = i2s_rx_dapm_routes, | |
324 | .num_dapm_routes = ARRAY_SIZE(i2s_rx_dapm_routes), | |
b291f42a CYC |
325 | }; |
326 | ||
727f1c71 | 327 | static int cros_ec_codec_platform_probe(struct platform_device *pdev) |
b291f42a | 328 | { |
727f1c71 TBS |
329 | struct device *dev = &pdev->dev; |
330 | struct cros_ec_device *ec_device = dev_get_drvdata(pdev->dev.parent); | |
331 | struct cros_ec_codec_priv *priv; | |
b291f42a | 332 | |
727f1c71 TBS |
333 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
334 | if (!priv) | |
b291f42a CYC |
335 | return -ENOMEM; |
336 | ||
727f1c71 TBS |
337 | priv->dev = dev; |
338 | priv->ec_device = ec_device; | |
b291f42a | 339 | |
727f1c71 | 340 | platform_set_drvdata(pdev, priv); |
b291f42a | 341 | |
727f1c71 TBS |
342 | return devm_snd_soc_register_component(dev, &i2s_rx_component_driver, |
343 | &i2s_rx_dai_driver, 1); | |
b291f42a CYC |
344 | } |
345 | ||
346 | #ifdef CONFIG_OF | |
347 | static const struct of_device_id cros_ec_codec_of_match[] = { | |
348 | { .compatible = "google,cros-ec-codec" }, | |
349 | {}, | |
350 | }; | |
351 | MODULE_DEVICE_TABLE(of, cros_ec_codec_of_match); | |
352 | #endif | |
353 | ||
354 | static struct platform_driver cros_ec_codec_platform_driver = { | |
355 | .driver = { | |
727f1c71 | 356 | .name = "cros-ec-codec", |
b291f42a CYC |
357 | .of_match_table = of_match_ptr(cros_ec_codec_of_match), |
358 | }, | |
359 | .probe = cros_ec_codec_platform_probe, | |
360 | }; | |
361 | ||
362 | module_platform_driver(cros_ec_codec_platform_driver); | |
363 | ||
364 | MODULE_LICENSE("GPL v2"); | |
365 | MODULE_DESCRIPTION("ChromeOS EC codec driver"); | |
366 | MODULE_AUTHOR("Cheng-Yi Chiang <cychiang@chromium.org>"); | |
727f1c71 | 367 | MODULE_ALIAS("platform:cros-ec-codec"); |