brintos

brintos / linux-shallow public Read only

0
0
Text · 9.9 KiB · c4039e4 Raw
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