linux/sound/soc/meson/g12a-toacodec.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2//
   3// Copyright (c) 2020 BayLibre, SAS.
   4// Author: Jerome Brunet <jbrunet@baylibre.com>
   5
   6#include <linux/bitfield.h>
   7#include <linux/clk.h>
   8#include <linux/module.h>
   9#include <sound/pcm_params.h>
  10#include <linux/regmap.h>
  11#include <linux/regulator/consumer.h>
  12#include <linux/reset.h>
  13#include <sound/soc.h>
  14#include <sound/soc-dai.h>
  15
  16#include <dt-bindings/sound/meson-g12a-toacodec.h>
  17#include "axg-tdm.h"
  18#include "meson-codec-glue.h"
  19
  20#define G12A_TOACODEC_DRV_NAME "g12a-toacodec"
  21
  22#define TOACODEC_CTRL0                  0x0
  23#define  CTRL0_ENABLE_SHIFT             31
  24#define  CTRL0_DAT_SEL_SM1_MSB          19
  25#define  CTRL0_DAT_SEL_SM1_LSB          18
  26#define  CTRL0_DAT_SEL_MSB              15
  27#define  CTRL0_DAT_SEL_LSB              14
  28#define  CTRL0_LANE_SEL_SM1             16
  29#define  CTRL0_LANE_SEL                 12
  30#define  CTRL0_LRCLK_SEL_SM1_MSB        14
  31#define  CTRL0_LRCLK_SEL_SM1_LSB        12
  32#define  CTRL0_LRCLK_SEL_MSB            9
  33#define  CTRL0_LRCLK_SEL_LSB            8
  34#define  CTRL0_LRCLK_INV_SM1            BIT(10)
  35#define  CTRL0_BLK_CAP_INV_SM1          BIT(9)
  36#define  CTRL0_BLK_CAP_INV              BIT(7)
  37#define  CTRL0_BCLK_O_INV_SM1           BIT(8)
  38#define  CTRL0_BCLK_O_INV               BIT(6)
  39#define  CTRL0_BCLK_SEL_SM1_MSB         6
  40#define  CTRL0_BCLK_SEL_MSB             5
  41#define  CTRL0_BCLK_SEL_LSB             4
  42#define  CTRL0_MCLK_SEL                 GENMASK(2, 0)
  43
  44#define TOACODEC_OUT_CHMAX              2
  45
  46struct g12a_toacodec {
  47        struct regmap_field *field_dat_sel;
  48        struct regmap_field *field_lrclk_sel;
  49        struct regmap_field *field_bclk_sel;
  50};
  51
  52struct g12a_toacodec_match_data {
  53        const struct snd_soc_component_driver *component_drv;
  54        struct reg_field field_dat_sel;
  55        struct reg_field field_lrclk_sel;
  56        struct reg_field field_bclk_sel;
  57};
  58
  59static const char * const g12a_toacodec_mux_texts[] = {
  60        "I2S A", "I2S B", "I2S C",
  61};
  62
  63static int g12a_toacodec_mux_put_enum(struct snd_kcontrol *kcontrol,
  64                                      struct snd_ctl_elem_value *ucontrol)
  65{
  66        struct snd_soc_component *component =
  67                snd_soc_dapm_kcontrol_component(kcontrol);
  68        struct g12a_toacodec *priv = snd_soc_component_get_drvdata(component);
  69        struct snd_soc_dapm_context *dapm =
  70                snd_soc_dapm_kcontrol_dapm(kcontrol);
  71        struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
  72        unsigned int mux, reg;
  73
  74        mux = snd_soc_enum_item_to_val(e, ucontrol->value.enumerated.item[0]);
  75        regmap_field_read(priv->field_dat_sel, &reg);
  76
  77        if (mux == reg)
  78                return 0;
  79
  80        /* Force disconnect of the mux while updating */
  81        snd_soc_dapm_mux_update_power(dapm, kcontrol, 0, NULL, NULL);
  82
  83        regmap_field_write(priv->field_dat_sel, mux);
  84        regmap_field_write(priv->field_lrclk_sel, mux);
  85        regmap_field_write(priv->field_bclk_sel, mux);
  86
  87        /*
  88         * FIXME:
  89         * On this soc, the glue gets the MCLK directly from the clock
  90         * controller instead of going the through the TDM interface.
  91         *
  92         * Here we assume interface A uses clock A, etc ... While it is
  93         * true for now, it could be different. Instead the glue should
  94         * find out the clock used by the interface and select the same
  95         * source. For that, we will need regmap backed clock mux which
  96         * is a work in progress
  97         */
  98        snd_soc_component_update_bits(component, e->reg,
  99                                      CTRL0_MCLK_SEL,
 100                                      FIELD_PREP(CTRL0_MCLK_SEL, mux));
 101
 102        snd_soc_dapm_mux_update_power(dapm, kcontrol, mux, e, NULL);
 103
 104        return 0;
 105}
 106
 107static SOC_ENUM_SINGLE_DECL(g12a_toacodec_mux_enum, TOACODEC_CTRL0,
 108                            CTRL0_DAT_SEL_LSB,
 109                            g12a_toacodec_mux_texts);
 110
 111static SOC_ENUM_SINGLE_DECL(sm1_toacodec_mux_enum, TOACODEC_CTRL0,
 112                            CTRL0_DAT_SEL_SM1_LSB,
 113                            g12a_toacodec_mux_texts);
 114
 115static const struct snd_kcontrol_new g12a_toacodec_mux =
 116        SOC_DAPM_ENUM_EXT("Source", g12a_toacodec_mux_enum,
 117                          snd_soc_dapm_get_enum_double,
 118                          g12a_toacodec_mux_put_enum);
 119
 120static const struct snd_kcontrol_new sm1_toacodec_mux =
 121        SOC_DAPM_ENUM_EXT("Source", sm1_toacodec_mux_enum,
 122                          snd_soc_dapm_get_enum_double,
 123                          g12a_toacodec_mux_put_enum);
 124
 125static const struct snd_kcontrol_new g12a_toacodec_out_enable =
 126        SOC_DAPM_SINGLE_AUTODISABLE("Switch", TOACODEC_CTRL0,
 127                                    CTRL0_ENABLE_SHIFT, 1, 0);
 128
 129static const struct snd_soc_dapm_widget g12a_toacodec_widgets[] = {
 130        SND_SOC_DAPM_MUX("SRC", SND_SOC_NOPM, 0, 0,
 131                         &g12a_toacodec_mux),
 132        SND_SOC_DAPM_SWITCH("OUT EN", SND_SOC_NOPM, 0, 0,
 133                            &g12a_toacodec_out_enable),
 134};
 135
 136static const struct snd_soc_dapm_widget sm1_toacodec_widgets[] = {
 137        SND_SOC_DAPM_MUX("SRC", SND_SOC_NOPM, 0, 0,
 138                         &sm1_toacodec_mux),
 139        SND_SOC_DAPM_SWITCH("OUT EN", SND_SOC_NOPM, 0, 0,
 140                            &g12a_toacodec_out_enable),
 141};
 142
 143static int g12a_toacodec_input_hw_params(struct snd_pcm_substream *substream,
 144                                         struct snd_pcm_hw_params *params,
 145                                         struct snd_soc_dai *dai)
 146{
 147        struct meson_codec_glue_input *data;
 148        int ret;
 149
 150        ret = meson_codec_glue_input_hw_params(substream, params, dai);
 151        if (ret)
 152                return ret;
 153
 154        /* The glue will provide 1 lane out of the 4 to the output */
 155        data = meson_codec_glue_input_get_data(dai);
 156        data->params.channels_min = min_t(unsigned int, TOACODEC_OUT_CHMAX,
 157                                        data->params.channels_min);
 158        data->params.channels_max = min_t(unsigned int, TOACODEC_OUT_CHMAX,
 159                                        data->params.channels_max);
 160
 161        return 0;
 162}
 163
 164static const struct snd_soc_dai_ops g12a_toacodec_input_ops = {
 165        .hw_params      = g12a_toacodec_input_hw_params,
 166        .set_fmt        = meson_codec_glue_input_set_fmt,
 167};
 168
 169static const struct snd_soc_dai_ops g12a_toacodec_output_ops = {
 170        .startup        = meson_codec_glue_output_startup,
 171};
 172
 173#define TOACODEC_STREAM(xname, xsuffix, xchmax)                 \
 174{                                                               \
 175        .stream_name    = xname " " xsuffix,                    \
 176        .channels_min   = 1,                                    \
 177        .channels_max   = (xchmax),                             \
 178        .rate_min       = 5512,                                 \
 179        .rate_max       = 192000,                               \
 180        .formats        = AXG_TDM_FORMATS,                      \
 181}
 182
 183#define TOACODEC_INPUT(xname, xid) {                                    \
 184        .name = xname,                                                  \
 185        .id = (xid),                                                    \
 186        .playback = TOACODEC_STREAM(xname, "Playback", 8),              \
 187        .ops = &g12a_toacodec_input_ops,                                \
 188        .probe = meson_codec_glue_input_dai_probe,                      \
 189        .remove = meson_codec_glue_input_dai_remove,                    \
 190}
 191
 192#define TOACODEC_OUTPUT(xname, xid) {                                   \
 193        .name = xname,                                                  \
 194        .id = (xid),                                                    \
 195        .capture = TOACODEC_STREAM(xname, "Capture", TOACODEC_OUT_CHMAX), \
 196        .ops = &g12a_toacodec_output_ops,                               \
 197}
 198
 199static struct snd_soc_dai_driver g12a_toacodec_dai_drv[] = {
 200        TOACODEC_INPUT("IN A", TOACODEC_IN_A),
 201        TOACODEC_INPUT("IN B", TOACODEC_IN_B),
 202        TOACODEC_INPUT("IN C", TOACODEC_IN_C),
 203        TOACODEC_OUTPUT("OUT", TOACODEC_OUT),
 204};
 205
 206static int g12a_toacodec_component_probe(struct snd_soc_component *c)
 207{
 208        /* Initialize the static clock parameters */
 209        return snd_soc_component_write(c, TOACODEC_CTRL0,
 210                                       CTRL0_BLK_CAP_INV);
 211}
 212
 213static int sm1_toacodec_component_probe(struct snd_soc_component *c)
 214{
 215        /* Initialize the static clock parameters */
 216        return snd_soc_component_write(c, TOACODEC_CTRL0,
 217                                       CTRL0_BLK_CAP_INV_SM1);
 218}
 219
 220static const struct snd_soc_dapm_route g12a_toacodec_routes[] = {
 221        { "SRC", "I2S A", "IN A Playback" },
 222        { "SRC", "I2S B", "IN B Playback" },
 223        { "SRC", "I2S C", "IN C Playback" },
 224        { "OUT EN", "Switch", "SRC" },
 225        { "OUT Capture", NULL, "OUT EN" },
 226};
 227
 228static const struct snd_kcontrol_new g12a_toacodec_controls[] = {
 229        SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL, 3, 0),
 230};
 231
 232static const struct snd_kcontrol_new sm1_toacodec_controls[] = {
 233        SOC_SINGLE("Lane Select", TOACODEC_CTRL0, CTRL0_LANE_SEL_SM1, 3, 0),
 234};
 235
 236static const struct snd_soc_component_driver g12a_toacodec_component_drv = {
 237        .probe                  = g12a_toacodec_component_probe,
 238        .controls               = g12a_toacodec_controls,
 239        .num_controls           = ARRAY_SIZE(g12a_toacodec_controls),
 240        .dapm_widgets           = g12a_toacodec_widgets,
 241        .num_dapm_widgets       = ARRAY_SIZE(g12a_toacodec_widgets),
 242        .dapm_routes            = g12a_toacodec_routes,
 243        .num_dapm_routes        = ARRAY_SIZE(g12a_toacodec_routes),
 244        .endianness             = 1,
 245        .non_legacy_dai_naming  = 1,
 246};
 247
 248static const struct snd_soc_component_driver sm1_toacodec_component_drv = {
 249        .probe                  = sm1_toacodec_component_probe,
 250        .controls               = sm1_toacodec_controls,
 251        .num_controls           = ARRAY_SIZE(sm1_toacodec_controls),
 252        .dapm_widgets           = sm1_toacodec_widgets,
 253        .num_dapm_widgets       = ARRAY_SIZE(sm1_toacodec_widgets),
 254        .dapm_routes            = g12a_toacodec_routes,
 255        .num_dapm_routes        = ARRAY_SIZE(g12a_toacodec_routes),
 256        .endianness             = 1,
 257        .non_legacy_dai_naming  = 1,
 258};
 259
 260static const struct regmap_config g12a_toacodec_regmap_cfg = {
 261        .reg_bits       = 32,
 262        .val_bits       = 32,
 263        .reg_stride     = 4,
 264};
 265
 266static const struct g12a_toacodec_match_data g12a_toacodec_match_data = {
 267        .component_drv  = &g12a_toacodec_component_drv,
 268        .field_dat_sel  = REG_FIELD(TOACODEC_CTRL0, 14, 15),
 269        .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 8, 9),
 270        .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 5),
 271};
 272
 273static const struct g12a_toacodec_match_data sm1_toacodec_match_data = {
 274        .component_drv  = &sm1_toacodec_component_drv,
 275        .field_dat_sel  = REG_FIELD(TOACODEC_CTRL0, 18, 19),
 276        .field_lrclk_sel = REG_FIELD(TOACODEC_CTRL0, 12, 14),
 277        .field_bclk_sel = REG_FIELD(TOACODEC_CTRL0, 4, 6),
 278};
 279
 280static const struct of_device_id g12a_toacodec_of_match[] = {
 281        {
 282                .compatible = "amlogic,g12a-toacodec",
 283                .data = &g12a_toacodec_match_data,
 284        },
 285        {
 286                .compatible = "amlogic,sm1-toacodec",
 287                .data = &sm1_toacodec_match_data,
 288        },
 289        {}
 290};
 291MODULE_DEVICE_TABLE(of, g12a_toacodec_of_match);
 292
 293static int g12a_toacodec_probe(struct platform_device *pdev)
 294{
 295        const struct g12a_toacodec_match_data *data;
 296        struct device *dev = &pdev->dev;
 297        struct g12a_toacodec *priv;
 298        void __iomem *regs;
 299        struct regmap *map;
 300        int ret;
 301
 302        data = device_get_match_data(dev);
 303        if (!data) {
 304                dev_err(dev, "failed to match device\n");
 305                return -ENODEV;
 306        }
 307
 308        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 309        if (!priv)
 310                return -ENOMEM;
 311
 312        platform_set_drvdata(pdev, priv);
 313
 314        ret = device_reset(dev);
 315        if (ret)
 316                return ret;
 317
 318        regs = devm_platform_ioremap_resource(pdev, 0);
 319        if (IS_ERR(regs))
 320                return PTR_ERR(regs);
 321
 322        map = devm_regmap_init_mmio(dev, regs, &g12a_toacodec_regmap_cfg);
 323        if (IS_ERR(map)) {
 324                dev_err(dev, "failed to init regmap: %ld\n",
 325                        PTR_ERR(map));
 326                return PTR_ERR(map);
 327        }
 328
 329        priv->field_dat_sel = devm_regmap_field_alloc(dev, map, data->field_dat_sel);
 330        if (IS_ERR(priv->field_dat_sel))
 331                return PTR_ERR(priv->field_dat_sel);
 332
 333        priv->field_lrclk_sel = devm_regmap_field_alloc(dev, map, data->field_lrclk_sel);
 334        if (IS_ERR(priv->field_lrclk_sel))
 335                return PTR_ERR(priv->field_lrclk_sel);
 336
 337        priv->field_bclk_sel = devm_regmap_field_alloc(dev, map, data->field_bclk_sel);
 338        if (IS_ERR(priv->field_bclk_sel))
 339                return PTR_ERR(priv->field_bclk_sel);
 340
 341        return devm_snd_soc_register_component(dev,
 342                        data->component_drv, g12a_toacodec_dai_drv,
 343                        ARRAY_SIZE(g12a_toacodec_dai_drv));
 344}
 345
 346static struct platform_driver g12a_toacodec_pdrv = {
 347        .driver = {
 348                .name = G12A_TOACODEC_DRV_NAME,
 349                .of_match_table = g12a_toacodec_of_match,
 350        },
 351        .probe = g12a_toacodec_probe,
 352};
 353module_platform_driver(g12a_toacodec_pdrv);
 354
 355MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
 356MODULE_DESCRIPTION("Amlogic G12a To Internal DAC Codec Driver");
 357MODULE_LICENSE("GPL v2");
 358