cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

xlnx_spdif.c (8133B)


      1// SPDX-License-Identifier: GPL-2.0
      2//
      3// Xilinx ASoC SPDIF audio support
      4//
      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		0x20
     27#define XSPDIF_IRQ_ENABLE_REG		0x28
     28#define XSPDIF_SOFT_RESET_REG		0x40
     29#define XSPDIF_CONTROL_REG		0x44
     30#define XSPDIF_CHAN_0_STS_REG		0x4C
     31#define XSPDIF_GLOBAL_IRQ_ENABLE_REG	0x1C
     32#define XSPDIF_CH_A_USER_DATA_REG_0	0x64
     33
     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	2
     40#define XSPDIF_SOFT_RESET_VALUE		0xA
     41
     42#define MAX_CHANNELS			2
     43#define AES_SAMPLE_WIDTH		32
     44#define CH_STATUS_UPDATE_TIMEOUT	40
     45
     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};
    230
    231static const struct of_device_id xlnx_spdif_of_match[] = {
    232	{ .compatible = "xlnx,spdif-2.0", },
    233	{},
    234};
    235MODULE_DEVICE_TABLE(of, xlnx_spdif_of_match);
    236
    237static int xlnx_spdif_probe(struct platform_device *pdev)
    238{
    239	int ret;
    240	struct snd_soc_dai_driver *dai_drv;
    241	struct spdif_dev_data *ctx;
    242
    243	struct device *dev = &pdev->dev;
    244	struct device_node *node = dev->of_node;
    245
    246	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
    247	if (!ctx)
    248		return -ENOMEM;
    249
    250	ctx->axi_clk = devm_clk_get(dev, "s_axi_aclk");
    251	if (IS_ERR(ctx->axi_clk)) {
    252		ret = PTR_ERR(ctx->axi_clk);
    253		dev_err(dev, "failed to get s_axi_aclk(%d)\n", ret);
    254		return ret;
    255	}
    256	ret = clk_prepare_enable(ctx->axi_clk);
    257	if (ret) {
    258		dev_err(dev, "failed to enable s_axi_aclk(%d)\n", ret);
    259		return ret;
    260	}
    261
    262	ctx->base = devm_platform_ioremap_resource(pdev, 0);
    263	if (IS_ERR(ctx->base)) {
    264		ret = PTR_ERR(ctx->base);
    265		goto clk_err;
    266	}
    267	ret = of_property_read_u32(node, "xlnx,spdif-mode", &ctx->mode);
    268	if (ret < 0) {
    269		dev_err(dev, "cannot get SPDIF mode\n");
    270		goto clk_err;
    271	}
    272	if (ctx->mode) {
    273		dai_drv = &xlnx_spdif_tx_dai;
    274	} else {
    275		ret = platform_get_irq(pdev, 0);
    276		if (ret < 0)
    277			goto clk_err;
    278		ret = devm_request_irq(dev, ret,
    279				       xlnx_spdifrx_irq_handler,
    280				       0, "XLNX_SPDIF_RX", ctx);
    281		if (ret) {
    282			dev_err(dev, "spdif rx irq request failed\n");
    283			ret = -ENODEV;
    284			goto clk_err;
    285		}
    286
    287		init_waitqueue_head(&ctx->chsts_q);
    288		dai_drv = &xlnx_spdif_rx_dai;
    289	}
    290
    291	ret = of_property_read_u32(node, "xlnx,aud_clk_i", &ctx->aclk);
    292	if (ret < 0) {
    293		dev_err(dev, "cannot get aud_clk_i value\n");
    294		goto clk_err;
    295	}
    296
    297	dev_set_drvdata(dev, ctx);
    298
    299	ret = devm_snd_soc_register_component(dev, &xlnx_spdif_component,
    300					      dai_drv, 1);
    301	if (ret) {
    302		dev_err(dev, "SPDIF component registration failed\n");
    303		goto clk_err;
    304	}
    305
    306	writel(XSPDIF_SOFT_RESET_VALUE, ctx->base + XSPDIF_SOFT_RESET_REG);
    307	dev_info(dev, "%s DAI registered\n", dai_drv->name);
    308
    309clk_err:
    310	clk_disable_unprepare(ctx->axi_clk);
    311	return ret;
    312}
    313
    314static int xlnx_spdif_remove(struct platform_device *pdev)
    315{
    316	struct spdif_dev_data *ctx = dev_get_drvdata(&pdev->dev);
    317
    318	clk_disable_unprepare(ctx->axi_clk);
    319	return 0;
    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");