340 lines · c
1// SPDX-License-Identifier: (GPL-2.0 OR MIT)2//3// Copyright (c) 2018 BayLibre, SAS.4// Author: Jerome Brunet <jbrunet@baylibre.com>5 6#include <linux/module.h>7#include <linux/of_platform.h>8#include <linux/regmap.h>9#include <sound/soc.h>10#include <sound/soc-dai.h>11 12#include "axg-tdm-formatter.h"13 14#define TDMOUT_CTRL0 0x0015#define TDMOUT_CTRL0_BITNUM_MASK GENMASK(4, 0)16#define TDMOUT_CTRL0_BITNUM(x) ((x) << 0)17#define TDMOUT_CTRL0_SLOTNUM_MASK GENMASK(9, 5)18#define TDMOUT_CTRL0_SLOTNUM(x) ((x) << 5)19#define TDMOUT_CTRL0_INIT_BITNUM_MASK GENMASK(19, 15)20#define TDMOUT_CTRL0_INIT_BITNUM(x) ((x) << 15)21#define TDMOUT_CTRL0_ENABLE BIT(31)22#define TDMOUT_CTRL0_RST_OUT BIT(29)23#define TDMOUT_CTRL0_RST_IN BIT(28)24#define TDMOUT_CTRL1 0x0425#define TDMOUT_CTRL1_TYPE_MASK GENMASK(6, 4)26#define TDMOUT_CTRL1_TYPE(x) ((x) << 4)27#define SM1_TDMOUT_CTRL1_GAIN_EN 728#define TDMOUT_CTRL1_MSB_POS_MASK GENMASK(12, 8)29#define TDMOUT_CTRL1_MSB_POS(x) ((x) << 8)30#define TDMOUT_CTRL1_SEL_SHIFT 2431#define TDMOUT_CTRL1_GAIN_EN 2632#define TDMOUT_CTRL1_WS_INV BIT(28)33#define TDMOUT_SWAP 0x0834#define TDMOUT_MASK0 0x0c35#define TDMOUT_MASK1 0x1036#define TDMOUT_MASK2 0x1437#define TDMOUT_MASK3 0x1838#define TDMOUT_STAT 0x1c39#define TDMOUT_GAIN0 0x2040#define TDMOUT_GAIN1 0x2441#define TDMOUT_MUTE_VAL 0x2842#define TDMOUT_MUTE0 0x2c43#define TDMOUT_MUTE1 0x3044#define TDMOUT_MUTE2 0x3445#define TDMOUT_MUTE3 0x3846#define TDMOUT_MASK_VAL 0x3c47 48static const struct regmap_config axg_tdmout_regmap_cfg = {49 .reg_bits = 32,50 .val_bits = 32,51 .reg_stride = 4,52 .max_register = TDMOUT_MASK_VAL,53};54 55static struct snd_soc_dai *56axg_tdmout_get_be(struct snd_soc_dapm_widget *w)57{58 struct snd_soc_dapm_path *p;59 struct snd_soc_dai *be;60 61 snd_soc_dapm_widget_for_each_sink_path(w, p) {62 if (!p->connect)63 continue;64 65 if (p->sink->id == snd_soc_dapm_dai_in)66 return (struct snd_soc_dai *)p->sink->priv;67 68 be = axg_tdmout_get_be(p->sink);69 if (be)70 return be;71 }72 73 return NULL;74}75 76static struct axg_tdm_stream *77axg_tdmout_get_tdm_stream(struct snd_soc_dapm_widget *w)78{79 struct snd_soc_dai *be = axg_tdmout_get_be(w);80 81 if (!be)82 return NULL;83 84 return snd_soc_dai_dma_data_get_playback(be);85}86 87static void axg_tdmout_enable(struct regmap *map)88{89 /* Apply both reset */90 regmap_update_bits(map, TDMOUT_CTRL0,91 TDMOUT_CTRL0_RST_OUT | TDMOUT_CTRL0_RST_IN, 0);92 93 /* Clear out reset before in reset */94 regmap_update_bits(map, TDMOUT_CTRL0,95 TDMOUT_CTRL0_RST_OUT, TDMOUT_CTRL0_RST_OUT);96 regmap_update_bits(map, TDMOUT_CTRL0,97 TDMOUT_CTRL0_RST_IN, TDMOUT_CTRL0_RST_IN);98 99 /* Actually enable tdmout */100 regmap_update_bits(map, TDMOUT_CTRL0,101 TDMOUT_CTRL0_ENABLE, TDMOUT_CTRL0_ENABLE);102}103 104static void axg_tdmout_disable(struct regmap *map)105{106 regmap_update_bits(map, TDMOUT_CTRL0, TDMOUT_CTRL0_ENABLE, 0);107}108 109static int axg_tdmout_prepare(struct regmap *map,110 const struct axg_tdm_formatter_hw *quirks,111 struct axg_tdm_stream *ts)112{113 unsigned int val, skew = quirks->skew_offset;114 115 /* Set the stream skew */116 switch (ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {117 case SND_SOC_DAIFMT_I2S:118 case SND_SOC_DAIFMT_DSP_A:119 break;120 121 case SND_SOC_DAIFMT_LEFT_J:122 case SND_SOC_DAIFMT_DSP_B:123 skew += 1;124 break;125 126 default:127 pr_err("Unsupported format: %u\n",128 ts->iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK);129 return -EINVAL;130 }131 132 val = TDMOUT_CTRL0_INIT_BITNUM(skew);133 134 /* Set the slot width */135 val |= TDMOUT_CTRL0_BITNUM(ts->iface->slot_width - 1);136 137 /* Set the slot number */138 val |= TDMOUT_CTRL0_SLOTNUM(ts->iface->slots - 1);139 140 regmap_update_bits(map, TDMOUT_CTRL0,141 TDMOUT_CTRL0_INIT_BITNUM_MASK |142 TDMOUT_CTRL0_BITNUM_MASK |143 TDMOUT_CTRL0_SLOTNUM_MASK, val);144 145 /* Set the sample width */146 val = TDMOUT_CTRL1_MSB_POS(ts->width - 1);147 148 /* FIFO data are arranged in chunks of 64bits */149 switch (ts->physical_width) {150 case 8:151 /* 8 samples of 8 bits */152 val |= TDMOUT_CTRL1_TYPE(0);153 break;154 case 16:155 /* 4 samples of 16 bits - right justified */156 val |= TDMOUT_CTRL1_TYPE(2);157 break;158 case 32:159 /* 2 samples of 32 bits - right justified */160 val |= TDMOUT_CTRL1_TYPE(4);161 break;162 default:163 pr_err("Unsupported physical width: %u\n",164 ts->physical_width);165 return -EINVAL;166 }167 168 /* If the sample clock is inverted, invert it back for the formatter */169 if (axg_tdm_lrclk_invert(ts->iface->fmt))170 val |= TDMOUT_CTRL1_WS_INV;171 172 regmap_update_bits(map, TDMOUT_CTRL1,173 (TDMOUT_CTRL1_TYPE_MASK | TDMOUT_CTRL1_MSB_POS_MASK |174 TDMOUT_CTRL1_WS_INV), val);175 176 /* Set static swap mask configuration */177 regmap_write(map, TDMOUT_SWAP, 0x76543210);178 179 return axg_tdm_formatter_set_channel_masks(map, ts, TDMOUT_MASK0);180}181 182static const struct snd_kcontrol_new axg_tdmout_controls[] = {183 SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0, 0, 8, 255, 0),184 SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),185 SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1, 0, 8, 255, 0),186 SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),187 SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,188 TDMOUT_CTRL1_GAIN_EN, 1, 0),189};190 191static const char * const axg_tdmout_sel_texts[] = {192 "IN 0", "IN 1", "IN 2",193};194 195static SOC_ENUM_SINGLE_DECL(axg_tdmout_sel_enum, TDMOUT_CTRL1,196 TDMOUT_CTRL1_SEL_SHIFT, axg_tdmout_sel_texts);197 198static const struct snd_kcontrol_new axg_tdmout_in_mux =199 SOC_DAPM_ENUM("Input Source", axg_tdmout_sel_enum);200 201static const struct snd_soc_dapm_widget axg_tdmout_dapm_widgets[] = {202 SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),203 SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),204 SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),205 SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_tdmout_in_mux),206 SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,207 axg_tdm_formatter_event,208 (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),209 SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),210};211 212static const struct snd_soc_dapm_route axg_tdmout_dapm_routes[] = {213 { "SRC SEL", "IN 0", "IN 0" },214 { "SRC SEL", "IN 1", "IN 1" },215 { "SRC SEL", "IN 2", "IN 2" },216 { "ENC", NULL, "SRC SEL" },217 { "OUT", NULL, "ENC" },218};219 220static const struct snd_soc_component_driver axg_tdmout_component_drv = {221 .controls = axg_tdmout_controls,222 .num_controls = ARRAY_SIZE(axg_tdmout_controls),223 .dapm_widgets = axg_tdmout_dapm_widgets,224 .num_dapm_widgets = ARRAY_SIZE(axg_tdmout_dapm_widgets),225 .dapm_routes = axg_tdmout_dapm_routes,226 .num_dapm_routes = ARRAY_SIZE(axg_tdmout_dapm_routes),227};228 229static const struct axg_tdm_formatter_ops axg_tdmout_ops = {230 .get_stream = axg_tdmout_get_tdm_stream,231 .prepare = axg_tdmout_prepare,232 .enable = axg_tdmout_enable,233 .disable = axg_tdmout_disable,234};235 236static const struct axg_tdm_formatter_driver axg_tdmout_drv = {237 .component_drv = &axg_tdmout_component_drv,238 .regmap_cfg = &axg_tdmout_regmap_cfg,239 .ops = &axg_tdmout_ops,240 .quirks = &(const struct axg_tdm_formatter_hw) {241 .skew_offset = 1,242 },243};244 245static const struct axg_tdm_formatter_driver g12a_tdmout_drv = {246 .component_drv = &axg_tdmout_component_drv,247 .regmap_cfg = &axg_tdmout_regmap_cfg,248 .ops = &axg_tdmout_ops,249 .quirks = &(const struct axg_tdm_formatter_hw) {250 .skew_offset = 2,251 },252};253 254static const struct snd_kcontrol_new sm1_tdmout_controls[] = {255 SOC_DOUBLE("Lane 0 Volume", TDMOUT_GAIN0, 0, 8, 255, 0),256 SOC_DOUBLE("Lane 1 Volume", TDMOUT_GAIN0, 16, 24, 255, 0),257 SOC_DOUBLE("Lane 2 Volume", TDMOUT_GAIN1, 0, 8, 255, 0),258 SOC_DOUBLE("Lane 3 Volume", TDMOUT_GAIN1, 16, 24, 255, 0),259 SOC_SINGLE("Gain Enable Switch", TDMOUT_CTRL1,260 SM1_TDMOUT_CTRL1_GAIN_EN, 1, 0),261};262 263static const char * const sm1_tdmout_sel_texts[] = {264 "IN 0", "IN 1", "IN 2", "IN 3", "IN 4",265};266 267static SOC_ENUM_SINGLE_DECL(sm1_tdmout_sel_enum, TDMOUT_CTRL1,268 TDMOUT_CTRL1_SEL_SHIFT, sm1_tdmout_sel_texts);269 270static const struct snd_kcontrol_new sm1_tdmout_in_mux =271 SOC_DAPM_ENUM("Input Source", sm1_tdmout_sel_enum);272 273static const struct snd_soc_dapm_widget sm1_tdmout_dapm_widgets[] = {274 SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0),275 SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0),276 SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0),277 SND_SOC_DAPM_AIF_IN("IN 3", NULL, 0, SND_SOC_NOPM, 0, 0),278 SND_SOC_DAPM_AIF_IN("IN 4", NULL, 0, SND_SOC_NOPM, 0, 0),279 SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &sm1_tdmout_in_mux),280 SND_SOC_DAPM_PGA_E("ENC", SND_SOC_NOPM, 0, 0, NULL, 0,281 axg_tdm_formatter_event,282 (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)),283 SND_SOC_DAPM_AIF_OUT("OUT", NULL, 0, SND_SOC_NOPM, 0, 0),284};285 286static const struct snd_soc_dapm_route sm1_tdmout_dapm_routes[] = {287 { "SRC SEL", "IN 0", "IN 0" },288 { "SRC SEL", "IN 1", "IN 1" },289 { "SRC SEL", "IN 2", "IN 2" },290 { "SRC SEL", "IN 3", "IN 3" },291 { "SRC SEL", "IN 4", "IN 4" },292 { "ENC", NULL, "SRC SEL" },293 { "OUT", NULL, "ENC" },294};295 296static const struct snd_soc_component_driver sm1_tdmout_component_drv = {297 .controls = sm1_tdmout_controls,298 .num_controls = ARRAY_SIZE(sm1_tdmout_controls),299 .dapm_widgets = sm1_tdmout_dapm_widgets,300 .num_dapm_widgets = ARRAY_SIZE(sm1_tdmout_dapm_widgets),301 .dapm_routes = sm1_tdmout_dapm_routes,302 .num_dapm_routes = ARRAY_SIZE(sm1_tdmout_dapm_routes),303};304 305static const struct axg_tdm_formatter_driver sm1_tdmout_drv = {306 .component_drv = &sm1_tdmout_component_drv,307 .regmap_cfg = &axg_tdmout_regmap_cfg,308 .ops = &axg_tdmout_ops,309 .quirks = &(const struct axg_tdm_formatter_hw) {310 .skew_offset = 2,311 },312};313 314static const struct of_device_id axg_tdmout_of_match[] = {315 {316 .compatible = "amlogic,axg-tdmout",317 .data = &axg_tdmout_drv,318 }, {319 .compatible = "amlogic,g12a-tdmout",320 .data = &g12a_tdmout_drv,321 }, {322 .compatible = "amlogic,sm1-tdmout",323 .data = &sm1_tdmout_drv,324 }, {}325};326MODULE_DEVICE_TABLE(of, axg_tdmout_of_match);327 328static struct platform_driver axg_tdmout_pdrv = {329 .probe = axg_tdm_formatter_probe,330 .driver = {331 .name = "axg-tdmout",332 .of_match_table = axg_tdmout_of_match,333 },334};335module_platform_driver(axg_tdmout_pdrv);336 337MODULE_DESCRIPTION("Amlogic AXG TDM output formatter driver");338MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");339MODULE_LICENSE("GPL v2");340