0001
0002
0003
0004
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
0090 regmap_update_bits(map, TDMOUT_CTRL0,
0091 TDMOUT_CTRL0_RST_OUT | TDMOUT_CTRL0_RST_IN, 0);
0092
0093
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
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
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
0135 val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1);
0136
0137
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
0146 val = TDMOUT_CTRL1_MSB_POS(ts->width - 1);
0147
0148
0149 switch (ts->physical_width) {
0150 case 8:
0151
0152 val |= TDMOUT_CTRL1_TYPE(0);
0153 break;
0154 case 16:
0155
0156 val |= TDMOUT_CTRL1_TYPE(2);
0157 break;
0158 case 32:
0159
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
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
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");