Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: (GPL-2.0 OR MIT)
0002 //
0003 // Copyright (c) 2018 BayLibre, SAS.
0004 // Author: Jerome Brunet <jbrunet@baylibre.com>
0005 
0006 #include <linux/module.h>
0007 #include <linux/of_platform.h>
0008 #include <linux/regmap.h>
0009 #include <sound/soc.h>
0010 #include <sound/soc-dai.h>
0011 
0012 #include "axg-tdm-formatter.h"
0013 
0014 #define TDMOUT_CTRL0            0x00
0015 #define  TDMOUT_CTRL0_BITNUM_MASK   GENMASK(4, 0)
0016 #define  TDMOUT_CTRL0_BITNUM(x)     ((x) << 0)
0017 #define  TDMOUT_CTRL0_SLOTNUM_MASK  GENMASK(9, 5)
0018 #define  TDMOUT_CTRL0_SLOTNUM(x)    ((x) << 5)
0019 #define  TDMOUT_CTRL0_INIT_BITNUM_MASK  GENMASK(19, 15)
0020 #define  TDMOUT_CTRL0_INIT_BITNUM(x)    ((x) << 15)
0021 #define  TDMOUT_CTRL0_ENABLE        BIT(31)
0022 #define  TDMOUT_CTRL0_RST_OUT       BIT(29)
0023 #define  TDMOUT_CTRL0_RST_IN        BIT(28)
0024 #define TDMOUT_CTRL1            0x04
0025 #define  TDMOUT_CTRL1_TYPE_MASK     GENMASK(6, 4)
0026 #define  TDMOUT_CTRL1_TYPE(x)       ((x) << 4)
0027 #define  SM1_TDMOUT_CTRL1_GAIN_EN   7
0028 #define  TDMOUT_CTRL1_MSB_POS_MASK  GENMASK(12, 8)
0029 #define  TDMOUT_CTRL1_MSB_POS(x)    ((x) << 8)
0030 #define  TDMOUT_CTRL1_SEL_SHIFT     24
0031 #define  TDMOUT_CTRL1_GAIN_EN       26
0032 #define  TDMOUT_CTRL1_WS_INV        BIT(28)
0033 #define TDMOUT_SWAP         0x08
0034 #define TDMOUT_MASK0            0x0c
0035 #define TDMOUT_MASK1            0x10
0036 #define TDMOUT_MASK2            0x14
0037 #define TDMOUT_MASK3            0x18
0038 #define TDMOUT_STAT         0x1c
0039 #define TDMOUT_GAIN0            0x20
0040 #define TDMOUT_GAIN1            0x24
0041 #define TDMOUT_MUTE_VAL         0x28
0042 #define TDMOUT_MUTE0            0x2c
0043 #define TDMOUT_MUTE1            0x30
0044 #define TDMOUT_MUTE2            0x34
0045 #define TDMOUT_MUTE3            0x38
0046 #define TDMOUT_MASK_VAL         0x3c
0047 
0048 static const struct regmap_config axg_tdmout_regmap_cfg = {
0049     .reg_bits   = 32,
0050     .val_bits   = 32,
0051     .reg_stride = 4,
0052     .max_register   = TDMOUT_MASK_VAL,
0053 };
0054 
0055 static struct snd_soc_dai *
0056 axg_tdmout_get_be(struct snd_soc_dapm_widget *w)
0057 {
0058     struct snd_soc_dapm_path *p;
0059     struct snd_soc_dai *be;
0060 
0061     snd_soc_dapm_widget_for_each_sink_path(w, p) {
0062         if (!p->connect)
0063             continue;
0064 
0065         if (p->sink->id == snd_soc_dapm_dai_in)
0066             return (struct snd_soc_dai *)p->sink->priv;
0067 
0068         be = axg_tdmout_get_be(p->sink);
0069         if (be)
0070             return be;
0071     }
0072 
0073     return NULL;
0074 }
0075 
0076 static struct axg_tdm_stream *
0077 axg_tdmout_get_tdm_stream(struct snd_soc_dapm_widget *w)
0078 {
0079     struct snd_soc_dai *be = axg_tdmout_get_be(w);
0080 
0081     if (!be)
0082         return NULL;
0083 
0084     return be->playback_dma_data;
0085 }
0086 
0087 static void axg_tdmout_enable(struct regmap *map)
0088 {
0089     /* Apply both reset */
0090     regmap_update_bits(map, TDMOUT_CTRL0,
0091                TDMOUT_CTRL0_RST_OUT | TDMOUT_CTRL0_RST_IN, 0);
0092 
0093     /* Clear out reset before in reset */
0094     regmap_update_bits(map, TDMOUT_CTRL0,
0095                TDMOUT_CTRL0_RST_OUT, TDMOUT_CTRL0_RST_OUT);
0096     regmap_update_bits(map, TDMOUT_CTRL0,
0097                TDMOUT_CTRL0_RST_IN,  TDMOUT_CTRL0_RST_IN);
0098 
0099     /* Actually enable tdmout */
0100     regmap_update_bits(map, TDMOUT_CTRL0,
0101                TDMOUT_CTRL0_ENABLE, TDMOUT_CTRL0_ENABLE);
0102 }
0103 
0104 static void axg_tdmout_disable(struct regmap *map)
0105 {
0106     regmap_update_bits(map, TDMOUT_CTRL0, TDMOUT_CTRL0_ENABLE, 0);
0107 }
0108 
0109 static int axg_tdmout_prepare(struct regmap *map,
0110                   const struct axg_tdm_formatter_hw *quirks,
0111                   struct axg_tdm_stream *ts)
0112 {
0113     unsigned int val, skew = quirks->skew_offset;
0114 
0115     /* Set the stream skew */
0116     switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
0117     case SND_SOC_DAIFMT_I2S:
0118     case SND_SOC_DAIFMT_DSP_A:
0119         break;
0120 
0121     case SND_SOC_DAIFMT_LEFT_J:
0122     case SND_SOC_DAIFMT_DSP_B:
0123         skew += 1;
0124         break;
0125 
0126     default:
0127         pr_err("Unsupported format: %u\n",
0128                ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
0129         return -EINVAL;
0130     }
0131 
0132     val = TDMOUT_CTRL0_INIT_BITNUM(skew);
0133 
0134     /* Set the slot width */
0135     val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1);
0136 
0137     /* Set the slot number */
0138     val |= TDMOUT_CTRL0_SLOTNUM(ts->iface->slots - 1);
0139 
0140     regmap_update_bits(map, TDMOUT_CTRL0,
0141                TDMOUT_CTRL0_INIT_BITNUM_MASK |
0142                TDMOUT_CTRL0_BITNUM_MASK |
0143                TDMOUT_CTRL0_SLOTNUM_MASK, val);
0144 
0145     /* Set the sample width */
0146     val = TDMOUT_CTRL1_MSB_POS(ts->width - 1);
0147 
0148     /* FIFO data are arranged in chunks of 64bits */
0149     switch (ts->physical_width) {
0150     case 8:
0151         /* 8 samples of 8 bits */
0152         val |= TDMOUT_CTRL1_TYPE(0);
0153         break;
0154     case 16:
0155         /* 4 samples of 16 bits - right justified */
0156         val |= TDMOUT_CTRL1_TYPE(2);
0157         break;
0158     case 32:
0159         /* 2 samples of 32 bits - right justified */
0160         val |= TDMOUT_CTRL1_TYPE(4);
0161         break;
0162     default:
0163         pr_err("Unsupported physical width: %u\n",
0164                ts->physical_width);
0165         return -EINVAL;
0166     }
0167 
0168     /* If the sample clock is inverted, invert it back for the formatter */
0169     if (axg_tdm_lrclk_invert(ts->iface->fmt))
0170         val |= TDMOUT_CTRL1_WS_INV;
0171 
0172     regmap_update_bits(map, TDMOUT_CTRL1,
0173                (TDMOUT_CTRL1_TYPE_MASK | TDMOUT_CTRL1_MSB_POS_MASK |
0174                 TDMOUT_CTRL1_WS_INV), val);
0175 
0176     /* Set static swap mask configuration */
0177     regmap_write(map, TDMOUT_SWAP, 0x76543210);
0178 
0179     return axg_tdm_formatter_set_channel_masks(map, ts, TDMOUT_MASK0);
0180 }
0181 
0182 static const struct snd_kcontrol_new axg_tdmout_controls[] = {
0183     SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0,  0,  8, 255, 0),
0184     SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),
0185     SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1,  0,  8, 255, 0),
0186     SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),
0187     SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,
0188            TDMOUT_CTRL1_GAIN_EN, 1, 0),
0189 };
0190 
0191 static const char * const axg_tdmout_sel_texts[] = {
0192     "IN 0", "IN 1", "IN 2",
0193 };
0194 
0195 static SOC_ENUM_SINGLE_DECL(axg_tdmout_sel_enum, TDMOUT_CTRL1,
0196                 TDMOUT_CTRL1_SEL_SHIFT, axg_tdmout_sel_texts);
0197 
0198 static const struct snd_kcontrol_new axg_tdmout_in_mux =
0199     SOC_DAPM_ENUM("Input Source", axg_tdmout_sel_enum);
0200 
0201 static const struct snd_soc_dapm_widget axg_tdmout_dapm_widgets[] = {
0202     SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
0203     SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
0204     SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
0205     SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmout_in_mux),
0206     SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
0207                axg_tdm_formatter_event,
0208                (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
0209     SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
0210 };
0211 
0212 static const struct snd_soc_dapm_route axg_tdmout_dapm_routes[] = {
0213     { "SRC SEL", "IN 0", "IN 0" },
0214     { "SRC SEL", "IN 1", "IN 1" },
0215     { "SRC SEL", "IN 2", "IN 2" },
0216     { "ENC", NULL, "SRC SEL" },
0217     { "OUT", NULL, "ENC" },
0218 };
0219 
0220 static const struct snd_soc_component_driver axg_tdmout_component_drv = {
0221     .controls       = axg_tdmout_controls,
0222     .num_controls       = ARRAY_SIZE(axg_tdmout_controls),
0223     .dapm_widgets       = axg_tdmout_dapm_widgets,
0224     .num_dapm_widgets   = ARRAY_SIZE(axg_tdmout_dapm_widgets),
0225     .dapm_routes        = axg_tdmout_dapm_routes,
0226     .num_dapm_routes    = ARRAY_SIZE(axg_tdmout_dapm_routes),
0227 };
0228 
0229 static const struct axg_tdm_formatter_ops axg_tdmout_ops = {
0230     .get_stream = axg_tdmout_get_tdm_stream,
0231     .prepare    = axg_tdmout_prepare,
0232     .enable     = axg_tdmout_enable,
0233     .disable    = axg_tdmout_disable,
0234 };
0235 
0236 static const struct axg_tdm_formatter_driver axg_tdmout_drv = {
0237     .component_drv  = &axg_tdmout_component_drv,
0238     .regmap_cfg = &axg_tdmout_regmap_cfg,
0239     .ops        = &axg_tdmout_ops,
0240     .quirks     = &(const struct axg_tdm_formatter_hw) {
0241         .skew_offset = 1,
0242     },
0243 };
0244 
0245 static const struct axg_tdm_formatter_driver g12a_tdmout_drv = {
0246     .component_drv  = &axg_tdmout_component_drv,
0247     .regmap_cfg = &axg_tdmout_regmap_cfg,
0248     .ops        = &axg_tdmout_ops,
0249     .quirks     = &(const struct axg_tdm_formatter_hw) {
0250         .skew_offset = 2,
0251     },
0252 };
0253 
0254 static const struct snd_kcontrol_new sm1_tdmout_controls[] = {
0255     SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0,  0,  8, 255, 0),
0256     SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),
0257     SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1,  0,  8, 255, 0),
0258     SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),
0259     SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,
0260            SM1_TDMOUT_CTRL1_GAIN_EN, 1, 0),
0261 };
0262 
0263 static const char * const sm1_tdmout_sel_texts[] = {
0264     "IN 0", "IN 1", "IN 2", "IN 3", "IN 4",
0265 };
0266 
0267 static SOC_ENUM_SINGLE_DECL(sm1_tdmout_sel_enum, TDMOUT_CTRL1,
0268                 TDMOUT_CTRL1_SEL_SHIFT, sm1_tdmout_sel_texts);
0269 
0270 static const struct snd_kcontrol_new sm1_tdmout_in_mux =
0271     SOC_DAPM_ENUM("Input Source", sm1_tdmout_sel_enum);
0272 
0273 static const struct snd_soc_dapm_widget sm1_tdmout_dapm_widgets[] = {
0274     SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
0275     SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
0276     SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
0277     SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
0278     SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
0279     SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &sm1_tdmout_in_mux),
0280     SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,
0281                axg_tdm_formatter_event,
0282                (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
0283     SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
0284 };
0285 
0286 static const struct snd_soc_dapm_route sm1_tdmout_dapm_routes[] = {
0287     { "SRC SEL", "IN 0", "IN 0" },
0288     { "SRC SEL", "IN 1", "IN 1" },
0289     { "SRC SEL", "IN 2", "IN 2" },
0290     { "SRC SEL", "IN 3", "IN 3" },
0291     { "SRC SEL", "IN 4", "IN 4" },
0292     { "ENC", NULL, "SRC SEL" },
0293     { "OUT", NULL, "ENC" },
0294 };
0295 
0296 static const struct snd_soc_component_driver sm1_tdmout_component_drv = {
0297     .controls       = sm1_tdmout_controls,
0298     .num_controls       = ARRAY_SIZE(sm1_tdmout_controls),
0299     .dapm_widgets       = sm1_tdmout_dapm_widgets,
0300     .num_dapm_widgets   = ARRAY_SIZE(sm1_tdmout_dapm_widgets),
0301     .dapm_routes        = sm1_tdmout_dapm_routes,
0302     .num_dapm_routes    = ARRAY_SIZE(sm1_tdmout_dapm_routes),
0303 };
0304 
0305 static const struct axg_tdm_formatter_driver sm1_tdmout_drv = {
0306     .component_drv  = &sm1_tdmout_component_drv,
0307     .regmap_cfg = &axg_tdmout_regmap_cfg,
0308     .ops        = &axg_tdmout_ops,
0309     .quirks     = &(const struct axg_tdm_formatter_hw) {
0310         .skew_offset = 2,
0311     },
0312 };
0313 
0314 static const struct of_device_id axg_tdmout_of_match[] = {
0315     {
0316         .compatible = "amlogic,axg-tdmout",
0317         .data = &axg_tdmout_drv,
0318     }, {
0319         .compatible = "amlogic,g12a-tdmout",
0320         .data = &g12a_tdmout_drv,
0321     }, {
0322         .compatible = "amlogic,sm1-tdmout",
0323         .data = &sm1_tdmout_drv,
0324     }, {}
0325 };
0326 MODULE_DEVICE_TABLE(of, axg_tdmout_of_match);
0327 
0328 static struct platform_driver axg_tdmout_pdrv = {
0329     .probe = axg_tdm_formatter_probe,
0330     .driver = {
0331         .name = "axg-tdmout",
0332         .of_match_table = axg_tdmout_of_match,
0333     },
0334 };
0335 module_platform_driver(axg_tdmout_pdrv);
0336 
0337 MODULE_DESCRIPTION("Amlogic AXG TDM output formatter driver");
0338 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
0339 MODULE_LICENSE("GPL v2");