linux/sound/soc/codecs/cros_ec_codec.c
<<
>>
Prefs
   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/mfd/cros_ec.h>
  13#include <linux/mfd/cros_ec_commands.h>
  14#include <linux/module.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 */
  30struct 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
  37static const DECLARE_TLV_DB_SCALE(ec_mic_gain_tlv, 0, 100, 0);
  38
  39static 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 */
  69static 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
  89static 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
 103static 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
 150static 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
 163static 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
 175static 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
 204static 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
 209static 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
 225static 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
 244static 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
 262static 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
 277static 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
 294static 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
 298static 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
 310static 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 */
 335static 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
 348static 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 */
 356static 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
 375static 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
 389static 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 */
 400static 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
 421static const struct of_device_id cros_ec_codec_of_match[] = {
 422        { .compatible = "google,cros-ec-codec" },
 423        {},
 424};
 425MODULE_DEVICE_TABLE(of, cros_ec_codec_of_match);
 426#endif
 427
 428static 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
 436module_platform_driver(cros_ec_codec_platform_driver);
 437
 438MODULE_LICENSE("GPL v2");
 439MODULE_DESCRIPTION("ChromeOS EC codec driver");
 440MODULE_AUTHOR("Cheng-Yi Chiang <cychiang@chromium.org>");
 441MODULE_ALIAS("platform:" DRV_NAME);
 442