113a22e6aSJerome Brunet // SPDX-License-Identifier: (GPL-2.0 OR MIT)
213a22e6aSJerome Brunet //
313a22e6aSJerome Brunet // Copyright (c) 2018 BayLibre, SAS.
413a22e6aSJerome Brunet // Author: Jerome Brunet <jbrunet@baylibre.com>
513a22e6aSJerome Brunet
613a22e6aSJerome Brunet #include <linux/module.h>
713a22e6aSJerome Brunet #include <linux/of_platform.h>
813a22e6aSJerome Brunet #include <linux/regmap.h>
913a22e6aSJerome Brunet #include <sound/soc.h>
1013a22e6aSJerome Brunet #include <sound/soc-dai.h>
1113a22e6aSJerome Brunet
1213a22e6aSJerome Brunet #include "axg-tdm-formatter.h"
1313a22e6aSJerome Brunet
1413a22e6aSJerome Brunet #define TDMIN_CTRL 0x00
1513a22e6aSJerome Brunet #define TDMIN_CTRL_ENABLE BIT(31)
1613a22e6aSJerome Brunet #define TDMIN_CTRL_I2S_MODE BIT(30)
1713a22e6aSJerome Brunet #define TDMIN_CTRL_RST_OUT BIT(29)
1813a22e6aSJerome Brunet #define TDMIN_CTRL_RST_IN BIT(28)
1913a22e6aSJerome Brunet #define TDMIN_CTRL_WS_INV BIT(25)
2013a22e6aSJerome Brunet #define TDMIN_CTRL_SEL_SHIFT 20
2113a22e6aSJerome Brunet #define TDMIN_CTRL_IN_BIT_SKEW_MASK GENMASK(18, 16)
2213a22e6aSJerome Brunet #define TDMIN_CTRL_IN_BIT_SKEW(x) ((x) << 16)
2313a22e6aSJerome Brunet #define TDMIN_CTRL_LSB_FIRST BIT(5)
2413a22e6aSJerome Brunet #define TDMIN_CTRL_BITNUM_MASK GENMASK(4, 0)
2513a22e6aSJerome Brunet #define TDMIN_CTRL_BITNUM(x) ((x) << 0)
2613a22e6aSJerome Brunet #define TDMIN_SWAP 0x04
2713a22e6aSJerome Brunet #define TDMIN_MASK0 0x08
2813a22e6aSJerome Brunet #define TDMIN_MASK1 0x0c
2913a22e6aSJerome Brunet #define TDMIN_MASK2 0x10
3013a22e6aSJerome Brunet #define TDMIN_MASK3 0x14
3113a22e6aSJerome Brunet #define TDMIN_STAT 0x18
3213a22e6aSJerome Brunet #define TDMIN_MUTE_VAL 0x1c
3313a22e6aSJerome Brunet #define TDMIN_MUTE0 0x20
3413a22e6aSJerome Brunet #define TDMIN_MUTE1 0x24
3513a22e6aSJerome Brunet #define TDMIN_MUTE2 0x28
3613a22e6aSJerome Brunet #define TDMIN_MUTE3 0x2c
3713a22e6aSJerome Brunet
3813a22e6aSJerome Brunet static const struct regmap_config axg_tdmin_regmap_cfg = {
3913a22e6aSJerome Brunet .reg_bits = 32,
4013a22e6aSJerome Brunet .val_bits = 32,
4113a22e6aSJerome Brunet .reg_stride = 4,
4213a22e6aSJerome Brunet .max_register = TDMIN_MUTE3,
4313a22e6aSJerome Brunet };
4413a22e6aSJerome Brunet
4513a22e6aSJerome Brunet static const char * const axg_tdmin_sel_texts[] = {
46cde9f12eSJerome Brunet "IN 0", "IN 1", "IN 2", "IN 3", "IN 4", "IN 5", "IN 6", "IN 7",
47cde9f12eSJerome Brunet "IN 8", "IN 9", "IN 10", "IN 11", "IN 12", "IN 13", "IN 14", "IN 15",
4813a22e6aSJerome Brunet };
4913a22e6aSJerome Brunet
5013a22e6aSJerome Brunet /* Change to special mux control to reset dapm */
5113a22e6aSJerome Brunet static SOC_ENUM_SINGLE_DECL(axg_tdmin_sel_enum, TDMIN_CTRL,
5213a22e6aSJerome Brunet TDMIN_CTRL_SEL_SHIFT, axg_tdmin_sel_texts);
5313a22e6aSJerome Brunet
5413a22e6aSJerome Brunet static const struct snd_kcontrol_new axg_tdmin_in_mux =
5513a22e6aSJerome Brunet SOC_DAPM_ENUM("Input Source", axg_tdmin_sel_enum);
5613a22e6aSJerome Brunet
5713a22e6aSJerome Brunet static struct snd_soc_dai *
axg_tdmin_get_be(struct snd_soc_dapm_widget * w)5813a22e6aSJerome Brunet axg_tdmin_get_be(struct snd_soc_dapm_widget *w)
5913a22e6aSJerome Brunet {
6084d0493fSPierre-Louis Bossart struct snd_soc_dapm_path *p;
6113a22e6aSJerome Brunet struct snd_soc_dai *be;
6213a22e6aSJerome Brunet
6313a22e6aSJerome Brunet snd_soc_dapm_widget_for_each_source_path(w, p) {
6413a22e6aSJerome Brunet if (!p->connect)
6513a22e6aSJerome Brunet continue;
6613a22e6aSJerome Brunet
6713a22e6aSJerome Brunet if (p->source->id == snd_soc_dapm_dai_out)
6813a22e6aSJerome Brunet return (struct snd_soc_dai *)p->source->priv;
6913a22e6aSJerome Brunet
7013a22e6aSJerome Brunet be = axg_tdmin_get_be(p->source);
7113a22e6aSJerome Brunet if (be)
7213a22e6aSJerome Brunet return be;
7313a22e6aSJerome Brunet }
7413a22e6aSJerome Brunet
7513a22e6aSJerome Brunet return NULL;
7613a22e6aSJerome Brunet }
7713a22e6aSJerome Brunet
7813a22e6aSJerome Brunet static struct axg_tdm_stream *
axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget * w)7913a22e6aSJerome Brunet axg_tdmin_get_tdm_stream(struct snd_soc_dapm_widget *w)
8013a22e6aSJerome Brunet {
8113a22e6aSJerome Brunet struct snd_soc_dai *be = axg_tdmin_get_be(w);
8213a22e6aSJerome Brunet
8313a22e6aSJerome Brunet if (!be)
8413a22e6aSJerome Brunet return NULL;
8513a22e6aSJerome Brunet
86*c765cedaSKuninori Morimoto return snd_soc_dai_dma_data_get_capture(be);
8713a22e6aSJerome Brunet }
8813a22e6aSJerome Brunet
axg_tdmin_enable(struct regmap * map)8913a22e6aSJerome Brunet static void axg_tdmin_enable(struct regmap *map)
9013a22e6aSJerome Brunet {
9113a22e6aSJerome Brunet /* Apply both reset */
9213a22e6aSJerome Brunet regmap_update_bits(map, TDMIN_CTRL,
9313a22e6aSJerome Brunet TDMIN_CTRL_RST_OUT | TDMIN_CTRL_RST_IN, 0);
9413a22e6aSJerome Brunet
9513a22e6aSJerome Brunet /* Clear out reset before in reset */
9613a22e6aSJerome Brunet regmap_update_bits(map, TDMIN_CTRL,
9713a22e6aSJerome Brunet TDMIN_CTRL_RST_OUT, TDMIN_CTRL_RST_OUT);
9813a22e6aSJerome Brunet regmap_update_bits(map, TDMIN_CTRL,
9913a22e6aSJerome Brunet TDMIN_CTRL_RST_IN, TDMIN_CTRL_RST_IN);
10013a22e6aSJerome Brunet
10113a22e6aSJerome Brunet /* Actually enable tdmin */
10213a22e6aSJerome Brunet regmap_update_bits(map, TDMIN_CTRL,
10313a22e6aSJerome Brunet TDMIN_CTRL_ENABLE, TDMIN_CTRL_ENABLE);
10413a22e6aSJerome Brunet }
10513a22e6aSJerome Brunet
axg_tdmin_disable(struct regmap * map)10613a22e6aSJerome Brunet static void axg_tdmin_disable(struct regmap *map)
10713a22e6aSJerome Brunet {
10813a22e6aSJerome Brunet regmap_update_bits(map, TDMIN_CTRL, TDMIN_CTRL_ENABLE, 0);
10913a22e6aSJerome Brunet }
11013a22e6aSJerome Brunet
axg_tdmin_prepare(struct regmap * map,const struct axg_tdm_formatter_hw * quirks,struct axg_tdm_stream * ts)111f01bc67fSJerome Brunet static int axg_tdmin_prepare(struct regmap *map,
112f01bc67fSJerome Brunet const struct axg_tdm_formatter_hw *quirks,
113f01bc67fSJerome Brunet struct axg_tdm_stream *ts)
11413a22e6aSJerome Brunet {
115f01bc67fSJerome Brunet unsigned int val, skew = quirks->skew_offset;
11613a22e6aSJerome Brunet
11713a22e6aSJerome Brunet /* Set stream skew */
11813a22e6aSJerome Brunet switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
11913a22e6aSJerome Brunet case SND_SOC_DAIFMT_I2S:
12013a22e6aSJerome Brunet case SND_SOC_DAIFMT_DSP_A:
121f01bc67fSJerome Brunet skew += 1;
12213a22e6aSJerome Brunet break;
12313a22e6aSJerome Brunet
12413a22e6aSJerome Brunet case SND_SOC_DAIFMT_LEFT_J:
12513a22e6aSJerome Brunet case SND_SOC_DAIFMT_DSP_B:
12613a22e6aSJerome Brunet break;
12713a22e6aSJerome Brunet
12813a22e6aSJerome Brunet default:
12913a22e6aSJerome Brunet pr_err("Unsupported format: %u\n",
13013a22e6aSJerome Brunet ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);
13113a22e6aSJerome Brunet return -EINVAL;
13213a22e6aSJerome Brunet }
13313a22e6aSJerome Brunet
134f01bc67fSJerome Brunet val = TDMIN_CTRL_IN_BIT_SKEW(skew);
135f01bc67fSJerome Brunet
13613a22e6aSJerome Brunet /* Set stream format mode */
13713a22e6aSJerome Brunet switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
13813a22e6aSJerome Brunet case SND_SOC_DAIFMT_I2S:
13913a22e6aSJerome Brunet case SND_SOC_DAIFMT_LEFT_J:
14013a22e6aSJerome Brunet case SND_SOC_DAIFMT_RIGHT_J:
14113a22e6aSJerome Brunet val |= TDMIN_CTRL_I2S_MODE;
14213a22e6aSJerome Brunet break;
14313a22e6aSJerome Brunet }
14413a22e6aSJerome Brunet
14513a22e6aSJerome Brunet /* If the sample clock is inverted, invert it back for the formatter */
14613a22e6aSJerome Brunet if (axg_tdm_lrclk_invert(ts->iface->fmt))
14713a22e6aSJerome Brunet val |= TDMIN_CTRL_WS_INV;
14813a22e6aSJerome Brunet
14913a22e6aSJerome Brunet /* Set the slot width */
15013a22e6aSJerome Brunet val |= TDMIN_CTRL_BITNUM(ts->iface->slot_width - 1);
15113a22e6aSJerome Brunet
15213a22e6aSJerome Brunet /*
15313a22e6aSJerome Brunet * The following also reset LSB_FIRST which result in the formatter
15413a22e6aSJerome Brunet * placing the first bit received at bit 31
15513a22e6aSJerome Brunet */
15613a22e6aSJerome Brunet regmap_update_bits(map, TDMIN_CTRL,
15713a22e6aSJerome Brunet (TDMIN_CTRL_IN_BIT_SKEW_MASK | TDMIN_CTRL_WS_INV |
15813a22e6aSJerome Brunet TDMIN_CTRL_I2S_MODE | TDMIN_CTRL_LSB_FIRST |
15913a22e6aSJerome Brunet TDMIN_CTRL_BITNUM_MASK), val);
16013a22e6aSJerome Brunet
16113a22e6aSJerome Brunet /* Set static swap mask configuration */
16213a22e6aSJerome Brunet regmap_write(map, TDMIN_SWAP, 0x76543210);
16313a22e6aSJerome Brunet
16413a22e6aSJerome Brunet return axg_tdm_formatter_set_channel_masks(map, ts, TDMIN_MASK0);
16513a22e6aSJerome Brunet }
16613a22e6aSJerome Brunet
16713a22e6aSJerome Brunet static const struct snd_soc_dapm_widget axg_tdmin_dapm_widgets[] = {
16813a22e6aSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),
16913a22e6aSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),
17013a22e6aSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),
17113a22e6aSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),
17213a22e6aSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),
17313a22e6aSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 5", NULL, 0, SND_SOC_NOPM, 0, 0),
174cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 6", NULL, 0, SND_SOC_NOPM, 0, 0),
175cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 7", NULL, 0, SND_SOC_NOPM, 0, 0),
176cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 8", NULL, 0, SND_SOC_NOPM, 0, 0),
177cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 9", NULL, 0, SND_SOC_NOPM, 0, 0),
178cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 10", NULL, 0, SND_SOC_NOPM, 0, 0),
179cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 11", NULL, 0, SND_SOC_NOPM, 0, 0),
180cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 12", NULL, 0, SND_SOC_NOPM, 0, 0),
181cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 13", NULL, 0, SND_SOC_NOPM, 0, 0),
182cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 14", NULL, 0, SND_SOC_NOPM, 0, 0),
183cde9f12eSJerome Brunet SND_SOC_DAPM_AIF_IN("IN 15", NULL, 0, SND_SOC_NOPM, 0, 0),
18413a22e6aSJerome Brunet SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmin_in_mux),
18513a22e6aSJerome Brunet SND_SOC_DAPM_PGA_E("DEC", SND_SOC_NOPM, 0, 0, NULL, 0,
18613a22e6aSJerome Brunet axg_tdm_formatter_event,
18713a22e6aSJerome Brunet (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),
18813a22e6aSJerome Brunet SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),
18913a22e6aSJerome Brunet };
19013a22e6aSJerome Brunet
19113a22e6aSJerome Brunet static const struct snd_soc_dapm_route axg_tdmin_dapm_routes[] = {
19213a22e6aSJerome Brunet { "SRC SEL", "IN 0", "IN 0" },
19313a22e6aSJerome Brunet { "SRC SEL", "IN 1", "IN 1" },
19413a22e6aSJerome Brunet { "SRC SEL", "IN 2", "IN 2" },
19513a22e6aSJerome Brunet { "SRC SEL", "IN 3", "IN 3" },
19613a22e6aSJerome Brunet { "SRC SEL", "IN 4", "IN 4" },
19713a22e6aSJerome Brunet { "SRC SEL", "IN 5", "IN 5" },
198cde9f12eSJerome Brunet { "SRC SEL", "IN 6", "IN 6" },
199cde9f12eSJerome Brunet { "SRC SEL", "IN 7", "IN 7" },
200cde9f12eSJerome Brunet { "SRC SEL", "IN 8", "IN 8" },
201cde9f12eSJerome Brunet { "SRC SEL", "IN 9", "IN 9" },
202cde9f12eSJerome Brunet { "SRC SEL", "IN 10", "IN 10" },
203cde9f12eSJerome Brunet { "SRC SEL", "IN 11", "IN 11" },
204cde9f12eSJerome Brunet { "SRC SEL", "IN 12", "IN 12" },
205cde9f12eSJerome Brunet { "SRC SEL", "IN 13", "IN 13" },
206cde9f12eSJerome Brunet { "SRC SEL", "IN 14", "IN 14" },
207cde9f12eSJerome Brunet { "SRC SEL", "IN 15", "IN 15" },
20813a22e6aSJerome Brunet { "DEC", NULL, "SRC SEL" },
20913a22e6aSJerome Brunet { "OUT", NULL, "DEC" },
21013a22e6aSJerome Brunet };
21113a22e6aSJerome Brunet
21213a22e6aSJerome Brunet static const struct snd_soc_component_driver axg_tdmin_component_drv = {
21313a22e6aSJerome Brunet .dapm_widgets = axg_tdmin_dapm_widgets,
21413a22e6aSJerome Brunet .num_dapm_widgets = ARRAY_SIZE(axg_tdmin_dapm_widgets),
21513a22e6aSJerome Brunet .dapm_routes = axg_tdmin_dapm_routes,
21613a22e6aSJerome Brunet .num_dapm_routes = ARRAY_SIZE(axg_tdmin_dapm_routes),
21713a22e6aSJerome Brunet };
21813a22e6aSJerome Brunet
21913a22e6aSJerome Brunet static const struct axg_tdm_formatter_ops axg_tdmin_ops = {
22013a22e6aSJerome Brunet .get_stream = axg_tdmin_get_tdm_stream,
22113a22e6aSJerome Brunet .prepare = axg_tdmin_prepare,
22213a22e6aSJerome Brunet .enable = axg_tdmin_enable,
22313a22e6aSJerome Brunet .disable = axg_tdmin_disable,
22413a22e6aSJerome Brunet };
22513a22e6aSJerome Brunet
22613a22e6aSJerome Brunet static const struct axg_tdm_formatter_driver axg_tdmin_drv = {
22713a22e6aSJerome Brunet .component_drv = &axg_tdmin_component_drv,
22813a22e6aSJerome Brunet .regmap_cfg = &axg_tdmin_regmap_cfg,
22913a22e6aSJerome Brunet .ops = &axg_tdmin_ops,
230f01bc67fSJerome Brunet .quirks = &(const struct axg_tdm_formatter_hw) {
23180a25439SJerome Brunet .skew_offset = 3,
23280a25439SJerome Brunet },
23380a25439SJerome Brunet };
23480a25439SJerome Brunet
23513a22e6aSJerome Brunet static const struct of_device_id axg_tdmin_of_match[] = {
23613a22e6aSJerome Brunet {
23713a22e6aSJerome Brunet .compatible = "amlogic,axg-tdmin",
23813a22e6aSJerome Brunet .data = &axg_tdmin_drv,
23980a25439SJerome Brunet }, {
24080a25439SJerome Brunet .compatible = "amlogic,g12a-tdmin",
241a84dfb3dSJerome Brunet .data = &axg_tdmin_drv,
24280a25439SJerome Brunet }, {
24380a25439SJerome Brunet .compatible = "amlogic,sm1-tdmin",
244a84dfb3dSJerome Brunet .data = &axg_tdmin_drv,
24513a22e6aSJerome Brunet }, {}
24613a22e6aSJerome Brunet };
24713a22e6aSJerome Brunet MODULE_DEVICE_TABLE(of, axg_tdmin_of_match);
24813a22e6aSJerome Brunet
24913a22e6aSJerome Brunet static struct platform_driver axg_tdmin_pdrv = {
25013a22e6aSJerome Brunet .probe = axg_tdm_formatter_probe,
25113a22e6aSJerome Brunet .driver = {
25213a22e6aSJerome Brunet .name = "axg-tdmin",
25313a22e6aSJerome Brunet .of_match_table = axg_tdmin_of_match,
25413a22e6aSJerome Brunet },
25513a22e6aSJerome Brunet };
25613a22e6aSJerome Brunet module_platform_driver(axg_tdmin_pdrv);
25713a22e6aSJerome Brunet
25813a22e6aSJerome Brunet MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver");
25913a22e6aSJerome Brunet MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
26013a22e6aSJerome Brunet MODULE_LICENSE("GPL v2");
261