1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright 2019 Google, Inc.
5 * ChromeOS Embedded Controller codec driver.
7 * This driver uses the cros-ec interface to communicate with the ChromeOS
8 * EC for audio function.
11 #include <linux/delay.h>
12 #include <linux/device.h>
13 #include <linux/kernel.h>
14 #include <linux/module.h>
15 #include <linux/platform_data/cros_ec_commands.h>
16 #include <linux/platform_data/cros_ec_proto.h>
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>
23 struct cros_ec_codec_priv {
25 struct cros_ec_device *ec_device;
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)
33 struct cros_ec_command *msg;
35 msg = kmalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL);
41 msg->outsize = outsize;
45 memcpy(msg->data, out, outsize);
47 ret = cros_ec_cmd_xfer_status(ec_dev, msg);
52 memcpy(in, msg->data, insize);
60 static int dmic_get_gain(struct snd_kcontrol *kcontrol,
61 struct snd_ctl_elem_value *ucontrol)
63 struct snd_soc_component *component =
64 snd_soc_kcontrol_component(kcontrol);
65 struct cros_ec_codec_priv *priv =
66 snd_soc_component_get_drvdata(component);
67 struct ec_param_ec_codec_dmic p;
68 struct ec_response_ec_codec_dmic_get_gain r;
71 p.cmd = EC_CODEC_DMIC_GET_GAIN;
72 ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_DMIC,
73 (uint8_t *)&p, sizeof(p),
74 (uint8_t *)&r, sizeof(r));
78 ucontrol->value.integer.value[0] = r.left;
79 ucontrol->value.integer.value[1] = r.right;
84 static int dmic_put_gain(struct snd_kcontrol *kcontrol,
85 struct snd_ctl_elem_value *ucontrol)
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_dmic p;
98 if (left > max_dmic_gain || right > max_dmic_gain)
101 dev_dbg(component->dev, "set mic gain to %u, %u\n", left, right);
103 p.cmd = EC_CODEC_DMIC_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_DMIC,
107 (uint8_t *)&p, sizeof(p), NULL, 0);
110 static const DECLARE_TLV_DB_SCALE(dmic_gain_tlv, 0, 100, 0);
116 static struct snd_kcontrol_new dmic_controls[] = {
118 SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM,
119 0, 0, 0, dmic_get_gain, dmic_put_gain,
123 static int dmic_probe(struct snd_soc_component *component)
125 struct cros_ec_codec_priv *priv =
126 snd_soc_component_get_drvdata(component);
127 struct device *dev = priv->dev;
129 struct soc_mixer_control *control;
131 ret = device_property_read_u32(dev, "max-dmic-gain", &val);
133 dev_err(dev, "Failed to read 'max-dmic-gain'\n");
137 control = (struct soc_mixer_control *)
138 dmic_controls[DMIC_CTL_GAIN].private_value;
140 control->platform_max = val;
142 return snd_soc_add_component_controls(component,
143 &dmic_controls[DMIC_CTL_GAIN], 1);
146 static int i2s_rx_hw_params(struct snd_pcm_substream *substream,
147 struct snd_pcm_hw_params *params,
148 struct snd_soc_dai *dai)
150 struct snd_soc_component *component = dai->component;
151 struct cros_ec_codec_priv *priv =
152 snd_soc_component_get_drvdata(component);
153 struct ec_param_ec_codec_i2s_rx p;
154 enum ec_codec_i2s_rx_sample_depth depth;
157 if (params_rate(params) != 48000)
160 switch (params_format(params)) {
161 case SNDRV_PCM_FORMAT_S16_LE:
162 depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_16;
164 case SNDRV_PCM_FORMAT_S24_LE:
165 depth = EC_CODEC_I2S_RX_SAMPLE_DEPTH_24;
171 dev_dbg(component->dev, "set depth to %u\n", depth);
173 p.cmd = EC_CODEC_I2S_RX_SET_SAMPLE_DEPTH;
174 p.set_sample_depth_param.depth = depth;
175 ret = send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX,
176 (uint8_t *)&p, sizeof(p), NULL, 0);
180 dev_dbg(component->dev, "set bclk to %u\n",
181 snd_soc_params_to_bclk(params));
183 p.cmd = EC_CODEC_I2S_RX_SET_BCLK;
184 p.set_bclk_param.bclk = snd_soc_params_to_bclk(params);
185 return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX,
186 (uint8_t *)&p, sizeof(p), NULL, 0);
189 static int i2s_rx_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
191 struct snd_soc_component *component = dai->component;
192 struct cros_ec_codec_priv *priv =
193 snd_soc_component_get_drvdata(component);
194 struct ec_param_ec_codec_i2s_rx p;
195 enum ec_codec_i2s_rx_daifmt daifmt;
197 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
198 case SND_SOC_DAIFMT_CBS_CFS:
204 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
205 case SND_SOC_DAIFMT_NB_NF:
211 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
212 case SND_SOC_DAIFMT_I2S:
213 daifmt = EC_CODEC_I2S_RX_DAIFMT_I2S;
215 case SND_SOC_DAIFMT_RIGHT_J:
216 daifmt = EC_CODEC_I2S_RX_DAIFMT_RIGHT_J;
218 case SND_SOC_DAIFMT_LEFT_J:
219 daifmt = EC_CODEC_I2S_RX_DAIFMT_LEFT_J;
225 dev_dbg(component->dev, "set format to %u\n", daifmt);
227 p.cmd = EC_CODEC_I2S_RX_SET_DAIFMT;
228 p.set_daifmt_param.daifmt = daifmt;
229 return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX,
230 (uint8_t *)&p, sizeof(p), NULL, 0);
233 static const struct snd_soc_dai_ops i2s_rx_dai_ops = {
234 .hw_params = i2s_rx_hw_params,
235 .set_fmt = i2s_rx_set_fmt,
238 static int i2s_rx_event(struct snd_soc_dapm_widget *w,
239 struct snd_kcontrol *kcontrol, int event)
241 struct snd_soc_component *component =
242 snd_soc_dapm_to_component(w->dapm);
243 struct cros_ec_codec_priv *priv =
244 snd_soc_component_get_drvdata(component);
245 struct ec_param_ec_codec_i2s_rx p;
248 case SND_SOC_DAPM_PRE_PMU:
249 dev_dbg(component->dev, "enable I2S RX\n");
250 p.cmd = EC_CODEC_I2S_RX_ENABLE;
252 case SND_SOC_DAPM_PRE_PMD:
253 dev_dbg(component->dev, "disable I2S RX\n");
254 p.cmd = EC_CODEC_I2S_RX_DISABLE;
260 return send_ec_host_command(priv->ec_device, EC_CMD_EC_CODEC_I2S_RX,
261 (uint8_t *)&p, sizeof(p), NULL, 0);
264 static struct snd_soc_dapm_widget i2s_rx_dapm_widgets[] = {
265 SND_SOC_DAPM_INPUT("DMIC"),
266 SND_SOC_DAPM_SUPPLY("I2S RX Enable", SND_SOC_NOPM, 0, 0, i2s_rx_event,
267 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
268 SND_SOC_DAPM_AIF_OUT("I2S RX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0),
271 static struct snd_soc_dapm_route i2s_rx_dapm_routes[] = {
272 {"I2S RX", NULL, "DMIC"},
273 {"I2S RX", NULL, "I2S RX Enable"},
276 static struct snd_soc_dai_driver i2s_rx_dai_driver = {
277 .name = "EC Codec I2S RX",
279 .stream_name = "I2S Capture",
282 .rates = SNDRV_PCM_RATE_48000,
283 .formats = SNDRV_PCM_FMTBIT_S16_LE |
284 SNDRV_PCM_FMTBIT_S24_LE,
286 .ops = &i2s_rx_dai_ops,
289 static int i2s_rx_probe(struct snd_soc_component *component)
291 return dmic_probe(component);
294 static const struct snd_soc_component_driver i2s_rx_component_driver = {
295 .probe = i2s_rx_probe,
296 .dapm_widgets = i2s_rx_dapm_widgets,
297 .num_dapm_widgets = ARRAY_SIZE(i2s_rx_dapm_widgets),
298 .dapm_routes = i2s_rx_dapm_routes,
299 .num_dapm_routes = ARRAY_SIZE(i2s_rx_dapm_routes),
302 static int cros_ec_codec_platform_probe(struct platform_device *pdev)
304 struct device *dev = &pdev->dev;
305 struct cros_ec_device *ec_device = dev_get_drvdata(pdev->dev.parent);
306 struct cros_ec_codec_priv *priv;
308 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
313 priv->ec_device = ec_device;
315 platform_set_drvdata(pdev, priv);
317 return devm_snd_soc_register_component(dev, &i2s_rx_component_driver,
318 &i2s_rx_dai_driver, 1);
322 static const struct of_device_id cros_ec_codec_of_match[] = {
323 { .compatible = "google,cros-ec-codec" },
326 MODULE_DEVICE_TABLE(of, cros_ec_codec_of_match);
329 static struct platform_driver cros_ec_codec_platform_driver = {
331 .name = "cros-ec-codec",
332 .of_match_table = of_match_ptr(cros_ec_codec_of_match),
334 .probe = cros_ec_codec_platform_probe,
337 module_platform_driver(cros_ec_codec_platform_driver);
339 MODULE_LICENSE("GPL v2");
340 MODULE_DESCRIPTION("ChromeOS EC codec driver");
341 MODULE_AUTHOR("Cheng-Yi Chiang <cychiang@chromium.org>");
342 MODULE_ALIAS("platform:cros-ec-codec");