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); |
727f1c71 TBS |
67 | struct ec_param_ec_codec_i2s_rx p; |
68 | struct ec_response_ec_codec_i2s_rx_get_gain r; | |
69 | int ret; | |
b291f42a | 70 | |
727f1c71 TBS |
71 | p.cmd = EC_CODEC_I2S_RX_GET_GAIN; |
72 | ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
73 | (uint8_t *)&p, sizeof(p), | |
74 | (uint8_t *)&r, sizeof(r)); | |
75 | if (ret < 0) | |
76 | return ret; | |
b291f42a | 77 | |
727f1c71 TBS |
78 | ucontrol->value.integer.value[0] = r.left; |
79 | ucontrol->value.integer.value[1] = r.right; | |
b291f42a | 80 | |
727f1c71 | 81 | return 0; |
b291f42a CYC |
82 | } |
83 | ||
727f1c71 TBS |
84 | static int dmic_put_gain(struct snd_kcontrol *kcontrol, |
85 | struct snd_ctl_elem_value *ucontrol) | |
b291f42a | 86 | { |
727f1c71 TBS |
87 | struct snd_soc_component *component = |
88 | snd_soc_kcontrol_component(kcontrol); | |
89 | struct cros_ec_codec_priv *priv = | |
90 | snd_soc_component_get_drvdata(component); | |
91 | struct soc_mixer_control *control = | |
92 | (struct soc_mixer_control *)kcontrol->private_value; | |
93 | int max_dmic_gain = control->max; | |
94 | int left = ucontrol->value.integer.value[0]; | |
95 | int right = ucontrol->value.integer.value[1]; | |
96 | struct ec_param_ec_codec_i2s_rx p; | |
b291f42a | 97 | |
727f1c71 | 98 | if (left > max_dmic_gain || right > max_dmic_gain) |
b291f42a | 99 | return -EINVAL; |
b291f42a | 100 | |
727f1c71 | 101 | dev_dbg(component->dev, "set mic gain to %u, %u\n", left, right); |
b291f42a | 102 | |
727f1c71 TBS |
103 | p.cmd = EC_CODEC_I2S_RX_SET_GAIN; |
104 | p.set_gain_param.left = left; | |
105 | p.set_gain_param.right = right; | |
106 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
107 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
108 | } |
109 | ||
727f1c71 | 110 | static const DECLARE_TLV_DB_SCALE(dmic_gain_tlv, 0, 100, 0); |
b291f42a | 111 | |
727f1c71 TBS |
112 | enum { |
113 | DMIC_CTL_GAIN = 0, | |
114 | }; | |
b291f42a | 115 | |
727f1c71 TBS |
116 | static struct snd_kcontrol_new dmic_controls[] = { |
117 | [DMIC_CTL_GAIN] = | |
118 | SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM, | |
119 | 0, 0, 0, dmic_get_gain, dmic_put_gain, | |
120 | dmic_gain_tlv), | |
121 | }; | |
b291f42a | 122 | |
727f1c71 TBS |
123 | static int i2s_rx_hw_params(struct snd_pcm_substream *substream, |
124 | struct snd_pcm_hw_params *params, | |
125 | struct snd_soc_dai *dai) | |
b291f42a CYC |
126 | { |
127 | struct snd_soc_component *component = dai->component; | |
727f1c71 TBS |
128 | struct cros_ec_codec_priv *priv = |
129 | snd_soc_component_get_drvdata(component); | |
130 | struct ec_param_ec_codec_i2s_rx p; | |
131 | enum ec_codec_i2s_rx_sample_depth depth; | |
b291f42a CYC |
132 | int ret; |
133 | ||
727f1c71 | 134 | if (params_rate(params) != 48000) |
b291f42a CYC |
135 | return -EINVAL; |
136 | ||
137 | switch (params_format(params)) { | |
138 | case SNDRV_PCM_FORMAT_S16_LE: | |
727f1c71 | 139 | depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_16; |
b291f42a CYC |
140 | break; |
141 | case SNDRV_PCM_FORMAT_S24_LE: | |
727f1c71 | 142 | depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_24; |
b291f42a CYC |
143 | break; |
144 | default: | |
145 | return -EINVAL; | |
146 | } | |
b291f42a | 147 | |
727f1c71 | 148 | dev_dbg(component->dev, "set depth to %u\n", depth); |
b291f42a | 149 | |
727f1c71 TBS |
150 | p.cmd = EC_CODEC_I2S_RX_SET_SAMPLE_DEPTH; |
151 | p.set_sample_depth_param.depth = depth; | |
152 | ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
153 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
154 | if (ret < 0) |
155 | return ret; | |
156 | ||
727f1c71 TBS |
157 | dev_dbg(component->dev, "set bclk to %u\n", |
158 | snd_soc_params_to_bclk(params)); | |
b291f42a | 159 | |
727f1c71 TBS |
160 | p.cmd = EC_CODEC_I2S_RX_SET_BCLK; |
161 | p.set_bclk_param.bclk = snd_soc_params_to_bclk(params); | |
162 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
163 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
164 | } |
165 | ||
727f1c71 | 166 | static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
b291f42a | 167 | { |
727f1c71 TBS |
168 | struct snd_soc_component *component = dai->component; |
169 | struct cros_ec_codec_priv *priv = | |
b291f42a | 170 | snd_soc_component_get_drvdata(component); |
727f1c71 TBS |
171 | struct ec_param_ec_codec_i2s_rx p; |
172 | enum ec_codec_i2s_rx_daifmt daifmt; | |
b291f42a | 173 | |
727f1c71 TBS |
174 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
175 | case SND_SOC_DAIFMT_CBS_CFS: | |
176 | break; | |
177 | default: | |
b291f42a | 178 | return -EINVAL; |
727f1c71 | 179 | } |
b291f42a | 180 | |
727f1c71 TBS |
181 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
182 | case SND_SOC_DAIFMT_NB_NF: | |
183 | break; | |
184 | default: | |
185 | return -EINVAL; | |
186 | } | |
b291f42a | 187 | |
727f1c71 TBS |
188 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
189 | case SND_SOC_DAIFMT_I2S: | |
190 | daifmt = EC_CODEC_I2S_RX_DAIFMT_I2S; | |
191 | break; | |
192 | case SND_SOC_DAIFMT_RIGHT_J: | |
193 | daifmt = EC_CODEC_I2S_RX_DAIFMT_RIGHT_J; | |
194 | break; | |
195 | case SND_SOC_DAIFMT_LEFT_J: | |
196 | daifmt = EC_CODEC_I2S_RX_DAIFMT_LEFT_J; | |
197 | break; | |
198 | default: | |
199 | return -EINVAL; | |
200 | } | |
b291f42a | 201 | |
727f1c71 | 202 | dev_dbg(component->dev, "set format to %u\n", daifmt); |
b291f42a | 203 | |
727f1c71 TBS |
204 | p.cmd = EC_CODEC_I2S_RX_SET_DAIFMT; |
205 | p.set_daifmt_param.daifmt = daifmt; | |
206 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, | |
207 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
208 | } |
209 | ||
727f1c71 TBS |
210 | static const struct snd_soc_dai_ops i2s_rx_dai_ops = { |
211 | .hw_params = i2s_rx_hw_params, | |
212 | .set_fmt = i2s_rx_set_fmt, | |
213 | }; | |
214 | ||
215 | static int i2s_rx_event(struct snd_soc_dapm_widget *w, | |
216 | struct snd_kcontrol *kcontrol, int event) | |
b291f42a CYC |
217 | { |
218 | struct snd_soc_component *component = | |
219 | snd_soc_dapm_to_component(w->dapm); | |
727f1c71 TBS |
220 | struct cros_ec_codec_priv *priv = |
221 | snd_soc_component_get_drvdata(component); | |
222 | struct ec_param_ec_codec_i2s_rx p; | |
b291f42a CYC |
223 | |
224 | switch (event) { | |
225 | case SND_SOC_DAPM_PRE_PMU: | |
727f1c71 TBS |
226 | dev_dbg(component->dev, "enable I2S RX\n"); |
227 | p.cmd = EC_CODEC_I2S_RX_ENABLE; | |
228 | break; | |
b291f42a | 229 | case SND_SOC_DAPM_PRE_PMD: |
727f1c71 TBS |
230 | dev_dbg(component->dev, "disable I2S RX\n"); |
231 | p.cmd = EC_CODEC_I2S_RX_DISABLE; | |
232 | break; | |
233 | default: | |
234 | return 0; | |
b291f42a CYC |
235 | } |
236 | ||
727f1c71 TBS |
237 | return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX, |
238 | (uint8_t *)&p, sizeof(p), NULL, 0); | |
b291f42a CYC |
239 | } |
240 | ||
727f1c71 | 241 | static struct snd_soc_dapm_widget i2s_rx_dapm_widgets[] = { |
b291f42a | 242 | SND_SOC_DAPM_INPUT("DMIC"), |
727f1c71 | 243 | SND_SOC_DAPM_SUPPLY("I2S RX Enable", SND_SOC_NOPM, 0, 0, i2s_rx_event, |
b291f42a | 244 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), |
727f1c71 TBS |
245 | SND_SOC_DAPM_AIF_OUT("I2S RX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0), |
246 | }; | |
b291f42a | 247 | |
727f1c71 TBS |
248 | static struct snd_soc_dapm_route i2s_rx_dapm_routes[] = { |
249 | {"I2S RX", NULL, "DMIC"}, | |
250 | {"I2S RX", NULL, "I2S RX Enable"}, | |
b291f42a CYC |
251 | }; |
252 | ||
727f1c71 TBS |
253 | static struct snd_soc_dai_driver i2s_rx_dai_driver = { |
254 | .name = "EC Codec I2S RX", | |
255 | .capture = { | |
256 | .stream_name = "I2S Capture", | |
257 | .channels_min = 2, | |
258 | .channels_max = 2, | |
259 | .rates = SNDRV_PCM_RATE_48000, | |
260 | .formats = SNDRV_PCM_FMTBIT_S16_LE | | |
261 | SNDRV_PCM_FMTBIT_S24_LE, | |
262 | }, | |
263 | .ops = &i2s_rx_dai_ops, | |
b291f42a CYC |
264 | }; |
265 | ||
727f1c71 | 266 | static int i2s_rx_probe(struct snd_soc_component *component) |
b291f42a | 267 | { |
727f1c71 TBS |
268 | struct cros_ec_codec_priv *priv = |
269 | snd_soc_component_get_drvdata(component); | |
270 | struct device *dev = priv->dev; | |
271 | int ret, val; | |
b291f42a | 272 | struct soc_mixer_control *control; |
b291f42a | 273 | |
727f1c71 TBS |
274 | ret = device_property_read_u32(dev, "max-dmic-gain", &val); |
275 | if (ret) { | |
276 | dev_err(dev, "Failed to read 'max-dmic-gain'\n"); | |
277 | return ret; | |
278 | } | |
b291f42a CYC |
279 | |
280 | control = (struct soc_mixer_control *) | |
727f1c71 TBS |
281 | dmic_controls[DMIC_CTL_GAIN].private_value; |
282 | control->max = val; | |
283 | control->platform_max = val; | |
b291f42a | 284 | |
727f1c71 TBS |
285 | return snd_soc_add_component_controls(component, |
286 | &dmic_controls[DMIC_CTL_GAIN], 1); | |
b291f42a CYC |
287 | } |
288 | ||
727f1c71 TBS |
289 | static const struct snd_soc_component_driver i2s_rx_component_driver = { |
290 | .probe = i2s_rx_probe, | |
291 | .dapm_widgets = i2s_rx_dapm_widgets, | |
292 | .num_dapm_widgets = ARRAY_SIZE(i2s_rx_dapm_widgets), | |
293 | .dapm_routes = i2s_rx_dapm_routes, | |
294 | .num_dapm_routes = ARRAY_SIZE(i2s_rx_dapm_routes), | |
b291f42a CYC |
295 | }; |
296 | ||
727f1c71 | 297 | static int cros_ec_codec_platform_probe(struct platform_device *pdev) |
b291f42a | 298 | { |
727f1c71 TBS |
299 | struct device *dev = &pdev->dev; |
300 | struct cros_ec_device *ec_device = dev_get_drvdata(pdev->dev.parent); | |
301 | struct cros_ec_codec_priv *priv; | |
b291f42a | 302 | |
727f1c71 TBS |
303 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
304 | if (!priv) | |
b291f42a CYC |
305 | return -ENOMEM; |
306 | ||
727f1c71 TBS |
307 | priv->dev = dev; |
308 | priv->ec_device = ec_device; | |
b291f42a | 309 | |
727f1c71 | 310 | platform_set_drvdata(pdev, priv); |
b291f42a | 311 | |
727f1c71 TBS |
312 | return devm_snd_soc_register_component(dev, &i2s_rx_component_driver, |
313 | &i2s_rx_dai_driver, 1); | |
b291f42a CYC |
314 | } |
315 | ||
316 | #ifdef CONFIG_OF | |
317 | static const struct of_device_id cros_ec_codec_of_match[] = { | |
318 | { .compatible = "google,cros-ec-codec" }, | |
319 | {}, | |
320 | }; | |
321 | MODULE_DEVICE_TABLE(of, cros_ec_codec_of_match); | |
322 | #endif | |
323 | ||
324 | static struct platform_driver cros_ec_codec_platform_driver = { | |
325 | .driver = { | |
727f1c71 | 326 | .name = "cros-ec-codec", |
b291f42a CYC |
327 | .of_match_table = of_match_ptr(cros_ec_codec_of_match), |
328 | }, | |
329 | .probe = cros_ec_codec_platform_probe, | |
330 | }; | |
331 | ||
332 | module_platform_driver(cros_ec_codec_platform_driver); | |
333 | ||
334 | MODULE_LICENSE("GPL v2"); | |
335 | MODULE_DESCRIPTION("ChromeOS EC codec driver"); | |
336 | MODULE_AUTHOR("Cheng-Yi Chiang <cychiang@chromium.org>"); | |
727f1c71 | 337 | MODULE_ALIAS("platform:cros-ec-codec"); |