linux/sound/soc/adi/axi-spdif.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2012-2013, Analog Devices Inc.
   4 * Author: Lars-Peter Clausen <lars@metafoo.de>
   5 */
   6
   7#include <linux/init.h>
   8#include <linux/kernel.h>
   9#include <linux/module.h>
  10#include <linux/platform_device.h>
  11#include <linux/slab.h>
  12#include <linux/of.h>
  13#include <linux/clk.h>
  14#include <linux/regmap.h>
  15
  16#include <sound/core.h>
  17#include <sound/pcm.h>
  18#include <sound/pcm_params.h>
  19#include <sound/soc.h>
  20#include <sound/initval.h>
  21#include <sound/dmaengine_pcm.h>
  22
  23#define AXI_SPDIF_REG_CTRL      0x0
  24#define AXI_SPDIF_REG_STAT      0x4
  25#define AXI_SPDIF_REG_TX_FIFO   0xc
  26
  27#define AXI_SPDIF_CTRL_TXDATA BIT(1)
  28#define AXI_SPDIF_CTRL_TXEN BIT(0)
  29#define AXI_SPDIF_CTRL_CLKDIV_OFFSET 8
  30#define AXI_SPDIF_CTRL_CLKDIV_MASK (0xff << 8)
  31
  32#define AXI_SPDIF_FREQ_44100    (0x0 << 6)
  33#define AXI_SPDIF_FREQ_48000    (0x1 << 6)
  34#define AXI_SPDIF_FREQ_32000    (0x2 << 6)
  35#define AXI_SPDIF_FREQ_NA       (0x3 << 6)
  36
  37struct axi_spdif {
  38        struct regmap *regmap;
  39        struct clk *clk;
  40        struct clk *clk_ref;
  41
  42        struct snd_dmaengine_dai_dma_data dma_data;
  43
  44        struct snd_ratnum ratnum;
  45        struct snd_pcm_hw_constraint_ratnums rate_constraints;
  46};
  47
  48static int axi_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
  49        struct snd_soc_dai *dai)
  50{
  51        struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
  52        unsigned int val;
  53
  54        switch (cmd) {
  55        case SNDRV_PCM_TRIGGER_START:
  56        case SNDRV_PCM_TRIGGER_RESUME:
  57        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
  58                val = AXI_SPDIF_CTRL_TXDATA;
  59                break;
  60        case SNDRV_PCM_TRIGGER_STOP:
  61        case SNDRV_PCM_TRIGGER_SUSPEND:
  62        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
  63                val = 0;
  64                break;
  65        default:
  66                return -EINVAL;
  67        }
  68
  69        regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
  70                AXI_SPDIF_CTRL_TXDATA, val);
  71
  72        return 0;
  73}
  74
  75static int axi_spdif_hw_params(struct snd_pcm_substream *substream,
  76        struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
  77{
  78        struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
  79        unsigned int rate = params_rate(params);
  80        unsigned int clkdiv, stat;
  81
  82        switch (params_rate(params)) {
  83        case 32000:
  84                stat = AXI_SPDIF_FREQ_32000;
  85                break;
  86        case 44100:
  87                stat = AXI_SPDIF_FREQ_44100;
  88                break;
  89        case 48000:
  90                stat = AXI_SPDIF_FREQ_48000;
  91                break;
  92        default:
  93                stat = AXI_SPDIF_FREQ_NA;
  94                break;
  95        }
  96
  97        clkdiv = DIV_ROUND_CLOSEST(clk_get_rate(spdif->clk_ref),
  98                        rate * 64 * 2) - 1;
  99        clkdiv <<= AXI_SPDIF_CTRL_CLKDIV_OFFSET;
 100
 101        regmap_write(spdif->regmap, AXI_SPDIF_REG_STAT, stat);
 102        regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
 103                AXI_SPDIF_CTRL_CLKDIV_MASK, clkdiv);
 104
 105        return 0;
 106}
 107
 108static int axi_spdif_dai_probe(struct snd_soc_dai *dai)
 109{
 110        struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
 111
 112        snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL);
 113
 114        return 0;
 115}
 116
 117static int axi_spdif_startup(struct snd_pcm_substream *substream,
 118        struct snd_soc_dai *dai)
 119{
 120        struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
 121        int ret;
 122
 123        ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
 124                           SNDRV_PCM_HW_PARAM_RATE,
 125                           &spdif->rate_constraints);
 126        if (ret)
 127                return ret;
 128
 129        ret = clk_prepare_enable(spdif->clk_ref);
 130        if (ret)
 131                return ret;
 132
 133        regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
 134                AXI_SPDIF_CTRL_TXEN, AXI_SPDIF_CTRL_TXEN);
 135
 136        return 0;
 137}
 138
 139static void axi_spdif_shutdown(struct snd_pcm_substream *substream,
 140        struct snd_soc_dai *dai)
 141{
 142        struct axi_spdif *spdif = snd_soc_dai_get_drvdata(dai);
 143
 144        regmap_update_bits(spdif->regmap, AXI_SPDIF_REG_CTRL,
 145                AXI_SPDIF_CTRL_TXEN, 0);
 146
 147        clk_disable_unprepare(spdif->clk_ref);
 148}
 149
 150static const struct snd_soc_dai_ops axi_spdif_dai_ops = {
 151        .startup = axi_spdif_startup,
 152        .shutdown = axi_spdif_shutdown,
 153        .trigger = axi_spdif_trigger,
 154        .hw_params = axi_spdif_hw_params,
 155};
 156
 157static struct snd_soc_dai_driver axi_spdif_dai = {
 158        .probe = axi_spdif_dai_probe,
 159        .playback = {
 160                .channels_min = 2,
 161                .channels_max = 2,
 162                .rates = SNDRV_PCM_RATE_KNOT,
 163                .formats = SNDRV_PCM_FMTBIT_S16_LE,
 164        },
 165        .ops = &axi_spdif_dai_ops,
 166};
 167
 168static const struct snd_soc_component_driver axi_spdif_component = {
 169        .name = "axi-spdif",
 170};
 171
 172static const struct regmap_config axi_spdif_regmap_config = {
 173        .reg_bits = 32,
 174        .reg_stride = 4,
 175        .val_bits = 32,
 176        .max_register = AXI_SPDIF_REG_STAT,
 177};
 178
 179static int axi_spdif_probe(struct platform_device *pdev)
 180{
 181        struct axi_spdif *spdif;
 182        struct resource *res;
 183        void __iomem *base;
 184        int ret;
 185
 186        spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL);
 187        if (!spdif)
 188                return -ENOMEM;
 189
 190        platform_set_drvdata(pdev, spdif);
 191
 192        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 193        base = devm_ioremap_resource(&pdev->dev, res);
 194        if (IS_ERR(base))
 195                return PTR_ERR(base);
 196
 197        spdif->regmap = devm_regmap_init_mmio(&pdev->dev, base,
 198                                            &axi_spdif_regmap_config);
 199        if (IS_ERR(spdif->regmap))
 200                return PTR_ERR(spdif->regmap);
 201
 202        spdif->clk = devm_clk_get(&pdev->dev, "axi");
 203        if (IS_ERR(spdif->clk))
 204                return PTR_ERR(spdif->clk);
 205
 206        spdif->clk_ref = devm_clk_get(&pdev->dev, "ref");
 207        if (IS_ERR(spdif->clk_ref))
 208                return PTR_ERR(spdif->clk_ref);
 209
 210        ret = clk_prepare_enable(spdif->clk);
 211        if (ret)
 212                return ret;
 213
 214        spdif->dma_data.addr = res->start + AXI_SPDIF_REG_TX_FIFO;
 215        spdif->dma_data.addr_width = 4;
 216        spdif->dma_data.maxburst = 1;
 217
 218        spdif->ratnum.num = clk_get_rate(spdif->clk_ref) / 128;
 219        spdif->ratnum.den_step = 1;
 220        spdif->ratnum.den_min = 1;
 221        spdif->ratnum.den_max = 64;
 222
 223        spdif->rate_constraints.rats = &spdif->ratnum;
 224        spdif->rate_constraints.nrats = 1;
 225
 226        ret = devm_snd_soc_register_component(&pdev->dev, &axi_spdif_component,
 227                                         &axi_spdif_dai, 1);
 228        if (ret)
 229                goto err_clk_disable;
 230
 231        ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
 232        if (ret)
 233                goto err_clk_disable;
 234
 235        return 0;
 236
 237err_clk_disable:
 238        clk_disable_unprepare(spdif->clk);
 239        return ret;
 240}
 241
 242static int axi_spdif_dev_remove(struct platform_device *pdev)
 243{
 244        struct axi_spdif *spdif = platform_get_drvdata(pdev);
 245
 246        clk_disable_unprepare(spdif->clk);
 247
 248        return 0;
 249}
 250
 251static const struct of_device_id axi_spdif_of_match[] = {
 252        { .compatible = "adi,axi-spdif-tx-1.00.a", },
 253        {},
 254};
 255MODULE_DEVICE_TABLE(of, axi_spdif_of_match);
 256
 257static struct platform_driver axi_spdif_driver = {
 258        .driver = {
 259                .name = "axi-spdif",
 260                .of_match_table = axi_spdif_of_match,
 261        },
 262        .probe = axi_spdif_probe,
 263        .remove = axi_spdif_dev_remove,
 264};
 265module_platform_driver(axi_spdif_driver);
 266
 267MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
 268MODULE_DESCRIPTION("AXI SPDIF driver");
 269MODULE_LICENSE("GPL");
 270