|
|
|
@@ -6,10 +6,12 @@
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#include <linux/clk.h>
|
|
|
|
|
#include <linux/dma-mapping.h>
|
|
|
|
|
#include <linux/io.h>
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
|
#include <linux/of.h>
|
|
|
|
|
#include <linux/of_device.h>
|
|
|
|
|
#include <sound/dmaengine_pcm.h>
|
|
|
|
|
#include <sound/pcm.h>
|
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
|
#include <sound/soc.h>
|
|
|
|
@@ -343,6 +345,53 @@ static const struct snd_soc_component_driver kmb_component = {
|
|
|
|
|
.pointer = kmb_pcm_pointer,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct snd_soc_component_driver kmb_component_dma = {
|
|
|
|
|
.name = "kmb",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int kmb_probe(struct snd_soc_dai *cpu_dai)
|
|
|
|
|
{
|
|
|
|
|
struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
|
|
|
|
|
|
|
|
|
|
if (kmb_i2s->use_pio)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
snd_soc_dai_init_dma_data(cpu_dai, &kmb_i2s->play_dma_data,
|
|
|
|
|
&kmb_i2s->capture_dma_data);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void kmb_i2s_enable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream)
|
|
|
|
|
{
|
|
|
|
|
u32 dma_reg;
|
|
|
|
|
|
|
|
|
|
dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR);
|
|
|
|
|
/* Enable DMA handshake for stream */
|
|
|
|
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
|
dma_reg |= I2S_DMAEN_TXBLOCK;
|
|
|
|
|
else
|
|
|
|
|
dma_reg |= I2S_DMAEN_RXBLOCK;
|
|
|
|
|
|
|
|
|
|
writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void kmb_i2s_disable_dma(struct kmb_i2s_info *kmb_i2s, u32 stream)
|
|
|
|
|
{
|
|
|
|
|
u32 dma_reg;
|
|
|
|
|
|
|
|
|
|
dma_reg = readl(kmb_i2s->i2s_base + I2S_DMACR);
|
|
|
|
|
/* Disable DMA handshake for stream */
|
|
|
|
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
|
dma_reg &= ~I2S_DMAEN_TXBLOCK;
|
|
|
|
|
writel(1, kmb_i2s->i2s_base + I2S_RTXDMA);
|
|
|
|
|
} else {
|
|
|
|
|
dma_reg &= ~I2S_DMAEN_RXBLOCK;
|
|
|
|
|
writel(1, kmb_i2s->i2s_base + I2S_RRXDMA);
|
|
|
|
|
}
|
|
|
|
|
writel(dma_reg, kmb_i2s->i2s_base + I2S_DMACR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s,
|
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
|
{
|
|
|
|
@@ -356,7 +405,11 @@ static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s,
|
|
|
|
|
else
|
|
|
|
|
writel(1, kmb_i2s->i2s_base + IRER);
|
|
|
|
|
|
|
|
|
|
kmb_i2s_irq_trigger(kmb_i2s, substream->stream, config->chan_nr, true);
|
|
|
|
|
if (kmb_i2s->use_pio)
|
|
|
|
|
kmb_i2s_irq_trigger(kmb_i2s, substream->stream,
|
|
|
|
|
config->chan_nr, true);
|
|
|
|
|
else
|
|
|
|
|
kmb_i2s_enable_dma(kmb_i2s, substream->stream);
|
|
|
|
|
|
|
|
|
|
if (kmb_i2s->clock_provider)
|
|
|
|
|
writel(1, kmb_i2s->i2s_base + CER);
|
|
|
|
@@ -434,7 +487,8 @@ static int kmb_dai_trigger(struct snd_pcm_substream *substream,
|
|
|
|
|
break;
|
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
|
|
|
kmb_i2s->active--;
|
|
|
|
|
kmb_i2s_stop(kmb_i2s, substream);
|
|
|
|
|
if (kmb_i2s->use_pio)
|
|
|
|
|
kmb_i2s_stop(kmb_i2s, substream);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return -EINVAL;
|
|
|
|
@@ -485,16 +539,22 @@ static int kmb_dai_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
|
config->data_width = 16;
|
|
|
|
|
kmb_i2s->ccr = 0x00;
|
|
|
|
|
kmb_i2s->xfer_resolution = 0x02;
|
|
|
|
|
kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
|
|
|
kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
|
|
|
break;
|
|
|
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
|
|
|
config->data_width = 32;
|
|
|
|
|
kmb_i2s->ccr = 0x14;
|
|
|
|
|
kmb_i2s->xfer_resolution = 0x05;
|
|
|
|
|
kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
|
|
|
kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
|
|
|
break;
|
|
|
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
|
|
|
config->data_width = 32;
|
|
|
|
|
kmb_i2s->ccr = 0x10;
|
|
|
|
|
kmb_i2s->xfer_resolution = 0x05;
|
|
|
|
|
kmb_i2s->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
|
|
|
kmb_i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt");
|
|
|
|
@@ -572,9 +632,56 @@ static int kmb_dai_prepare(struct snd_pcm_substream *substream,
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int kmb_dai_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
struct snd_soc_dai *cpu_dai)
|
|
|
|
|
{
|
|
|
|
|
struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
|
|
|
|
|
struct snd_dmaengine_dai_dma_data *dma_data;
|
|
|
|
|
|
|
|
|
|
if (kmb_i2s->use_pio)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
|
dma_data = &kmb_i2s->play_dma_data;
|
|
|
|
|
else
|
|
|
|
|
dma_data = &kmb_i2s->capture_dma_data;
|
|
|
|
|
|
|
|
|
|
snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int kmb_dai_hw_free(struct snd_pcm_substream *substream,
|
|
|
|
|
struct snd_soc_dai *cpu_dai)
|
|
|
|
|
{
|
|
|
|
|
struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
|
|
|
|
|
/* I2S Programming sequence in Keem_Bay_VPU_DB_v1.1 */
|
|
|
|
|
if (kmb_i2s->use_pio)
|
|
|
|
|
kmb_i2s_clear_irqs(kmb_i2s, substream->stream);
|
|
|
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
|
writel(0, kmb_i2s->i2s_base + ITER);
|
|
|
|
|
else
|
|
|
|
|
writel(0, kmb_i2s->i2s_base + IRER);
|
|
|
|
|
|
|
|
|
|
if (kmb_i2s->use_pio)
|
|
|
|
|
kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false);
|
|
|
|
|
else
|
|
|
|
|
kmb_i2s_disable_dma(kmb_i2s, substream->stream);
|
|
|
|
|
|
|
|
|
|
if (!kmb_i2s->active) {
|
|
|
|
|
writel(0, kmb_i2s->i2s_base + CER);
|
|
|
|
|
writel(0, kmb_i2s->i2s_base + IER);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct snd_soc_dai_ops kmb_dai_ops = {
|
|
|
|
|
.startup = kmb_dai_startup,
|
|
|
|
|
.trigger = kmb_dai_trigger,
|
|
|
|
|
.hw_params = kmb_dai_hw_params,
|
|
|
|
|
.hw_free = kmb_dai_hw_free,
|
|
|
|
|
.prepare = kmb_dai_prepare,
|
|
|
|
|
.set_fmt = kmb_set_dai_fmt,
|
|
|
|
|
};
|
|
|
|
@@ -607,6 +714,7 @@ static struct snd_soc_dai_driver intel_kmb_i2s_dai[] = {
|
|
|
|
|
SNDRV_PCM_FMTBIT_S16_LE),
|
|
|
|
|
},
|
|
|
|
|
.ops = &kmb_dai_ops,
|
|
|
|
|
.probe = kmb_probe,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -626,6 +734,7 @@ static struct snd_soc_dai_driver intel_kmb_tdm_dai[] = {
|
|
|
|
|
SNDRV_PCM_FMTBIT_S16_LE),
|
|
|
|
|
},
|
|
|
|
|
.ops = &kmb_dai_ops,
|
|
|
|
|
.probe = kmb_probe,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -637,10 +746,12 @@ static const struct of_device_id kmb_plat_of_match[] = {
|
|
|
|
|
|
|
|
|
|
static int kmb_plat_dai_probe(struct platform_device *pdev)
|
|
|
|
|
{
|
|
|
|
|
struct device_node *np = pdev->dev.of_node;
|
|
|
|
|
struct snd_soc_dai_driver *kmb_i2s_dai;
|
|
|
|
|
const struct of_device_id *match;
|
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
|
struct kmb_i2s_info *kmb_i2s;
|
|
|
|
|
struct resource *res;
|
|
|
|
|
int ret, irq;
|
|
|
|
|
u32 comp1_reg;
|
|
|
|
|
|
|
|
|
@@ -682,7 +793,7 @@ static int kmb_plat_dai_probe(struct platform_device *pdev)
|
|
|
|
|
return PTR_ERR(kmb_i2s->clk_i2s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kmb_i2s->i2s_base = devm_platform_ioremap_resource(pdev, 0);
|
|
|
|
|
kmb_i2s->i2s_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
|
|
|
|
if (IS_ERR(kmb_i2s->i2s_base))
|
|
|
|
|
return PTR_ERR(kmb_i2s->i2s_base);
|
|
|
|
|
|
|
|
|
@@ -692,22 +803,38 @@ static int kmb_plat_dai_probe(struct platform_device *pdev)
|
|
|
|
|
|
|
|
|
|
kmb_i2s->dev = &pdev->dev;
|
|
|
|
|
|
|
|
|
|
irq = platform_get_irq_optional(pdev, 0);
|
|
|
|
|
if (irq > 0) {
|
|
|
|
|
ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0,
|
|
|
|
|
pdev->name, kmb_i2s);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
dev_err(dev, "failed to request irq\n");
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1);
|
|
|
|
|
|
|
|
|
|
kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2;
|
|
|
|
|
|
|
|
|
|
ret = devm_snd_soc_register_component(dev, &kmb_component,
|
|
|
|
|
kmb_i2s_dai, 1);
|
|
|
|
|
kmb_i2s->use_pio = !(of_property_read_bool(np, "dmas"));
|
|
|
|
|
|
|
|
|
|
if (kmb_i2s->use_pio) {
|
|
|
|
|
irq = platform_get_irq_optional(pdev, 0);
|
|
|
|
|
if (irq > 0) {
|
|
|
|
|
ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0,
|
|
|
|
|
pdev->name, kmb_i2s);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
dev_err(dev, "failed to request irq\n");
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ret = devm_snd_soc_register_component(dev, &kmb_component,
|
|
|
|
|
kmb_i2s_dai, 1);
|
|
|
|
|
} else {
|
|
|
|
|
kmb_i2s->play_dma_data.addr = res->start + I2S_TXDMA;
|
|
|
|
|
kmb_i2s->capture_dma_data.addr = res->start + I2S_RXDMA;
|
|
|
|
|
ret = snd_dmaengine_pcm_register(&pdev->dev,
|
|
|
|
|
NULL, 0);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(&pdev->dev, "could not register dmaengine: %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
ret = devm_snd_soc_register_component(dev, &kmb_component_dma,
|
|
|
|
|
kmb_i2s_dai, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dev, "not able to register dai\n");
|
|
|
|
|
return ret;
|
|
|
|
|