diff options
Diffstat (limited to 'sound/soc/sh')
-rw-r--r-- | sound/soc/sh/Kconfig | 23 | ||||
-rw-r--r-- | sound/soc/sh/Makefile | 6 | ||||
-rw-r--r-- | sound/soc/sh/fsi-da7210.c | 83 | ||||
-rw-r--r-- | sound/soc/sh/fsi.c | 227 | ||||
-rw-r--r-- | sound/soc/sh/migor.c | 218 | ||||
-rw-r--r-- | sound/soc/sh/siu.h | 193 | ||||
-rw-r--r-- | sound/soc/sh/siu_dai.c | 847 | ||||
-rw-r--r-- | sound/soc/sh/siu_pcm.c | 616 |
8 files changed, 2130 insertions, 83 deletions
diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index 9e6976586554..106674979b53 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -26,6 +26,13 @@ config SND_SOC_SH4_FSI help This option enables FSI sound support +config SND_SOC_SH4_SIU + tristate + depends on (SUPERH || ARCH_SHMOBILE) && HAVE_CLK + select DMA_ENGINE + select DMADEVICES + select SH_DMAE + ## ## Boards ## @@ -47,4 +54,20 @@ config SND_FSI_AK4642 This option enables generic sound support for the FSI - AK4642 unit +config SND_FSI_DA7210 + bool "FSI-DA7210 sound support" + depends on SND_SOC_SH4_FSI + select SND_SOC_DA7210 + help + This option enables generic sound support for the + FSI - DA7210 unit + +config SND_SIU_MIGOR + tristate "SIU sound support on Migo-R" + depends on SH_MIGOR + select SND_SOC_SH4_SIU + select SND_SOC_WM8978 + help + This option enables sound support for the SH7722 Migo-R board + endmenu diff --git a/sound/soc/sh/Makefile b/sound/soc/sh/Makefile index a6997872f24e..8a5a19293bda 100644 --- a/sound/soc/sh/Makefile +++ b/sound/soc/sh/Makefile @@ -6,13 +6,19 @@ obj-$(CONFIG_SND_SOC_PCM_SH7760) += snd-soc-dma-sh7760.o snd-soc-hac-objs := hac.o snd-soc-ssi-objs := ssi.o snd-soc-fsi-objs := fsi.o +snd-soc-siu-objs := siu_pcm.o siu_dai.o obj-$(CONFIG_SND_SOC_SH4_HAC) += snd-soc-hac.o obj-$(CONFIG_SND_SOC_SH4_SSI) += snd-soc-ssi.o obj-$(CONFIG_SND_SOC_SH4_FSI) += snd-soc-fsi.o +obj-$(CONFIG_SND_SOC_SH4_SIU) += snd-soc-siu.o ## boards snd-soc-sh7760-ac97-objs := sh7760-ac97.o snd-soc-fsi-ak4642-objs := fsi-ak4642.o +snd-soc-fsi-da7210-objs := fsi-da7210.o +snd-soc-migor-objs := migor.o obj-$(CONFIG_SND_SH7760_AC97) += snd-soc-sh7760-ac97.o obj-$(CONFIG_SND_FSI_AK4642) += snd-soc-fsi-ak4642.o +obj-$(CONFIG_SND_FSI_DA7210) += snd-soc-fsi-da7210.o +obj-$(CONFIG_SND_SIU_MIGOR) += snd-soc-migor.o diff --git a/sound/soc/sh/fsi-da7210.c b/sound/soc/sh/fsi-da7210.c new file mode 100644 index 000000000000..33b4d177f466 --- /dev/null +++ b/sound/soc/sh/fsi-da7210.c @@ -0,0 +1,83 @@ +/* + * fsi-da7210.c + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <sound/sh_fsi.h> +#include "../codecs/da7210.h" + +static int fsi_da7210_init(struct snd_soc_codec *codec) +{ + return snd_soc_dai_set_fmt(&da7210_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); +} + +static struct snd_soc_dai_link fsi_da7210_dai = { + .name = "DA7210", + .stream_name = "DA7210", + .cpu_dai = &fsi_soc_dai[1], /* FSI B */ + .codec_dai = &da7210_dai, + .init = fsi_da7210_init, +}; + +static struct snd_soc_card fsi_soc_card = { + .name = "FSI", + .platform = &fsi_soc_platform, + .dai_link = &fsi_da7210_dai, + .num_links = 1, +}; + +static struct snd_soc_device fsi_da7210_snd_devdata = { + .card = &fsi_soc_card, + .codec_dev = &soc_codec_dev_da7210, +}; + +static struct platform_device *fsi_da7210_snd_device; + +static int __init fsi_da7210_sound_init(void) +{ + int ret; + + fsi_da7210_snd_device = platform_device_alloc("soc-audio", -1); + if (!fsi_da7210_snd_device) + return -ENOMEM; + + platform_set_drvdata(fsi_da7210_snd_device, &fsi_da7210_snd_devdata); + fsi_da7210_snd_devdata.dev = &fsi_da7210_snd_device->dev; + ret = platform_device_add(fsi_da7210_snd_device); + if (ret) + platform_device_put(fsi_da7210_snd_device); + + return ret; +} + +static void __exit fsi_da7210_sound_exit(void) +{ + platform_device_unregister(fsi_da7210_snd_device); +} + +module_init(fsi_da7210_sound_init); +module_exit(fsi_da7210_sound_exit); + +/* Module information */ +MODULE_DESCRIPTION("ALSA SoC FSI DA2710"); +MODULE_AUTHOR("Kuninori Morimoto <morimoto.kuninori@renesas.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c index 42813b808389..993abb730dfa 100644 --- a/sound/soc/sh/fsi.c +++ b/sound/soc/sh/fsi.c @@ -67,6 +67,7 @@ /* DOFF_ST */ #define ERR_OVER 0x00000010 #define ERR_UNDER 0x00000001 +#define ST_ERR (ERR_OVER | ERR_UNDER) /* CLK_RST */ #define B_CLK 0x00000010 @@ -92,6 +93,7 @@ struct fsi_priv { void __iomem *base; struct snd_pcm_substream *substream; + struct fsi_master *master; int fifo_max; int chan; @@ -108,10 +110,9 @@ struct fsi_master { struct fsi_priv fsia; struct fsi_priv fsib; struct sh_fsi_platform_info *info; + spinlock_t lock; }; -static struct fsi_master *master; - /************************************************************************ @@ -119,35 +120,35 @@ static struct fsi_master *master; ************************************************************************/ -static int __fsi_reg_write(u32 reg, u32 data) +static void __fsi_reg_write(u32 reg, u32 data) { /* valid data area is 24bit */ data &= 0x00ffffff; - return ctrl_outl(data, reg); + __raw_writel(data, reg); } static u32 __fsi_reg_read(u32 reg) { - return ctrl_inl(reg); + return __raw_readl(reg); } -static int __fsi_reg_mask_set(u32 reg, u32 mask, u32 data) +static void __fsi_reg_mask_set(u32 reg, u32 mask, u32 data) { u32 val = __fsi_reg_read(reg); val &= ~mask; val |= data & mask; - return __fsi_reg_write(reg, val); + __fsi_reg_write(reg, val); } -static int fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data) +static void fsi_reg_write(struct fsi_priv *fsi, u32 reg, u32 data) { if (reg > REG_END) - return -1; + return; - return __fsi_reg_write((u32)(fsi->base + reg), data); + __fsi_reg_write((u32)(fsi->base + reg), data); } static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg) @@ -158,39 +159,55 @@ static u32 fsi_reg_read(struct fsi_priv *fsi, u32 reg) return __fsi_reg_read((u32)(fsi->base + reg)); } -static int fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data) +static void fsi_reg_mask_set(struct fsi_priv *fsi, u32 reg, u32 mask, u32 data) { if (reg > REG_END) - return -1; + return; - return __fsi_reg_mask_set((u32)(fsi->base + reg), mask, data); + __fsi_reg_mask_set((u32)(fsi->base + reg), mask, data); } -static int fsi_master_write(u32 reg, u32 data) +static void fsi_master_write(struct fsi_master *master, u32 reg, u32 data) { + unsigned long flags; + if ((reg < MREG_START) || (reg > MREG_END)) - return -1; + return; - return __fsi_reg_write((u32)(master->base + reg), data); + spin_lock_irqsave(&master->lock, flags); + __fsi_reg_write((u32)(master->base + reg), data); + spin_unlock_irqrestore(&master->lock, flags); } -static u32 fsi_master_read(u32 reg) +static u32 fsi_master_read(struct fsi_master *master, u32 reg) { + u32 ret; + unsigned long flags; + if ((reg < MREG_START) || (reg > MREG_END)) return 0; - return __fsi_reg_read((u32)(master->base + reg)); + spin_lock_irqsave(&master->lock, flags); + ret = __fsi_reg_read((u32)(master->base + reg)); + spin_unlock_irqrestore(&master->lock, flags); + + return ret; } -static int fsi_master_mask_set(u32 reg, u32 mask, u32 data) +static void fsi_master_mask_set(struct fsi_master *master, + u32 reg, u32 mask, u32 data) { + unsigned long flags; + if ((reg < MREG_START) || (reg > MREG_END)) - return -1; + return; - return __fsi_reg_mask_set((u32)(master->base + reg), mask, data); + spin_lock_irqsave(&master->lock, flags); + __fsi_reg_mask_set((u32)(master->base + reg), mask, data); + spin_unlock_irqrestore(&master->lock, flags); } /************************************************************************ @@ -200,43 +217,35 @@ static int fsi_master_mask_set(u32 reg, u32 mask, u32 data) ************************************************************************/ -static struct fsi_priv *fsi_get(struct snd_pcm_substream *substream) +static struct fsi_master *fsi_get_master(struct fsi_priv *fsi) { - struct snd_soc_pcm_runtime *rtd; - struct fsi_priv *fsi = NULL; + return fsi->master; +} - if (!substream || !master) - return NULL; +static int fsi_is_port_a(struct fsi_priv *fsi) +{ + return fsi->master->base == fsi->base; +} - rtd = substream->private_data; - switch (rtd->dai->cpu_dai->id) { - case 0: - fsi = &master->fsia; - break; - case 1: - fsi = &master->fsib; - break; - } +static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *machine = rtd->dai; - return fsi; + return machine->cpu_dai; } -static int fsi_is_port_a(struct fsi_priv *fsi) +static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream) { - /* return - * 1 : port a - * 0 : port b - */ + struct snd_soc_dai *dai = fsi_get_dai(substream); - if (fsi == &master->fsia) - return 1; - - return 0; + return dai->private_data; } static u32 fsi_get_info_flags(struct fsi_priv *fsi) { int is_porta = fsi_is_port_a(fsi); + struct fsi_master *master = fsi_get_master(fsi); return is_porta ? master->info->porta_flags : master->info->portb_flags; @@ -314,27 +323,30 @@ static int fsi_get_fifo_residue(struct fsi_priv *fsi, int is_play) static void fsi_irq_enable(struct fsi_priv *fsi, int is_play) { u32 data = fsi_port_ab_io_bit(fsi, is_play); + struct fsi_master *master = fsi_get_master(fsi); - fsi_master_mask_set(IMSK, data, data); - fsi_master_mask_set(IEMSK, data, data); + fsi_master_mask_set(master, IMSK, data, data); + fsi_master_mask_set(master, IEMSK, data, data); } static void fsi_irq_disable(struct fsi_priv *fsi, int is_play) { u32 data = fsi_port_ab_io_bit(fsi, is_play); + struct fsi_master *master = fsi_get_master(fsi); - fsi_master_mask_set(IMSK, data, 0); - fsi_master_mask_set(IEMSK, data, 0); + fsi_master_mask_set(master, IMSK, data, 0); + fsi_master_mask_set(master, IEMSK, data, 0); } static void fsi_clk_ctrl(struct fsi_priv *fsi, int enable) { u32 val = fsi_is_port_a(fsi) ? (1 << 0) : (1 << 4); + struct fsi_master *master = fsi_get_master(fsi); if (enable) - fsi_master_mask_set(CLK_RST, val, val); + fsi_master_mask_set(master, CLK_RST, val, val); else - fsi_master_mask_set(CLK_RST, val, 0); + fsi_master_mask_set(master, CLK_RST, val, 0); } static void fsi_irq_init(struct fsi_priv *fsi, int is_play) @@ -355,43 +367,46 @@ static void fsi_irq_init(struct fsi_priv *fsi, int is_play) fsi_reg_mask_set(fsi, ctrl, FIFO_CLR, FIFO_CLR); /* clear interrupt factor */ - fsi_master_mask_set(INT_ST, data, 0); + fsi_master_mask_set(fsi_get_master(fsi), INT_ST, data, 0); } -static void fsi_soft_all_reset(void) +static void fsi_soft_all_reset(struct fsi_master *master) { - u32 status = fsi_master_read(SOFT_RST); + u32 status = fsi_master_read(master, SOFT_RST); /* port AB reset */ status &= 0x000000ff; - fsi_master_write(SOFT_RST, status); + fsi_master_write(master, SOFT_RST, status); mdelay(10); /* soft reset */ status &= 0x000000f0; - fsi_master_write(SOFT_RST, status); + fsi_master_write(master, SOFT_RST, status); status |= 0x00000001; - fsi_master_write(SOFT_RST, status); + fsi_master_write(master, SOFT_RST, status); mdelay(10); } /* playback interrupt */ -static int fsi_data_push(struct fsi_priv *fsi) +static int fsi_data_push(struct fsi_priv *fsi, int startup) { struct snd_pcm_runtime *runtime; struct snd_pcm_substream *substream = NULL; + u32 status; int send; int fifo_free; int width; u8 *start; - int i; + int i, over_period; if (!fsi || !fsi->substream || !fsi->substream->runtime) return -EINVAL; - runtime = fsi->substream->runtime; + over_period = 0; + substream = fsi->substream; + runtime = substream->runtime; /* FSI FIFO has limit. * So, this driver can not send periods data at a time @@ -399,7 +414,7 @@ static int fsi_data_push(struct fsi_priv *fsi) if (fsi->byte_offset >= fsi->period_len * (fsi->periods + 1)) { - substream = fsi->substream; + over_period = 1; fsi->periods = (fsi->periods + 1) % runtime->periods; if (0 == fsi->periods) @@ -438,30 +453,44 @@ static int fsi_data_push(struct fsi_priv *fsi) fsi->byte_offset += send * width; + status = fsi_reg_read(fsi, DOFF_ST); + if (!startup) { + struct snd_soc_dai *dai = fsi_get_dai(substream); + + if (status & ERR_OVER) + dev_err(dai->dev, "over run\n"); + if (status & ERR_UNDER) + dev_err(dai->dev, "under run\n"); + } + fsi_reg_write(fsi, DOFF_ST, 0); + fsi_irq_enable(fsi, 1); - if (substream) + if (over_period) snd_pcm_period_elapsed(substream); return 0; } -static int fsi_data_pop(struct fsi_priv *fsi) +static int fsi_data_pop(struct fsi_priv *fsi, int startup) { struct snd_pcm_runtime *runtime; struct snd_pcm_substream *substream = NULL; + u32 status; int free; int fifo_fill; int width; u8 *start; - int i; + int i, over_period; if (!fsi || !fsi->substream || !fsi->substream->runtime) return -EINVAL; - runtime = fsi->substream->runtime; + over_period = 0; + substream = fsi->substream; + runtime = substream->runtime; /* FSI FIFO has limit. * So, this driver can not send periods data at a time @@ -469,7 +498,7 @@ static int fsi_data_pop(struct fsi_priv *fsi) if (fsi->byte_offset >= fsi->period_len * (fsi->periods + 1)) { - substream = fsi->substream; + over_period = 1; fsi->periods = (fsi->periods + 1) % runtime->periods; if (0 == fsi->periods) @@ -507,9 +536,20 @@ static int fsi_data_pop(struct fsi_priv *fsi) fsi->byte_offset += fifo_fill * width; + status = fsi_reg_read(fsi, DIFF_ST); + if (!startup) { + struct snd_soc_dai *dai = fsi_get_dai(substream); + + if (status & ERR_OVER) + dev_err(dai->dev, "over run\n"); + if (status & ERR_UNDER) + dev_err(dai->dev, "under run\n"); + } + fsi_reg_write(fsi, DIFF_ST, 0); + fsi_irq_enable(fsi, 0); - if (substream) + if (over_period) snd_pcm_period_elapsed(substream); return 0; @@ -517,23 +557,24 @@ static int fsi_data_pop(struct fsi_priv *fsi) static irqreturn_t fsi_interrupt(int irq, void *data) { - u32 status = fsi_master_read(SOFT_RST) & ~0x00000010; - u32 int_st = fsi_master_read(INT_ST); + struct fsi_master *master = data; + u32 status = fsi_master_read(master, SOFT_RST) & ~0x00000010; + u32 int_st = fsi_master_read(master, INT_ST); /* clear irq status */ - fsi_master_write(SOFT_RST, status); - fsi_master_write(SOFT_RST, status | 0x00000010); + fsi_master_write(master, SOFT_RST, status); + fsi_master_write(master, SOFT_RST, status | 0x00000010); if (int_st & INT_A_OUT) - fsi_data_push(&master->fsia); + fsi_data_push(&master->fsia, 0); if (int_st & INT_B_OUT) - fsi_data_push(&master->fsib); + fsi_data_push(&master->fsib, 0); if (int_st & INT_A_IN) - fsi_data_pop(&master->fsia); + fsi_data_pop(&master->fsia, 0); if (int_st & INT_B_IN) - fsi_data_pop(&master->fsib); + fsi_data_pop(&master->fsib, 0); - fsi_master_write(INT_ST, 0x0000000); + fsi_master_write(master, INT_ST, 0x0000000); return IRQ_HANDLED; } @@ -548,7 +589,7 @@ static irqreturn_t fsi_interrupt(int irq, void *data) static int fsi_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct fsi_priv *fsi = fsi_get(substream); + struct fsi_priv *fsi = fsi_get_priv(substream); const char *msg; u32 flags = fsi_get_info_flags(fsi); u32 fmt; @@ -667,7 +708,7 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream, static void fsi_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct fsi_priv *fsi = fsi_get(substream); + struct fsi_priv *fsi = fsi_get_priv(substream); int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; fsi_irq_disable(fsi, is_play); @@ -679,7 +720,7 @@ static void fsi_dai_shutdown(struct snd_pcm_substream *substream, static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct fsi_priv *fsi = fsi_get(substream); + struct fsi_priv *fsi = fsi_get_priv(substream); struct snd_pcm_runtime *runtime = substream->runtime; int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int ret = 0; @@ -689,7 +730,7 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd, fsi_stream_push(fsi, substream, frames_to_bytes(runtime, runtime->buffer_size), frames_to_bytes(runtime, runtime->period_size)); - ret = is_play ? fsi_data_push(fsi) : fsi_data_pop(fsi); + ret = is_play ? fsi_data_push(fsi, 1) : fsi_data_pop(fsi, 1); break; case SNDRV_PCM_TRIGGER_STOP: fsi_irq_disable(fsi, is_play); @@ -760,7 +801,7 @@ static int fsi_hw_free(struct snd_pcm_substream *substream) static snd_pcm_uframes_t fsi_pointer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - struct fsi_priv *fsi = fsi_get(substream); + struct fsi_priv *fsi = fsi_get_priv(substream); long location; location = (fsi->byte_offset - 1); @@ -870,10 +911,16 @@ EXPORT_SYMBOL_GPL(fsi_soc_platform); ************************************************************************/ static int fsi_probe(struct platform_device *pdev) { + struct fsi_master *master; struct resource *res; unsigned int irq; int ret; + if (0 != pdev->id) { + dev_err(&pdev->dev, "current fsi support id 0 only now\n"); + return -ENODEV; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!res || (int)irq <= 0) { @@ -899,15 +946,20 @@ static int fsi_probe(struct platform_device *pdev) master->irq = irq; master->info = pdev->dev.platform_data; master->fsia.base = master->base; + master->fsia.master = master; master->fsib.base = master->base + 0x40; + master->fsib.master = master; + spin_lock_init(&master->lock); pm_runtime_enable(&pdev->dev); pm_runtime_resume(&pdev->dev); fsi_soc_dai[0].dev = &pdev->dev; + fsi_soc_dai[0].private_data = &master->fsia; fsi_soc_dai[1].dev = &pdev->dev; + fsi_soc_dai[1].private_data = &master->fsib; - fsi_soft_all_reset(); + fsi_soft_all_reset(master); ret = request_irq(irq, &fsi_interrupt, IRQF_DISABLED, "fsi", master); if (ret) { @@ -937,6 +989,10 @@ exit: static int fsi_remove(struct platform_device *pdev) { + struct fsi_master *master; + + master = fsi_get_master(fsi_soc_dai[0].private_data); + snd_soc_unregister_dais(fsi_soc_dai, ARRAY_SIZE(fsi_soc_dai)); snd_soc_unregister_platform(&fsi_soc_platform); @@ -946,7 +1002,12 @@ static int fsi_remove(struct platform_device *pdev) iounmap(master->base); kfree(master); - master = NULL; + + fsi_soc_dai[0].dev = NULL; + fsi_soc_dai[0].private_data = NULL; + fsi_soc_dai[1].dev = NULL; + fsi_soc_dai[1].private_data = NULL; + return 0; } diff --git a/sound/soc/sh/migor.c b/sound/soc/sh/migor.c new file mode 100644 index 000000000000..b823a5c9b9bc --- /dev/null +++ b/sound/soc/sh/migor.c @@ -0,0 +1,218 @@ +/* + * ALSA SoC driver for Migo-R + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/module.h> + +#include <asm/clock.h> + +#include <cpu/sh7722.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "../codecs/wm8978.h" +#include "siu.h" + +/* Default 8000Hz sampling frequency */ +static unsigned long codec_freq = 8000 * 512; + +static unsigned int use_count; + +/* External clock, sourced from the codec at the SIUMCKB pin */ +static unsigned long siumckb_recalc(struct clk *clk) +{ + return codec_freq; +} + +static struct clk_ops siumckb_clk_ops = { + .recalc = siumckb_recalc, +}; + +static struct clk siumckb_clk = { + .name = "siumckb_clk", + .id = -1, + .ops = &siumckb_clk_ops, + .rate = 0, /* initialised at run-time */ +}; + +static int migor_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + int ret; + unsigned int rate = params_rate(params); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 13000000, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8978_OPCLKRATE, rate * 512); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(rtd->dai->cpu_dai, SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + codec_freq = rate * 512; + /* + * This propagates the parent frequency change to children and + * recalculates the frequency table + */ + clk_set_rate(&siumckb_clk, codec_freq); + dev_dbg(codec_dai->dev, "%s: configure %luHz\n", __func__, codec_freq); + + ret = snd_soc_dai_set_sysclk(rtd->dai->cpu_dai, SIU_CLKB_EXT, + codec_freq / 2, SND_SOC_CLOCK_IN); + + if (!ret) + use_count++; + + return ret; +} + +static int migor_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + + if (use_count) { + use_count--; + + if (!use_count) + snd_soc_dai_set_sysclk(codec_dai, WM8978_PLL, 0, + SND_SOC_CLOCK_IN); + } else { + dev_dbg(codec_dai->dev, "Unbalanced hw_free!\n"); + } + + return 0; +} + +static struct snd_soc_ops migor_dai_ops = { + .hw_params = migor_hw_params, + .hw_free = migor_hw_free, +}; + +static const struct snd_soc_dapm_widget migor_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Onboard Microphone", NULL), + SND_SOC_DAPM_MIC("External Microphone", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Headphone output connected to LHP/RHP, enable OUT4 for VMID */ + { "Headphone", NULL, "OUT4 VMID" }, + { "OUT4 VMID", NULL, "LHP" }, + { "OUT4 VMID", NULL, "RHP" }, + + /* On-board microphone */ + { "RMICN", NULL, "Mic Bias" }, + { "RMICP", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "Onboard Microphone" }, + + /* External microphone */ + { "LMICN", NULL, "Mic Bias" }, + { "LMICP", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "External Microphone" }, +}; + +static int migor_dai_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, migor_dapm_widgets, + ARRAY_SIZE(migor_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + return 0; +} + +/* migor digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link migor_dai = { + .name = "wm8978", + .stream_name = "WM8978", + .cpu_dai = &siu_i2s_dai, + .codec_dai = &wm8978_dai, + .ops = &migor_dai_ops, + .init = migor_dai_init, +}; + +/* migor audio machine driver */ +static struct snd_soc_card snd_soc_migor = { + .name = "Migo-R", + .platform = &siu_platform, + .dai_link = &migor_dai, + .num_links = 1, +}; + +/* migor audio subsystem */ +static struct snd_soc_device migor_snd_devdata = { + .card = &snd_soc_migor, + .codec_dev = &soc_codec_dev_wm8978, +}; + +static struct platform_device *migor_snd_device; + +static int __init migor_init(void) +{ + int ret; + + ret = clk_register(&siumckb_clk); + if (ret < 0) + return ret; + + /* Port number used on this machine: port B */ + migor_snd_device = platform_device_alloc("soc-audio", 1); + if (!migor_snd_device) { + ret = -ENOMEM; + goto epdevalloc; + } + + platform_set_drvdata(migor_snd_device, &migor_snd_devdata); + + migor_snd_devdata.dev = &migor_snd_device->dev; + + ret = platform_device_add(migor_snd_device); + if (ret) + goto epdevadd; + + return 0; + +epdevadd: + platform_device_put(migor_snd_device); +epdevalloc: + clk_unregister(&siumckb_clk); + return ret; +} + +static void __exit migor_exit(void) +{ + clk_unregister(&siumckb_clk); + platform_device_unregister(migor_snd_device); +} + +module_init(migor_init); +module_exit(migor_exit); + +MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); +MODULE_DESCRIPTION("ALSA SoC Migor"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sh/siu.h b/sound/soc/sh/siu.h new file mode 100644 index 000000000000..c0bfab8fed3d --- /dev/null +++ b/sound/soc/sh/siu.h @@ -0,0 +1,193 @@ +/* + * siu.h - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral. + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SIU_H +#define SIU_H + +/* Common kernel and user-space firmware-building defines and types */ + +#define YRAM0_SIZE (0x0040 / 4) /* 16 */ +#define YRAM1_SIZE (0x0080 / 4) /* 32 */ +#define YRAM2_SIZE (0x0040 / 4) /* 16 */ +#define YRAM3_SIZE (0x0080 / 4) /* 32 */ +#define YRAM4_SIZE (0x0080 / 4) /* 32 */ +#define YRAM_DEF_SIZE (YRAM0_SIZE + YRAM1_SIZE + YRAM2_SIZE + \ + YRAM3_SIZE + YRAM4_SIZE) +#define YRAM_FIR_SIZE (0x0400 / 4) /* 256 */ +#define YRAM_IIR_SIZE (0x0200 / 4) /* 128 */ + +#define XRAM0_SIZE (0x0400 / 4) /* 256 */ +#define XRAM1_SIZE (0x0200 / 4) /* 128 */ +#define XRAM2_SIZE (0x0200 / 4) /* 128 */ + +/* PRAM program array size */ +#define PRAM0_SIZE (0x0100 / 4) /* 64 */ +#define PRAM1_SIZE ((0x2000 - 0x0100) / 4) /* 1984 */ + +#include <linux/types.h> + +struct siu_spb_param { + __u32 ab1a; /* input FIFO address */ + __u32 ab0a; /* output FIFO address */ + __u32 dir; /* 0=the ather except CPUOUTPUT, 1=CPUINPUT */ + __u32 event; /* SPB program starting conditions */ + __u32 stfifo; /* STFIFO register setting value */ + __u32 trdat; /* TRDAT register setting value */ +}; + +struct siu_firmware { + __u32 yram_fir_coeff[YRAM_FIR_SIZE]; + __u32 pram0[PRAM0_SIZE]; + __u32 pram1[PRAM1_SIZE]; + __u32 yram0[YRAM0_SIZE]; + __u32 yram1[YRAM1_SIZE]; + __u32 yram2[YRAM2_SIZE]; + __u32 yram3[YRAM3_SIZE]; + __u32 yram4[YRAM4_SIZE]; + __u32 spbpar_num; + struct siu_spb_param spbpar[32]; +}; + +#ifdef __KERNEL__ + +#include <linux/dmaengine.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <asm/dmaengine.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc-dai.h> + +#define SIU_PERIOD_BYTES_MAX 8192 /* DMA transfer/period size */ +#define SIU_PERIOD_BYTES_MIN 256 /* DMA transfer/period size */ +#define SIU_PERIODS_MAX 64 /* Max periods in buffer */ +#define SIU_PERIODS_MIN 4 /* Min periods in buffer */ +#define SIU_BUFFER_BYTES_MAX (SIU_PERIOD_BYTES_MAX * SIU_PERIODS_MAX) + +/* SIU ports: only one can be used at a time */ +enum { + SIU_PORT_A, + SIU_PORT_B, + SIU_PORT_NUM, +}; + +/* SIU clock configuration */ +enum { + SIU_CLKA_PLL, + SIU_CLKA_EXT, + SIU_CLKB_PLL, + SIU_CLKB_EXT +}; + +struct siu_info { + int port_id; + u32 __iomem *pram; + u32 __iomem *xram; + u32 __iomem *yram; + u32 __iomem *reg; + struct siu_firmware fw; +}; + +struct siu_stream { + struct tasklet_struct tasklet; + struct snd_pcm_substream *substream; + snd_pcm_format_t format; + size_t buf_bytes; + size_t period_bytes; + int cur_period; /* Period currently in dma */ + u32 volume; + snd_pcm_sframes_t xfer_cnt; /* Number of frames */ + u8 rw_flg; /* transfer status */ + /* DMA status */ + struct dma_chan *chan; /* DMA channel */ + struct dma_async_tx_descriptor *tx_desc; + dma_cookie_t cookie; + struct sh_dmae_slave param; +}; + +struct siu_port { + unsigned long play_cap; /* Used to track full duplex */ + struct snd_pcm *pcm; + struct siu_stream playback; + struct siu_stream capture; + u32 stfifo; /* STFIFO value from firmware */ + u32 trdat; /* TRDAT value from firmware */ +}; + +extern struct siu_port *siu_ports[SIU_PORT_NUM]; + +static inline struct siu_port *siu_port_info(struct snd_pcm_substream *substream) +{ + struct platform_device *pdev = + to_platform_device(substream->pcm->card->dev); + return siu_ports[pdev->id]; +} + +/* Register access */ +static inline void siu_write32(u32 __iomem *addr, u32 val) +{ + __raw_writel(val, addr); +} + +static inline u32 siu_read32(u32 __iomem *addr) +{ + return __raw_readl(addr); +} + +/* SIU registers */ +#define SIU_IFCTL (0x000 / sizeof(u32)) +#define SIU_SRCTL (0x004 / sizeof(u32)) +#define SIU_SFORM (0x008 / sizeof(u32)) +#define SIU_CKCTL (0x00c / sizeof(u32)) +#define SIU_TRDAT (0x010 / sizeof(u32)) +#define SIU_STFIFO (0x014 / sizeof(u32)) +#define SIU_DPAK (0x01c / sizeof(u32)) +#define SIU_CKREV (0x020 / sizeof(u32)) +#define SIU_EVNTC (0x028 / sizeof(u32)) +#define SIU_SBCTL (0x040 / sizeof(u32)) +#define SIU_SBPSET (0x044 / sizeof(u32)) +#define SIU_SBFSTS (0x068 / sizeof(u32)) +#define SIU_SBDVCA (0x06c / sizeof(u32)) +#define SIU_SBDVCB (0x070 / sizeof(u32)) +#define SIU_SBACTIV (0x074 / sizeof(u32)) +#define SIU_DMAIA (0x090 / sizeof(u32)) +#define SIU_DMAIB (0x094 / sizeof(u32)) +#define SIU_DMAOA (0x098 / sizeof(u32)) +#define SIU_DMAOB (0x09c / sizeof(u32)) +#define SIU_DMAML (0x0a0 / sizeof(u32)) +#define SIU_SPSTS (0x0cc / sizeof(u32)) +#define SIU_SPCTL (0x0d0 / sizeof(u32)) +#define SIU_BRGASEL (0x100 / sizeof(u32)) +#define SIU_BRRA (0x104 / sizeof(u32)) +#define SIU_BRGBSEL (0x108 / sizeof(u32)) +#define SIU_BRRB (0x10c / sizeof(u32)) + +extern struct snd_soc_platform siu_platform; +extern struct snd_soc_dai siu_i2s_dai; + +int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card); +void siu_free_port(struct siu_port *port_info); + +#endif + +#endif /* SIU_H */ diff --git a/sound/soc/sh/siu_dai.c b/sound/soc/sh/siu_dai.c new file mode 100644 index 000000000000..5452d19607e1 --- /dev/null +++ b/sound/soc/sh/siu_dai.c @@ -0,0 +1,847 @@ +/* + * siu_dai.c - ALSA SoC driver for Renesas SH7343, SH7722 SIU peripheral. + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/pm_runtime.h> + +#include <asm/clock.h> +#include <asm/siu.h> + +#include <sound/control.h> +#include <sound/soc-dai.h> + +#include "siu.h" + +/* Board specifics */ +#if defined(CONFIG_CPU_SUBTYPE_SH7722) +# define SIU_MAX_VOLUME 0x1000 +#else +# define SIU_MAX_VOLUME 0x7fff +#endif + +#define PRAM_SIZE 0x2000 +#define XRAM_SIZE 0x800 +#define YRAM_SIZE 0x800 + +#define XRAM_OFFSET 0x4000 +#define YRAM_OFFSET 0x6000 +#define REG_OFFSET 0xc000 + +#define PLAYBACK_ENABLED 1 +#define CAPTURE_ENABLED 2 + +#define VOLUME_CAPTURE 0 +#define VOLUME_PLAYBACK 1 +#define DFLT_VOLUME_LEVEL 0x08000800 + +/* + * SPDIF is only available on port A and on some SIU implementations it is only + * available for input. Due to the lack of hardware to test it, SPDIF is left + * disabled in this driver version + */ +struct format_flag { + u32 i2s; + u32 pcm; + u32 spdif; + u32 mask; +}; + +struct port_flag { + struct format_flag playback; + struct format_flag capture; +}; + +static struct port_flag siu_flags[SIU_PORT_NUM] = { + [SIU_PORT_A] = { + .playback = { + .i2s = 0x50000000, + .pcm = 0x40000000, + .spdif = 0x80000000, /* not on all SIU versions */ + .mask = 0xd0000000, + }, + .capture = { + .i2s = 0x05000000, + .pcm = 0x04000000, + .spdif = 0x08000000, + .mask = 0x0d000000, + }, + }, + [SIU_PORT_B] = { + .playback = { + .i2s = 0x00500000, + .pcm = 0x00400000, + .spdif = 0, /* impossible - turn off */ + .mask = 0x00500000, + }, + .capture = { + .i2s = 0x00050000, + .pcm = 0x00040000, + .spdif = 0, /* impossible - turn off */ + .mask = 0x00050000, + }, + }, +}; + +static void siu_dai_start(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + + dev_dbg(port_info->pcm->card->dev, "%s\n", __func__); + + /* Turn on SIU clock */ + pm_runtime_get_sync(siu_i2s_dai.dev); + + /* Issue software reset to siu */ + siu_write32(base + SIU_SRCTL, 0); + + /* Wait for the reset to take effect */ + udelay(1); + + port_info->stfifo = 0; + port_info->trdat = 0; + + /* portA, portB, SIU operate */ + siu_write32(base + SIU_SRCTL, 0x301); + + /* portA=256fs, portB=256fs */ + siu_write32(base + SIU_CKCTL, 0x40400000); + + /* portA's BRG does not divide SIUCKA */ + siu_write32(base + SIU_BRGASEL, 0); + siu_write32(base + SIU_BRRA, 0); + + /* portB's BRG divides SIUCKB by half */ + siu_write32(base + SIU_BRGBSEL, 1); + siu_write32(base + SIU_BRRB, 0); + + siu_write32(base + SIU_IFCTL, 0x44440000); + + /* portA: 32 bit/fs, master; portB: 32 bit/fs, master */ + siu_write32(base + SIU_SFORM, 0x0c0c0000); + + /* + * Volume levels: looks like the DSP firmware implements volume controls + * differently from what's described in the datasheet + */ + siu_write32(base + SIU_SBDVCA, port_info->playback.volume); + siu_write32(base + SIU_SBDVCB, port_info->capture.volume); +} + +static void siu_dai_stop(void) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + + /* SIU software reset */ + siu_write32(base + SIU_SRCTL, 0); + + /* Turn off SIU clock */ + pm_runtime_put_sync(siu_i2s_dai.dev); +} + +static void siu_dai_spbAselect(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct siu_firmware *fw = &info->fw; + u32 *ydef = fw->yram0; + u32 idx; + + /* path A use */ + if (!info->port_id) + idx = 1; /* portA */ + else + idx = 2; /* portB */ + + ydef[0] = (fw->spbpar[idx].ab1a << 16) | + (fw->spbpar[idx].ab0a << 8) | + (fw->spbpar[idx].dir << 7) | 3; + ydef[1] = fw->yram0[1]; /* 0x03000300 */ + ydef[2] = (16 / 2) << 24; + ydef[3] = fw->yram0[3]; /* 0 */ + ydef[4] = fw->yram0[4]; /* 0 */ + ydef[7] = fw->spbpar[idx].event; + port_info->stfifo |= fw->spbpar[idx].stfifo; + port_info->trdat |= fw->spbpar[idx].trdat; +} + +static void siu_dai_spbBselect(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct siu_firmware *fw = &info->fw; + u32 *ydef = fw->yram0; + u32 idx; + + /* path B use */ + if (!info->port_id) + idx = 7; /* portA */ + else + idx = 8; /* portB */ + + ydef[5] = (fw->spbpar[idx].ab1a << 16) | + (fw->spbpar[idx].ab0a << 8) | 1; + ydef[6] = fw->spbpar[idx].event; + port_info->stfifo |= fw->spbpar[idx].stfifo; + port_info->trdat |= fw->spbpar[idx].trdat; +} + +static void siu_dai_open(struct siu_stream *siu_stream) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + u32 srctl, ifctl; + + srctl = siu_read32(base + SIU_SRCTL); + ifctl = siu_read32(base + SIU_IFCTL); + + switch (info->port_id) { + case SIU_PORT_A: + /* portA operates */ + srctl |= 0x200; + ifctl &= ~0xc2; + break; + case SIU_PORT_B: + /* portB operates */ + srctl |= 0x100; + ifctl &= ~0x31; + break; + } + + siu_write32(base + SIU_SRCTL, srctl); + /* Unmute and configure portA */ + siu_write32(base + SIU_IFCTL, ifctl); +} + +/* + * At the moment only fixed Left-upper, Left-lower, Right-upper, Right-lower + * packing is supported + */ +static void siu_dai_pcmdatapack(struct siu_stream *siu_stream) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + u32 dpak; + + dpak = siu_read32(base + SIU_DPAK); + + switch (info->port_id) { + case SIU_PORT_A: + dpak &= ~0xc0000000; + break; + case SIU_PORT_B: + dpak &= ~0x00c00000; + break; + } + + siu_write32(base + SIU_DPAK, dpak); +} + +static int siu_dai_spbstart(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + struct siu_firmware *fw = &info->fw; + u32 *ydef = fw->yram0; + int cnt; + u32 __iomem *add; + u32 *ptr; + + /* Load SPB Program in PRAM */ + ptr = fw->pram0; + add = info->pram; + for (cnt = 0; cnt < PRAM0_SIZE; cnt++, add++, ptr++) + siu_write32(add, *ptr); + + ptr = fw->pram1; + add = info->pram + (0x0100 / sizeof(u32)); + for (cnt = 0; cnt < PRAM1_SIZE; cnt++, add++, ptr++) + siu_write32(add, *ptr); + + /* XRAM initialization */ + add = info->xram; + for (cnt = 0; cnt < XRAM0_SIZE + XRAM1_SIZE + XRAM2_SIZE; cnt++, add++) + siu_write32(add, 0); + + /* YRAM variable area initialization */ + add = info->yram; + for (cnt = 0; cnt < YRAM_DEF_SIZE; cnt++, add++) + siu_write32(add, ydef[cnt]); + + /* YRAM FIR coefficient area initialization */ + add = info->yram + (0x0200 / sizeof(u32)); + for (cnt = 0; cnt < YRAM_FIR_SIZE; cnt++, add++) + siu_write32(add, fw->yram_fir_coeff[cnt]); + + /* YRAM IIR coefficient area initialization */ + add = info->yram + (0x0600 / sizeof(u32)); + for (cnt = 0; cnt < YRAM_IIR_SIZE; cnt++, add++) + siu_write32(add, 0); + + siu_write32(base + SIU_TRDAT, port_info->trdat); + port_info->trdat = 0x0; + + + /* SPB start condition: software */ + siu_write32(base + SIU_SBACTIV, 0); + /* Start SPB */ + siu_write32(base + SIU_SBCTL, 0xc0000000); + /* Wait for program to halt */ + cnt = 0x10000; + while (--cnt && siu_read32(base + SIU_SBCTL) != 0x80000000) + cpu_relax(); + + if (!cnt) + return -EBUSY; + + /* SPB program start address setting */ + siu_write32(base + SIU_SBPSET, 0x00400000); + /* SPB hardware start(FIFOCTL source) */ + siu_write32(base + SIU_SBACTIV, 0xc0000000); + + return 0; +} + +static void siu_dai_spbstop(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + + siu_write32(base + SIU_SBACTIV, 0); + /* SPB stop */ + siu_write32(base + SIU_SBCTL, 0); + + port_info->stfifo = 0; +} + +/* API functions */ + +/* Playback and capture hardware properties are identical */ +static struct snd_pcm_hardware siu_dai_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = SIU_BUFFER_BYTES_MAX, + .period_bytes_min = SIU_PERIOD_BYTES_MIN, + .period_bytes_max = SIU_PERIOD_BYTES_MAX, + .periods_min = SIU_PERIODS_MIN, + .periods_max = SIU_PERIODS_MAX, +}; + +static int siu_dai_info_volume(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_info *uinfo) +{ + struct siu_port *port_info = snd_kcontrol_chip(kctrl); + + dev_dbg(port_info->pcm->card->dev, "%s\n", __func__); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SIU_MAX_VOLUME; + + return 0; +} + +static int siu_dai_get_volume(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct siu_port *port_info = snd_kcontrol_chip(kctrl); + struct device *dev = port_info->pcm->card->dev; + u32 vol; + + dev_dbg(dev, "%s\n", __func__); + + switch (kctrl->private_value) { + case VOLUME_PLAYBACK: + /* Playback is always on port 0 */ + vol = port_info->playback.volume; + ucontrol->value.integer.value[0] = vol & 0xffff; + ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; + break; + case VOLUME_CAPTURE: + /* Capture is always on port 1 */ + vol = port_info->capture.volume; + ucontrol->value.integer.value[0] = vol & 0xffff; + ucontrol->value.integer.value[1] = vol >> 16 & 0xffff; + break; + default: + dev_err(dev, "%s() invalid private_value=%ld\n", + __func__, kctrl->private_value); + return -EINVAL; + } + + return 0; +} + +static int siu_dai_put_volume(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct siu_port *port_info = snd_kcontrol_chip(kctrl); + struct device *dev = port_info->pcm->card->dev; + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + u32 new_vol; + u32 cur_vol; + + dev_dbg(dev, "%s\n", __func__); + + if (ucontrol->value.integer.value[0] < 0 || + ucontrol->value.integer.value[0] > SIU_MAX_VOLUME || + ucontrol->value.integer.value[1] < 0 || + ucontrol->value.integer.value[1] > SIU_MAX_VOLUME) + return -EINVAL; + + new_vol = ucontrol->value.integer.value[0] | + ucontrol->value.integer.value[1] << 16; + + /* See comment above - DSP firmware implementation */ + switch (kctrl->private_value) { + case VOLUME_PLAYBACK: + /* Playback is always on port 0 */ + cur_vol = port_info->playback.volume; + siu_write32(base + SIU_SBDVCA, new_vol); + port_info->playback.volume = new_vol; + break; + case VOLUME_CAPTURE: + /* Capture is always on port 1 */ + cur_vol = port_info->capture.volume; + siu_write32(base + SIU_SBDVCB, new_vol); + port_info->capture.volume = new_vol; + break; + default: + dev_err(dev, "%s() invalid private_value=%ld\n", + __func__, kctrl->private_value); + return -EINVAL; + } + + if (cur_vol != new_vol) + return 1; + + return 0; +} + +static struct snd_kcontrol_new playback_controls = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .info = siu_dai_info_volume, + .get = siu_dai_get_volume, + .put = siu_dai_put_volume, + .private_value = VOLUME_PLAYBACK, +}; + +static struct snd_kcontrol_new capture_controls = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Volume", + .index = 0, + .info = siu_dai_info_volume, + .get = siu_dai_get_volume, + .put = siu_dai_put_volume, + .private_value = VOLUME_CAPTURE, +}; + +int siu_init_port(int port, struct siu_port **port_info, struct snd_card *card) +{ + struct device *dev = card->dev; + struct snd_kcontrol *kctrl; + int ret; + + *port_info = kzalloc(sizeof(**port_info), GFP_KERNEL); + if (!*port_info) + return -ENOMEM; + + dev_dbg(dev, "%s: port #%d@%p\n", __func__, port, *port_info); + + (*port_info)->playback.volume = DFLT_VOLUME_LEVEL; + (*port_info)->capture.volume = DFLT_VOLUME_LEVEL; + + /* + * Add mixer support. The SPB is used to change the volume. Both + * ports use the same SPB. Therefore, we only register one + * control instance since it will be used by both channels. + * In error case we continue without controls. + */ + kctrl = snd_ctl_new1(&playback_controls, *port_info); + ret = snd_ctl_add(card, kctrl); + if (ret < 0) + dev_err(dev, + "failed to add playback controls %p port=%d err=%d\n", + kctrl, port, ret); + + kctrl = snd_ctl_new1(&capture_controls, *port_info); + ret = snd_ctl_add(card, kctrl); + if (ret < 0) + dev_err(dev, + "failed to add capture controls %p port=%d err=%d\n", + kctrl, port, ret); + + return 0; +} + +void siu_free_port(struct siu_port *port_info) +{ + kfree(port_info); +} + +static int siu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct snd_pcm_runtime *rt = substream->runtime; + struct siu_port *port_info = siu_port_info(substream); + int ret; + + dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__, + info->port_id, port_info); + + snd_soc_set_runtime_hwparams(substream, &siu_dai_pcm_hw); + + ret = snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS); + if (unlikely(ret < 0)) + return ret; + + siu_dai_start(port_info); + + return 0; +} + +static void siu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct siu_port *port_info = siu_port_info(substream); + + dev_dbg(substream->pcm->card->dev, "%s: port=%d@%p\n", __func__, + info->port_id, port_info); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + port_info->play_cap &= ~PLAYBACK_ENABLED; + else + port_info->play_cap &= ~CAPTURE_ENABLED; + + /* Stop the siu if the other stream is not using it */ + if (!port_info->play_cap) { + /* during stmread or stmwrite ? */ + BUG_ON(port_info->playback.rw_flg || port_info->capture.rw_flg); + siu_dai_spbstop(port_info); + siu_dai_stop(); + } +} + +/* PCM part of siu_dai_playback_prepare() / siu_dai_capture_prepare() */ +static int siu_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct snd_pcm_runtime *rt = substream->runtime; + struct siu_port *port_info = siu_port_info(substream); + struct siu_stream *siu_stream; + int self, ret; + + dev_dbg(substream->pcm->card->dev, + "%s: port %d, active streams %lx, %d channels\n", + __func__, info->port_id, port_info->play_cap, rt->channels); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + self = PLAYBACK_ENABLED; + siu_stream = &port_info->playback; + } else { + self = CAPTURE_ENABLED; + siu_stream = &port_info->capture; + } + + /* Set up the siu if not already done */ + if (!port_info->play_cap) { + siu_stream->rw_flg = 0; /* stream-data transfer flag */ + + siu_dai_spbAselect(port_info); + siu_dai_spbBselect(port_info); + + siu_dai_open(siu_stream); + + siu_dai_pcmdatapack(siu_stream); + + ret = siu_dai_spbstart(port_info); + if (ret < 0) + goto fail; + } + + port_info->play_cap |= self; + +fail: + return ret; +} + +/* + * SIU can set bus format to I2S / PCM / SPDIF independently for playback and + * capture, however, the current API sets the bus format globally for a DAI. + */ +static int siu_dai_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + u32 ifctl; + + dev_dbg(dai->dev, "%s: fmt 0x%x on port %d\n", + __func__, fmt, info->port_id); + + if (info->port_id < 0) + return -ENODEV; + + /* Here select between I2S / PCM / SPDIF */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ifctl = siu_flags[info->port_id].playback.i2s | + siu_flags[info->port_id].capture.i2s; + break; + case SND_SOC_DAIFMT_LEFT_J: + ifctl = siu_flags[info->port_id].playback.pcm | + siu_flags[info->port_id].capture.pcm; + break; + /* SPDIF disabled - see comment at the top */ + default: + return -EINVAL; + } + + ifctl |= ~(siu_flags[info->port_id].playback.mask | + siu_flags[info->port_id].capture.mask) & + siu_read32(base + SIU_IFCTL); + siu_write32(base + SIU_IFCTL, ifctl); + + return 0; +} + +static int siu_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct clk *siu_clk, *parent_clk; + char *siu_name, *parent_name; + int ret; + + if (dir != SND_SOC_CLOCK_IN) + return -EINVAL; + + dev_dbg(dai->dev, "%s: using clock %d\n", __func__, clk_id); + + switch (clk_id) { + case SIU_CLKA_PLL: + siu_name = "siua_clk"; + parent_name = "pll_clk"; + break; + case SIU_CLKA_EXT: + siu_name = "siua_clk"; + parent_name = "siumcka_clk"; + break; + case SIU_CLKB_PLL: + siu_name = "siub_clk"; + parent_name = "pll_clk"; + break; + case SIU_CLKB_EXT: + siu_name = "siub_clk"; + parent_name = "siumckb_clk"; + break; + default: + return -EINVAL; + } + + siu_clk = clk_get(siu_i2s_dai.dev, siu_name); + if (IS_ERR(siu_clk)) + return PTR_ERR(siu_clk); + + parent_clk = clk_get(siu_i2s_dai.dev, parent_name); + if (!IS_ERR(parent_clk)) { + ret = clk_set_parent(siu_clk, parent_clk); + if (!ret) + clk_set_rate(siu_clk, freq); + clk_put(parent_clk); + } + + clk_put(siu_clk); + + return 0; +} + +static struct snd_soc_dai_ops siu_dai_ops = { + .startup = siu_dai_startup, + .shutdown = siu_dai_shutdown, + .prepare = siu_dai_prepare, + .set_sysclk = siu_dai_set_sysclk, + .set_fmt = siu_dai_set_fmt, +}; + +struct snd_soc_dai siu_i2s_dai = { + .name = "sh-siu", + .id = 0, + .playback = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_8000_48000, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_8000_48000, + }, + .ops = &siu_dai_ops, +}; +EXPORT_SYMBOL_GPL(siu_i2s_dai); + +static int __devinit siu_probe(struct platform_device *pdev) +{ + const struct firmware *fw_entry; + struct resource *res, *region; + struct siu_info *info; + int ret; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = request_firmware(&fw_entry, "siu_spb.bin", &pdev->dev); + if (ret) + goto ereqfw; + + /* + * Loaded firmware is "const" - read only, but we have to modify it in + * snd_siu_sh7343_spbAselect() and snd_siu_sh7343_spbBselect() + */ + memcpy(&info->fw, fw_entry->data, fw_entry->size); + + release_firmware(fw_entry); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto egetres; + } + + region = request_mem_region(res->start, resource_size(res), + pdev->name); + if (!region) { + dev_err(&pdev->dev, "SIU region already claimed\n"); + ret = -EBUSY; + goto ereqmemreg; + } + + ret = -ENOMEM; + info->pram = ioremap(res->start, PRAM_SIZE); + if (!info->pram) + goto emappram; + info->xram = ioremap(res->start + XRAM_OFFSET, XRAM_SIZE); + if (!info->xram) + goto emapxram; + info->yram = ioremap(res->start + YRAM_OFFSET, YRAM_SIZE); + if (!info->yram) + goto emapyram; + info->reg = ioremap(res->start + REG_OFFSET, resource_size(res) - + REG_OFFSET); + if (!info->reg) + goto emapreg; + + siu_i2s_dai.dev = &pdev->dev; + siu_i2s_dai.private_data = info; + + ret = snd_soc_register_dais(&siu_i2s_dai, 1); + if (ret < 0) + goto edaiinit; + + ret = snd_soc_register_platform(&siu_platform); + if (ret < 0) + goto esocregp; + + pm_runtime_enable(&pdev->dev); + + return ret; + +esocregp: + snd_soc_unregister_dais(&siu_i2s_dai, 1); +edaiinit: + iounmap(info->reg); +emapreg: + iounmap(info->yram); +emapyram: + iounmap(info->xram); +emapxram: + iounmap(info->pram); +emappram: + release_mem_region(res->start, resource_size(res)); +ereqmemreg: +egetres: +ereqfw: + kfree(info); + + return ret; +} + +static int __devexit siu_remove(struct platform_device *pdev) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct resource *res; + + pm_runtime_disable(&pdev->dev); + + snd_soc_unregister_platform(&siu_platform); + snd_soc_unregister_dais(&siu_i2s_dai, 1); + + iounmap(info->reg); + iounmap(info->yram); + iounmap(info->xram); + iounmap(info->pram); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + kfree(info); + + return 0; +} + +static struct platform_driver siu_driver = { + .driver = { + .name = "sh_siu", + }, + .probe = siu_probe, + .remove = __devexit_p(siu_remove), +}; + +static int __init siu_init(void) +{ + return platform_driver_register(&siu_driver); +} + +static void __exit siu_exit(void) +{ + platform_driver_unregister(&siu_driver); +} + +module_init(siu_init) +module_exit(siu_exit) + +MODULE_AUTHOR("Carlos Munoz <carlos@kenati.com>"); +MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sh/siu_pcm.c b/sound/soc/sh/siu_pcm.c new file mode 100644 index 000000000000..ba7f8d05d977 --- /dev/null +++ b/sound/soc/sh/siu_pcm.c @@ -0,0 +1,616 @@ +/* + * siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral. + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dai.h> + +#include <asm/dmaengine.h> +#include <asm/siu.h> + +#include "siu.h" + +#define GET_MAX_PERIODS(buf_bytes, period_bytes) \ + ((buf_bytes) / (period_bytes)) +#define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \ + ((buf_addr) + ((period_num) * (period_bytes))) + +#define RWF_STM_RD 0x01 /* Read in progress */ +#define RWF_STM_WT 0x02 /* Write in progress */ + +struct siu_port *siu_ports[SIU_PORT_NUM]; + +/* transfersize is number of u32 dma transfers per period */ +static int siu_pcm_stmwrite_stop(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->playback; + u32 stfifo; + + if (!siu_stream->rw_flg) + return -EPERM; + + /* output FIFO disable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, stfifo & ~0x0c180c18); + pr_debug("%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo & ~0x0c180c18); + + /* during stmwrite clear */ + siu_stream->rw_flg = 0; + + return 0; +} + +static int siu_pcm_stmwrite_start(struct siu_port *port_info) +{ + struct siu_stream *siu_stream = &port_info->playback; + + if (siu_stream->rw_flg) + return -EPERM; + + /* Current period in buffer */ + port_info->playback.cur_period = 0; + + /* during stmwrite flag set */ + siu_stream->rw_flg = RWF_STM_WT; + + /* DMA transfer start */ + tasklet_schedule(&siu_stream->tasklet); + + return 0; +} + +static void siu_dma_tx_complete(void *arg) +{ + struct siu_stream *siu_stream = arg; + + if (!siu_stream->rw_flg) + return; + + /* Update completed period count */ + if (++siu_stream->cur_period >= + GET_MAX_PERIODS(siu_stream->buf_bytes, + siu_stream->period_bytes)) + siu_stream->cur_period = 0; + + pr_debug("%s: done period #%d (%u/%u bytes), cookie %d\n", + __func__, siu_stream->cur_period, + siu_stream->cur_period * siu_stream->period_bytes, + siu_stream->buf_bytes, siu_stream->cookie); + + tasklet_schedule(&siu_stream->tasklet); + + /* Notify alsa: a period is done */ + snd_pcm_period_elapsed(siu_stream->substream); +} + +static int siu_pcm_wr_set(struct siu_port *port_info, + dma_addr_t buff, u32 size) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->playback; + struct snd_pcm_substream *substream = siu_stream->substream; + struct device *dev = substream->pcm->card->dev; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + struct scatterlist sg; + u32 stfifo; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)), + size, offset_in_page(buff)); + sg_dma_address(&sg) = buff; + + desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan, + &sg, 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(dev, "Failed to allocate a dma descriptor\n"); + return -ENOMEM; + } + + desc->callback = siu_dma_tx_complete; + desc->callback_param = siu_stream; + cookie = desc->tx_submit(desc); + if (cookie < 0) { + dev_err(dev, "Failed to submit a dma transfer\n"); + return cookie; + } + + siu_stream->tx_desc = desc; + siu_stream->cookie = cookie; + + dma_async_issue_pending(siu_stream->chan); + + /* only output FIFO enable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, stfifo | (port_info->stfifo & 0x0c180c18)); + dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo | (port_info->stfifo & 0x0c180c18)); + + return 0; +} + +static int siu_pcm_rd_set(struct siu_port *port_info, + dma_addr_t buff, size_t size) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->capture; + struct snd_pcm_substream *substream = siu_stream->substream; + struct device *dev = substream->pcm->card->dev; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + struct scatterlist sg; + u32 stfifo; + + dev_dbg(dev, "%s: %u@%llx\n", __func__, size, (unsigned long long)buff); + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)), + size, offset_in_page(buff)); + sg_dma_address(&sg) = buff; + + desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan, + &sg, 1, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(dev, "Failed to allocate dma descriptor\n"); + return -ENOMEM; + } + + desc->callback = siu_dma_tx_complete; + desc->callback_param = siu_stream; + cookie = desc->tx_submit(desc); + if (cookie < 0) { + dev_err(dev, "Failed to submit dma descriptor\n"); + return cookie; + } + + siu_stream->tx_desc = desc; + siu_stream->cookie = cookie; + + dma_async_issue_pending(siu_stream->chan); + + /* only input FIFO enable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, siu_read32(base + SIU_STFIFO) | + (port_info->stfifo & 0x13071307)); + dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo | (port_info->stfifo & 0x13071307)); + + return 0; +} + +static void siu_io_tasklet(unsigned long data) +{ + struct siu_stream *siu_stream = (struct siu_stream *)data; + struct snd_pcm_substream *substream = siu_stream->substream; + struct device *dev = substream->pcm->card->dev; + struct snd_pcm_runtime *rt = substream->runtime; + struct siu_port *port_info = siu_port_info(substream); + + dev_dbg(dev, "%s: flags %x\n", __func__, siu_stream->rw_flg); + + if (!siu_stream->rw_flg) { + dev_dbg(dev, "%s: stream inactive\n", __func__); + return; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + dma_addr_t buff; + size_t count; + u8 *virt; + + buff = (dma_addr_t)PERIOD_OFFSET(rt->dma_addr, + siu_stream->cur_period, + siu_stream->period_bytes); + virt = PERIOD_OFFSET(rt->dma_area, + siu_stream->cur_period, + siu_stream->period_bytes); + count = siu_stream->period_bytes; + + /* DMA transfer start */ + siu_pcm_rd_set(port_info, buff, count); + } else { + siu_pcm_wr_set(port_info, + (dma_addr_t)PERIOD_OFFSET(rt->dma_addr, + siu_stream->cur_period, + siu_stream->period_bytes), + siu_stream->period_bytes); + } +} + +/* Capture */ +static int siu_pcm_stmread_start(struct siu_port *port_info) +{ + struct siu_stream *siu_stream = &port_info->capture; + + if (siu_stream->xfer_cnt > 0x1000000) + return -EINVAL; + if (siu_stream->rw_flg) + return -EPERM; + + /* Current period in buffer */ + siu_stream->cur_period = 0; + + /* during stmread flag set */ + siu_stream->rw_flg = RWF_STM_RD; + + tasklet_schedule(&siu_stream->tasklet); + + return 0; +} + +static int siu_pcm_stmread_stop(struct siu_port *port_info) +{ + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + struct siu_stream *siu_stream = &port_info->capture; + struct device *dev = siu_stream->substream->pcm->card->dev; + u32 stfifo; + + if (!siu_stream->rw_flg) + return -EPERM; + + /* input FIFO disable */ + stfifo = siu_read32(base + SIU_STFIFO); + siu_write32(base + SIU_STFIFO, stfifo & ~0x13071307); + dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, + stfifo, stfifo & ~0x13071307); + + /* during stmread flag clear */ + siu_stream->rw_flg = 0; + + return 0; +} + +static int siu_pcm_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *hw_params) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct device *dev = ss->pcm->card->dev; + int ret; + + dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); + + ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); + if (ret < 0) + dev_err(dev, "snd_pcm_lib_malloc_pages() failed\n"); + + return ret; +} + +static int siu_pcm_hw_free(struct snd_pcm_substream *ss) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct siu_port *port_info = siu_port_info(ss); + struct device *dev = ss->pcm->card->dev; + struct siu_stream *siu_stream; + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); + + return snd_pcm_lib_free_pages(ss); +} + +static bool filter(struct dma_chan *chan, void *slave) +{ + struct sh_dmae_slave *param = slave; + + pr_debug("%s: slave ID %d\n", __func__, param->slave_id); + + if (unlikely(param->dma_dev != chan->device->dev)) + return false; + + chan->private = param; + return true; +} + +static int siu_pcm_open(struct snd_pcm_substream *ss) +{ + /* Playback / Capture */ + struct siu_info *info = siu_i2s_dai.private_data; + struct siu_port *port_info = siu_port_info(ss); + struct siu_stream *siu_stream; + u32 port = info->port_id; + struct siu_platform *pdata = siu_i2s_dai.dev->platform_data; + struct device *dev = ss->pcm->card->dev; + dma_cap_mask_t mask; + struct sh_dmae_slave *param; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + dev_dbg(dev, "%s, port=%d@%p\n", __func__, port, port_info); + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) { + siu_stream = &port_info->playback; + param = &siu_stream->param; + param->slave_id = port ? SHDMA_SLAVE_SIUB_TX : + SHDMA_SLAVE_SIUA_TX; + } else { + siu_stream = &port_info->capture; + param = &siu_stream->param; + param->slave_id = port ? SHDMA_SLAVE_SIUB_RX : + SHDMA_SLAVE_SIUA_RX; + } + + param->dma_dev = pdata->dma_dev; + /* Get DMA channel */ + siu_stream->chan = dma_request_channel(mask, filter, param); + if (!siu_stream->chan) { + dev_err(dev, "DMA channel allocation failed!\n"); + return -EBUSY; + } + + siu_stream->substream = ss; + + return 0; +} + +static int siu_pcm_close(struct snd_pcm_substream *ss) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct device *dev = ss->pcm->card->dev; + struct siu_port *port_info = siu_port_info(ss); + struct siu_stream *siu_stream; + + dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + dma_release_channel(siu_stream->chan); + siu_stream->chan = NULL; + + siu_stream->substream = NULL; + + return 0; +} + +static int siu_pcm_prepare(struct snd_pcm_substream *ss) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct siu_port *port_info = siu_port_info(ss); + struct device *dev = ss->pcm->card->dev; + struct snd_pcm_runtime *rt = ss->runtime; + struct siu_stream *siu_stream; + snd_pcm_sframes_t xfer_cnt; + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + rt = siu_stream->substream->runtime; + + siu_stream->buf_bytes = snd_pcm_lib_buffer_bytes(ss); + siu_stream->period_bytes = snd_pcm_lib_period_bytes(ss); + + dev_dbg(dev, "%s: port=%d, %d channels, period=%u bytes\n", __func__, + info->port_id, rt->channels, siu_stream->period_bytes); + + /* We only support buffers that are multiples of the period */ + if (siu_stream->buf_bytes % siu_stream->period_bytes) { + dev_err(dev, "%s() - buffer=%d not multiple of period=%d\n", + __func__, siu_stream->buf_bytes, + siu_stream->period_bytes); + return -EINVAL; + } + + xfer_cnt = bytes_to_frames(rt, siu_stream->period_bytes); + if (!xfer_cnt || xfer_cnt > 0x1000000) + return -EINVAL; + + siu_stream->format = rt->format; + siu_stream->xfer_cnt = xfer_cnt; + + dev_dbg(dev, "port=%d buf=%lx buf_bytes=%d period_bytes=%d " + "format=%d channels=%d xfer_cnt=%d\n", info->port_id, + (unsigned long)rt->dma_addr, siu_stream->buf_bytes, + siu_stream->period_bytes, + siu_stream->format, rt->channels, (int)xfer_cnt); + + return 0; +} + +static int siu_pcm_trigger(struct snd_pcm_substream *ss, int cmd) +{ + struct siu_info *info = siu_i2s_dai.private_data; + struct device *dev = ss->pcm->card->dev; + struct siu_port *port_info = siu_port_info(ss); + int ret; + + dev_dbg(dev, "%s: port=%d@%p, cmd=%d\n", __func__, + info->port_id, port_info, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = siu_pcm_stmwrite_start(port_info); + else + ret = siu_pcm_stmread_start(port_info); + + if (ret < 0) + dev_warn(dev, "%s: start failed on port=%d\n", + __func__, info->port_id); + + break; + case SNDRV_PCM_TRIGGER_STOP: + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_pcm_stmwrite_stop(port_info); + else + siu_pcm_stmread_stop(port_info); + ret = 0; + + break; + default: + dev_err(dev, "%s() unsupported cmd=%d\n", __func__, cmd); + ret = -EINVAL; + } + + return ret; +} + +/* + * So far only resolution of one period is supported, subject to extending the + * dmangine API + */ +static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss) +{ + struct device *dev = ss->pcm->card->dev; + struct siu_info *info = siu_i2s_dai.private_data; + u32 __iomem *base = info->reg; + struct siu_port *port_info = siu_port_info(ss); + struct snd_pcm_runtime *rt = ss->runtime; + size_t ptr; + struct siu_stream *siu_stream; + + if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) + siu_stream = &port_info->playback; + else + siu_stream = &port_info->capture; + + /* + * ptr is the offset into the buffer where the dma is currently at. We + * check if the dma buffer has just wrapped. + */ + ptr = PERIOD_OFFSET(rt->dma_addr, + siu_stream->cur_period, + siu_stream->period_bytes) - rt->dma_addr; + + dev_dbg(dev, + "%s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d\n", + __func__, info->port_id, siu_read32(base + SIU_EVNTC), + siu_read32(base + SIU_SBFSTS), ptr, siu_stream->buf_bytes, + siu_stream->cookie); + + if (ptr >= siu_stream->buf_bytes) + ptr = 0; + + return bytes_to_frames(ss->runtime, ptr); +} + +static int siu_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + /* card->dev == socdev->dev, see snd_soc_new_pcms() */ + struct siu_info *info = siu_i2s_dai.private_data; + struct platform_device *pdev = to_platform_device(card->dev); + int ret; + int i; + + /* pdev->id selects between SIUA and SIUB */ + if (pdev->id < 0 || pdev->id >= SIU_PORT_NUM) + return -EINVAL; + + info->port_id = pdev->id; + + /* + * While the siu has 2 ports, only one port can be on at a time (only 1 + * SPB). So far all the boards using the siu had only one of the ports + * wired to a codec. To simplify things, we only register one port with + * alsa. In case both ports are needed, it should be changed here + */ + for (i = pdev->id; i < pdev->id + 1; i++) { + struct siu_port **port_info = &siu_ports[i]; + + ret = siu_init_port(i, port_info, card); + if (ret < 0) + return ret; + + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, NULL, + SIU_BUFFER_BYTES_MAX, SIU_BUFFER_BYTES_MAX); + if (ret < 0) { + dev_err(card->dev, + "snd_pcm_lib_preallocate_pages_for_all() err=%d", + ret); + goto fail; + } + + (*port_info)->pcm = pcm; + + /* IO tasklets */ + tasklet_init(&(*port_info)->playback.tasklet, siu_io_tasklet, + (unsigned long)&(*port_info)->playback); + tasklet_init(&(*port_info)->capture.tasklet, siu_io_tasklet, + (unsigned long)&(*port_info)->capture); + } + + dev_info(card->dev, "SuperH SIU driver initialized.\n"); + return 0; + +fail: + siu_free_port(siu_ports[pdev->id]); + dev_err(card->dev, "SIU: failed to initialize.\n"); + return ret; +} + +static void siu_pcm_free(struct snd_pcm *pcm) +{ + struct platform_device *pdev = to_platform_device(pcm->card->dev); + struct siu_port *port_info = siu_ports[pdev->id]; + + tasklet_kill(&port_info->capture.tasklet); + tasklet_kill(&port_info->playback.tasklet); + + siu_free_port(port_info); + snd_pcm_lib_preallocate_free_for_all(pcm); + + dev_dbg(pcm->card->dev, "%s\n", __func__); +} + +static struct snd_pcm_ops siu_pcm_ops = { + .open = siu_pcm_open, + .close = siu_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = siu_pcm_hw_params, + .hw_free = siu_pcm_hw_free, + .prepare = siu_pcm_prepare, + .trigger = siu_pcm_trigger, + .pointer = siu_pcm_pointer_dma, +}; + +struct snd_soc_platform siu_platform = { + .name = "siu-audio", + .pcm_ops = &siu_pcm_ops, + .pcm_new = siu_pcm_new, + .pcm_free = siu_pcm_free, +}; +EXPORT_SYMBOL_GPL(siu_platform); |