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