diff options
Diffstat (limited to 'sound/soc/meson/axg-spdifout.c')
| -rw-r--r-- | sound/soc/meson/axg-spdifout.c | 456 | 
1 files changed, 456 insertions, 0 deletions
| diff --git a/sound/soc/meson/axg-spdifout.c b/sound/soc/meson/axg-spdifout.c new file mode 100644 index 000000000000..9dea528053ad --- /dev/null +++ b/sound/soc/meson/axg-spdifout.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet <[email protected]> + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/pcm_params.h> +#include <sound/pcm_iec958.h> + +/* + * NOTE: + * The meaning of bits SPDIFOUT_CTRL0_XXX_SEL is actually the opposite + * of what the documentation says. Manual control on V, U and C bits is + * applied when the related sel bits are cleared + */ + +#define SPDIFOUT_STAT			0x00 +#define SPDIFOUT_GAIN0			0x04 +#define SPDIFOUT_GAIN1			0x08 +#define SPDIFOUT_CTRL0			0x0c +#define  SPDIFOUT_CTRL0_EN		BIT(31) +#define  SPDIFOUT_CTRL0_RST_OUT		BIT(29) +#define  SPDIFOUT_CTRL0_RST_IN		BIT(28) +#define  SPDIFOUT_CTRL0_USEL		BIT(26) +#define  SPDIFOUT_CTRL0_USET		BIT(25) +#define  SPDIFOUT_CTRL0_CHSTS_SEL	BIT(24) +#define  SPDIFOUT_CTRL0_DATA_SEL	BIT(20) +#define  SPDIFOUT_CTRL0_MSB_FIRST	BIT(19) +#define  SPDIFOUT_CTRL0_VSEL		BIT(18) +#define  SPDIFOUT_CTRL0_VSET		BIT(17) +#define  SPDIFOUT_CTRL0_MASK_MASK	GENMASK(11, 4) +#define  SPDIFOUT_CTRL0_MASK(x)		((x) << 4) +#define SPDIFOUT_CTRL1			0x10 +#define  SPDIFOUT_CTRL1_MSB_POS_MASK	GENMASK(12, 8) +#define  SPDIFOUT_CTRL1_MSB_POS(x)	((x) << 8) +#define  SPDIFOUT_CTRL1_TYPE_MASK	GENMASK(6, 4) +#define  SPDIFOUT_CTRL1_TYPE(x)		((x) << 4) +#define SPDIFOUT_PREAMB			0x14 +#define SPDIFOUT_SWAP			0x18 +#define SPDIFOUT_CHSTS0			0x1c +#define SPDIFOUT_CHSTS1			0x20 +#define SPDIFOUT_CHSTS2			0x24 +#define SPDIFOUT_CHSTS3			0x28 +#define SPDIFOUT_CHSTS4			0x2c +#define SPDIFOUT_CHSTS5			0x30 +#define SPDIFOUT_CHSTS6			0x34 +#define SPDIFOUT_CHSTS7			0x38 +#define SPDIFOUT_CHSTS8			0x3c +#define SPDIFOUT_CHSTS9			0x40 +#define SPDIFOUT_CHSTSA			0x44 +#define SPDIFOUT_CHSTSB			0x48 +#define SPDIFOUT_MUTE_VAL		0x4c + +struct axg_spdifout { +	struct regmap *map; +	struct clk *mclk; +	struct clk *pclk; +}; + +static void axg_spdifout_enable(struct regmap *map) +{ +	/* Apply both reset */ +	regmap_update_bits(map, SPDIFOUT_CTRL0, +			   SPDIFOUT_CTRL0_RST_OUT | SPDIFOUT_CTRL0_RST_IN, +			   0); + +	/* Clear out reset before in reset */ +	regmap_update_bits(map, SPDIFOUT_CTRL0, +			   SPDIFOUT_CTRL0_RST_OUT, SPDIFOUT_CTRL0_RST_OUT); +	regmap_update_bits(map, SPDIFOUT_CTRL0, +			   SPDIFOUT_CTRL0_RST_IN,  SPDIFOUT_CTRL0_RST_IN); + +	/* Enable spdifout */ +	regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN, +			   SPDIFOUT_CTRL0_EN); +} + +static void axg_spdifout_disable(struct regmap *map) +{ +	regmap_update_bits(map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_EN, 0); +} + +static int axg_spdifout_trigger(struct snd_pcm_substream *substream, int cmd, +				struct snd_soc_dai *dai) +{ +	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +	case SNDRV_PCM_TRIGGER_RESUME: +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +		axg_spdifout_enable(priv->map); +		return 0; + +	case SNDRV_PCM_TRIGGER_STOP: +	case SNDRV_PCM_TRIGGER_SUSPEND: +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +		axg_spdifout_disable(priv->map); +		return 0; + +	default: +		return -EINVAL; +	} +} + +static int axg_spdifout_digital_mute(struct snd_soc_dai *dai, int mute) +{ +	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + +	/* Use spdif valid bit to perform digital mute */ +	regmap_update_bits(priv->map, SPDIFOUT_CTRL0, SPDIFOUT_CTRL0_VSET, +			   mute ? SPDIFOUT_CTRL0_VSET : 0); + +	return 0; +} + +static int axg_spdifout_sample_fmt(struct snd_pcm_hw_params *params, +				   struct snd_soc_dai *dai) +{ +	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); +	unsigned int val; + +	/* Set the samples spdifout will pull from the FIFO */ +	switch (params_channels(params)) { +	case 1: +		val = SPDIFOUT_CTRL0_MASK(0x1); +		break; +	case 2: +		val = SPDIFOUT_CTRL0_MASK(0x3); +		break; +	default: +		dev_err(dai->dev, "too many channels for spdif dai: %u\n", +			params_channels(params)); +		return -EINVAL; +	} + +	regmap_update_bits(priv->map, SPDIFOUT_CTRL0, +			   SPDIFOUT_CTRL0_MASK_MASK, val); + +	/* FIFO data are arranged in chunks of 64bits */ +	switch (params_physical_width(params)) { +	case 8: +		/* 8 samples of 8 bits */ +		val = SPDIFOUT_CTRL1_TYPE(0); +		break; +	case 16: +		/* 4 samples of 16 bits - right justified */ +		val = SPDIFOUT_CTRL1_TYPE(2); +		break; +	case 32: +		/* 2 samples of 32 bits - right justified */ +		val = SPDIFOUT_CTRL1_TYPE(4); +		break; +	default: +		dev_err(dai->dev, "Unsupported physical width: %u\n", +			params_physical_width(params)); +		return -EINVAL; +	} + +	/* Position of the MSB in FIFO samples */ +	val |= SPDIFOUT_CTRL1_MSB_POS(params_width(params) - 1); + +	regmap_update_bits(priv->map, SPDIFOUT_CTRL1, +			   SPDIFOUT_CTRL1_MSB_POS_MASK | +			   SPDIFOUT_CTRL1_TYPE_MASK, val); + +	regmap_update_bits(priv->map, SPDIFOUT_CTRL0, +			   SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL, +			   0); + +	return 0; +} + +static int axg_spdifout_set_chsts(struct snd_pcm_hw_params *params, +				  struct snd_soc_dai *dai) +{ +	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); +	unsigned int offset; +	int ret; +	u8 cs[4]; +	u32 val; + +	ret = snd_pcm_create_iec958_consumer_hw_params(params, cs, 4); +	if (ret < 0) { +		dev_err(dai->dev, "Creating IEC958 channel status failed %d\n", +			ret); +		return ret; +	} +	val = cs[0] | cs[1] << 8 | cs[2] << 16 | cs[3] << 24; + +	/* Setup channel status A bits [31 - 0]*/ +	regmap_write(priv->map, SPDIFOUT_CHSTS0, val); + +	/* Clear channel status A bits [191 - 32] */ +	for (offset = SPDIFOUT_CHSTS1; offset <= SPDIFOUT_CHSTS5; +	     offset += regmap_get_reg_stride(priv->map)) +		regmap_write(priv->map, offset, 0); + +	/* Setup channel status B bits [31 - 0]*/ +	regmap_write(priv->map, SPDIFOUT_CHSTS6, val); + +	/* Clear channel status B bits [191 - 32] */ +	for (offset = SPDIFOUT_CHSTS7; offset <= SPDIFOUT_CHSTSB; +	     offset += regmap_get_reg_stride(priv->map)) +		regmap_write(priv->map, offset, 0); + +	return 0; +} + +static int axg_spdifout_hw_params(struct snd_pcm_substream *substream, +				  struct snd_pcm_hw_params *params, +				  struct snd_soc_dai *dai) +{ +	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); +	unsigned int rate = params_rate(params); +	int ret; + +	/* 2 * 32bits per subframe * 2 channels = 128 */ +	ret = clk_set_rate(priv->mclk, rate * 128); +	if (ret) { +		dev_err(dai->dev, "failed to set spdif clock\n"); +		return ret; +	} + +	ret = axg_spdifout_sample_fmt(params, dai); +	if (ret) { +		dev_err(dai->dev, "failed to setup sample format\n"); +		return ret; +	} + +	ret = axg_spdifout_set_chsts(params, dai); +	if (ret) { +		dev_err(dai->dev, "failed to setup channel status words\n"); +		return ret; +	} + +	return 0; +} + +static int axg_spdifout_startup(struct snd_pcm_substream *substream, +				struct snd_soc_dai *dai) +{ +	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); +	int ret; + +	/* Clock the spdif output block */ +	ret = clk_prepare_enable(priv->pclk); +	if (ret) { +		dev_err(dai->dev, "failed to enable pclk\n"); +		return ret; +	} + +	/* Make sure the block is initially stopped */ +	axg_spdifout_disable(priv->map); + +	/* Insert data from bit 27 lsb first */ +	regmap_update_bits(priv->map, SPDIFOUT_CTRL0, +			   SPDIFOUT_CTRL0_MSB_FIRST | SPDIFOUT_CTRL0_DATA_SEL, +			   0); + +	/* Manual control of V, C and U, U = 0 */ +	regmap_update_bits(priv->map, SPDIFOUT_CTRL0, +			   SPDIFOUT_CTRL0_CHSTS_SEL | SPDIFOUT_CTRL0_VSEL | +			   SPDIFOUT_CTRL0_USEL | SPDIFOUT_CTRL0_USET, +			   0); + +	/* Static SWAP configuration ATM */ +	regmap_write(priv->map, SPDIFOUT_SWAP, 0x10); + +	return 0; +} + +static void axg_spdifout_shutdown(struct snd_pcm_substream *substream, +				  struct snd_soc_dai *dai) +{ +	struct axg_spdifout *priv = snd_soc_dai_get_drvdata(dai); + +	clk_disable_unprepare(priv->pclk); +} + +static const struct snd_soc_dai_ops axg_spdifout_ops = { +	.trigger	= axg_spdifout_trigger, +	.digital_mute	= axg_spdifout_digital_mute, +	.hw_params	= axg_spdifout_hw_params, +	.startup	= axg_spdifout_startup, +	.shutdown	= axg_spdifout_shutdown, +}; + +static struct snd_soc_dai_driver axg_spdifout_dai_drv[] = { +	{ +		.name = "SPDIF Output", +		.playback = { +			.stream_name	= "Playback", +			.channels_min	= 1, +			.channels_max	= 2, +			.rates		= (SNDRV_PCM_RATE_32000  | +					   SNDRV_PCM_RATE_44100  | +					   SNDRV_PCM_RATE_48000  | +					   SNDRV_PCM_RATE_88200  | +					   SNDRV_PCM_RATE_96000  | +					   SNDRV_PCM_RATE_176400 | +					   SNDRV_PCM_RATE_192000), +			.formats	= (SNDRV_PCM_FMTBIT_S8     | +					   SNDRV_PCM_FMTBIT_S16_LE | +					   SNDRV_PCM_FMTBIT_S20_LE | +					   SNDRV_PCM_FMTBIT_S24_LE), +		}, +		.ops = &axg_spdifout_ops, +	}, +}; + +static const char * const spdifout_sel_texts[] = { +	"IN 0", "IN 1", "IN 2", +}; + +static SOC_ENUM_SINGLE_DECL(axg_spdifout_sel_enum, SPDIFOUT_CTRL1, 24, +			    spdifout_sel_texts); + +static const struct snd_kcontrol_new axg_spdifout_in_mux = +	SOC_DAPM_ENUM("Input Source", axg_spdifout_sel_enum); + +static const struct snd_soc_dapm_widget axg_spdifout_dapm_widgets[] = { +	SND_SOC_DAPM_AIF_IN("IN 0", NULL, 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_IN("IN 1", NULL, 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_IN("IN 2", NULL, 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_MUX("SRC SEL", SND_SOC_NOPM, 0, 0, &axg_spdifout_in_mux), +}; + +static const struct snd_soc_dapm_route axg_spdifout_dapm_routes[] = { +	{ "SRC SEL", "IN 0", "IN 0" }, +	{ "SRC SEL", "IN 1", "IN 1" }, +	{ "SRC SEL", "IN 2", "IN 2" }, +	{ "Playback", NULL, "SRC SEL" }, +}; + +static const struct snd_kcontrol_new axg_spdifout_controls[] = { +	SOC_DOUBLE("Playback Volume", SPDIFOUT_GAIN0,  0,  8, 255, 0), +	SOC_DOUBLE("Playback Switch", SPDIFOUT_CTRL0, 22, 21, 1, 1), +	SOC_SINGLE("Playback Gain Enable Switch", +		   SPDIFOUT_CTRL1, 26, 1, 0), +	SOC_SINGLE("Playback Channels Mix Switch", +		   SPDIFOUT_CTRL0, 23, 1, 0), +}; + +static int axg_spdifout_set_bias_level(struct snd_soc_component *component, +				       enum snd_soc_bias_level level) +{ +	struct axg_spdifout *priv = snd_soc_component_get_drvdata(component); +	enum snd_soc_bias_level now = +		snd_soc_component_get_bias_level(component); +	int ret = 0; + +	switch (level) { +	case SND_SOC_BIAS_PREPARE: +		if (now == SND_SOC_BIAS_STANDBY) +			ret = clk_prepare_enable(priv->mclk); +		break; + +	case SND_SOC_BIAS_STANDBY: +		if (now == SND_SOC_BIAS_PREPARE) +			clk_disable_unprepare(priv->mclk); +		break; + +	case SND_SOC_BIAS_OFF: +	case SND_SOC_BIAS_ON: +		break; +	} + +	return ret; +} + +static const struct snd_soc_component_driver axg_spdifout_component_drv = { +	.controls		= axg_spdifout_controls, +	.num_controls		= ARRAY_SIZE(axg_spdifout_controls), +	.dapm_widgets		= axg_spdifout_dapm_widgets, +	.num_dapm_widgets	= ARRAY_SIZE(axg_spdifout_dapm_widgets), +	.dapm_routes		= axg_spdifout_dapm_routes, +	.num_dapm_routes	= ARRAY_SIZE(axg_spdifout_dapm_routes), +	.set_bias_level		= axg_spdifout_set_bias_level, +}; + +static const struct regmap_config axg_spdifout_regmap_cfg = { +	.reg_bits	= 32, +	.val_bits	= 32, +	.reg_stride	= 4, +	.max_register	= SPDIFOUT_MUTE_VAL, +}; + +static const struct of_device_id axg_spdifout_of_match[] = { +	{ .compatible = "amlogic,axg-spdifout", }, +	{} +}; +MODULE_DEVICE_TABLE(of, axg_spdifout_of_match); + +static int axg_spdifout_probe(struct platform_device *pdev) +{ +	struct device *dev = &pdev->dev; +	struct axg_spdifout *priv; +	struct resource *res; +	void __iomem *regs; +	int ret; + +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +	if (!priv) +		return -ENOMEM; +	platform_set_drvdata(pdev, priv); + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	regs = devm_ioremap_resource(dev, res); +	if (IS_ERR(regs)) +		return PTR_ERR(regs); + +	priv->map = devm_regmap_init_mmio(dev, regs, &axg_spdifout_regmap_cfg); +	if (IS_ERR(priv->map)) { +		dev_err(dev, "failed to init regmap: %ld\n", +			PTR_ERR(priv->map)); +		return PTR_ERR(priv->map); +	} + +	priv->pclk = devm_clk_get(dev, "pclk"); +	if (IS_ERR(priv->pclk)) { +		ret = PTR_ERR(priv->pclk); +		if (ret != -EPROBE_DEFER) +			dev_err(dev, "failed to get pclk: %d\n", ret); +		return ret; +	} + +	priv->mclk = devm_clk_get(dev, "mclk"); +	if (IS_ERR(priv->mclk)) { +		ret = PTR_ERR(priv->mclk); +		if (ret != -EPROBE_DEFER) +			dev_err(dev, "failed to get mclk: %d\n", ret); +		return ret; +	} + +	return devm_snd_soc_register_component(dev, &axg_spdifout_component_drv, +			axg_spdifout_dai_drv, ARRAY_SIZE(axg_spdifout_dai_drv)); +} + +static struct platform_driver axg_spdifout_pdrv = { +	.probe = axg_spdifout_probe, +	.driver = { +		.name = "axg-spdifout", +		.of_match_table = axg_spdifout_of_match, +	}, +}; +module_platform_driver(axg_spdifout_pdrv); + +MODULE_DESCRIPTION("Amlogic AXG SPDIF Output driver"); +MODULE_AUTHOR("Jerome Brunet <[email protected]>"); +MODULE_LICENSE("GPL v2"); |