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