linux/sound/soc/intel/boards/bdw-rt5650.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * ASoC machine driver for Intel Broadwell platforms with RT5650 codec
   4 *
   5 * Copyright 2019, The Chromium OS Authors.  All rights reserved.
   6 */
   7
   8#include <linux/delay.h>
   9#include <linux/gpio/consumer.h>
  10#include <linux/module.h>
  11#include <linux/platform_device.h>
  12#include <sound/core.h>
  13#include <sound/jack.h>
  14#include <sound/pcm.h>
  15#include <sound/pcm_params.h>
  16#include <sound/soc.h>
  17#include <sound/soc-acpi.h>
  18
  19#include "../../codecs/rt5645.h"
  20
  21struct bdw_rt5650_priv {
  22        struct gpio_desc *gpio_hp_en;
  23        struct snd_soc_component *component;
  24};
  25
  26static const struct snd_soc_dapm_widget bdw_rt5650_widgets[] = {
  27        SND_SOC_DAPM_HP("Headphone", NULL),
  28        SND_SOC_DAPM_SPK("Speaker", NULL),
  29        SND_SOC_DAPM_MIC("Headset Mic", NULL),
  30        SND_SOC_DAPM_MIC("DMIC Pair1", NULL),
  31        SND_SOC_DAPM_MIC("DMIC Pair2", NULL),
  32};
  33
  34static const struct snd_soc_dapm_route bdw_rt5650_map[] = {
  35        /* Speakers */
  36        {"Speaker", NULL, "SPOL"},
  37        {"Speaker", NULL, "SPOR"},
  38
  39        /* Headset jack connectors */
  40        {"Headphone", NULL, "HPOL"},
  41        {"Headphone", NULL, "HPOR"},
  42        {"IN1P", NULL, "Headset Mic"},
  43        {"IN1N", NULL, "Headset Mic"},
  44
  45        /* Digital MICs
  46         * DMIC Pair1 are the two DMICs connected on the DMICN1 connector.
  47         * DMIC Pair2 are the two DMICs connected on the DMICN2 connector.
  48         * Facing the camera, DMIC Pair1 are on the left side, DMIC Pair2
  49         * are on the right side.
  50         */
  51        {"DMIC L1", NULL, "DMIC Pair1"},
  52        {"DMIC R1", NULL, "DMIC Pair1"},
  53        {"DMIC L2", NULL, "DMIC Pair2"},
  54        {"DMIC R2", NULL, "DMIC Pair2"},
  55
  56        /* CODEC BE connections */
  57        {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
  58        {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
  59};
  60
  61static const struct snd_kcontrol_new bdw_rt5650_controls[] = {
  62        SOC_DAPM_PIN_SWITCH("Speaker"),
  63        SOC_DAPM_PIN_SWITCH("Headphone"),
  64        SOC_DAPM_PIN_SWITCH("Headset Mic"),
  65        SOC_DAPM_PIN_SWITCH("DMIC Pair1"),
  66        SOC_DAPM_PIN_SWITCH("DMIC Pair2"),
  67};
  68
  69
  70static struct snd_soc_jack headphone_jack;
  71static struct snd_soc_jack mic_jack;
  72
  73static struct snd_soc_jack_pin headphone_jack_pin = {
  74        .pin    = "Headphone",
  75        .mask   = SND_JACK_HEADPHONE,
  76};
  77
  78static struct snd_soc_jack_pin mic_jack_pin = {
  79        .pin    = "Headset Mic",
  80        .mask   = SND_JACK_MICROPHONE,
  81};
  82
  83static int broadwell_ssp0_fixup(struct snd_soc_pcm_runtime *rtd,
  84                        struct snd_pcm_hw_params *params)
  85{
  86        struct snd_interval *rate = hw_param_interval(params,
  87                                                      SNDRV_PCM_HW_PARAM_RATE);
  88        struct snd_interval *chan = hw_param_interval(params,
  89                                                      SNDRV_PCM_HW_PARAM_CHANNELS);
  90
  91        /* The ADSP will covert the FE rate to 48k, max 4-channels */
  92        rate->min = rate->max = 48000;
  93        chan->min = 2;
  94        chan->max = 4;
  95
  96        /* set SSP0 to 24 bit */
  97        snd_mask_set_format(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
  98                            SNDRV_PCM_FORMAT_S24_LE);
  99
 100        return 0;
 101}
 102
 103static int bdw_rt5650_hw_params(struct snd_pcm_substream *substream,
 104        struct snd_pcm_hw_params *params)
 105{
 106        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
 107        struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
 108        int ret;
 109
 110        /* Workaround: set codec PLL to 19.2MHz that PLL source is
 111         * from MCLK(24MHz) to conform 2.4MHz DMIC clock.
 112         */
 113        ret = snd_soc_dai_set_pll(codec_dai, 0, RT5645_PLL1_S_MCLK,
 114                24000000, 19200000);
 115        if (ret < 0) {
 116                dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
 117                return ret;
 118        }
 119
 120        /* The actual MCLK freq is 24MHz. The codec is told that MCLK is
 121         * 24.576MHz to satisfy the requirement of rl6231_get_clk_info.
 122         * ASRC is enabled on AD and DA filters to ensure good audio quality.
 123         */
 124        ret = snd_soc_dai_set_sysclk(codec_dai, RT5645_SCLK_S_PLL1, 24576000,
 125                SND_SOC_CLOCK_IN);
 126        if (ret < 0) {
 127                dev_err(rtd->dev, "can't set codec sysclk configuration\n");
 128                return ret;
 129        }
 130
 131        return ret;
 132}
 133
 134static struct snd_soc_ops bdw_rt5650_ops = {
 135        .hw_params = bdw_rt5650_hw_params,
 136};
 137
 138static const unsigned int channels[] = {
 139        2, 4,
 140};
 141
 142static const struct snd_pcm_hw_constraint_list constraints_channels = {
 143        .count = ARRAY_SIZE(channels),
 144        .list = channels,
 145        .mask = 0,
 146};
 147
 148static int bdw_rt5650_fe_startup(struct snd_pcm_substream *substream)
 149{
 150        struct snd_pcm_runtime *runtime = substream->runtime;
 151
 152        /* Board supports stereo and quad configurations for capture */
 153        if (substream->stream != SNDRV_PCM_STREAM_CAPTURE)
 154                return 0;
 155
 156        runtime->hw.channels_max = 4;
 157        return snd_pcm_hw_constraint_list(runtime, 0,
 158                                          SNDRV_PCM_HW_PARAM_CHANNELS,
 159                                          &constraints_channels);
 160}
 161
 162static const struct snd_soc_ops bdw_rt5650_fe_ops = {
 163        .startup = bdw_rt5650_fe_startup,
 164};
 165
 166static int bdw_rt5650_init(struct snd_soc_pcm_runtime *rtd)
 167{
 168        struct bdw_rt5650_priv *bdw_rt5650 =
 169                snd_soc_card_get_drvdata(rtd->card);
 170        struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
 171        struct snd_soc_component *component = codec_dai->component;
 172        int ret;
 173
 174        /* Enable codec ASRC function for Stereo DAC/Stereo1 ADC/DMIC/I2S1.
 175         * The ASRC clock source is clk_i2s1_asrc.
 176         */
 177        rt5645_sel_asrc_clk_src(component,
 178                                RT5645_DA_STEREO_FILTER |
 179                                RT5645_DA_MONO_L_FILTER |
 180                                RT5645_DA_MONO_R_FILTER |
 181                                RT5645_AD_STEREO_FILTER |
 182                                RT5645_AD_MONO_L_FILTER |
 183                                RT5645_AD_MONO_R_FILTER,
 184                                RT5645_CLK_SEL_I2S1_ASRC);
 185
 186        /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */
 187        ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24);
 188
 189        if (ret < 0) {
 190                dev_err(rtd->dev, "can't set codec TDM slot %d\n", ret);
 191                return ret;
 192        }
 193
 194        /* Create and initialize headphone jack */
 195        if (snd_soc_card_jack_new(rtd->card, "Headphone Jack",
 196                        SND_JACK_HEADPHONE, &headphone_jack,
 197                        &headphone_jack_pin, 1)) {
 198                dev_err(component->dev, "Can't create headphone jack\n");
 199        }
 200
 201        /* Create and initialize mic jack */
 202        if (snd_soc_card_jack_new(rtd->card, "Mic Jack", SND_JACK_MICROPHONE,
 203                        &mic_jack, &mic_jack_pin, 1)) {
 204                dev_err(component->dev, "Can't create mic jack\n");
 205        }
 206
 207        rt5645_set_jack_detect(component, &headphone_jack, &mic_jack, NULL);
 208
 209        bdw_rt5650->component = component;
 210
 211        return 0;
 212}
 213
 214/* broadwell digital audio interface glue - connects codec <--> CPU */
 215SND_SOC_DAILINK_DEF(dummy,
 216        DAILINK_COMP_ARRAY(COMP_DUMMY()));
 217
 218SND_SOC_DAILINK_DEF(fe,
 219        DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
 220
 221SND_SOC_DAILINK_DEF(platform,
 222        DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
 223
 224SND_SOC_DAILINK_DEF(be,
 225        DAILINK_COMP_ARRAY(COMP_CODEC("i2c-10EC5650:00", "rt5645-aif1")));
 226
 227SND_SOC_DAILINK_DEF(ssp0_port,
 228            DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
 229
 230static struct snd_soc_dai_link bdw_rt5650_dais[] = {
 231        /* Front End DAI links */
 232        {
 233                .name = "System PCM",
 234                .stream_name = "System Playback",
 235                .nonatomic = 1,
 236                .dynamic = 1,
 237                .ops = &bdw_rt5650_fe_ops,
 238                .trigger = {
 239                        SND_SOC_DPCM_TRIGGER_POST,
 240                        SND_SOC_DPCM_TRIGGER_POST
 241                },
 242                .dpcm_playback = 1,
 243                .dpcm_capture = 1,
 244                SND_SOC_DAILINK_REG(fe, dummy, platform),
 245        },
 246
 247        /* Back End DAI links */
 248        {
 249                /* SSP0 - Codec */
 250                .name = "Codec",
 251                .id = 0,
 252                .no_pcm = 1,
 253                .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
 254                        SND_SOC_DAIFMT_CBS_CFS,
 255                .ignore_pmdown_time = 1,
 256                .be_hw_params_fixup = broadwell_ssp0_fixup,
 257                .ops = &bdw_rt5650_ops,
 258                .dpcm_playback = 1,
 259                .dpcm_capture = 1,
 260                .init = bdw_rt5650_init,
 261                SND_SOC_DAILINK_REG(ssp0_port, be, platform),
 262        },
 263};
 264
 265/* use space before codec name to simplify card ID, and simplify driver name */
 266#define SOF_CARD_NAME "bdw rt5650" /* card name will be 'sof-bdw rt5650' */
 267#define SOF_DRIVER_NAME "SOF"
 268
 269#define CARD_NAME "bdw-rt5650"
 270#define DRIVER_NAME NULL /* card name will be used for driver name */
 271
 272/* ASoC machine driver for Broadwell DSP + RT5650 */
 273static struct snd_soc_card bdw_rt5650_card = {
 274        .name = CARD_NAME,
 275        .driver_name = DRIVER_NAME,
 276        .owner = THIS_MODULE,
 277        .dai_link = bdw_rt5650_dais,
 278        .num_links = ARRAY_SIZE(bdw_rt5650_dais),
 279        .dapm_widgets = bdw_rt5650_widgets,
 280        .num_dapm_widgets = ARRAY_SIZE(bdw_rt5650_widgets),
 281        .dapm_routes = bdw_rt5650_map,
 282        .num_dapm_routes = ARRAY_SIZE(bdw_rt5650_map),
 283        .controls = bdw_rt5650_controls,
 284        .num_controls = ARRAY_SIZE(bdw_rt5650_controls),
 285        .fully_routed = true,
 286};
 287
 288static int bdw_rt5650_probe(struct platform_device *pdev)
 289{
 290        struct bdw_rt5650_priv *bdw_rt5650;
 291        struct snd_soc_acpi_mach *mach;
 292        int ret;
 293
 294        bdw_rt5650_card.dev = &pdev->dev;
 295
 296        /* Allocate driver private struct */
 297        bdw_rt5650 = devm_kzalloc(&pdev->dev, sizeof(struct bdw_rt5650_priv),
 298                GFP_KERNEL);
 299        if (!bdw_rt5650)
 300                return -ENOMEM;
 301
 302        /* override plaform name, if required */
 303        mach = pdev->dev.platform_data;
 304        ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt5650_card,
 305                                                    mach->mach_params.platform);
 306
 307        if (ret)
 308                return ret;
 309
 310        /* set card and driver name */
 311        if (snd_soc_acpi_sof_parent(&pdev->dev)) {
 312                bdw_rt5650_card.name = SOF_CARD_NAME;
 313                bdw_rt5650_card.driver_name = SOF_DRIVER_NAME;
 314        } else {
 315                bdw_rt5650_card.name = CARD_NAME;
 316                bdw_rt5650_card.driver_name = DRIVER_NAME;
 317        }
 318
 319        snd_soc_card_set_drvdata(&bdw_rt5650_card, bdw_rt5650);
 320
 321        return devm_snd_soc_register_card(&pdev->dev, &bdw_rt5650_card);
 322}
 323
 324static struct platform_driver bdw_rt5650_audio = {
 325        .probe = bdw_rt5650_probe,
 326        .driver = {
 327                .name = "bdw-rt5650",
 328                .pm = &snd_soc_pm_ops,
 329        },
 330};
 331
 332module_platform_driver(bdw_rt5650_audio)
 333
 334/* Module information */
 335MODULE_AUTHOR("Ben Zhang <benzh@chromium.org>");
 336MODULE_DESCRIPTION("Intel Broadwell RT5650 machine driver");
 337MODULE_LICENSE("GPL v2");
 338MODULE_ALIAS("platform:bdw-rt5650");
 339