1/* 2 * ALSA SoC SPDIF In Audio Layer for spear processors 3 * 4 * Copyright (C) 2012 ST Microelectronics 5 * Vipin Kumar <vipin.kumar@st.com> 6 * 7 * This file is licensed under the terms of the GNU General Public 8 * License version 2. This program is licensed "as is" without any 9 * warranty of any kind, whether express or implied. 10 */ 11 12#include <linux/clk.h> 13#include <linux/delay.h> 14#include <linux/device.h> 15#include <linux/kernel.h> 16#include <linux/init.h> 17#include <linux/io.h> 18#include <linux/ioport.h> 19#include <linux/module.h> 20#include <linux/platform_device.h> 21#include <sound/dmaengine_pcm.h> 22#include <sound/pcm.h> 23#include <sound/pcm_params.h> 24#include <sound/soc.h> 25#include <sound/spear_dma.h> 26#include <sound/spear_spdif.h> 27#include "spdif_in_regs.h" 28#include "spear_pcm.h" 29 30struct spdif_in_params { 31 u32 format; 32}; 33 34struct spdif_in_dev { 35 struct clk *clk; 36 struct spear_dma_data dma_params; 37 struct spdif_in_params saved_params; 38 void *io_base; 39 struct device *dev; 40 void (*reset_perip)(void); 41 int irq; 42 struct snd_dmaengine_dai_dma_data dma_params_rx; 43 struct snd_dmaengine_pcm_config config; 44}; 45 46static void spdif_in_configure(struct spdif_in_dev *host) 47{ 48 u32 ctrl = SPDIF_IN_PRTYEN | SPDIF_IN_STATEN | SPDIF_IN_USREN | 49 SPDIF_IN_VALEN | SPDIF_IN_BLKEN; 50 ctrl |= SPDIF_MODE_16BIT | SPDIF_FIFO_THRES_16; 51 52 writel(ctrl, host->io_base + SPDIF_IN_CTRL); 53 writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK); 54} 55 56static int spdif_in_dai_probe(struct snd_soc_dai *dai) 57{ 58 struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); 59 60 host->dma_params_rx.filter_data = &host->dma_params; 61 dai->capture_dma_data = &host->dma_params_rx; 62 63 return 0; 64} 65 66static void spdif_in_shutdown(struct snd_pcm_substream *substream, 67 struct snd_soc_dai *dai) 68{ 69 struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); 70 71 if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) 72 return; 73 74 writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK); 75} 76 77static void spdif_in_format(struct spdif_in_dev *host, u32 format) 78{ 79 u32 ctrl = readl(host->io_base + SPDIF_IN_CTRL); 80 81 switch (format) { 82 case SNDRV_PCM_FORMAT_S16_LE: 83 ctrl |= SPDIF_XTRACT_16BIT; 84 break; 85 86 case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: 87 ctrl &= ~SPDIF_XTRACT_16BIT; 88 break; 89 } 90 91 writel(ctrl, host->io_base + SPDIF_IN_CTRL); 92} 93 94static int spdif_in_hw_params(struct snd_pcm_substream *substream, 95 struct snd_pcm_hw_params *params, 96 struct snd_soc_dai *dai) 97{ 98 struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); 99 u32 format; 100 101 if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) 102 return -EINVAL; 103 104 format = params_format(params); 105 host->saved_params.format = format; 106 107 return 0; 108} 109 110static int spdif_in_trigger(struct snd_pcm_substream *substream, int cmd, 111 struct snd_soc_dai *dai) 112{ 113 struct spdif_in_dev *host = snd_soc_dai_get_drvdata(dai); 114 u32 ctrl; 115 int ret = 0; 116 117 if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) 118 return -EINVAL; 119 120 switch (cmd) { 121 case SNDRV_PCM_TRIGGER_START: 122 case SNDRV_PCM_TRIGGER_RESUME: 123 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 124 clk_enable(host->clk); 125 spdif_in_configure(host); 126 spdif_in_format(host, host->saved_params.format); 127 128 ctrl = readl(host->io_base + SPDIF_IN_CTRL); 129 ctrl |= SPDIF_IN_SAMPLE | SPDIF_IN_ENB; 130 writel(ctrl, host->io_base + SPDIF_IN_CTRL); 131 writel(0xF, host->io_base + SPDIF_IN_IRQ_MASK); 132 break; 133 134 case SNDRV_PCM_TRIGGER_STOP: 135 case SNDRV_PCM_TRIGGER_SUSPEND: 136 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 137 ctrl = readl(host->io_base + SPDIF_IN_CTRL); 138 ctrl &= ~(SPDIF_IN_SAMPLE | SPDIF_IN_ENB); 139 writel(ctrl, host->io_base + SPDIF_IN_CTRL); 140 writel(0x0, host->io_base + SPDIF_IN_IRQ_MASK); 141 142 if (host->reset_perip) 143 host->reset_perip(); 144 clk_disable(host->clk); 145 break; 146 147 default: 148 ret = -EINVAL; 149 break; 150 } 151 return ret; 152} 153 154static struct snd_soc_dai_ops spdif_in_dai_ops = { 155 .shutdown = spdif_in_shutdown, 156 .trigger = spdif_in_trigger, 157 .hw_params = spdif_in_hw_params, 158}; 159 160static struct snd_soc_dai_driver spdif_in_dai = { 161 .probe = spdif_in_dai_probe, 162 .capture = { 163 .channels_min = 2, 164 .channels_max = 2, 165 .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ 166 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ 167 SNDRV_PCM_RATE_192000), 168 .formats = SNDRV_PCM_FMTBIT_S16_LE | \ 169 SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE, 170 }, 171 .ops = &spdif_in_dai_ops, 172}; 173 174static const struct snd_soc_component_driver spdif_in_component = { 175 .name = "spdif-in", 176}; 177 178static irqreturn_t spdif_in_irq(int irq, void *arg) 179{ 180 struct spdif_in_dev *host = (struct spdif_in_dev *)arg; 181 182 u32 irq_status = readl(host->io_base + SPDIF_IN_IRQ); 183 184 if (!irq_status) 185 return IRQ_NONE; 186 187 if (irq_status & SPDIF_IRQ_FIFOWRITE) 188 dev_err(host->dev, "spdif in: fifo write error"); 189 if (irq_status & SPDIF_IRQ_EMPTYFIFOREAD) 190 dev_err(host->dev, "spdif in: empty fifo read error"); 191 if (irq_status & SPDIF_IRQ_FIFOFULL) 192 dev_err(host->dev, "spdif in: fifo full error"); 193 if (irq_status & SPDIF_IRQ_OUTOFRANGE) 194 dev_err(host->dev, "spdif in: out of range error"); 195 196 writel(0, host->io_base + SPDIF_IN_IRQ); 197 198 return IRQ_HANDLED; 199} 200 201static int spdif_in_probe(struct platform_device *pdev) 202{ 203 struct spdif_in_dev *host; 204 struct spear_spdif_platform_data *pdata; 205 struct resource *res, *res_fifo; 206 int ret; 207 208 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 209 if (!res) 210 return -EINVAL; 211 212 res_fifo = platform_get_resource(pdev, IORESOURCE_IO, 0); 213 if (!res_fifo) 214 return -EINVAL; 215 216 if (!devm_request_mem_region(&pdev->dev, res->start, 217 resource_size(res), pdev->name)) { 218 dev_warn(&pdev->dev, "Failed to get memory resourse\n"); 219 return -ENOENT; 220 } 221 222 host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); 223 if (!host) { 224 dev_warn(&pdev->dev, "kzalloc fail\n"); 225 return -ENOMEM; 226 } 227 228 host->io_base = devm_ioremap(&pdev->dev, res->start, 229 resource_size(res)); 230 if (!host->io_base) { 231 dev_warn(&pdev->dev, "ioremap failed\n"); 232 return -ENOMEM; 233 } 234 235 host->irq = platform_get_irq(pdev, 0); 236 if (host->irq < 0) 237 return -EINVAL; 238 239 host->clk = devm_clk_get(&pdev->dev, NULL); 240 if (IS_ERR(host->clk)) 241 return PTR_ERR(host->clk); 242 243 pdata = dev_get_platdata(&pdev->dev); 244 245 if (!pdata) 246 return -EINVAL; 247 248 host->dma_params.data = pdata->dma_params; 249 host->dma_params.addr = res_fifo->start; 250 host->dma_params.max_burst = 16; 251 host->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 252 host->reset_perip = pdata->reset_perip; 253 254 host->dev = &pdev->dev; 255 dev_set_drvdata(&pdev->dev, host); 256 257 ret = devm_request_irq(&pdev->dev, host->irq, spdif_in_irq, 0, 258 "spdif-in", host); 259 if (ret) { 260 dev_warn(&pdev->dev, "request_irq failed\n"); 261 return ret; 262 } 263 264 ret = devm_snd_soc_register_component(&pdev->dev, &spdif_in_component, 265 &spdif_in_dai, 1); 266 if (ret) 267 return ret; 268 269 return devm_spear_pcm_platform_register(&pdev->dev, &host->config, 270 pdata->filter); 271} 272 273static struct platform_driver spdif_in_driver = { 274 .probe = spdif_in_probe, 275 .driver = { 276 .name = "spdif-in", 277 }, 278}; 279 280module_platform_driver(spdif_in_driver); 281 282MODULE_AUTHOR("Vipin Kumar <vipin.kumar@st.com>"); 283MODULE_DESCRIPTION("SPEAr SPDIF IN SoC Interface"); 284MODULE_LICENSE("GPL"); 285MODULE_ALIAS("platform:spdif_in"); 286