brintos

brintos / linux-shallow public Read only

0
0
Text · 8.0 KiB · 7febb38 Raw
335 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// Xilinx ASoC SPDIF audio support4//5// Copyright (C) 2018 Xilinx, Inc.6//7// Author: Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>8//9 10#include <linux/clk.h>11#include <linux/io.h>12#include <linux/module.h>13#include <linux/of.h>14#include <linux/of_platform.h>15#include <linux/platform_device.h>16#include <sound/pcm_params.h>17#include <sound/soc.h>18 19#define XLNX_SPDIF_RATES \20	(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \21	SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \22	SNDRV_PCM_RATE_192000)23 24#define XLNX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)25 26#define XSPDIF_IRQ_STS_REG		0x2027#define XSPDIF_IRQ_ENABLE_REG		0x2828#define XSPDIF_SOFT_RESET_REG		0x4029#define XSPDIF_CONTROL_REG		0x4430#define XSPDIF_CHAN_0_STS_REG		0x4C31#define XSPDIF_GLOBAL_IRQ_ENABLE_REG	0x1C32#define XSPDIF_CH_A_USER_DATA_REG_0	0x6433 34#define XSPDIF_CORE_ENABLE_MASK		BIT(0)35#define XSPDIF_FIFO_FLUSH_MASK		BIT(1)36#define XSPDIF_CH_STS_MASK		BIT(5)37#define XSPDIF_GLOBAL_IRQ_ENABLE	BIT(31)38#define XSPDIF_CLOCK_CONFIG_BITS_MASK	GENMASK(5, 2)39#define XSPDIF_CLOCK_CONFIG_BITS_SHIFT	240#define XSPDIF_SOFT_RESET_VALUE		0xA41 42#define MAX_CHANNELS			243#define AES_SAMPLE_WIDTH		3244#define CH_STATUS_UPDATE_TIMEOUT	4045 46struct spdif_dev_data {47	u32 mode;48	u32 aclk;49	bool rx_chsts_updated;50	void __iomem *base;51	struct clk *axi_clk;52	wait_queue_head_t chsts_q;53};54 55static irqreturn_t xlnx_spdifrx_irq_handler(int irq, void *arg)56{57	u32 val;58	struct spdif_dev_data *ctx = arg;59 60	val = readl(ctx->base + XSPDIF_IRQ_STS_REG);61	if (val & XSPDIF_CH_STS_MASK) {62		writel(val & XSPDIF_CH_STS_MASK,63		       ctx->base + XSPDIF_IRQ_STS_REG);64		val = readl(ctx->base +65			    XSPDIF_IRQ_ENABLE_REG);66		writel(val & ~XSPDIF_CH_STS_MASK,67		       ctx->base + XSPDIF_IRQ_ENABLE_REG);68 69		ctx->rx_chsts_updated = true;70		wake_up_interruptible(&ctx->chsts_q);71		return IRQ_HANDLED;72	}73 74	return IRQ_NONE;75}76 77static int xlnx_spdif_startup(struct snd_pcm_substream *substream,78			      struct snd_soc_dai *dai)79{80	u32 val;81	struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);82 83	val = readl(ctx->base + XSPDIF_CONTROL_REG);84	val |= XSPDIF_FIFO_FLUSH_MASK;85	writel(val, ctx->base + XSPDIF_CONTROL_REG);86 87	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {88		writel(XSPDIF_CH_STS_MASK,89		       ctx->base + XSPDIF_IRQ_ENABLE_REG);90		writel(XSPDIF_GLOBAL_IRQ_ENABLE,91		       ctx->base + XSPDIF_GLOBAL_IRQ_ENABLE_REG);92	}93 94	return 0;95}96 97static void xlnx_spdif_shutdown(struct snd_pcm_substream *substream,98				struct snd_soc_dai *dai)99{100	struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);101 102	writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG);103}104 105static int xlnx_spdif_hw_params(struct snd_pcm_substream *substream,106				struct snd_pcm_hw_params *params,107				struct snd_soc_dai *dai)108{109	u32 val, clk_div, clk_cfg;110	struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);111 112	clk_div = DIV_ROUND_CLOSEST(ctx->aclk, MAX_CHANNELS * AES_SAMPLE_WIDTH *113				    params_rate(params));114 115	switch (clk_div) {116	case 4:117		clk_cfg = 0;118		break;119	case 8:120		clk_cfg = 1;121		break;122	case 16:123		clk_cfg = 2;124		break;125	case 24:126		clk_cfg = 3;127		break;128	case 32:129		clk_cfg = 4;130		break;131	case 48:132		clk_cfg = 5;133		break;134	case 64:135		clk_cfg = 6;136		break;137	default:138		return -EINVAL;139	}140 141	val = readl(ctx->base + XSPDIF_CONTROL_REG);142	val &= ~XSPDIF_CLOCK_CONFIG_BITS_MASK;143	val |= clk_cfg << XSPDIF_CLOCK_CONFIG_BITS_SHIFT;144	writel(val, ctx->base + XSPDIF_CONTROL_REG);145 146	return 0;147}148 149static int rx_stream_detect(struct snd_soc_dai *dai)150{151	int err;152	struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);153	unsigned long jiffies = msecs_to_jiffies(CH_STATUS_UPDATE_TIMEOUT);154 155	/* start capture only if stream is detected within 40ms timeout */156	err = wait_event_interruptible_timeout(ctx->chsts_q,157					       ctx->rx_chsts_updated,158					       jiffies);159	if (!err) {160		dev_err(dai->dev, "No streaming audio detected!\n");161		return -EINVAL;162	}163	ctx->rx_chsts_updated = false;164 165	return 0;166}167 168static int xlnx_spdif_trigger(struct snd_pcm_substream *substream, int cmd,169			      struct snd_soc_dai *dai)170{171	u32 val;172	int ret = 0;173	struct spdif_dev_data *ctx = dev_get_drvdata(dai->dev);174 175	val = readl(ctx->base + XSPDIF_CONTROL_REG);176	switch (cmd) {177	case SNDRV_PCM_TRIGGER_START:178	case SNDRV_PCM_TRIGGER_RESUME:179	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:180		val |= XSPDIF_CORE_ENABLE_MASK;181		writel(val, ctx->base + XSPDIF_CONTROL_REG);182		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)183			ret = rx_stream_detect(dai);184		break;185	case SNDRV_PCM_TRIGGER_STOP:186	case SNDRV_PCM_TRIGGER_SUSPEND:187	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:188		val &= ~XSPDIF_CORE_ENABLE_MASK;189		writel(val, ctx->base + XSPDIF_CONTROL_REG);190		break;191	default:192		ret = -EINVAL;193	}194 195	return ret;196}197 198static const struct snd_soc_dai_ops xlnx_spdif_dai_ops = {199	.startup = xlnx_spdif_startup,200	.shutdown = xlnx_spdif_shutdown,201	.trigger = xlnx_spdif_trigger,202	.hw_params = xlnx_spdif_hw_params,203};204 205static struct snd_soc_dai_driver xlnx_spdif_tx_dai = {206	.name = "xlnx_spdif_tx",207	.playback = {208		.channels_min = 2,209		.channels_max = 2,210		.rates = XLNX_SPDIF_RATES,211		.formats = XLNX_SPDIF_FORMATS,212	},213	.ops = &xlnx_spdif_dai_ops,214};215 216static struct snd_soc_dai_driver xlnx_spdif_rx_dai = {217	.name = "xlnx_spdif_rx",218	.capture = {219		.channels_min = 2,220		.channels_max = 2,221		.rates = XLNX_SPDIF_RATES,222		.formats = XLNX_SPDIF_FORMATS,223	},224	.ops = &xlnx_spdif_dai_ops,225};226 227static const struct snd_soc_component_driver xlnx_spdif_component = {228	.name = "xlnx-spdif",229	.legacy_dai_naming = 1,230};231 232static const struct of_device_id xlnx_spdif_of_match[] = {233	{ .compatible = "xlnx,spdif-2.0", },234	{},235};236MODULE_DEVICE_TABLE(of, xlnx_spdif_of_match);237 238static int xlnx_spdif_probe(struct platform_device *pdev)239{240	int ret;241	struct snd_soc_dai_driver *dai_drv;242	struct spdif_dev_data *ctx;243 244	struct device *dev = &pdev->dev;245	struct device_node *node = dev->of_node;246 247	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);248	if (!ctx)249		return -ENOMEM;250 251	ctx->axi_clk = devm_clk_get(dev, "s_axi_aclk");252	if (IS_ERR(ctx->axi_clk)) {253		ret = PTR_ERR(ctx->axi_clk);254		dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret);255		return ret;256	}257	ret = clk_prepare_enable(ctx->axi_clk);258	if (ret) {259		dev_err(dev, "failed to enable s_axi_aclk(%d)\n", ret);260		return ret;261	}262 263	ctx->base = devm_platform_ioremap_resource(pdev, 0);264	if (IS_ERR(ctx->base)) {265		ret = PTR_ERR(ctx->base);266		goto clk_err;267	}268	ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode);269	if (ret < 0) {270		dev_err(dev, "cannot get SPDIF mode\n");271		goto clk_err;272	}273	if (ctx->mode) {274		dai_drv = &xlnx_spdif_tx_dai;275	} else {276		ret = platform_get_irq(pdev, 0);277		if (ret < 0)278			goto clk_err;279		ret = devm_request_irq(dev, ret,280				       xlnx_spdifrx_irq_handler,281				       0, "XLNX_SPDIF_RX", ctx);282		if (ret) {283			dev_err(dev, "spdif rx irq request failed\n");284			ret = -ENODEV;285			goto clk_err;286		}287 288		init_waitqueue_head(&ctx->chsts_q);289		dai_drv = &xlnx_spdif_rx_dai;290	}291 292	ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk);293	if (ret < 0) {294		dev_err(dev, "cannot get aud_clk_i value\n");295		goto clk_err;296	}297 298	dev_set_drvdata(dev, ctx);299 300	ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component,301					      dai_drv, 1);302	if (ret) {303		dev_err(dev, "SPDIF component registration failed\n");304		goto clk_err;305	}306 307	writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG);308	dev_info(dev, "%s DAI registered\n", dai_drv->name);309 310clk_err:311	clk_disable_unprepare(ctx->axi_clk);312	return ret;313}314 315static void xlnx_spdif_remove(struct platform_device *pdev)316{317	struct spdif_dev_data *ctx = dev_get_drvdata(&pdev->dev);318 319	clk_disable_unprepare(ctx->axi_clk);320}321 322static struct platform_driver xlnx_spdif_driver = {323	.driver = {324		.name = "xlnx-spdif",325		.of_match_table = xlnx_spdif_of_match,326	},327	.probe = xlnx_spdif_probe,328	.remove = xlnx_spdif_remove,329};330module_platform_driver(xlnx_spdif_driver);331 332MODULE_AUTHOR("Maruthi Srinivas Bayyavarapu <maruthis@xilinx.com>");333MODULE_DESCRIPTION("XILINX SPDIF driver");334MODULE_LICENSE("GPL v2");335