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 * 5813a22e6aSJerome Brunet axg_tdmin_get_be(struct snd_soc_dapm_widget *w) 5913a22e6aSJerome Brunet { 6013a22e6aSJerome Brunet struct snd_soc_dapm_path *p = NULL; 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 * 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 8613a22e6aSJerome Brunet return be->capture_dma_data; 8713a22e6aSJerome Brunet } 8813a22e6aSJerome Brunet 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 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 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) { 23113a22e6aSJerome Brunet .invert_sclk = false, 232f01bc67fSJerome Brunet .skew_offset = 2, 233f01bc67fSJerome Brunet }, 23413a22e6aSJerome Brunet }; 23513a22e6aSJerome Brunet 23613a22e6aSJerome Brunet static const struct of_device_id axg_tdmin_of_match[] = { 23713a22e6aSJerome Brunet { 23813a22e6aSJerome Brunet .compatible = "amlogic,axg-tdmin", 23913a22e6aSJerome Brunet .data = &axg_tdmin_drv, 24013a22e6aSJerome Brunet }, {} 24113a22e6aSJerome Brunet }; 24213a22e6aSJerome Brunet MODULE_DEVICE_TABLE(of, axg_tdmin_of_match); 24313a22e6aSJerome Brunet 24413a22e6aSJerome Brunet static struct platform_driver axg_tdmin_pdrv = { 24513a22e6aSJerome Brunet .probe = axg_tdm_formatter_probe, 24613a22e6aSJerome Brunet .driver = { 24713a22e6aSJerome Brunet .name = "axg-tdmin", 24813a22e6aSJerome Brunet .of_match_table = axg_tdmin_of_match, 24913a22e6aSJerome Brunet }, 25013a22e6aSJerome Brunet }; 25113a22e6aSJerome Brunet module_platform_driver(axg_tdmin_pdrv); 25213a22e6aSJerome Brunet 25313a22e6aSJerome Brunet MODULE_DESCRIPTION("Amlogic AXG TDM input formatter driver"); 25413a22e6aSJerome Brunet MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 25513a22e6aSJerome Brunet MODULE_LICENSE("GPL v2"); 256