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 TDMIN_CTRL 0x00
0015 #define TDMIN_CTRL_ENABLE BIT(31)
0016 #define TDMIN_CTRL_I2S_MODE BIT(30)
0017 #define TDMIN_CTRL_RST_OUT BIT(29)
0018 #define TDMIN_CTRL_RST_IN BIT(28)
0019 #define TDMIN_CTRL_WS_INV BIT(25)
0020 #define TDMIN_CTRL_SEL_SHIFT 20
0021 #define TDMIN_CTRL_IN_BIT_SKEW_MASK GENMASK(18, 16)
0022 #define TDMIN_CTRL_IN_BIT_SKEW(x) ((x) << 16)
0023 #define TDMIN_CTRL_LSB_FIRST BIT(5)
0024 #define TDMIN_CTRL_BITNUM_MASK GENMASK(4, 0)
0025 #define TDMIN_CTRL_BITNUM(x) ((x) << 0)
0026 #define TDMIN_SWAP 0x04
0027 #define TDMIN_MASK0 0x08
0028 #define TDMIN_MASK1 0x0c
0029 #define TDMIN_MASK2 0x10
0030 #define TDMIN_MASK3 0x14
0031 #define TDMIN_STAT 0x18
0032 #define TDMIN_MUTE_VAL 0x1c
0033 #define TDMIN_MUTE0 0x20
0034 #define TDMIN_MUTE1 0x24
0035 #define TDMIN_MUTE2 0x28
0036 #define TDMIN_MUTE3 0x2c
0037
0038 static const struct regmap_config axg_tdmin_regmap_cfg = {
0039 .reg_bits = 32,
0040 .val_bits = 32,
0041 .reg_stride = 4,
0042 .max_register = TDMIN_MUTE3,
0043 };
0044
0045 static const char * const axg_tdmin_sel_texts[] = {
0046 "IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5", "IN 6", "IN 7",
0047 "IN 8", "IN 9", "IN 10", "IN 11", "IN 12", "IN 13", "IN 14", "IN 15",
0048 };
0049
0050
0051 static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL,
0052 TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts);
0053
0054 static const struct snd_kcontrol_new axg_tdmin_in_mux =
0055 SOC_DAPM_ENUM("Input Source", axg_tdmin_sel_enum);
0056
0057 static struct snd_soc_dai *
0058 axg_tdmin_get_be(struct snd_soc_dapm_widget *w)
0059 {
0060 struct snd_soc_dapm_path *p;
0061 struct snd_soc_dai *be;
0062
0063 snd_soc_dapm_widget_for_each_source_path(w, p) {
0064 if (!p->connect)
0065 continue;
0066
0067 if (p->source->id == snd_soc_dapm_dai_out)
0068 return (struct snd_soc_dai *)p->source->priv;
0069
0070 be = axg_tdmin_get_be(p->source);
0071 if (be)
0072 return be;
0073 }
0074
0075 return NULL;
0076 }
0077
0078 static struct axg_tdm_stream *
0079 axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget *w)
0080 {
0081 struct snd_soc_dai *be = axg_tdmin_get_be(w);
0082
0083 if (!be)
0084 return NULL;
0085
0086 return be->capture_dma_data;
0087 }
0088
0089 static void axg_tdmin_enable(struct regmap *map)
0090 {
0091
0092 regmap_update_bits(map, TDMIN_CTRL,
0093 TDMIN_CTRL_RST_OUT | TDMIN_CTRL_RST_IN, 0);
0094
0095
0096 regmap_update_bits(map, TDMIN_CTRL,
0097 TDMIN_CTRL_RST_OUT, TDMIN_CTRL_RST_OUT);
0098 regmap_update_bits(map, TDMIN_CTRL,
0099 TDMIN_CTRL_RST_IN, TDMIN_CTRL_RST_IN);
0100
0101
0102 regmap_update_bits(map, TDMIN_CTRL,
0103 TDMIN_CTRL_ENABLE, TDMIN_CTRL_ENABLE);
0104 }
0105
0106 static void axg_tdmin_disable(struct regmap *map)
0107 {
0108 regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0);
0109 }
0110
0111 static int axg_tdmin_prepare(struct regmap *map,
0112 const struct axg_tdm_formatter_hw *quirks,
0113 struct axg_tdm_stream *ts)
0114 {
0115 unsigned int val, skew = quirks->skew_offset;
0116
0117
0118 switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
0119 case SND_SOC_DAIFMT_I2S:
0120 case SND_SOC_DAIFMT_DSP_A:
0121 skew += 1;
0122 break;
0123
0124 case SND_SOC_DAIFMT_LEFT_J:
0125 case SND_SOC_DAIFMT_DSP_B:
0126 break;
0127
0128 default:
0129 pr_err("Unsupported format: %u\n",
0130 ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
0131 return -EINVAL;
0132 }
0133
0134 val = TDMIN_CTRL_IN_BIT_SKEW(skew);
0135
0136
0137 switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
0138 case SND_SOC_DAIFMT_I2S:
0139 case SND_SOC_DAIFMT_LEFT_J:
0140 case SND_SOC_DAIFMT_RIGHT_J:
0141 val |= TDMIN_CTRL_I2S_MODE;
0142 break;
0143 }
0144
0145
0146 if (axg_tdm_lrclk_invert(ts->iface->fmt))
0147 val |= TDMIN_CTRL_WS_INV;
0148
0149
0150 val |= TDMIN_CTRL_BITNUM(ts->iface->slot_width - 1);
0151
0152
0153
0154
0155
0156 regmap_update_bits(map, TDMIN_CTRL,
0157 (TDMIN_CTRL_IN_BIT_SKEW_MASK | TDMIN_CTRL_WS_INV |
0158 TDMIN_CTRL_I2S_MODE | TDMIN_CTRL_LSB_FIRST |
0159 TDMIN_CTRL_BITNUM_MASK), val);
0160
0161
0162 regmap_write(map, TDMIN_SWAP, 0x76543210);
0163
0164 return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0);
0165 }
0166
0167 static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = {
0168 SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
0169 SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
0170 SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
0171 SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
0172 SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
0173 SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
0174 SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
0175 SND_SOC_DAPM_AIF_IN("IN 7", NULL, 0, SND_SOC_NOPM, 0, 0),
0176 SND_SOC_DAPM_AIF_IN("IN 8", NULL, 0, SND_SOC_NOPM, 0, 0),
0177 SND_SOC_DAPM_AIF_IN("IN 9", NULL, 0, SND_SOC_NOPM, 0, 0),
0178 SND_SOC_DAPM_AIF_IN("IN 10", NULL, 0, SND_SOC_NOPM, 0, 0),
0179 SND_SOC_DAPM_AIF_IN("IN 11", NULL, 0, SND_SOC_NOPM, 0, 0),
0180 SND_SOC_DAPM_AIF_IN("IN 12", NULL, 0, SND_SOC_NOPM, 0, 0),
0181 SND_SOC_DAPM_AIF_IN("IN 13", NULL, 0, SND_SOC_NOPM, 0, 0),
0182 SND_SOC_DAPM_AIF_IN("IN 14", NULL, 0, SND_SOC_NOPM, 0, 0),
0183 SND_SOC_DAPM_AIF_IN("IN 15", NULL, 0, SND_SOC_NOPM, 0, 0),
0184 SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmin_in_mux),
0185 SND_SOC_DAPM_PGA_E("DEC", SND_SOC_NOPM, 0, 0, NULL, 0,
0186 axg_tdm_formatter_event,
0187 (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
0188 SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
0189 };
0190
0191 static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = {
0192 { "SRC SEL", "IN 0", "IN 0" },
0193 { "SRC SEL", "IN 1", "IN 1" },
0194 { "SRC SEL", "IN 2", "IN 2" },
0195 { "SRC SEL", "IN 3", "IN 3" },
0196 { "SRC SEL", "IN 4", "IN 4" },
0197 { "SRC SEL", "IN 5", "IN 5" },
0198 { "SRC SEL", "IN 6", "IN 6" },
0199 { "SRC SEL", "IN 7", "IN 7" },
0200 { "SRC SEL", "IN 8", "IN 8" },
0201 { "SRC SEL", "IN 9", "IN 9" },
0202 { "SRC SEL", "IN 10", "IN 10" },
0203 { "SRC SEL", "IN 11", "IN 11" },
0204 { "SRC SEL", "IN 12", "IN 12" },
0205 { "SRC SEL", "IN 13", "IN 13" },
0206 { "SRC SEL", "IN 14", "IN 14" },
0207 { "SRC SEL", "IN 15", "IN 15" },
0208 { "DEC", NULL, "SRC SEL" },
0209 { "OUT", NULL, "DEC" },
0210 };
0211
0212 static const struct snd_soc_component_driver axg_tdmin_component_drv = {
0213 .dapm_widgets = axg_tdmin_dapm_widgets,
0214 .num_dapm_widgets = ARRAY_SIZE(axg_tdmin_dapm_widgets),
0215 .dapm_routes = axg_tdmin_dapm_routes,
0216 .num_dapm_routes = ARRAY_SIZE(axg_tdmin_dapm_routes),
0217 };
0218
0219 static const struct axg_tdm_formatter_ops axg_tdmin_ops = {
0220 .get_stream = axg_tdmin_get_tdm_stream,
0221 .prepare = axg_tdmin_prepare,
0222 .enable = axg_tdmin_enable,
0223 .disable = axg_tdmin_disable,
0224 };
0225
0226 static const struct axg_tdm_formatter_driver axg_tdmin_drv = {
0227 .component_drv = &axg_tdmin_component_drv,
0228 .regmap_cfg = &axg_tdmin_regmap_cfg,
0229 .ops = &axg_tdmin_ops,
0230 .quirks = &(const struct axg_tdm_formatter_hw) {
0231 .skew_offset = 3,
0232 },
0233 };
0234
0235 static const struct of_device_id axg_tdmin_of_match[] = {
0236 {
0237 .compatible = "amlogic,axg-tdmin",
0238 .data = &axg_tdmin_drv,
0239 }, {
0240 .compatible = "amlogic,g12a-tdmin",
0241 .data = &axg_tdmin_drv,
0242 }, {
0243 .compatible = "amlogic,sm1-tdmin",
0244 .data = &axg_tdmin_drv,
0245 }, {}
0246 };
0247 MODULE_DEVICE_TABLE(of, axg_tdmin_of_match);
0248
0249 static struct platform_driver axg_tdmin_pdrv = {
0250 .probe = axg_tdm_formatter_probe,
0251 .driver = {
0252 .name = "axg-tdmin",
0253 .of_match_table = axg_tdmin_of_match,
0254 },
0255 };
0256 module_platform_driver(axg_tdmin_pdrv);
0257
0258 MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver");
0259 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
0260 MODULE_LICENSE("GPL v2");