root/sound/soc/codecs/cros_ec_codec.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ec_command_get_gain
  2. ec_command_no_resp
  3. set_i2s_config
  4. cros_ec_i2s_set_dai_fmt
  5. set_i2s_sample_depth
  6. set_i2s_bclk
  7. cros_ec_i2s_hw_params
  8. get_ec_mic_gain
  9. mic_gain_get
  10. set_ec_mic_gain
  11. mic_gain_put
  12. enable_i2s
  13. cros_ec_i2s_enable_event
  14. cros_ec_set_gain_range
  15. cros_ec_codec_probe
  16. cros_ec_codec_platform_probe

   1 // SPDX-License-Identifier: GPL-2.0
   2 /*
   3  * Driver for ChromeOS Embedded Controller codec.
   4  *
   5  * This driver uses the cros-ec interface to communicate with the ChromeOS
   6  * EC for audio function.
   7  */
   8 
   9 #include <linux/delay.h>
  10 #include <linux/device.h>
  11 #include <linux/kernel.h>
  12 #include <linux/module.h>
  13 #include <linux/platform_data/cros_ec_commands.h>
  14 #include <linux/platform_data/cros_ec_proto.h>
  15 #include <linux/platform_device.h>
  16 #include <sound/pcm.h>
  17 #include <sound/pcm_params.h>
  18 #include <sound/soc.h>
  19 #include <sound/tlv.h>
  20 
  21 #define DRV_NAME "cros-ec-codec"
  22 
  23 /**
  24  * struct cros_ec_codec_data - ChromeOS EC codec driver data.
  25  * @dev:                Device structure used in sysfs.
  26  * @ec_device:          cros_ec_device structure to talk to the physical device.
  27  * @component:          Pointer to the component.
  28  * @max_dmic_gain:      Maximum gain in dB supported by EC codec.
  29  */
  30 struct cros_ec_codec_data {
  31         struct device *dev;
  32         struct cros_ec_device *ec_device;
  33         struct snd_soc_component *component;
  34         unsigned int max_dmic_gain;
  35 };
  36 
  37 static const DECLARE_TLV_DB_SCALE(ec_mic_gain_tlv, 0, 100, 0);
  38 
  39 static int ec_command_get_gain(struct snd_soc_component *component,
  40                                struct ec_param_codec_i2s *param,
  41                                struct ec_codec_i2s_gain *resp)
  42 {
  43         struct cros_ec_codec_data *codec_data =
  44                 snd_soc_component_get_drvdata(component);
  45         struct cros_ec_device *ec_device = codec_data->ec_device;
  46         u8 buffer[sizeof(struct cros_ec_command) +
  47                   max(sizeof(struct ec_param_codec_i2s),
  48                       sizeof(struct ec_codec_i2s_gain))];
  49         struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
  50         int ret;
  51 
  52         msg->version = 0;
  53         msg->command = EC_CMD_CODEC_I2S;
  54         msg->outsize = sizeof(struct ec_param_codec_i2s);
  55         msg->insize = sizeof(struct ec_codec_i2s_gain);
  56 
  57         memcpy(msg->data, param, msg->outsize);
  58 
  59         ret = cros_ec_cmd_xfer_status(ec_device, msg);
  60         if (ret > 0)
  61                 memcpy(resp, msg->data, msg->insize);
  62 
  63         return ret;
  64 }
  65 
  66 /*
  67  * Wrapper for EC command without response.
  68  */
  69 static int ec_command_no_resp(struct snd_soc_component *component,
  70                               struct ec_param_codec_i2s *param)
  71 {
  72         struct cros_ec_codec_data *codec_data =
  73                 snd_soc_component_get_drvdata(component);
  74         struct cros_ec_device *ec_device = codec_data->ec_device;
  75         u8 buffer[sizeof(struct cros_ec_command) +
  76                   sizeof(struct ec_param_codec_i2s)];
  77         struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
  78 
  79         msg->version = 0;
  80         msg->command = EC_CMD_CODEC_I2S;
  81         msg->outsize = sizeof(struct ec_param_codec_i2s);
  82         msg->insize = 0;
  83 
  84         memcpy(msg->data, param, msg->outsize);
  85 
  86         return cros_ec_cmd_xfer_status(ec_device, msg);
  87 }
  88 
  89 static int set_i2s_config(struct snd_soc_component *component,
  90                           enum ec_i2s_config i2s_config)
  91 {
  92         struct ec_param_codec_i2s param;
  93 
  94         dev_dbg(component->dev, "%s set I2S format to %u\n", __func__,
  95                 i2s_config);
  96 
  97         param.cmd = EC_CODEC_I2S_SET_CONFIG;
  98         param.i2s_config = i2s_config;
  99 
 100         return ec_command_no_resp(component, &param);
 101 }
 102 
 103 static int cros_ec_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 104 {
 105         struct snd_soc_component *component = dai->component;
 106         enum ec_i2s_config i2s_config;
 107 
 108         switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 109         case SND_SOC_DAIFMT_CBS_CFS:
 110                 break;
 111         default:
 112                 return -EINVAL;
 113         }
 114 
 115         switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
 116         case SND_SOC_DAIFMT_NB_NF:
 117                 break;
 118         default:
 119                 return -EINVAL;
 120         }
 121 
 122         switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 123         case SND_SOC_DAIFMT_I2S:
 124                 i2s_config = EC_DAI_FMT_I2S;
 125                 break;
 126 
 127         case SND_SOC_DAIFMT_RIGHT_J:
 128                 i2s_config = EC_DAI_FMT_RIGHT_J;
 129                 break;
 130 
 131         case SND_SOC_DAIFMT_LEFT_J:
 132                 i2s_config = EC_DAI_FMT_LEFT_J;
 133                 break;
 134 
 135         case SND_SOC_DAIFMT_DSP_A:
 136                 i2s_config = EC_DAI_FMT_PCM_A;
 137                 break;
 138 
 139         case SND_SOC_DAIFMT_DSP_B:
 140                 i2s_config = EC_DAI_FMT_PCM_B;
 141                 break;
 142 
 143         default:
 144                 return -EINVAL;
 145         }
 146 
 147         return set_i2s_config(component, i2s_config);
 148 }
 149 
 150 static int set_i2s_sample_depth(struct snd_soc_component *component,
 151                                 enum ec_sample_depth_value depth)
 152 {
 153         struct ec_param_codec_i2s param;
 154 
 155         dev_dbg(component->dev, "%s set depth to %u\n", __func__, depth);
 156 
 157         param.cmd = EC_CODEC_SET_SAMPLE_DEPTH;
 158         param.depth = depth;
 159 
 160         return ec_command_no_resp(component, &param);
 161 }
 162 
 163 static int set_i2s_bclk(struct snd_soc_component *component, uint32_t bclk)
 164 {
 165         struct ec_param_codec_i2s param;
 166 
 167         dev_dbg(component->dev, "%s set i2s bclk to %u\n", __func__, bclk);
 168 
 169         param.cmd = EC_CODEC_I2S_SET_BCLK;
 170         param.bclk = bclk;
 171 
 172         return ec_command_no_resp(component, &param);
 173 }
 174 
 175 static int cros_ec_i2s_hw_params(struct snd_pcm_substream *substream,
 176                                  struct snd_pcm_hw_params *params,
 177                                  struct snd_soc_dai *dai)
 178 {
 179         struct snd_soc_component *component = dai->component;
 180         unsigned int rate, bclk;
 181         int ret;
 182 
 183         rate = params_rate(params);
 184         if (rate != 48000)
 185                 return -EINVAL;
 186 
 187         switch (params_format(params)) {
 188         case SNDRV_PCM_FORMAT_S16_LE:
 189                 ret = set_i2s_sample_depth(component, EC_CODEC_SAMPLE_DEPTH_16);
 190                 break;
 191         case SNDRV_PCM_FORMAT_S24_LE:
 192                 ret = set_i2s_sample_depth(component, EC_CODEC_SAMPLE_DEPTH_24);
 193                 break;
 194         default:
 195                 return -EINVAL;
 196         }
 197         if (ret < 0)
 198                 return ret;
 199 
 200         bclk = snd_soc_params_to_bclk(params);
 201         return set_i2s_bclk(component, bclk);
 202 }
 203 
 204 static const struct snd_soc_dai_ops cros_ec_i2s_dai_ops = {
 205         .hw_params = cros_ec_i2s_hw_params,
 206         .set_fmt = cros_ec_i2s_set_dai_fmt,
 207 };
 208 
 209 static struct snd_soc_dai_driver cros_ec_dai[] = {
 210         {
 211                 .name = "cros_ec_codec I2S",
 212                 .id = 0,
 213                 .capture = {
 214                         .stream_name = "I2S Capture",
 215                         .channels_min = 2,
 216                         .channels_max = 2,
 217                         .rates = SNDRV_PCM_RATE_48000,
 218                         .formats = SNDRV_PCM_FMTBIT_S16_LE |
 219                                    SNDRV_PCM_FMTBIT_S24_LE,
 220                 },
 221                 .ops = &cros_ec_i2s_dai_ops,
 222         }
 223 };
 224 
 225 static int get_ec_mic_gain(struct snd_soc_component *component,
 226                            u8 *left, u8 *right)
 227 {
 228         struct ec_param_codec_i2s param;
 229         struct ec_codec_i2s_gain resp;
 230         int ret;
 231 
 232         param.cmd = EC_CODEC_GET_GAIN;
 233 
 234         ret = ec_command_get_gain(component, &param, &resp);
 235         if (ret < 0)
 236                 return ret;
 237 
 238         *left = resp.left;
 239         *right = resp.right;
 240 
 241         return 0;
 242 }
 243 
 244 static int mic_gain_get(struct snd_kcontrol *kcontrol,
 245                         struct snd_ctl_elem_value *ucontrol)
 246 {
 247         struct snd_soc_component *component =
 248                 snd_soc_kcontrol_component(kcontrol);
 249         u8 left, right;
 250         int ret;
 251 
 252         ret = get_ec_mic_gain(component, &left, &right);
 253         if (ret)
 254                 return ret;
 255 
 256         ucontrol->value.integer.value[0] = left;
 257         ucontrol->value.integer.value[1] = right;
 258 
 259         return 0;
 260 }
 261 
 262 static int set_ec_mic_gain(struct snd_soc_component *component,
 263                            u8 left, u8 right)
 264 {
 265         struct ec_param_codec_i2s param;
 266 
 267         dev_dbg(component->dev, "%s set mic gain to %u, %u\n",
 268                 __func__, left, right);
 269 
 270         param.cmd = EC_CODEC_SET_GAIN;
 271         param.gain.left = left;
 272         param.gain.right = right;
 273 
 274         return ec_command_no_resp(component, &param);
 275 }
 276 
 277 static int mic_gain_put(struct snd_kcontrol *kcontrol,
 278                         struct snd_ctl_elem_value *ucontrol)
 279 {
 280         struct snd_soc_component *component =
 281                 snd_soc_kcontrol_component(kcontrol);
 282         struct cros_ec_codec_data *codec_data =
 283                 snd_soc_component_get_drvdata(component);
 284         int left = ucontrol->value.integer.value[0];
 285         int right = ucontrol->value.integer.value[1];
 286         unsigned int max_dmic_gain = codec_data->max_dmic_gain;
 287 
 288         if (left > max_dmic_gain || right > max_dmic_gain)
 289                 return -EINVAL;
 290 
 291         return set_ec_mic_gain(component, (u8)left, (u8)right);
 292 }
 293 
 294 static struct snd_kcontrol_new mic_gain_control =
 295         SOC_DOUBLE_EXT_TLV("EC Mic Gain", SND_SOC_NOPM, SND_SOC_NOPM, 0, 0, 0,
 296                            mic_gain_get, mic_gain_put, ec_mic_gain_tlv);
 297 
 298 static int enable_i2s(struct snd_soc_component *component, int enable)
 299 {
 300         struct ec_param_codec_i2s param;
 301 
 302         dev_dbg(component->dev, "%s set i2s to %u\n", __func__, enable);
 303 
 304         param.cmd = EC_CODEC_I2S_ENABLE;
 305         param.i2s_enable = enable;
 306 
 307         return ec_command_no_resp(component, &param);
 308 }
 309 
 310 static int cros_ec_i2s_enable_event(struct snd_soc_dapm_widget *w,
 311                                     struct snd_kcontrol *kcontrol, int event)
 312 {
 313         struct snd_soc_component *component =
 314                 snd_soc_dapm_to_component(w->dapm);
 315 
 316         switch (event) {
 317         case SND_SOC_DAPM_PRE_PMU:
 318                 dev_dbg(component->dev,
 319                         "%s got SND_SOC_DAPM_PRE_PMU event\n", __func__);
 320                 return enable_i2s(component, 1);
 321 
 322         case SND_SOC_DAPM_PRE_PMD:
 323                 dev_dbg(component->dev,
 324                         "%s got SND_SOC_DAPM_PRE_PMD event\n", __func__);
 325                 return enable_i2s(component, 0);
 326         }
 327 
 328         return 0;
 329 }
 330 
 331 /*
 332  * The goal of this DAPM route is to turn on/off I2S using EC
 333  * host command when capture stream is started/stopped.
 334  */
 335 static const struct snd_soc_dapm_widget cros_ec_codec_dapm_widgets[] = {
 336         SND_SOC_DAPM_INPUT("DMIC"),
 337 
 338         /*
 339          * Control EC to enable/disable I2S.
 340          */
 341         SND_SOC_DAPM_SUPPLY("I2S Enable", SND_SOC_NOPM,
 342                             0, 0, cros_ec_i2s_enable_event,
 343                             SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
 344 
 345         SND_SOC_DAPM_AIF_OUT("I2STX", "I2S Capture", 0, SND_SOC_NOPM, 0, 0),
 346 };
 347 
 348 static const struct snd_soc_dapm_route cros_ec_codec_dapm_routes[] = {
 349         { "I2STX", NULL, "DMIC" },
 350         { "I2STX", NULL, "I2S Enable" },
 351 };
 352 
 353 /*
 354  * Read maximum gain from device property and set it to mixer control.
 355  */
 356 static int cros_ec_set_gain_range(struct device *dev)
 357 {
 358         struct soc_mixer_control *control;
 359         struct cros_ec_codec_data *codec_data = dev_get_drvdata(dev);
 360         int rc;
 361 
 362         rc = device_property_read_u32(dev, "max-dmic-gain",
 363                                       &codec_data->max_dmic_gain);
 364         if (rc)
 365                 return rc;
 366 
 367         control = (struct soc_mixer_control *)
 368                                 mic_gain_control.private_value;
 369         control->max = codec_data->max_dmic_gain;
 370         control->platform_max = codec_data->max_dmic_gain;
 371 
 372         return 0;
 373 }
 374 
 375 static int cros_ec_codec_probe(struct snd_soc_component *component)
 376 {
 377         int rc;
 378 
 379         struct cros_ec_codec_data *codec_data =
 380                 snd_soc_component_get_drvdata(component);
 381 
 382         rc = cros_ec_set_gain_range(codec_data->dev);
 383         if (rc)
 384                 return rc;
 385 
 386         return snd_soc_add_component_controls(component, &mic_gain_control, 1);
 387 }
 388 
 389 static const struct snd_soc_component_driver cros_ec_component_driver = {
 390         .probe                  = cros_ec_codec_probe,
 391         .dapm_widgets           = cros_ec_codec_dapm_widgets,
 392         .num_dapm_widgets       = ARRAY_SIZE(cros_ec_codec_dapm_widgets),
 393         .dapm_routes            = cros_ec_codec_dapm_routes,
 394         .num_dapm_routes        = ARRAY_SIZE(cros_ec_codec_dapm_routes),
 395 };
 396 
 397 /*
 398  * Platform device and platform driver fro cros-ec-codec.
 399  */
 400 static int cros_ec_codec_platform_probe(struct platform_device *pd)
 401 {
 402         struct device *dev = &pd->dev;
 403         struct cros_ec_device *ec_device = dev_get_drvdata(pd->dev.parent);
 404         struct cros_ec_codec_data *codec_data;
 405 
 406         codec_data = devm_kzalloc(dev, sizeof(struct cros_ec_codec_data),
 407                                   GFP_KERNEL);
 408         if (!codec_data)
 409                 return -ENOMEM;
 410 
 411         codec_data->dev = dev;
 412         codec_data->ec_device = ec_device;
 413 
 414         platform_set_drvdata(pd, codec_data);
 415 
 416         return devm_snd_soc_register_component(dev, &cros_ec_component_driver,
 417                                           cros_ec_dai, ARRAY_SIZE(cros_ec_dai));
 418 }
 419 
 420 #ifdef CONFIG_OF
 421 static const struct of_device_id cros_ec_codec_of_match[] = {
 422         { .compatible = "google,cros-ec-codec" },
 423         {},
 424 };
 425 MODULE_DEVICE_TABLE(of, cros_ec_codec_of_match);
 426 #endif
 427 
 428 static struct platform_driver cros_ec_codec_platform_driver = {
 429         .driver = {
 430                 .name = DRV_NAME,
 431                 .of_match_table = of_match_ptr(cros_ec_codec_of_match),
 432         },
 433         .probe = cros_ec_codec_platform_probe,
 434 };
 435 
 436 module_platform_driver(cros_ec_codec_platform_driver);
 437 
 438 MODULE_LICENSE("GPL v2");
 439 MODULE_DESCRIPTION("ChromeOS EC codec driver");
 440 MODULE_AUTHOR("Cheng-Yi Chiang <cychiang@chromium.org>");
 441 MODULE_ALIAS("platform:" DRV_NAME);

/* [<][>][^][v][top][bottom][index][help] */