linux/sound/soc/intel/boards/sof_pcm512x.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2// Copyright(c) 2018-2020 Intel Corporation.
   3
   4/*
   5 * Intel SOF Machine Driver for Intel platforms with TI PCM512x codec,
   6 * e.g. Up or Up2 with Hifiberry DAC+ HAT
   7 */
   8#include <linux/clk.h>
   9#include <linux/dmi.h>
  10#include <linux/i2c.h>
  11#include <linux/input.h>
  12#include <linux/module.h>
  13#include <linux/platform_device.h>
  14#include <linux/types.h>
  15#include <sound/core.h>
  16#include <sound/jack.h>
  17#include <sound/pcm.h>
  18#include <sound/pcm_params.h>
  19#include <sound/soc.h>
  20#include <sound/soc-acpi.h>
  21#include "../../codecs/pcm512x.h"
  22#include "../common/soc-intel-quirks.h"
  23#include "hda_dsp_common.h"
  24
  25#define NAME_SIZE 32
  26
  27#define SOF_PCM512X_SSP_CODEC(quirk)            ((quirk) & GENMASK(3, 0))
  28#define SOF_PCM512X_SSP_CODEC_MASK                      (GENMASK(3, 0))
  29#define SOF_PCM512X_ENABLE_SSP_CAPTURE          BIT(4)
  30#define SOF_PCM512X_ENABLE_DMIC                 BIT(5)
  31
  32#define IDISP_CODEC_MASK        0x4
  33
  34/* Default: SSP5 */
  35static unsigned long sof_pcm512x_quirk =
  36        SOF_PCM512X_SSP_CODEC(5) |
  37        SOF_PCM512X_ENABLE_SSP_CAPTURE |
  38        SOF_PCM512X_ENABLE_DMIC;
  39
  40static bool is_legacy_cpu;
  41
  42struct sof_hdmi_pcm {
  43        struct list_head head;
  44        struct snd_soc_dai *codec_dai;
  45        int device;
  46};
  47
  48struct sof_card_private {
  49        struct list_head hdmi_pcm_list;
  50        bool idisp_codec;
  51};
  52
  53static int sof_pcm512x_quirk_cb(const struct dmi_system_id *id)
  54{
  55        sof_pcm512x_quirk = (unsigned long)id->driver_data;
  56        return 1;
  57}
  58
  59static const struct dmi_system_id sof_pcm512x_quirk_table[] = {
  60        {
  61                .callback = sof_pcm512x_quirk_cb,
  62                .matches = {
  63                        DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
  64                        DMI_MATCH(DMI_PRODUCT_NAME, "UP-CHT01"),
  65                },
  66                .driver_data = (void *)(SOF_PCM512X_SSP_CODEC(2)),
  67        },
  68        {}
  69};
  70
  71static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd)
  72{
  73        struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card);
  74        struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
  75        struct sof_hdmi_pcm *pcm;
  76
  77        pcm = devm_kzalloc(rtd->card->dev, sizeof(*pcm), GFP_KERNEL);
  78        if (!pcm)
  79                return -ENOMEM;
  80
  81        /* dai_link id is 1:1 mapped to the PCM device */
  82        pcm->device = rtd->dai_link->id;
  83        pcm->codec_dai = dai;
  84
  85        list_add_tail(&pcm->head, &ctx->hdmi_pcm_list);
  86
  87        return 0;
  88}
  89
  90static int sof_pcm512x_codec_init(struct snd_soc_pcm_runtime *rtd)
  91{
  92        struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component;
  93
  94        snd_soc_component_update_bits(codec, PCM512x_GPIO_EN, 0x08, 0x08);
  95        snd_soc_component_update_bits(codec, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
  96        snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1,
  97                                      0x08, 0x08);
  98
  99        return 0;
 100}
 101
 102static int aif1_startup(struct snd_pcm_substream *substream)
 103{
 104        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
 105        struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component;
 106
 107        snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1,
 108                                      0x08, 0x08);
 109
 110        return 0;
 111}
 112
 113static void aif1_shutdown(struct snd_pcm_substream *substream)
 114{
 115        struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
 116        struct snd_soc_component *codec = asoc_rtd_to_codec(rtd, 0)->component;
 117
 118        snd_soc_component_update_bits(codec, PCM512x_GPIO_CONTROL_1,
 119                                      0x08, 0x00);
 120}
 121
 122static const struct snd_soc_ops sof_pcm512x_ops = {
 123        .startup = aif1_startup,
 124        .shutdown = aif1_shutdown,
 125};
 126
 127static struct snd_soc_dai_link_component platform_component[] = {
 128        {
 129                /* name might be overridden during probe */
 130                .name = "0000:00:1f.3"
 131        }
 132};
 133
 134static int sof_card_late_probe(struct snd_soc_card *card)
 135{
 136        struct sof_card_private *ctx = snd_soc_card_get_drvdata(card);
 137        struct sof_hdmi_pcm *pcm;
 138
 139        /* HDMI is not supported by SOF on Baytrail/CherryTrail */
 140        if (is_legacy_cpu)
 141                return 0;
 142
 143        if (list_empty(&ctx->hdmi_pcm_list))
 144                return -EINVAL;
 145
 146        if (!ctx->idisp_codec)
 147                return 0;
 148
 149        pcm = list_first_entry(&ctx->hdmi_pcm_list, struct sof_hdmi_pcm, head);
 150
 151        return hda_dsp_hdmi_build_controls(card, pcm->codec_dai->component);
 152}
 153
 154static const struct snd_kcontrol_new sof_controls[] = {
 155        SOC_DAPM_PIN_SWITCH("Ext Spk"),
 156};
 157
 158static const struct snd_soc_dapm_widget sof_widgets[] = {
 159        SND_SOC_DAPM_SPK("Ext Spk", NULL),
 160};
 161
 162static const struct snd_soc_dapm_widget dmic_widgets[] = {
 163        SND_SOC_DAPM_MIC("SoC DMIC", NULL),
 164};
 165
 166static const struct snd_soc_dapm_route sof_map[] = {
 167        /* Speaker */
 168        {"Ext Spk", NULL, "OUTR"},
 169        {"Ext Spk", NULL, "OUTL"},
 170};
 171
 172static const struct snd_soc_dapm_route dmic_map[] = {
 173        /* digital mics */
 174        {"DMic", NULL, "SoC DMIC"},
 175};
 176
 177static int dmic_init(struct snd_soc_pcm_runtime *rtd)
 178{
 179        struct snd_soc_card *card = rtd->card;
 180        int ret;
 181
 182        ret = snd_soc_dapm_new_controls(&card->dapm, dmic_widgets,
 183                                        ARRAY_SIZE(dmic_widgets));
 184        if (ret) {
 185                dev_err(card->dev, "DMic widget addition failed: %d\n", ret);
 186                /* Don't need to add routes if widget addition failed */
 187                return ret;
 188        }
 189
 190        ret = snd_soc_dapm_add_routes(&card->dapm, dmic_map,
 191                                      ARRAY_SIZE(dmic_map));
 192
 193        if (ret)
 194                dev_err(card->dev, "DMic map addition failed: %d\n", ret);
 195
 196        return ret;
 197}
 198
 199/* sof audio machine driver for pcm512x codec */
 200static struct snd_soc_card sof_audio_card_pcm512x = {
 201        .name = "pcm512x",
 202        .owner = THIS_MODULE,
 203        .controls = sof_controls,
 204        .num_controls = ARRAY_SIZE(sof_controls),
 205        .dapm_widgets = sof_widgets,
 206        .num_dapm_widgets = ARRAY_SIZE(sof_widgets),
 207        .dapm_routes = sof_map,
 208        .num_dapm_routes = ARRAY_SIZE(sof_map),
 209        .fully_routed = true,
 210        .late_probe = sof_card_late_probe,
 211};
 212
 213SND_SOC_DAILINK_DEF(pcm512x_component,
 214        DAILINK_COMP_ARRAY(COMP_CODEC("i2c-104C5122:00", "pcm512x-hifi")));
 215SND_SOC_DAILINK_DEF(dmic_component,
 216        DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi")));
 217
 218static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev,
 219                                                          int ssp_codec,
 220                                                          int dmic_be_num,
 221                                                          int hdmi_num,
 222                                                          bool idisp_codec)
 223{
 224        struct snd_soc_dai_link_component *idisp_components;
 225        struct snd_soc_dai_link_component *cpus;
 226        struct snd_soc_dai_link *links;
 227        int i, id = 0;
 228
 229        links = devm_kcalloc(dev, sof_audio_card_pcm512x.num_links,
 230                        sizeof(struct snd_soc_dai_link), GFP_KERNEL);
 231        cpus = devm_kcalloc(dev, sof_audio_card_pcm512x.num_links,
 232                        sizeof(struct snd_soc_dai_link_component), GFP_KERNEL);
 233        if (!links || !cpus)
 234                goto devm_err;
 235
 236        /* codec SSP */
 237        links[id].name = devm_kasprintf(dev, GFP_KERNEL,
 238                                        "SSP%d-Codec", ssp_codec);
 239        if (!links[id].name)
 240                goto devm_err;
 241
 242        links[id].id = id;
 243        links[id].codecs = pcm512x_component;
 244        links[id].num_codecs = ARRAY_SIZE(pcm512x_component);
 245        links[id].platforms = platform_component;
 246        links[id].num_platforms = ARRAY_SIZE(platform_component);
 247        links[id].init = sof_pcm512x_codec_init;
 248        links[id].ops = &sof_pcm512x_ops;
 249        links[id].dpcm_playback = 1;
 250        /*
 251         * capture only supported with specific versions of the Hifiberry DAC+
 252         */
 253        if (sof_pcm512x_quirk & SOF_PCM512X_ENABLE_SSP_CAPTURE)
 254                links[id].dpcm_capture = 1;
 255        links[id].no_pcm = 1;
 256        links[id].cpus = &cpus[id];
 257        links[id].num_cpus = 1;
 258        if (is_legacy_cpu) {
 259                links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
 260                                                          "ssp%d-port",
 261                                                          ssp_codec);
 262                if (!links[id].cpus->dai_name)
 263                        goto devm_err;
 264        } else {
 265                links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
 266                                                          "SSP%d Pin",
 267                                                          ssp_codec);
 268                if (!links[id].cpus->dai_name)
 269                        goto devm_err;
 270        }
 271        id++;
 272
 273        /* dmic */
 274        if (dmic_be_num > 0) {
 275                /* at least we have dmic01 */
 276                links[id].name = "dmic01";
 277                links[id].cpus = &cpus[id];
 278                links[id].cpus->dai_name = "DMIC01 Pin";
 279                links[id].init = dmic_init;
 280                if (dmic_be_num > 1) {
 281                        /* set up 2 BE links at most */
 282                        links[id + 1].name = "dmic16k";
 283                        links[id + 1].cpus = &cpus[id + 1];
 284                        links[id + 1].cpus->dai_name = "DMIC16k Pin";
 285                        dmic_be_num = 2;
 286                }
 287        }
 288
 289        for (i = 0; i < dmic_be_num; i++) {
 290                links[id].id = id;
 291                links[id].num_cpus = 1;
 292                links[id].codecs = dmic_component;
 293                links[id].num_codecs = ARRAY_SIZE(dmic_component);
 294                links[id].platforms = platform_component;
 295                links[id].num_platforms = ARRAY_SIZE(platform_component);
 296                links[id].ignore_suspend = 1;
 297                links[id].dpcm_capture = 1;
 298                links[id].no_pcm = 1;
 299                id++;
 300        }
 301
 302        /* HDMI */
 303        if (hdmi_num > 0) {
 304                idisp_components = devm_kcalloc(dev, hdmi_num,
 305                                sizeof(struct snd_soc_dai_link_component),
 306                                GFP_KERNEL);
 307                if (!idisp_components)
 308                        goto devm_err;
 309        }
 310        for (i = 1; i <= hdmi_num; i++) {
 311                links[id].name = devm_kasprintf(dev, GFP_KERNEL,
 312                                                "iDisp%d", i);
 313                if (!links[id].name)
 314                        goto devm_err;
 315
 316                links[id].id = id;
 317                links[id].cpus = &cpus[id];
 318                links[id].num_cpus = 1;
 319                links[id].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL,
 320                                                          "iDisp%d Pin", i);
 321                if (!links[id].cpus->dai_name)
 322                        goto devm_err;
 323
 324                /*
 325                 * topology cannot be loaded if codec is missing, so
 326                 * use the dummy codec if needed
 327                 */
 328                if (idisp_codec) {
 329                        idisp_components[i - 1].name = "ehdaudio0D2";
 330                        idisp_components[i - 1].dai_name =
 331                                devm_kasprintf(dev, GFP_KERNEL,
 332                                               "intel-hdmi-hifi%d", i);
 333                } else {
 334                        idisp_components[i - 1].name = "snd-soc-dummy";
 335                        idisp_components[i - 1].dai_name = "snd-soc-dummy-dai";
 336                }
 337                if (!idisp_components[i - 1].dai_name)
 338                        goto devm_err;
 339
 340                links[id].codecs = &idisp_components[i - 1];
 341                links[id].num_codecs = 1;
 342                links[id].platforms = platform_component;
 343                links[id].num_platforms = ARRAY_SIZE(platform_component);
 344                links[id].init = sof_hdmi_init;
 345                links[id].dpcm_playback = 1;
 346                links[id].no_pcm = 1;
 347                id++;
 348        }
 349
 350        return links;
 351devm_err:
 352        return NULL;
 353}
 354
 355static int sof_audio_probe(struct platform_device *pdev)
 356{
 357        struct snd_soc_acpi_mach *mach = pdev->dev.platform_data;
 358        struct snd_soc_dai_link *dai_links;
 359        struct sof_card_private *ctx;
 360        int dmic_be_num, hdmi_num;
 361        int ret, ssp_codec;
 362
 363        ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
 364        if (!ctx)
 365                return -ENOMEM;
 366
 367        hdmi_num = 0;
 368        if (soc_intel_is_byt() || soc_intel_is_cht()) {
 369                is_legacy_cpu = true;
 370                dmic_be_num = 0;
 371                /* default quirk for legacy cpu */
 372                sof_pcm512x_quirk = SOF_PCM512X_SSP_CODEC(2);
 373        } else {
 374                dmic_be_num = 2;
 375                if (mach->mach_params.common_hdmi_codec_drv &&
 376                    (mach->mach_params.codec_mask & IDISP_CODEC_MASK))
 377                        ctx->idisp_codec = true;
 378
 379                /* links are always present in topology */
 380                hdmi_num = 3;
 381        }
 382
 383        dmi_check_system(sof_pcm512x_quirk_table);
 384
 385        dev_dbg(&pdev->dev, "sof_pcm512x_quirk = %lx\n", sof_pcm512x_quirk);
 386
 387        ssp_codec = sof_pcm512x_quirk & SOF_PCM512X_SSP_CODEC_MASK;
 388
 389        if (!(sof_pcm512x_quirk & SOF_PCM512X_ENABLE_DMIC))
 390                dmic_be_num = 0;
 391
 392        /* compute number of dai links */
 393        sof_audio_card_pcm512x.num_links = 1 + dmic_be_num + hdmi_num;
 394
 395        dai_links = sof_card_dai_links_create(&pdev->dev, ssp_codec,
 396                                              dmic_be_num, hdmi_num,
 397                                              ctx->idisp_codec);
 398        if (!dai_links)
 399                return -ENOMEM;
 400
 401        sof_audio_card_pcm512x.dai_link = dai_links;
 402
 403        INIT_LIST_HEAD(&ctx->hdmi_pcm_list);
 404
 405        sof_audio_card_pcm512x.dev = &pdev->dev;
 406
 407        /* set platform name for each dailink */
 408        ret = snd_soc_fixup_dai_links_platform_name(&sof_audio_card_pcm512x,
 409                                                    mach->mach_params.platform);
 410        if (ret)
 411                return ret;
 412
 413        snd_soc_card_set_drvdata(&sof_audio_card_pcm512x, ctx);
 414
 415        return devm_snd_soc_register_card(&pdev->dev,
 416                                          &sof_audio_card_pcm512x);
 417}
 418
 419static int sof_pcm512x_remove(struct platform_device *pdev)
 420{
 421        struct snd_soc_card *card = platform_get_drvdata(pdev);
 422        struct snd_soc_component *component = NULL;
 423
 424        for_each_card_components(card, component) {
 425                if (!strcmp(component->name, pcm512x_component[0].name)) {
 426                        snd_soc_component_set_jack(component, NULL, NULL);
 427                        break;
 428                }
 429        }
 430
 431        return 0;
 432}
 433
 434static struct platform_driver sof_audio = {
 435        .probe = sof_audio_probe,
 436        .remove = sof_pcm512x_remove,
 437        .driver = {
 438                .name = "sof_pcm512x",
 439                .pm = &snd_soc_pm_ops,
 440        },
 441};
 442module_platform_driver(sof_audio)
 443
 444MODULE_DESCRIPTION("ASoC Intel(R) SOF + PCM512x Machine driver");
 445MODULE_AUTHOR("Pierre-Louis Bossart");
 446MODULE_LICENSE("GPL v2");
 447MODULE_ALIAS("platform:sof_pcm512x");
 448MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON);
 449