diff options
Diffstat (limited to 'sound/soc/starfive/spdif.c')
-rw-r--r-- | sound/soc/starfive/spdif.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/sound/soc/starfive/spdif.c b/sound/soc/starfive/spdif.c new file mode 100644 index 000000000000..f021e99bf7ac --- /dev/null +++ b/sound/soc/starfive/spdif.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/regmap.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/dmaengine_pcm.h> + +#include "spdif.h" + +static irqreturn_t spdif_irq_handler(int irq, void *dev_id) +{ + struct sf_spdif_dev *dev = dev_id; + bool irq_valid = false; + unsigned int intr; + unsigned int stat; + + regmap_read(dev->regmap, SPDIF_INT_REG, &intr); + regmap_read(dev->regmap, SPDIF_STAT_REG, &stat); + regmap_update_bits(dev->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, 0); + regmap_update_bits(dev->regmap, SPDIF_INT_REG, + SPDIF_INT_REG_BIT, 0); + + if ((stat & SPDIF_EMPTY_FLAG) || (stat & SPDIF_AEMPTY_FLAG)) { + sf_spdif_pcm_push_tx(dev); + irq_valid = true; + } + + if ((stat & SPDIF_FULL_FLAG) || (stat & SPDIF_AFULL_FLAG)) { + sf_spdif_pcm_pop_rx(dev); + irq_valid = true; + } + + if (stat & SPDIF_PARITY_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_UNDERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_OVRERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_SYNCERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_LOCK_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_BEGIN_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_RIGHT_LEFT) { + irq_valid = true; + } + + regmap_update_bits(dev->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); + + if (irq_valid) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static int sf_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + bool tx; + + tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (tx) { + /* tx mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_TR_MODE, SPDIF_TR_MODE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_FIFO, SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK); + } else { + /* rx mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_TR_MODE, 0); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_FIFO, SPDIF_FULL_MASK | SPDIF_AFULL_MASK); + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* clock recovery form the SPDIF data stream 0:clk_enable */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, 0); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE, SPDIF_ENABLE); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* clock recovery form the SPDIF data stream 1:power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE, 0); + break; + default: + printk(KERN_ERR "%s L.%d cmd:%d\n", __func__, __LINE__, cmd); + return -EINVAL; + } + + return 0; +} + +static int sf_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int channels; + unsigned int rate; + snd_pcm_format_t format; + unsigned int tsamplerate; + + channels = params_channels(params); + rate = params_rate(params); + format = params_format(params); + + switch (channels) { + case 2: + break; + default: + dev_err(dai->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + break; + default: + dev_err(spdif->dev, "invalid format\n"); + return -EINVAL; + } + + switch (rate) { + case 8000: + case 11025: + case 16000: + case 22050: + break; + default: + printk(KERN_ERR "channel:%d sample rate:%d\n", channels, rate); + return -EINVAL; + } + + /* 12288000/128=96000 */ + tsamplerate = (96000 + rate/2)/rate - 1; + + if (rate < 3) { + return -EINVAL; + } + + /* transmission sample rate */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, 0xFF, tsamplerate); + + return 0; +} + +static int sf_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + + #if 0 + spdif->play_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR; + spdif->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->play_dma_data.fifo_size = 16; + spdif->play_dma_data.maxburst = 16; + spdif->capture_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR; + spdif->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->capture_dma_data.fifo_size = 16; + spdif->capture_dma_data.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &spdif->play_dma_data, &spdif->capture_dma_data); + snd_soc_dai_set_drvdata(dai, spdif); + #endif + + /* reset */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE | SPDIF_SFR_ENABLE | SPDIF_FIFO_ENABLE, 0); + + /* clear irq */ + regmap_update_bits(spdif->regmap, SPDIF_INT_REG, + SPDIF_INT_REG_BIT, 0); + + /* power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + /* power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE, + SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_SETPREAMBB, SPDIF_SETPREAMBB); + + regmap_update_bits(spdif->regmap, SPDIF_INT_REG, + 0x1FFF<<SPDIF_PREAMBLEDEL, 0x3<<SPDIF_PREAMBLEDEL); + + regmap_update_bits(spdif->regmap, SPDIF_FIFO_CTRL, + 0xFFFFFFFF, 0x20|(0x20<<SPDIF_AFULL_THRESHOLD)); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_PARITYGEN, SPDIF_PARITYGEN); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); + + /* APB access to FIFO enable, disable if use DMA/FIFO */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_USE_FIFO_IF, 0); + + /* two channel */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CHANNEL_MODE, 0); + + return 0; +} + +static const struct snd_soc_dai_ops sf_spdif_dai_ops = { + .probe = sf_spdif_dai_probe, + .trigger = sf_spdif_trigger, + .hw_params = sf_spdif_hw_params, +}; + +#define SF_PCM_RATE_44100_192000 (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + +#define SF_PCM_RATE_8000_22050 (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050) + +static struct snd_soc_dai_driver sf_spdif_dai = { + .name = "spdif", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SF_PCM_RATE_8000_22050, + .formats = SNDRV_PCM_FMTBIT_S16_LE \ + |SNDRV_PCM_FMTBIT_S24_LE \ + |SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SF_PCM_RATE_8000_22050, + .formats = SNDRV_PCM_FMTBIT_S16_LE \ + |SNDRV_PCM_FMTBIT_S24_LE \ + |SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &sf_spdif_dai_ops, + .symmetric_rate = 1, +}; + +static const struct snd_soc_component_driver sf_spdif_component = { + .name = "sf-spdif", +}; + +static const struct regmap_config sf_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x200, +}; + +static int sf_spdif_probe(struct platform_device *pdev) +{ + struct sf_spdif_dev *spdif; + struct resource *res; + void __iomem *base; + int ret; + int irq; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->spdif_base = base; + spdif->regmap = devm_regmap_init_mmio(&pdev->dev, spdif->spdif_base, + &sf_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) + return PTR_ERR(spdif->regmap); + + spdif->dev = &pdev->dev; + spdif->fifo_th = 16; + + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + ret = devm_request_irq(&pdev->dev, irq, spdif_irq_handler, 0, + pdev->name, spdif); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + } + + ret = devm_snd_soc_register_component(&pdev->dev, &sf_spdif_component, + &sf_spdif_dai, 1); + if (ret) + goto err_clk_disable; + + if (irq >= 0) { + ret = sf_spdif_pcm_register(pdev); + spdif->use_pio = true; + } else { + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + spdif->use_pio = false; + } + + if (ret) + goto err_clk_disable; + + return 0; + +err_clk_disable: + return ret; +} + +static const struct of_device_id sf_spdif_of_match[] = { + { .compatible = "starfive,sf-spdif", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sf_spdif_of_match); + +static struct platform_driver sf_spdif_driver = { + .driver = { + .name = "sf-spdif", + .of_match_table = sf_spdif_of_match, + }, + .probe = sf_spdif_probe, +}; +module_platform_driver(sf_spdif_driver); + +MODULE_AUTHOR("michael.yan <michael.yan@starfive.com>"); +MODULE_DESCRIPTION("starfive SPDIF driver"); +MODULE_LICENSE("GPL v2"); |