Path: blob/master/sound/soc/kirkwood/kirkwood-dma.c
10817 views
/*1* kirkwood-dma.c2*3* (c) 2010 Arnaud Patard <[email protected]>4* (c) 2010 Arnaud Patard <[email protected]>5*6* This program is free software; you can redistribute it and/or modify it7* under the terms of the GNU General Public License as published by the8* Free Software Foundation; either version 2 of the License, or (at your9* option) any later version.10*/1112#include <linux/init.h>13#include <linux/module.h>14#include <linux/device.h>15#include <linux/io.h>16#include <linux/slab.h>17#include <linux/interrupt.h>18#include <linux/dma-mapping.h>19#include <linux/mbus.h>20#include <sound/soc.h>21#include "kirkwood.h"2223#define KIRKWOOD_RATES \24(SNDRV_PCM_RATE_44100 | \25SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)26#define KIRKWOOD_FORMATS \27(SNDRV_PCM_FMTBIT_S16_LE | \28SNDRV_PCM_FMTBIT_S24_LE | \29SNDRV_PCM_FMTBIT_S32_LE)3031struct kirkwood_dma_priv {32struct snd_pcm_substream *play_stream;33struct snd_pcm_substream *rec_stream;34struct kirkwood_dma_data *data;35};3637static struct snd_pcm_hardware kirkwood_dma_snd_hw = {38.info = (SNDRV_PCM_INFO_INTERLEAVED |39SNDRV_PCM_INFO_MMAP |40SNDRV_PCM_INFO_MMAP_VALID |41SNDRV_PCM_INFO_BLOCK_TRANSFER |42SNDRV_PCM_INFO_PAUSE),43.formats = KIRKWOOD_FORMATS,44.rates = KIRKWOOD_RATES,45.rate_min = 44100,46.rate_max = 96000,47.channels_min = 1,48.channels_max = 2,49.buffer_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS,50.period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES,51.period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES,52.periods_min = KIRKWOOD_SND_MIN_PERIODS,53.periods_max = KIRKWOOD_SND_MAX_PERIODS,54.fifo_size = 0,55};5657static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL;5859static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id)60{61struct kirkwood_dma_priv *prdata = dev_id;62struct kirkwood_dma_data *priv = prdata->data;63unsigned long mask, status, cause;6465mask = readl(priv->io + KIRKWOOD_INT_MASK);66status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask;6768cause = readl(priv->io + KIRKWOOD_ERR_CAUSE);69if (unlikely(cause)) {70printk(KERN_WARNING "%s: got err interrupt 0x%lx\n",71__func__, cause);72writel(cause, priv->io + KIRKWOOD_ERR_CAUSE);73return IRQ_HANDLED;74}7576/* we've enabled only bytes interrupts ... */77if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \78KIRKWOOD_INT_CAUSE_REC_BYTES)) {79printk(KERN_WARNING "%s: unexpected interrupt %lx\n",80__func__, status);81return IRQ_NONE;82}8384/* ack int */85writel(status, priv->io + KIRKWOOD_INT_CAUSE);8687if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES)88snd_pcm_period_elapsed(prdata->play_stream);8990if (status & KIRKWOOD_INT_CAUSE_REC_BYTES)91snd_pcm_period_elapsed(prdata->rec_stream);9293return IRQ_HANDLED;94}9596static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win,97unsigned long dma,98struct mbus_dram_target_info *dram)99{100int i;101102/* First disable and clear windows */103writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));104writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));105106/* try to find matching cs for current dma address */107for (i = 0; i < dram->num_cs; i++) {108struct mbus_dram_window *cs = dram->cs + i;109if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) {110writel(cs->base & 0xffff0000,111base + KIRKWOOD_AUDIO_WIN_BASE_REG(win));112writel(((cs->size - 1) & 0xffff0000) |113(cs->mbus_attr << 8) |114(dram->mbus_dram_target_id << 4) | 1,115base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win));116}117}118}119120static int kirkwood_dma_open(struct snd_pcm_substream *substream)121{122int err;123struct snd_pcm_runtime *runtime = substream->runtime;124struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;125struct snd_soc_platform *platform = soc_runtime->platform;126struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;127struct kirkwood_dma_data *priv;128struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);129unsigned long addr;130131priv = snd_soc_dai_get_dma_data(cpu_dai, substream);132snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw);133134/* Ensure that all constraints linked to dma burst are fulfilled */135err = snd_pcm_hw_constraint_minmax(runtime,136SNDRV_PCM_HW_PARAM_BUFFER_BYTES,137priv->burst * 2,138KIRKWOOD_AUDIO_BUF_MAX-1);139if (err < 0)140return err;141142err = snd_pcm_hw_constraint_step(runtime, 0,143SNDRV_PCM_HW_PARAM_BUFFER_BYTES,144priv->burst);145if (err < 0)146return err;147148err = snd_pcm_hw_constraint_step(substream->runtime, 0,149SNDRV_PCM_HW_PARAM_PERIOD_BYTES,150priv->burst);151if (err < 0)152return err;153154if (prdata == NULL) {155prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL);156if (prdata == NULL)157return -ENOMEM;158159prdata->data = priv;160161err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED,162"kirkwood-i2s", prdata);163if (err) {164kfree(prdata);165return -EBUSY;166}167168snd_soc_platform_set_drvdata(platform, prdata);169170/*171* Enable Error interrupts. We're only ack'ing them but172* it's useful for diagnostics173*/174writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK);175}176177addr = virt_to_phys(substream->dma_buffer.area);178if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {179prdata->play_stream = substream;180kirkwood_dma_conf_mbus_windows(priv->io,181KIRKWOOD_PLAYBACK_WIN, addr, priv->dram);182} else {183prdata->rec_stream = substream;184kirkwood_dma_conf_mbus_windows(priv->io,185KIRKWOOD_RECORD_WIN, addr, priv->dram);186}187188return 0;189}190191static int kirkwood_dma_close(struct snd_pcm_substream *substream)192{193struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;194struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;195struct snd_soc_platform *platform = soc_runtime->platform;196struct kirkwood_dma_priv *prdata = snd_soc_platform_get_drvdata(platform);197struct kirkwood_dma_data *priv;198199priv = snd_soc_dai_get_dma_data(cpu_dai, substream);200201if (!prdata || !priv)202return 0;203204if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)205prdata->play_stream = NULL;206else207prdata->rec_stream = NULL;208209if (!prdata->play_stream && !prdata->rec_stream) {210writel(0, priv->io + KIRKWOOD_ERR_MASK);211free_irq(priv->irq, prdata);212kfree(prdata);213snd_soc_platform_set_drvdata(platform, NULL);214}215216return 0;217}218219static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream,220struct snd_pcm_hw_params *params)221{222struct snd_pcm_runtime *runtime = substream->runtime;223224snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);225runtime->dma_bytes = params_buffer_bytes(params);226227return 0;228}229230static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream)231{232snd_pcm_set_runtime_buffer(substream, NULL);233return 0;234}235236static int kirkwood_dma_prepare(struct snd_pcm_substream *substream)237{238struct snd_pcm_runtime *runtime = substream->runtime;239struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;240struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;241struct kirkwood_dma_data *priv;242unsigned long size, count;243244priv = snd_soc_dai_get_dma_data(cpu_dai, substream);245246/* compute buffer size in term of "words" as requested in specs */247size = frames_to_bytes(runtime, runtime->buffer_size);248size = (size>>2)-1;249count = snd_pcm_lib_period_bytes(substream);250251if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {252writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT);253writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR);254writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE);255} else {256writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT);257writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR);258writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE);259}260261262return 0;263}264265static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream266*substream)267{268struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;269struct snd_soc_dai *cpu_dai = soc_runtime->cpu_dai;270struct kirkwood_dma_data *priv;271snd_pcm_uframes_t count;272273priv = snd_soc_dai_get_dma_data(cpu_dai, substream);274275if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)276count = bytes_to_frames(substream->runtime,277readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT));278else279count = bytes_to_frames(substream->runtime,280readl(priv->io + KIRKWOOD_REC_BYTE_COUNT));281282return count;283}284285struct snd_pcm_ops kirkwood_dma_ops = {286.open = kirkwood_dma_open,287.close = kirkwood_dma_close,288.ioctl = snd_pcm_lib_ioctl,289.hw_params = kirkwood_dma_hw_params,290.hw_free = kirkwood_dma_hw_free,291.prepare = kirkwood_dma_prepare,292.pointer = kirkwood_dma_pointer,293};294295static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm,296int stream)297{298struct snd_pcm_substream *substream = pcm->streams[stream].substream;299struct snd_dma_buffer *buf = &substream->dma_buffer;300size_t size = kirkwood_dma_snd_hw.buffer_bytes_max;301302buf->dev.type = SNDRV_DMA_TYPE_DEV;303buf->dev.dev = pcm->card->dev;304buf->area = dma_alloc_coherent(pcm->card->dev, size,305&buf->addr, GFP_KERNEL);306if (!buf->area)307return -ENOMEM;308buf->bytes = size;309buf->private_data = NULL;310311return 0;312}313314static int kirkwood_dma_new(struct snd_card *card,315struct snd_soc_dai *dai, struct snd_pcm *pcm)316{317int ret;318319if (!card->dev->dma_mask)320card->dev->dma_mask = &kirkwood_dma_dmamask;321if (!card->dev->coherent_dma_mask)322card->dev->coherent_dma_mask = 0xffffffff;323324if (dai->driver->playback.channels_min) {325ret = kirkwood_dma_preallocate_dma_buffer(pcm,326SNDRV_PCM_STREAM_PLAYBACK);327if (ret)328return ret;329}330331if (dai->driver->capture.channels_min) {332ret = kirkwood_dma_preallocate_dma_buffer(pcm,333SNDRV_PCM_STREAM_CAPTURE);334if (ret)335return ret;336}337338return 0;339}340341static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm)342{343struct snd_pcm_substream *substream;344struct snd_dma_buffer *buf;345int stream;346347for (stream = 0; stream < 2; stream++) {348substream = pcm->streams[stream].substream;349if (!substream)350continue;351buf = &substream->dma_buffer;352if (!buf->area)353continue;354355dma_free_coherent(pcm->card->dev, buf->bytes,356buf->area, buf->addr);357buf->area = NULL;358}359}360361static struct snd_soc_platform_driver kirkwood_soc_platform = {362.ops = &kirkwood_dma_ops,363.pcm_new = kirkwood_dma_new,364.pcm_free = kirkwood_dma_free_dma_buffers,365};366367static int __devinit kirkwood_soc_platform_probe(struct platform_device *pdev)368{369return snd_soc_register_platform(&pdev->dev, &kirkwood_soc_platform);370}371372static int __devexit kirkwood_soc_platform_remove(struct platform_device *pdev)373{374snd_soc_unregister_platform(&pdev->dev);375return 0;376}377378static struct platform_driver kirkwood_pcm_driver = {379.driver = {380.name = "kirkwood-pcm-audio",381.owner = THIS_MODULE,382},383384.probe = kirkwood_soc_platform_probe,385.remove = __devexit_p(kirkwood_soc_platform_remove),386};387388static int __init kirkwood_pcm_init(void)389{390return platform_driver_register(&kirkwood_pcm_driver);391}392module_init(kirkwood_pcm_init);393394static void __exit kirkwood_pcm_exit(void)395{396platform_driver_unregister(&kirkwood_pcm_driver);397}398module_exit(kirkwood_pcm_exit);399400MODULE_AUTHOR("Arnaud Patard <[email protected]>");401MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module");402MODULE_LICENSE("GPL");403MODULE_ALIAS("platform:kirkwood-pcm-audio");404405406