linux/sound/soc/qcom/apq8016_sbc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2015 The Linux Foundation. All rights reserved.
   4 */
   5
   6#include <linux/device.h>
   7#include <linux/module.h>
   8#include <linux/kernel.h>
   9#include <linux/io.h>
  10#include <linux/of.h>
  11#include <linux/clk.h>
  12#include <linux/platform_device.h>
  13#include <sound/pcm.h>
  14#include <sound/pcm_params.h>
  15#include <sound/jack.h>
  16#include <sound/soc.h>
  17#include <uapi/linux/input-event-codes.h>
  18#include <dt-bindings/sound/apq8016-lpass.h>
  19#include <dt-bindings/sound/qcom,q6afe.h>
  20#include "common.h"
  21#include "qdsp6/q6afe.h"
  22
  23#define MI2S_COUNT  (MI2S_QUATERNARY + 1)
  24
  25struct apq8016_sbc_data {
  26        struct snd_soc_card card;
  27        void __iomem *mic_iomux;
  28        void __iomem *spkr_iomux;
  29        struct snd_soc_jack jack;
  30        bool jack_setup;
  31        int mi2s_clk_count[MI2S_COUNT];
  32};
  33
  34#define MIC_CTRL_TER_WS_SLAVE_SEL       BIT(21)
  35#define MIC_CTRL_QUA_WS_SLAVE_SEL_10    BIT(17)
  36#define MIC_CTRL_TLMM_SCLK_EN           BIT(1)
  37#define SPKR_CTL_PRI_WS_SLAVE_SEL_11    (BIT(17) | BIT(16))
  38#define SPKR_CTL_TLMM_MCLK_EN           BIT(1)
  39#define SPKR_CTL_TLMM_SCLK_EN           BIT(2)
  40#define SPKR_CTL_TLMM_DATA1_EN          BIT(3)
  41#define SPKR_CTL_TLMM_WS_OUT_SEL_MASK   GENMASK(7, 6)
  42#define SPKR_CTL_TLMM_WS_OUT_SEL_SEC    BIT(6)
  43#define SPKR_CTL_TLMM_WS_EN_SEL_MASK    GENMASK(19, 18)
  44#define SPKR_CTL_TLMM_WS_EN_SEL_SEC     BIT(18)
  45#define DEFAULT_MCLK_RATE               9600000
  46#define MI2S_BCLK_RATE                  1536000
  47
  48static struct snd_soc_jack_pin apq8016_sbc_jack_pins[] = {
  49        {
  50                .pin = "Mic Jack",
  51                .mask = SND_JACK_MICROPHONE,
  52        },
  53        {
  54                .pin = "Headphone Jack",
  55                .mask = SND_JACK_HEADPHONE,
  56        },
  57};
  58
  59static int apq8016_dai_init(struct snd_soc_pcm_runtime *rtd, int mi2s)
  60{
  61        struct snd_soc_dai *codec_dai;
  62        struct snd_soc_component *component;
  63        struct snd_soc_card *card = rtd->card;
  64        struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card);
  65        int i, rval;
  66        u32 value;
  67
  68        switch (mi2s) {
  69        case MI2S_PRIMARY:
  70                writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11,
  71                        pdata->spkr_iomux);
  72                break;
  73
  74        case MI2S_QUATERNARY:
  75                /* Configure the Quat MI2S to TLMM */
  76                writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 |
  77                        MIC_CTRL_TLMM_SCLK_EN,
  78                        pdata->mic_iomux);
  79                break;
  80        case MI2S_SECONDARY:
  81                /* Clear TLMM_WS_OUT_SEL and TLMM_WS_EN_SEL fields */
  82                value = readl(pdata->spkr_iomux) &
  83                        ~(SPKR_CTL_TLMM_WS_OUT_SEL_MASK | SPKR_CTL_TLMM_WS_EN_SEL_MASK);
  84                /* Configure the Sec MI2S to TLMM */
  85                writel(value | SPKR_CTL_TLMM_MCLK_EN | SPKR_CTL_TLMM_SCLK_EN |
  86                        SPKR_CTL_TLMM_DATA1_EN | SPKR_CTL_TLMM_WS_OUT_SEL_SEC |
  87                        SPKR_CTL_TLMM_WS_EN_SEL_SEC, pdata->spkr_iomux);
  88                break;
  89        case MI2S_TERTIARY:
  90                writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL |
  91                        MIC_CTRL_TLMM_SCLK_EN,
  92                        pdata->mic_iomux);
  93
  94                break;
  95
  96        default:
  97                dev_err(card->dev, "unsupported cpu dai configuration\n");
  98                return -EINVAL;
  99
 100        }
 101
 102        if (!pdata->jack_setup) {
 103                struct snd_jack *jack;
 104
 105                rval = snd_soc_card_jack_new_pins(card, "Headset Jack",
 106                                                  SND_JACK_HEADSET |
 107                                                  SND_JACK_HEADPHONE |
 108                                                  SND_JACK_BTN_0 | SND_JACK_BTN_1 |
 109                                                  SND_JACK_BTN_2 | SND_JACK_BTN_3 |
 110                                                  SND_JACK_BTN_4,
 111                                                  &pdata->jack,
 112                                                  apq8016_sbc_jack_pins,
 113                                                  ARRAY_SIZE(apq8016_sbc_jack_pins));
 114
 115                if (rval < 0) {
 116                        dev_err(card->dev, "Unable to add Headphone Jack\n");
 117                        return rval;
 118                }
 119
 120                jack = pdata->jack.jack;
 121
 122                snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
 123                snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
 124                snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
 125                snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
 126                pdata->jack_setup = true;
 127        }
 128
 129        for_each_rtd_codec_dais(rtd, i, codec_dai) {
 130
 131                component = codec_dai->component;
 132                /* Set default mclk for internal codec */
 133                rval = snd_soc_component_set_sysclk(component, 0, 0, DEFAULT_MCLK_RATE,
 134                                       SND_SOC_CLOCK_IN);
 135                if (rval != 0 && rval != -ENOTSUPP) {
 136                        dev_warn(card->dev, "Failed to set mclk: %d\n", rval);
 137                        return rval;
 138                }
 139                rval = snd_soc_component_set_jack(component, &pdata->jack, NULL);
 140                if (rval != 0 && rval != -ENOTSUPP) {
 141                        dev_warn(card->dev, "Failed to set jack: %d\n", rval);
 142                        return rval;
 143                }
 144        }
 145
 146        return 0;
 147}
 148
 149static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd)
 150{
 151        struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
 152
 153        return apq8016_dai_init(rtd, cpu_dai->id);
 154}
 155
 156static void apq8016_sbc_add_ops(struct snd_soc_card *card)
 157{
 158        struct snd_soc_dai_link *link;
 159        int i;
 160
 161        for_each_card_prelinks(card, i, link)
 162                link->init = apq8016_sbc_dai_init;
 163}
 164
 165static int qdsp6_dai_get_lpass_id(struct snd_soc_dai *cpu_dai)
 166{
 167        switch (cpu_dai->id) {
 168        case PRIMARY_MI2S_RX:
 169        case PRIMARY_MI2S_TX:
 170                return MI2S_PRIMARY;
 171        case SECONDARY_MI2S_RX:
 172        case SECONDARY_MI2S_TX:
 173                return MI2S_SECONDARY;
 174        case TERTIARY_MI2S_RX:
 175        case TERTIARY_MI2S_TX:
 176                return MI2S_TERTIARY;
 177        case QUATERNARY_MI2S_RX:
 178        case QUATERNARY_MI2S_TX:
 179                return MI2S_QUATERNARY;
 180        default:
 181                return -EINVAL;
 182        }
 183}
 184
 185static int msm8916_qdsp6_dai_init(struct snd_soc_pcm_runtime *rtd)
 186{
 187        struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
 188
 189        snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
 190        return apq8016_dai_init(rtd, qdsp6_dai_get_lpass_id(cpu_dai));
 191}
 192
 193static int msm8916_qdsp6_startup(struct snd_pcm_substream *substream)
 194{
 195        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
 196        struct snd_soc_card *card = rtd->card;
 197        struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card);
 198        struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
 199        int mi2s, ret;
 200
 201        mi2s = qdsp6_dai_get_lpass_id(cpu_dai);
 202        if (mi2s < 0)
 203                return mi2s;
 204
 205        if (++data->mi2s_clk_count[mi2s] > 1)
 206                return 0;
 207
 208        ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_BIT_CLK, MI2S_BCLK_RATE, 0);
 209        if (ret)
 210                dev_err(card->dev, "Failed to enable LPAIF bit clk: %d\n", ret);
 211        return ret;
 212}
 213
 214static void msm8916_qdsp6_shutdown(struct snd_pcm_substream *substream)
 215{
 216        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
 217        struct snd_soc_card *card = rtd->card;
 218        struct apq8016_sbc_data *data = snd_soc_card_get_drvdata(card);
 219        struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
 220        int mi2s, ret;
 221
 222        mi2s = qdsp6_dai_get_lpass_id(cpu_dai);
 223        if (mi2s < 0)
 224                return;
 225
 226        if (--data->mi2s_clk_count[mi2s] > 0)
 227                return;
 228
 229        ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_BIT_CLK, 0, 0);
 230        if (ret)
 231                dev_err(card->dev, "Failed to disable LPAIF bit clk: %d\n", ret);
 232}
 233
 234static const struct snd_soc_ops msm8916_qdsp6_be_ops = {
 235        .startup = msm8916_qdsp6_startup,
 236        .shutdown = msm8916_qdsp6_shutdown,
 237};
 238
 239static int msm8916_qdsp6_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
 240                                            struct snd_pcm_hw_params *params)
 241{
 242        struct snd_interval *rate = hw_param_interval(params,
 243                                        SNDRV_PCM_HW_PARAM_RATE);
 244        struct snd_interval *channels = hw_param_interval(params,
 245                                        SNDRV_PCM_HW_PARAM_CHANNELS);
 246        struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
 247
 248        rate->min = rate->max = 48000;
 249        channels->min = channels->max = 2;
 250        snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
 251
 252        return 0;
 253}
 254
 255static void msm8916_qdsp6_add_ops(struct snd_soc_card *card)
 256{
 257        struct snd_soc_dai_link *link;
 258        int i;
 259
 260        /* Make it obvious to userspace that QDSP6 is used */
 261        card->components = "qdsp6";
 262
 263        for_each_card_prelinks(card, i, link) {
 264                if (link->no_pcm) {
 265                        link->init = msm8916_qdsp6_dai_init;
 266                        link->ops = &msm8916_qdsp6_be_ops;
 267                        link->be_hw_params_fixup = msm8916_qdsp6_be_hw_params_fixup;
 268                }
 269        }
 270}
 271
 272static const struct snd_kcontrol_new apq8016_sbc_snd_controls[] = {
 273        SOC_DAPM_PIN_SWITCH("Headphone Jack"),
 274        SOC_DAPM_PIN_SWITCH("Mic Jack"),
 275};
 276
 277static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = {
 278        SND_SOC_DAPM_HP("Headphone Jack", NULL),
 279        SND_SOC_DAPM_MIC("Mic Jack", NULL),
 280        SND_SOC_DAPM_MIC("Handset Mic", NULL),
 281        SND_SOC_DAPM_MIC("Headset Mic", NULL),
 282        SND_SOC_DAPM_MIC("Secondary Mic", NULL),
 283        SND_SOC_DAPM_MIC("Digital Mic1", NULL),
 284        SND_SOC_DAPM_MIC("Digital Mic2", NULL),
 285};
 286
 287static int apq8016_sbc_platform_probe(struct platform_device *pdev)
 288{
 289        void (*add_ops)(struct snd_soc_card *card);
 290        struct device *dev = &pdev->dev;
 291        struct snd_soc_card *card;
 292        struct apq8016_sbc_data *data;
 293        int ret;
 294
 295        add_ops = device_get_match_data(&pdev->dev);
 296        if (!add_ops)
 297                return -EINVAL;
 298
 299        data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 300        if (!data)
 301                return -ENOMEM;
 302
 303        card = &data->card;
 304        card->dev = dev;
 305        card->owner = THIS_MODULE;
 306        card->dapm_widgets = apq8016_sbc_dapm_widgets;
 307        card->num_dapm_widgets = ARRAY_SIZE(apq8016_sbc_dapm_widgets);
 308        card->controls = apq8016_sbc_snd_controls;
 309        card->num_controls = ARRAY_SIZE(apq8016_sbc_snd_controls);
 310
 311        ret = qcom_snd_parse_of(card);
 312        if (ret)
 313                return ret;
 314
 315        data->mic_iomux = devm_platform_ioremap_resource_byname(pdev, "mic-iomux");
 316        if (IS_ERR(data->mic_iomux))
 317                return PTR_ERR(data->mic_iomux);
 318
 319        data->spkr_iomux = devm_platform_ioremap_resource_byname(pdev, "spkr-iomux");
 320        if (IS_ERR(data->spkr_iomux))
 321                return PTR_ERR(data->spkr_iomux);
 322
 323        snd_soc_card_set_drvdata(card, data);
 324
 325        add_ops(card);
 326        return devm_snd_soc_register_card(&pdev->dev, card);
 327}
 328
 329static const struct of_device_id apq8016_sbc_device_id[] __maybe_unused = {
 330        { .compatible = "qcom,apq8016-sbc-sndcard", .data = apq8016_sbc_add_ops },
 331        { .compatible = "qcom,msm8916-qdsp6-sndcard", .data = msm8916_qdsp6_add_ops },
 332        {},
 333};
 334MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id);
 335
 336static struct platform_driver apq8016_sbc_platform_driver = {
 337        .driver = {
 338                .name = "qcom-apq8016-sbc",
 339                .of_match_table = of_match_ptr(apq8016_sbc_device_id),
 340        },
 341        .probe = apq8016_sbc_platform_probe,
 342};
 343module_platform_driver(apq8016_sbc_platform_driver);
 344
 345MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org");
 346MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver");
 347MODULE_LICENSE("GPL");
 348