[alsa-devel] [PATCH V2 0/2] ALSA: ASoC for Au1000/1500/1100
Hello,
These 2 patches implement ASoC drivers for the AC97 and I2S controllers found on early Alchemy chips. They are largely based on the old mips/au1x00.c driver which they replace.
AC97 Tested on a Db1500 development board; I2S untested since none of the testboards I have actually have an I2S codec (just testpoints).
Changes since V1: - added untested I2S controller driver for completeness, removed the audio defines from the au1000 header.
Manuel Lauss (2): ALSA: Alchemy AC97C/I2SC audio support ALSA: delete MIPS au1x00 driver
arch/mips/alchemy/devboards/db1x00/platform.c | 37 ++ arch/mips/include/asm/mach-au1x00/au1000.h | 61 --- sound/mips/Kconfig | 8 - sound/mips/Makefile | 2 - sound/mips/au1x00.c | 695 ------------------------- sound/soc/au1x/Kconfig | 28 + sound/soc/au1x/Makefile | 10 + sound/soc/au1x/ac97c.c | 398 ++++++++++++++ sound/soc/au1x/db1000.c | 75 +++ sound/soc/au1x/dma.c | 470 +++++++++++++++++ sound/soc/au1x/i2sc.c | 353 +++++++++++++ sound/soc/au1x/psc.h | 31 +- 12 files changed, 1393 insertions(+), 775 deletions(-) delete mode 100644 sound/mips/au1x00.c create mode 100644 sound/soc/au1x/ac97c.c create mode 100644 sound/soc/au1x/db1000.c create mode 100644 sound/soc/au1x/dma.c create mode 100644 sound/soc/au1x/i2sc.c
This patch adds ASoC support for the AC97 and I2S controllers on the old Au1000/Au1500/Au1100 chips and a universal machine driver for the Db1000/Db1500/Db1100 boards.
AC97 Tested on a Db1500. I2S untested since none of the boards actually have and I2S codec wired up.
Signed-off-by: Manuel Lauss manuel.lauss@googlemail.com --- V2: added untested I2S controller driver for completeness, removed the audio defines from the au1000 header as well.
arch/mips/alchemy/devboards/db1x00/platform.c | 37 ++ arch/mips/include/asm/mach-au1x00/au1000.h | 61 ---- sound/soc/au1x/Kconfig | 28 ++ sound/soc/au1x/Makefile | 10 + sound/soc/au1x/ac97c.c | 398 +++++++++++++++++++++ sound/soc/au1x/db1000.c | 75 ++++ sound/soc/au1x/dma.c | 470 +++++++++++++++++++++++++ sound/soc/au1x/i2sc.c | 353 +++++++++++++++++++ sound/soc/au1x/psc.h | 31 ++- 9 files changed, 1393 insertions(+), 70 deletions(-) create mode 100644 sound/soc/au1x/ac97c.c create mode 100644 sound/soc/au1x/db1000.c create mode 100644 sound/soc/au1x/dma.c create mode 100644 sound/soc/au1x/i2sc.c
diff --git a/arch/mips/alchemy/devboards/db1x00/platform.c b/arch/mips/alchemy/devboards/db1x00/platform.c index 978d5ab..e2025ac 100644 --- a/arch/mips/alchemy/devboards/db1x00/platform.c +++ b/arch/mips/alchemy/devboards/db1x00/platform.c @@ -19,8 +19,11 @@ */
#include <linux/init.h> +#include <linux/interrupt.h> #include <linux/platform_device.h>
+#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1000_dma.h> #include <asm/mach-au1x00/au1xxx.h> #include <asm/mach-db1x00/bcsr.h> #include "../platform.h" @@ -85,6 +88,36 @@ #endif #endif
+static struct resource alchemy_ac97c_res[] = { + [0] = { + .start = AU1000_AC97_PHYS_ADDR, + .end = AU1000_AC97_PHYS_ADDR + 0xfff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = DMA_ID_AC97C_TX, + .end = DMA_ID_AC97C_TX, + .flags = IORESOURCE_DMA, + }, + [2] = { + .start = DMA_ID_AC97C_RX, + .end = DMA_ID_AC97C_RX, + .flags = IORESOURCE_DMA, + }, +}; + +static struct platform_device alchemy_ac97c_dev = { + .name = "alchemy-ac97c", + .id = -1, + .resource = alchemy_ac97c_res, + .num_resources = ARRAY_SIZE(alchemy_ac97c_res), +}; + +static struct platform_device db1x00_codec_dev = { + .name = "ac97-codec", + .id = -1, +}; + static int __init db1xxx_dev_init(void) { #ifdef DB1XXX_HAS_PCMCIA @@ -113,6 +146,10 @@ static int __init db1xxx_dev_init(void) 1); #endif db1x_register_norflash(BOARD_FLASH_SIZE, BOARD_FLASH_WIDTH, F_SWAPPED); + + platform_device_register(&db1x00_codec_dev); + platform_device_register(&alchemy_ac97c_dev); + return 0; } device_initcall(db1xxx_dev_init); diff --git a/arch/mips/include/asm/mach-au1x00/au1000.h b/arch/mips/include/asm/mach-au1x00/au1000.h index f260ebe..6de3c43 100644 --- a/arch/mips/include/asm/mach-au1x00/au1000.h +++ b/arch/mips/include/asm/mach-au1x00/au1000.h @@ -927,36 +927,6 @@ enum soc_au1200_ints { #define SYS_RTCMATCH2 (SYS_BASE + 0x54) #define SYS_RTCREAD (SYS_BASE + 0x58)
-/* I2S Controller */ -#define I2S_DATA 0xB1000000 -# define I2S_DATA_MASK 0xffffff -#define I2S_CONFIG 0xB1000004 -# define I2S_CONFIG_XU (1 << 25) -# define I2S_CONFIG_XO (1 << 24) -# define I2S_CONFIG_RU (1 << 23) -# define I2S_CONFIG_RO (1 << 22) -# define I2S_CONFIG_TR (1 << 21) -# define I2S_CONFIG_TE (1 << 20) -# define I2S_CONFIG_TF (1 << 19) -# define I2S_CONFIG_RR (1 << 18) -# define I2S_CONFIG_RE (1 << 17) -# define I2S_CONFIG_RF (1 << 16) -# define I2S_CONFIG_PD (1 << 11) -# define I2S_CONFIG_LB (1 << 10) -# define I2S_CONFIG_IC (1 << 9) -# define I2S_CONFIG_FM_BIT 7 -# define I2S_CONFIG_FM_MASK (0x3 << I2S_CONFIG_FM_BIT) -# define I2S_CONFIG_FM_I2S (0x0 << I2S_CONFIG_FM_BIT) -# define I2S_CONFIG_FM_LJ (0x1 << I2S_CONFIG_FM_BIT) -# define I2S_CONFIG_FM_RJ (0x2 << I2S_CONFIG_FM_BIT) -# define I2S_CONFIG_TN (1 << 6) -# define I2S_CONFIG_RN (1 << 5) -# define I2S_CONFIG_SZ_BIT 0 -# define I2S_CONFIG_SZ_MASK (0x1F << I2S_CONFIG_SZ_BIT) - -#define I2S_CONTROL 0xB1000008 -# define I2S_CONTROL_D (1 << 1) -# define I2S_CONTROL_CE (1 << 0)
/* USB Host Controller */ #ifndef USB_OHCI_LEN @@ -1436,37 +1406,6 @@ enum soc_au1200_ints { #define SYS_CPUPLL 0xB1900060 #define SYS_AUXPLL 0xB1900064
-/* AC97 Controller */ -#define AC97C_CONFIG 0xB0000000 -# define AC97C_RECV_SLOTS_BIT 13 -# define AC97C_RECV_SLOTS_MASK (0x3ff << AC97C_RECV_SLOTS_BIT) -# define AC97C_XMIT_SLOTS_BIT 3 -# define AC97C_XMIT_SLOTS_MASK (0x3ff << AC97C_XMIT_SLOTS_BIT) -# define AC97C_SG (1 << 2) -# define AC97C_SYNC (1 << 1) -# define AC97C_RESET (1 << 0) -#define AC97C_STATUS 0xB0000004 -# define AC97C_XU (1 << 11) -# define AC97C_XO (1 << 10) -# define AC97C_RU (1 << 9) -# define AC97C_RO (1 << 8) -# define AC97C_READY (1 << 7) -# define AC97C_CP (1 << 6) -# define AC97C_TR (1 << 5) -# define AC97C_TE (1 << 4) -# define AC97C_TF (1 << 3) -# define AC97C_RR (1 << 2) -# define AC97C_RE (1 << 1) -# define AC97C_RF (1 << 0) -#define AC97C_DATA 0xB0000008 -#define AC97C_CMD 0xB000000C -# define AC97C_WD_BIT 16 -# define AC97C_READ (1 << 7) -# define AC97C_INDEX_MASK 0x7f -#define AC97C_CNTRL 0xB0000010 -# define AC97C_RS (1 << 1) -# define AC97C_CE (1 << 0) - #if defined(CONFIG_SOC_AU1500) || defined(CONFIG_SOC_AU1550) /* Au1500 PCI Controller */ #define Au1500_CFG_BASE 0xB4005000 /* virtual, KSEG1 addr */ diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig index 4b67140..6d59254 100644 --- a/sound/soc/au1x/Kconfig +++ b/sound/soc/au1x/Kconfig @@ -18,10 +18,38 @@ config SND_SOC_AU1XPSC_AC97 select SND_AC97_CODEC select SND_SOC_AC97_BUS
+## +## Au1000/1500/1100 DMA + AC97C/I2SC +## +config SND_SOC_AU1XAUDIO + tristate "SoC Audio for Au1000/Au1500/Au1100" + depends on MIPS_ALCHEMY + help + This is a driver set for the AC97 unit and the + old DMA controller as found on the Au1000/Au1500/Au1100 chips. + +config SND_SOC_AU1XAC97C + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + +config SND_SOC_AU1XI2SC + tristate +
## ## Boards ## +config SND_SOC_DB1000 + tristate "DB1000 Audio support" + depends on SND_SOC_AU1XAUDIO + select SND_SOC_AU1XAC97C + select SND_SOC_AC97_CODEC + help + Select this option to enable AC97 audio on the early DB1x00 series + of boards (DB1000/DB1500/DB1100). + config SND_SOC_DB1200 tristate "DB1200 AC97+I2S audio support" depends on SND_SOC_AU1XPSC diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile index 1687307..9207105 100644 --- a/sound/soc/au1x/Makefile +++ b/sound/soc/au1x/Makefile @@ -3,11 +3,21 @@ snd-soc-au1xpsc-dbdma-objs := dbdma2.o snd-soc-au1xpsc-i2s-objs := psc-i2s.o snd-soc-au1xpsc-ac97-objs := psc-ac97.o
+# Au1000/1500/1100 Audio units +snd-soc-au1x-dma-objs := dma.o +snd-soc-au1x-ac97c-objs := ac97c.o +snd-soc-au1x-i2sc-objs := i2sc.o + obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o +obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o +obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o +obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
# Boards +snd-soc-db1000-objs := db1000.o snd-soc-db1200-objs := db1200.o
+obj-$(CONFIG_SND_SOC_DB1000) += snd-soc-db1000.o obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c new file mode 100644 index 0000000..8fc25d0 --- /dev/null +++ b/sound/soc/au1x/ac97c.c @@ -0,0 +1,398 @@ +/* + * Au1000/Au1500/Au1100 AC97C controller driver for ASoC + * + * (c) 2011 Manuel Lauss manuel.lauss@googlemail.com + * + * based on the old ALSA driver by Charles Eidsness. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1xxx_psc.h> + +#include "psc.h" + +/*#define AC_DEBUG*/ + +#define MSG(x...) printk(KERN_ERR "ac97c: " x) +#ifdef AC_DEBUG +#define DBG(x...) MSG(x) +#else +#define DBG(x...) do {} while (0) +#endif + +/* register offsets and bits */ +#define AC97_CONFIG 0x00 +#define AC97_STATUS 0x04 +#define AC97_DATA 0x08 +#define AC97_CMDRESP 0x0c +#define AC97_ENABLE 0x10 + +#define CFG_RC(x) (((x) & 0x3ff) << 13) +#define CFG_XS(x) (((x) & 0x3ff) << 3) +#define CFG_SG (1 << 2) /* sync gate */ +#define CFG_SN (1 << 1) /* sync control */ +#define CFG_RS (1 << 0) /* acrst# control */ +#define STAT_XU (1 << 11) /* tx underflow */ +#define STAT_XO (1 << 10) /* tx overflow */ +#define STAT_RU (1 << 9) /* rx underflow */ +#define STAT_RO (1 << 8) /* rx overflow */ +#define STAT_RD (1 << 7) /* codec ready */ +#define STAT_CP (1 << 6) /* command pending */ +#define STAT_TE (1 << 4) /* tx fifo empty */ +#define STAT_TF (1 << 3) /* tx fifo full */ +#define STAT_RE (1 << 1) /* rx fifo empty */ +#define STAT_RF (1 << 0) /* rx fifo full */ +#define CMD_SET_DATA(x) (((x) & 0xffff) << 16) +#define CMD_GET_DATA(x) ((x) & 0xffff) +#define CMD_READ (1 << 7) +#define CMD_WRITE (0 << 7) +#define CMD_IDX(x) ((x) & 0x7f) +#define EN_D (1 << 1) /* DISable bit */ +#define EN_CE (1 << 0) /* clock enable bit */ + +/* how often to retry failed codec register reads/writes */ +#define AC97_RW_RETRIES 5 + +#define AC97_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_44100 + +#define AC97_FMTS \ + SNDRV_PCM_FMTBIT_S16_LE + +struct ac97c_ctx { + void __iomem *mmio; + + unsigned long cfg; + + struct mutex lock; /* codec access lock */ + + struct platform_device *dmapd; +}; + +/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only + * once AC97C on early Alchemy chips. + */ +static struct ac97c_ctx *ac97c_workdata; + + +#define ac97_to_ctx(x) ac97c_workdata + +static inline unsigned long RD(struct ac97c_ctx *ctx, int reg) +{ + return __raw_readl(ctx->mmio + reg); +} + +static inline void WR(struct ac97c_ctx *ctx, int reg, unsigned long v) +{ + __raw_writel(v, ctx->mmio + reg); + wmb(); +} + +static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97, + unsigned short r) +{ + struct ac97c_ctx *ctx = ac97_to_ctx(ac97); + unsigned int tmo, retry; + unsigned long data; + + data = ~0; + retry = AC97_RW_RETRIES; + do { + mutex_lock(&ctx->lock); + + tmo = 5; + while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--) + udelay(21); /* wait an ac97 frame time */ + if (!tmo) { + DBG("ac97rd timeout #1\n"); + goto next; + } + + WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ); + + /* stupid errata: data is only valid for 21us, so + * poll, forrest, poll... + */ + tmo = 0x10000; + while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--) + asm volatile ("nop"); + data = RD(ctx, AC97_CMDRESP); + + if (!tmo) + DBG("ac97rd timeout #2\n"); + +next: + mutex_unlock(&ctx->lock); + } while (--retry && !tmo); + + DBG("AC97RD %04x %04lx %d\n", r, data, retry); + + return retry ? data & 0xffff : 0xffff; +} + +static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r, + unsigned short v) +{ + struct ac97c_ctx *ctx = ac97_to_ctx(ac97); + unsigned int tmo, retry; + + retry = AC97_RW_RETRIES; + do { + mutex_lock(&ctx->lock); + + for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--) + udelay(21); /* wait an ac97 frame time */ + if (!tmo) { + DBG("ac97wr timeout #1\n"); + goto next; + } + + WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v)); + + for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--) + udelay(21); /* wait an ac97 frame time */ + if (!tmo) + DBG("ac97wr timeout #2\n"); +next: + mutex_unlock(&ctx->lock); + } while (--retry && !tmo); + + DBG("AC97WR %04x %04x %d\n", r, v, retry); +} + +static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct ac97c_ctx *ctx = ac97_to_ctx(ac97); + + DBG("entering WARM_RESET\n"); + + WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN); + msleep(20); + WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG); + WR(ctx, AC97_CONFIG, ctx->cfg); + + DBG("leaving WARM_RESET\n"); +} + +static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct ac97c_ctx *ctx = ac97_to_ctx(ac97); + int i; + + DBG("entering COLD_RESET\n"); + + WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS); + msleep(500); + WR(ctx, AC97_CONFIG, ctx->cfg); + + /* wait for codec ready */ + i = 1000; + while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i) + msleep(20); + if (!i) + printk(KERN_ERR "ac97c: codec not ready\n"); + + DBG("leaving COLD_RESET\n"); +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = au1xac97c_ac97_read, + .write = au1xac97c_ac97_write, + .reset = au1xac97c_ac97_cold_reset, + .warm_reset = au1xac97c_ac97_warm_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int au1xac97c_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int au1xac97c_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + return 0; +} + +static int au1xac97c_dai_probe(struct snd_soc_dai *dai) +{ + return ac97c_workdata ? 0 : -ENODEV; +} + +static struct snd_soc_dai_ops au1xac97c_dai_ops = { + .trigger = au1xac97c_trigger, + .hw_params = au1xac97c_hw_params, +}; + +static struct snd_soc_dai_driver au1xac97c_dai_driver = { + .name = AC97C_DAINAME, + .ac97_control = 1, + .probe = au1xac97c_dai_probe, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &au1xac97c_dai_ops, +}; + +static int __devinit au1xac97c_drvprobe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct ac97c_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_init(&ctx->lock); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENODEV; + goto out0; + } + + ret = -EBUSY; + if (!request_mem_region(r->start, resource_size(r), pdev->name)) + goto out0; + + ctx->mmio = ioremap_nocache(r->start, resource_size(r)); + if (!ctx->mmio) + goto out1; + + /* switch it on */ + WR(ctx, AC97_ENABLE, EN_D | EN_CE); + WR(ctx, AC97_ENABLE, EN_CE); + + ctx->cfg = CFG_RC(3) | CFG_XS(3); + WR(ctx, AC97_CONFIG, ctx->cfg); + + platform_set_drvdata(pdev, ctx); + + ret = snd_soc_register_dai(&pdev->dev, &au1xac97c_dai_driver); + if (ret) + goto out1; + + ctx->dmapd = alchemy_pcm_add(pdev, 0); /* 0 == AC97 */ + if (ctx->dmapd) { + ac97c_workdata = ctx; + return 0; + } + + snd_soc_unregister_dai(&pdev->dev); +out1: + release_mem_region(r->start, resource_size(r)); +out0: + kfree(ctx); + return ret; +} + +static int __devexit au1xac97c_drvremove(struct platform_device *pdev) +{ + struct ac97c_ctx *ctx = platform_get_drvdata(pdev); + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (ctx->dmapd) + alchemy_pcm_destroy(ctx->dmapd); + + snd_soc_unregister_dai(&pdev->dev); + + WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */ + + iounmap(ctx->mmio); + release_mem_region(r->start, resource_size(r)); + kfree(ctx); + + ac97c_workdata = NULL; /* MDEV */ + + return 0; +} + +#ifdef CONFIG_PM +static int au1xac97c_drvsuspend(struct device *dev) +{ + struct ac97c_ctx *ctx = dev_get_drvdata(dev); + + WR(ctx, AC97_ENABLE, EN_D); /* clock off, disable */ + + return 0; +} + +static int au1xac97c_drvresume(struct device *dev) +{ + struct ac97c_ctx *ctx = dev_get_drvdata(dev); + + WR(ctx, AC97_ENABLE, EN_D | EN_CE); + WR(ctx, AC97_ENABLE, EN_CE); + WR(ctx, AC97_CONFIG, ctx->cfg); + + return 0; +} + +static const struct dev_pm_ops au1xpscac97_pmops = { + .suspend = au1xac97c_drvsuspend, + .resume = au1xac97c_drvresume, +}; + +#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops) + +#else + +#define AU1XPSCAC97_PMOPS NULL + +#endif + +static struct platform_driver au1xac97c_driver = { + .driver = { + .name = "alchemy-ac97c", + .owner = THIS_MODULE, + .pm = AU1XPSCAC97_PMOPS, + }, + .probe = au1xac97c_drvprobe, + .remove = __devexit_p(au1xac97c_drvremove), +}; + +static int __init au1xac97c_load(void) +{ + ac97c_workdata = NULL; + return platform_driver_register(&au1xac97c_driver); +} + +static void __exit au1xac97c_unload(void) +{ + platform_driver_unregister(&au1xac97c_driver); +} + +module_init(au1xac97c_load); +module_exit(au1xac97c_unload); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ALSA ASoC audio driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c new file mode 100644 index 0000000..9368b5d --- /dev/null +++ b/sound/soc/au1x/db1000.c @@ -0,0 +1,75 @@ +/* + * DB1000/DB1500/DB1100 ASoC audio fabric support code. + * + * (c) 2011 Manuel Lauss manuel.lauss@googlemail.com + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-db1x00/bcsr.h> + +#include "psc.h" + +/*------------------------- AC97 PART ---------------------------*/ + +static struct snd_soc_dai_link db1000_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .codec_dai_name = "ac97-hifi", + .cpu_dai_name = AC97C_DAINAME, + .platform_name = AC97C_DMANAME, + .codec_name = "ac97-codec", +}; + +static struct snd_soc_card db1000_ac97_machine = { + .name = "DB1000_AC97", + .dai_link = &db1000_ac97_dai, + .num_links = 1, +}; + +/*------------------------- COMMON PART ---------------------------*/ + +static struct platform_device *db1000_asoc97_dev; + +static int __init db1000_audio_load(void) +{ + int ret, id; + + /* impostor check */ + id = BCSR_WHOAMI_BOARD(bcsr_read(BCSR_WHOAMI)); + + ret = -ENOMEM; + db1000_asoc97_dev = platform_device_alloc("soc-audio", 0); + if (!db1000_asoc97_dev) + goto out; + + platform_set_drvdata(db1000_asoc97_dev, &db1000_ac97_machine); + ret = platform_device_add(db1000_asoc97_dev); + + if (ret) { + platform_device_put(db1000_asoc97_dev); + db1000_asoc97_dev = NULL; + } +out: + return ret; +} + +static void __exit db1000_audio_unload(void) +{ + platform_device_unregister(db1000_asoc97_dev); +} + +module_init(db1000_audio_load); +module_exit(db1000_audio_unload); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DB1000/DB1500/DB1100 ASoC audio support"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 0000000..0f7d90a --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,470 @@ +/* + * Au1000/Au1500/Au1100 Audio DMA support. + * + * (c) 2011 Manuel Lauss manuel.lauss@googlemail.com + * + * copied almost verbatim from the old ALSA driver, written by + * Charles Eidsness charles@cooper-street.com + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1000_dma.h> + +#include "psc.h" + +/*#define PCM_DEBUG*/ + +#define MSG(x...) printk(KERN_INFO "alchemy-pcm: " x) +#ifdef PCM_DEBUG +#define DBG(x...) MSG(x) +#else +#define DBG(x...) do {} while (0) +#endif + +#define ALCHEMY_PCM_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | \ + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE | \ + 0) + + +struct pcm_period { + u32 start; + u32 relative_end; /* relative to start of buffer */ + struct pcm_period *next; +}; + +struct audio_stream { + struct snd_pcm_substream *substream; + int dma; + struct pcm_period *buffer; + unsigned int period_size; + unsigned int periods; +}; + +struct alchemy_pcm_ctx { + struct audio_stream stream[2]; /* playback & capture */ +}; + +static void au1000_release_dma_link(struct audio_stream *stream) +{ + struct pcm_period *pointer; + struct pcm_period *pointer_next; + + stream->period_size = 0; + stream->periods = 0; + pointer = stream->buffer; + if (!pointer) + return; + do { + pointer_next = pointer->next; + kfree(pointer); + pointer = pointer_next; + } while (pointer != stream->buffer); + stream->buffer = NULL; +} + +static int au1000_setup_dma_link(struct audio_stream *stream, + unsigned int period_bytes, + unsigned int periods) +{ + struct snd_pcm_substream *substream = stream->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcm_period *pointer; + unsigned long dma_start; + int i; + + dma_start = virt_to_phys(runtime->dma_area); + + if (stream->period_size == period_bytes && + stream->periods == periods) + return 0; /* not changed */ + + au1000_release_dma_link(stream); + + stream->period_size = period_bytes; + stream->periods = periods; + + stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL); + if (!stream->buffer) + return -ENOMEM; + pointer = stream->buffer; + for (i = 0; i < periods; i++) { + pointer->start = (u32)(dma_start + (i * period_bytes)); + pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1); + if (i < periods - 1) { + pointer->next = kmalloc(sizeof(struct pcm_period), + GFP_KERNEL); + if (!pointer->next) { + au1000_release_dma_link(stream); + return -ENOMEM; + } + pointer = pointer->next; + } + } + pointer->next = stream->buffer; + return 0; +} + +static void au1000_dma_stop(struct audio_stream *stream) +{ + if (stream->buffer) + disable_dma(stream->dma); +} + +static void au1000_dma_start(struct audio_stream *stream) +{ + if (!stream->buffer) + return; + + init_dma(stream->dma); + if (get_dma_active_buffer(stream->dma) == 0) { + clear_dma_done0(stream->dma); + set_dma_addr0(stream->dma, stream->buffer->start); + set_dma_count0(stream->dma, stream->period_size >> 1); + set_dma_addr1(stream->dma, stream->buffer->next->start); + set_dma_count1(stream->dma, stream->period_size >> 1); + } else { + clear_dma_done1(stream->dma); + set_dma_addr1(stream->dma, stream->buffer->start); + set_dma_count1(stream->dma, stream->period_size >> 1); + set_dma_addr0(stream->dma, stream->buffer->next->start); + set_dma_count0(stream->dma, stream->period_size >> 1); + } + enable_dma_buffers(stream->dma); + start_dma(stream->dma); +} + +static irqreturn_t au1000_dma_interrupt(int irq, void *ptr) +{ + struct audio_stream *stream = (struct audio_stream *)ptr; + struct snd_pcm_substream *substream = stream->substream; + + switch (get_dma_buffer_done(stream->dma)) { + case DMA_D0: + stream->buffer = stream->buffer->next; + clear_dma_done0(stream->dma); + set_dma_addr0(stream->dma, stream->buffer->next->start); + set_dma_count0(stream->dma, stream->period_size >> 1); + enable_dma_buffer0(stream->dma); + break; + case DMA_D1: + stream->buffer = stream->buffer->next; + clear_dma_done1(stream->dma); + set_dma_addr1(stream->dma, stream->buffer->next->start); + set_dma_count1(stream->dma, stream->period_size >> 1); + enable_dma_buffer1(stream->dma); + break; + case (DMA_D0 | DMA_D1): + DBG("DMA %d missed interrupt.\n", stream->dma); + au1000_dma_stop(stream); + au1000_dma_start(stream); + break; + case (~DMA_D0 & ~DMA_D1): + DBG("DMA %d empty irq.\n", stream->dma); + } + snd_pcm_period_elapsed(substream); + return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware alchemy_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH, + .formats = ALCHEMY_PCM_FMTS, + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = SNDRV_PCM_RATE_8000, + .rate_max = SNDRV_PCM_RATE_192000, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 1024, + .period_bytes_max = 16 * 1024 - 1, + .periods_min = 4, + .periods_max = 255, + .buffer_bytes_max = 128 * 1024, + .fifo_size = 16, +}; + +static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss) +{ + struct snd_soc_pcm_runtime *rtd = ss->private_data; + return snd_soc_platform_get_drvdata(rtd->platform); +} + +static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss) +{ + struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss); + return &(ctx->stream[SUBSTREAM_TYPE(ss)]); +} + +static int alchemy_pcm_open(struct snd_pcm_substream *substream) +{ + struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream); + int stype = SUBSTREAM_TYPE(substream); + + ctx->stream[stype].substream = substream; + ctx->stream[stype].buffer = NULL; + snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware); + + return 0; +} + +static int alchemy_pcm_close(struct snd_pcm_substream *substream) +{ + struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream); + + ctx->stream[SUBSTREAM_TYPE(substream)].substream = NULL; + + return 0; +} + +static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct audio_stream *stream = ss_to_as(substream); + int err; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + return au1000_setup_dma_link(stream, + params_period_bytes(hw_params), + params_periods(hw_params)); +} + +static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct audio_stream *stream = ss_to_as(substream); + au1000_release_dma_link(stream); + return snd_pcm_lib_free_pages(substream); +} + +static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct audio_stream *stream = ss_to_as(substream); + int err = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + au1000_dma_start(stream); + break; + case SNDRV_PCM_TRIGGER_STOP: + au1000_dma_stop(stream); + break; + default: + err = -EINVAL; + break; + } + return err; +} + +static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss) +{ + struct audio_stream *stream = ss_to_as(ss); + long location; + + location = get_dma_residue(stream->dma); + location = stream->buffer->relative_end - location; + if (location == -1) + location = 0; + return bytes_to_frames(ss->runtime, location); +} + +static struct snd_pcm_ops alchemy_pcm_ops = { + .open = alchemy_pcm_open, + .close = alchemy_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = alchemy_pcm_hw_params, + .hw_free = alchemy_pcm_hw_free, + .trigger = alchemy_pcm_trigger, + .pointer = alchemy_pcm_pointer, +}; + +static void alchemy_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int alchemy_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1); + + return 0; +} + +struct snd_soc_platform_driver alchemy_pcm_soc_platform = { + .ops = &alchemy_pcm_ops, + .pcm_new = alchemy_pcm_new, + .pcm_free = alchemy_pcm_free_dma_buffers, +}; + + +static int __devinit alchemy_pcm_drvprobe(struct platform_device *pdev) +{ + struct alchemy_pcm_ctx *ctx; + struct resource *r; + int ret; + + DBG("probing %s\n", pdev->name); + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) { + ret = -ENODEV; + goto out1; + } + ctx->stream[PCM_TX].dma = request_au1000_dma(r->start, "audio-tx", + au1000_dma_interrupt, IRQF_DISABLED, + &ctx->stream[PCM_TX]); + set_dma_mode(ctx->stream[PCM_TX].dma, + get_dma_mode(ctx->stream[PCM_TX].dma) & ~DMA_NC); + + /* RX DMA */ + r = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!r) { + ret = -ENODEV; + goto out2; + } + ctx->stream[PCM_RX].dma = request_au1000_dma(r->start, "audio-rx", + au1000_dma_interrupt, IRQF_DISABLED, + &ctx->stream[PCM_RX]); + set_dma_mode(ctx->stream[PCM_RX].dma, + get_dma_mode(ctx->stream[PCM_RX].dma) & ~DMA_NC); + + DBG("DMA: %d %d\n", ctx->stream[PCM_TX].dma, ctx->stream[PCM_RX].dma); + + platform_set_drvdata(pdev, ctx); + + ret = snd_soc_register_platform(&pdev->dev, &alchemy_pcm_soc_platform); + if (!ret) + return ret; + + free_au1000_dma(ctx->stream[PCM_RX].dma); +out2: + free_au1000_dma(ctx->stream[PCM_TX].dma); +out1: + kfree(ctx); + DBG("bailing out\n"); + return ret; +} + +static int __devexit alchemy_pcm_drvremove(struct platform_device *pdev) +{ + struct alchemy_pcm_ctx *ctx = platform_get_drvdata(pdev); + + free_au1000_dma(ctx->stream[PCM_RX].dma); + free_au1000_dma(ctx->stream[PCM_TX].dma); + + kfree(ctx); + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver alchemy_ac97pcm_driver = { + .driver = { + .name = AC97C_DMANAME, + .owner = THIS_MODULE, + }, + .probe = alchemy_pcm_drvprobe, + .remove = __devexit_p(alchemy_pcm_drvremove), +}; + +static struct platform_driver alchemy_i2spcm_driver = { + .driver = { + .name = I2SC_DMANAME, + .owner = THIS_MODULE, + }, + .probe = alchemy_pcm_drvprobe, + .remove = __devexit_p(alchemy_pcm_drvremove), +}; + +static int __init alchemy_audio_dma_load(void) +{ + (void)platform_driver_register(&alchemy_i2spcm_driver); + return platform_driver_register(&alchemy_ac97pcm_driver); +} + +static void __exit alchemy_audio_dma_unload(void) +{ + platform_driver_unregister(&alchemy_i2spcm_driver); + platform_driver_unregister(&alchemy_ac97pcm_driver); +} + +module_init(alchemy_audio_dma_load); +module_exit(alchemy_audio_dma_unload); + + +struct platform_device *alchemy_pcm_add(struct platform_device *pdev, int type) +{ + struct resource *res, *r; + struct platform_device *pd; + char *pdevname; + int id[2]; + int ret; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) + return NULL; + id[0] = r->start; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!r) + return NULL; + id[1] = r->start; + + res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL); + if (!res) + return NULL; + + res[0].start = res[0].end = id[0]; + res[1].start = res[1].end = id[1]; + res[0].flags = res[1].flags = IORESOURCE_DMA; + + /* "alchemy-pcm-ac97" or "alchemy-pcm-i2s" */ + pdevname = (type == 0) ? AC97C_DMANAME : I2SC_DMANAME; + pd = platform_device_alloc(pdevname, -1); + if (!pd) + goto out; + + pd->resource = res; + pd->num_resources = 2; + + ret = platform_device_add(pd); + if (!ret) + return pd; + + platform_device_put(pd); +out: + kfree(res); + return NULL; +} +EXPORT_SYMBOL_GPL(alchemy_pcm_add); + +void alchemy_pcm_destroy(struct platform_device *dmapd) +{ + if (dmapd) + platform_device_unregister(dmapd); +} +EXPORT_SYMBOL_GPL(alchemy_pcm_destroy); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c new file mode 100644 index 0000000..1a31d51 --- /dev/null +++ b/sound/soc/au1x/i2sc.c @@ -0,0 +1,353 @@ +/* + * Au1000/Au1500/Au1100 I2S controller driver for ASoC + * + * (c) 2011 Manuel Lauss manuel.lauss@googlemail.com + * + * Note: clock supplied to the I2S controller must be 256x samplerate. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> + +#include "psc.h" + +#define I2S_DATA 0x00 +#define I2S_CONFIG 0x04 +#define I2S_ENABLE 0x08 + +#define CFG_XU (1 << 25) /* tx underflow */ +#define CFG_XO (1 << 24) +#define CFG_RU (1 << 23) +#define CFG_RO (1 << 22) +#define CFG_TR (1 << 21) +#define CFG_TE (1 << 20) +#define CFG_TF (1 << 19) +#define CFG_RR (1 << 18) +#define CFG_RF (1 << 17) +#define CFG_ICK (1 << 12) /* clock invert */ +#define CFG_PD (1 << 11) /* set to make I2SDIO INPUT */ +#define CFG_LB (1 << 10) /* loopback */ +#define CFG_IC (1 << 9) /* word select invert */ +#define CFG_FM_I2S (0 << 7) /* I2S format */ +#define CFG_FM_LJ (1 << 7) /* left-justified */ +#define CFG_FM_RJ (2 << 7) /* right-justified */ +#define CFG_FM_MASK (3 << 7) +#define CFG_TN (1 << 6) /* tx fifo en */ +#define CFG_RN (1 << 5) /* rx fifo en */ +#define CFG_SZ_8 (0x08) +#define CFG_SZ_16 (0x10) +#define CFG_SZ_18 (0x12) +#define CFG_SZ_20 (0x14) +#define CFG_SZ_24 (0x18) +#define CFG_SZ_MASK (0x1f) +#define EN_D (1 << 1) /* DISable */ +#define EN_CE (1 << 0) /* clock enable */ + +/* supported I2S DAI hardware formats */ +#define AU1XI2SC_DAIFMT \ + (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \ + SND_SOC_DAIFMT_NB_NF) + +/* supported I2S direction */ +#define AU1XI2SC_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AU1XI2SC_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AU1XI2SC_FMTS \ + SNDRV_PCM_FMTBIT_S16_LE + +struct i2sc_ctx { + void __iomem *mmio; + unsigned long cfg, rate; + struct platform_device *dmapd; +}; + +static inline unsigned long RD(struct i2sc_ctx *ctx, int reg) +{ + return __raw_readl(ctx->mmio + reg); +} + +static inline void WR(struct i2sc_ctx *ctx, int reg, unsigned long v) +{ + __raw_writel(v, ctx->mmio + reg); + wmb(); +} + +static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct i2sc_ctx *ctx = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long c; + int ret; + + ret = -EINVAL; + c = ctx->cfg; + + c &= ~CFG_FM_MASK; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + c |= CFG_FM_I2S; /* enable I2S mode */ + break; + case SND_SOC_DAIFMT_MSB: + c |= CFG_FM_RJ; + break; + case SND_SOC_DAIFMT_LSB: + c |= CFG_FM_LJ; + break; + default: + goto out; + } + + c &= ~(CFG_IC | CFG_ICK); /* IB-IF */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + c |= CFG_IC | CFG_ICK; + break; + case SND_SOC_DAIFMT_NB_IF: + c |= CFG_IC; + break; + case SND_SOC_DAIFMT_IB_NF: + c |= CFG_ICK; + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + goto out; + } + + /* I2S controller only supports master */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ + break; + default: + goto out; + } + + ret = 0; + ctx->cfg = c; +out: + return ret; +} + +static int au1xi2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct i2sc_ctx *ctx = snd_soc_dai_get_drvdata(dai); + int stype = SUBSTREAM_TYPE(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN; + WR(ctx, I2S_CONFIG, ctx->cfg); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN); + WR(ctx, I2S_CONFIG, ctx->cfg); + break; + default: + return -EINVAL; + } + + return 0; +} + +static unsigned long msbits_to_reg(int msbits) +{ + switch (msbits) { + case 8: return CFG_SZ_8; + case 16: return CFG_SZ_16; + case 18: return CFG_SZ_18; + case 20: return CFG_SZ_20; + case 24: return CFG_SZ_24; + } + return 0; +} + +static int au1xi2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct i2sc_ctx *ctx = snd_soc_dai_get_drvdata(dai); + unsigned long stat, v; + + v = msbits_to_reg(params->msbits); + /* check if the PSC is already streaming data */ + stat = RD(ctx, I2S_CONFIG); + if (stat & (CFG_TN | CFG_RN)) { + /* reject parameters not currently set up in hardware */ + if ((ctx->rate != params_rate(params)) || + ((stat & CFG_SZ_MASK) != v)) + return -EINVAL; + } else { + /* set sample bitdepth */ + ctx->cfg &= ~CFG_SZ_MASK; + if (v) + ctx->cfg |= v; + else + return -EINVAL; + /* remember current rate for other stream */ + ctx->rate = params_rate(params); + } + return 0; +} + +static struct snd_soc_dai_ops au1xi2s_dai_ops = { + .trigger = au1xi2s_trigger, + .hw_params = au1xi2s_hw_params, + .set_fmt = au1xi2s_set_fmt, +}; + +static struct snd_soc_dai_driver au1xi2s_dai_driver = { + .playback = { + .rates = AU1XI2SC_RATES, + .formats = AU1XI2SC_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AU1XI2SC_RATES, + .formats = AU1XI2SC_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &au1xi2s_dai_ops, +}; + +static int __devinit au1xi2s_drvprobe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct i2sc_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENODEV; + goto out0; + } + + ret = -EBUSY; + if (!request_mem_region(r->start, resource_size(r), pdev->name)) + goto out0; + + ctx->mmio = ioremap_nocache(r->start, resource_size(r)); + if (!ctx->mmio) + goto out1; + + /* switch it on */ + WR(ctx, I2S_ENABLE, EN_D | EN_CE); + WR(ctx, I2S_ENABLE, EN_CE); + + ctx->cfg = CFG_FM_I2S | CFG_SZ_16; + WR(ctx, I2S_CONFIG, ctx->cfg); + + platform_set_drvdata(pdev, ctx); + + ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver); + if (ret) + goto out1; + + ctx->dmapd = alchemy_pcm_add(pdev, 1); /* 1 == I2S */ + if (ctx->dmapd) + return 0; + + snd_soc_unregister_dai(&pdev->dev); +out1: + release_mem_region(r->start, resource_size(r)); +out0: + kfree(ctx); + return ret; +} + +static int __devexit au1xi2s_drvremove(struct platform_device *pdev) +{ + struct i2sc_ctx *ctx = platform_get_drvdata(pdev); + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (ctx->dmapd) + alchemy_pcm_destroy(ctx->dmapd); + + snd_soc_unregister_dai(&pdev->dev); + + WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ + + iounmap(ctx->mmio); + release_mem_region(r->start, resource_size(r)); + kfree(ctx); + + return 0; +} + +#ifdef CONFIG_PM +static int au1xi2s_drvsuspend(struct device *dev) +{ + struct i2sc_ctx *ctx = dev_get_drvdata(dev); + + WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ + + return 0; +} + +static int au1xi2s_drvresume(struct device *dev) +{ + struct i2sc_ctx *ctx = dev_get_drvdata(dev); + + WR(ctx, I2S_ENABLE, EN_D | EN_CE); + WR(ctx, I2S_ENABLE, EN_CE); + WR(ctx, I2S_CONFIG, ctx->cfg); + + return 0; +} + +static const struct dev_pm_ops au1xpscac97_pmops = { + .suspend = au1xi2s_drvsuspend, + .resume = au1xi2s_drvresume, +}; + +#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops) + +#else + +#define AU1XPSCAC97_PMOPS NULL + +#endif + +static struct platform_driver au1xi2s_driver = { + .driver = { + .name = "alchemy-ac97c", + .owner = THIS_MODULE, + .pm = AU1XPSCAC97_PMOPS, + }, + .probe = au1xi2s_drvprobe, + .remove = __devexit_p(au1xi2s_drvremove), +}; + +static int __init au1xi2s_load(void) +{ + return platform_driver_register(&au1xi2s_driver); +} + +static void __exit au1xi2s_unload(void) +{ + platform_driver_unregister(&au1xi2s_driver); +} + +module_init(au1xi2s_load); +module_exit(au1xi2s_unload); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ALSA ASoC audio driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h index b30eadd..21b944a 100644 --- a/sound/soc/au1x/psc.h +++ b/sound/soc/au1x/psc.h @@ -1,7 +1,7 @@ /* - * Au12x0/Au1550 PSC ALSA ASoC audio support. + * Alchemy ALSA ASoC audio support. * - * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * (c) 2007-2011 MSC Vertriebsges.m.b.H., * Manuel Lauss manuel.lauss@gmail.com * * This program is free software; you can redistribute it and/or modify @@ -13,7 +13,26 @@ #ifndef _AU1X_PCM_H #define _AU1X_PCM_H
-/* DBDMA helpers */ +#define PCM_TX 0 +#define PCM_RX 1 + +#define SUBSTREAM_TYPE(substream) \ + ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX) + + +/* AC97C/I2SC DMA helpers */ +extern struct platform_device *alchemy_pcm_add(struct platform_device *pdev, + int type); +extern void alchemy_pcm_destroy(struct platform_device *dmapd); + +/* Au1000 AC97C/I2SC DAI names. Required to get at correct DMA instance */ +#define AC97C_DAINAME "alchemy-ac97c" +#define I2SC_DAINAME "alchemy-i2sc" +#define AC97C_DMANAME "alchemy-pcm-ac97" +#define I2SC_DMANAME "alchemy-pcm-i2s" + + +/* PSC/DBDMA helpers */ extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev); extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
@@ -30,12 +49,6 @@ struct au1xpsc_audio_data { struct platform_device *dmapd; };
-#define PCM_TX 0 -#define PCM_RX 1 - -#define SUBSTREAM_TYPE(substream) \ - ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX) - /* easy access macros */ #define PSC_CTRL(x) ((unsigned long)((x)->mmio) + PSC_CTRL_OFFSET) #define PSC_SEL(x) ((unsigned long)((x)->mmio) + PSC_SEL_OFFSET)
You should always Cc Mark and Liam on ASoC patch. Also the proper commit title prefix is ASoC.
On 07/21/2011 06:34 PM, Manuel Lauss wrote:
This patch adds ASoC support for the AC97 and I2S controllers on the old Au1000/Au1500/Au1100 chips and a universal machine driver for the Db1000/Db1500/Db1100 boards.
AC97 Tested on a Db1500. I2S untested since none of the boards actually have and I2S codec wired up.
Signed-off-by: Manuel Lauss manuel.lauss@googlemail.com
V2: added untested I2S controller driver for completeness, removed the audio defines from the au1000 header as well.
arch/mips/alchemy/devboards/db1x00/platform.c | 37 ++ arch/mips/include/asm/mach-au1x00/au1000.h | 61 ---- sound/soc/au1x/Kconfig | 28 ++ sound/soc/au1x/Makefile | 10 + sound/soc/au1x/ac97c.c | 398 +++++++++++++++++++++ sound/soc/au1x/db1000.c | 75 ++++ sound/soc/au1x/dma.c | 470 +++++++++++++++++++++++++ sound/soc/au1x/i2sc.c | 353 +++++++++++++++++++ sound/soc/au1x/psc.h | 31 ++- 9 files changed, 1393 insertions(+), 70 deletions(-) create mode 100644 sound/soc/au1x/ac97c.c create mode 100644 sound/soc/au1x/db1000.c create mode 100644 sound/soc/au1x/dma.c create mode 100644 sound/soc/au1x/i2sc.c
It might make sense to split this into multiple patches. Especially the platform part should be put in a seperate patch, since there isn't really any compile time dependency to the other parts it could go via the MIPS tree.
diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c new file mode 100644 index 0000000..8fc25d0 --- /dev/null +++ b/sound/soc/au1x/ac97c.c @@ -0,0 +1,398 @@ +/*
- Au1000/Au1500/Au1100 AC97C controller driver for ASoC
- (c) 2011 Manuel Lauss manuel.lauss@googlemail.com
- based on the old ALSA driver by Charles Eidsness.
- */
+#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1xxx_psc.h>
+#include "psc.h"
+/*#define AC_DEBUG*/
+#define MSG(x...) printk(KERN_ERR "ac97c: " x)
dev_err or pr_err
+#ifdef AC_DEBUG +#define DBG(x...) MSG(x) +#else +#define DBG(x...) do {} while (0) +#endif
dev_dbg or pr_dbg
[...]
+/* how often to retry failed codec register reads/writes */ +#define AC97_RW_RETRIES 5
+#define AC97_DIR \
- (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
Unused
[...]
+static int au1xac97c_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- return 0;
+}
+static int au1xac97c_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
+{
- return 0;
+}
If you don't want to do anything in the callbacks just leave them out.
+static int au1xac97c_dai_probe(struct snd_soc_dai *dai) +{
- return ac97c_workdata ? 0 : -ENODEV;
+}
+static struct snd_soc_dai_ops au1xac97c_dai_ops = {
const
- .trigger = au1xac97c_trigger,
- .hw_params = au1xac97c_hw_params,
+};
[...] diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c new file mode 100644 index 0000000..9368b5d [...] +static int __init db1000_audio_load(void) +{
- int ret, id;
- /* impostor check */
- id = BCSR_WHOAMI_BOARD(bcsr_read(BCSR_WHOAMI));
- ret = -ENOMEM;
- db1000_asoc97_dev = platform_device_alloc("soc-audio", 0);
New drivers shouldn't user soc-audio anymore, just register a normal platform device driver.
- if (!db1000_asoc97_dev)
goto out;
- platform_set_drvdata(db1000_asoc97_dev, &db1000_ac97_machine);
- ret = platform_device_add(db1000_asoc97_dev);
- if (ret) {
platform_device_put(db1000_asoc97_dev);
db1000_asoc97_dev = NULL;
- }
+out:
- return ret;
+}
+static void __exit db1000_audio_unload(void) +{
- platform_device_unregister(db1000_asoc97_dev);
+}
+module_init(db1000_audio_load); +module_exit(db1000_audio_unload);
+MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DB1000/DB1500/DB1100 ASoC audio support"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 0000000..0f7d90a --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,470 @@ [...]
+static struct platform_driver alchemy_ac97pcm_driver = {
- .driver = {
.name = AC97C_DMANAME,
.owner = THIS_MODULE,
- },
- .probe = alchemy_pcm_drvprobe,
- .remove = __devexit_p(alchemy_pcm_drvremove),
+};
+static struct platform_driver alchemy_i2spcm_driver = {
- .driver = {
.name = I2SC_DMANAME,
.owner = THIS_MODULE,
- },
- .probe = alchemy_pcm_drvprobe,
- .remove = __devexit_p(alchemy_pcm_drvremove),
+};
You shouldn't really have to register two identical drivers for this. If you really want to be able to instantiate the driver with two different names use platform_device_id. But in my opinion it should be enough to just have one generic name, since there is nothing AC97 or I2S specific in this driver.
[...]
+struct platform_device *alchemy_pcm_add(struct platform_device *pdev, int type) +{
+ struct resource *res, *r; + struct platform_device *pd; + char *pdevname; + int id[2]; + int ret; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) + return NULL; + id[0] = r->start; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!r) + return NULL; + id[1] = r->start; + + res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL); + if (!res) + return NULL; + + res[0].start = res[0].end = id[0]; + res[1].start = res[1].end = id[1]; + res[0].flags = res[1].flags = IORESOURCE_DMA; + + /* "alchemy-pcm-ac97" or "alchemy-pcm-i2s" */ + pdevname = (type == 0) ? AC97C_DMANAME : I2SC_DMANAME; + pd = platform_device_alloc(pdevname, -1); + if (!pd) + goto out; + + pd->resource = res; + pd->num_resources = 2; + + ret = platform_device_add(pd); + if (!ret) + return pd; + + platform_device_put(pd); +out: + kfree(res); + return NULL;
+}
This function looks a bit fishy. The pcm driver should be registered by the platform code file as well. If you need different DMA regions for I2C and AC97 use snd_soc_dai_set_dma_data and snd_soc_dai_get_dma_data to pass them to the PCM driver from the I2S or AC97 driver.
[...] diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c new file mode 100644 index 0000000..1a31d51 --- /dev/null +++ b/sound/soc/au1x/i2sc.c @@ -0,0 +1,353 @@ [...]
+/* supported I2S DAI hardware formats */ +#define AU1XI2SC_DAIFMT \
- (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \
SND_SOC_DAIFMT_NB_NF)
Unused
+/* supported I2S direction */ +#define AU1XI2SC_DIR \
- (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
Unused
+#define AU1XI2SC_RATES \
- SNDRV_PCM_RATE_8000_192000
+#define AU1XI2SC_FMTS \
- SNDRV_PCM_FMTBIT_S16_LE
+struct i2sc_ctx {
- void __iomem *mmio;
- unsigned long cfg, rate;
- struct platform_device *dmapd;
+};
[...]
+static int au1xi2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct i2sc_ctx *ctx = snd_soc_dai_get_drvdata(dai);
- unsigned long stat, v;
- v = msbits_to_reg(params->msbits);
- /* check if the PSC is already streaming data */
Use .symmetric_rates = 1 in your dai_driver struct for this.
- stat = RD(ctx, I2S_CONFIG);
- if (stat & (CFG_TN | CFG_RN)) {
/* reject parameters not currently set up in hardware */
if ((ctx->rate != params_rate(params)) ||
((stat & CFG_SZ_MASK) != v))
return -EINVAL;
- } else {
/* set sample bitdepth */
ctx->cfg &= ~CFG_SZ_MASK;
if (v)
ctx->cfg |= v;
else
return -EINVAL;
/* remember current rate for other stream */
ctx->rate = params_rate(params);
- }
- return 0;
+}
+static struct snd_soc_dai_ops au1xi2s_dai_ops = {
const
- .trigger = au1xi2s_trigger,
- .hw_params = au1xi2s_hw_params,
- .set_fmt = au1xi2s_set_fmt,
+};
[...]
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Fri, Jul 22, 2011 at 1:54 AM, Lars-Peter Clausen lars@metafoo.de wrote:
diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c
- ret = -ENOMEM;
- db1000_asoc97_dev = platform_device_alloc("soc-audio", 0);
New drivers shouldn't user soc-audio anymore, just register a normal platform device driver.
Can you point to an example of the new way?
diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 0000000..0f7d90a --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,470 @@ [...]
+static struct platform_driver alchemy_ac97pcm_driver = {
- .driver = {
- .name = AC97C_DMANAME,
- .owner = THIS_MODULE,
- },
- .probe = alchemy_pcm_drvprobe,
- .remove = __devexit_p(alchemy_pcm_drvremove),
+};
+static struct platform_driver alchemy_i2spcm_driver = {
- .driver = {
- .name = I2SC_DMANAME,
- .owner = THIS_MODULE,
- },
- .probe = alchemy_pcm_drvprobe,
- .remove = __devexit_p(alchemy_pcm_drvremove),
+};
You shouldn't really have to register two identical drivers for this. If you really want to be able to instantiate the driver with two different names use platform_device_id. But in my opinion it should be enough to just have one generic name, since there is nothing AC97 or I2S specific in this driver.
I need a unique name for the DMA device in soc_dai_link. This was the easiest way. Especially since both ac97 and i2s can be active at runtime.
[...]
+struct platform_device *alchemy_pcm_add(struct platform_device *pdev, int type) +{
- struct resource *res, *r;
- struct platform_device *pd;
- char *pdevname;
- int id[2];
- int ret;
- r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
- if (!r)
- return NULL;
- id[0] = r->start;
- r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
- if (!r)
- return NULL;
- id[1] = r->start;
- res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL);
- if (!res)
- return NULL;
- res[0].start = res[0].end = id[0];
- res[1].start = res[1].end = id[1];
- res[0].flags = res[1].flags = IORESOURCE_DMA;
- /* "alchemy-pcm-ac97" or "alchemy-pcm-i2s" */
- pdevname = (type == 0) ? AC97C_DMANAME : I2SC_DMANAME;
- pd = platform_device_alloc(pdevname, -1);
- if (!pd)
- goto out;
- pd->resource = res;
- pd->num_resources = 2;
- ret = platform_device_add(pd);
- if (!ret)
- return pd;
- platform_device_put(pd);
+out:
- kfree(res);
- return NULL;
+}
This function looks a bit fishy. The pcm driver should be registered by the platform code file as well. If you need different DMA regions for I2C and AC97 use snd_soc_dai_set_dma_data and snd_soc_dai_get_dma_data to pass them to the PCM driver from the I2S or AC97 driver.
I like to pass the DMA id's along with the ac97/i2s resource information (since they belong together anyway). As an added benefit I get a sensibly named dma device with the correct DMA information, all by simply registering the ac97 platform device.
I'll think about a way to change it.
Thank you! Manuel Lauss -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 07/22/2011 08:54 AM, Manuel Lauss wrote:
On Fri, Jul 22, 2011 at 1:54 AM, Lars-Peter Clausen lars@metafoo.de wrote:
diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c
ret = -ENOMEM;
db1000_asoc97_dev = platform_device_alloc("soc-audio", 0);
New drivers shouldn't user soc-audio anymore, just register a normal platform device driver.
Can you point to an example of the new way?
sound/soc/samsung/speyside.c
diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 0000000..0f7d90a --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,470 @@ [...]
+static struct platform_driver alchemy_ac97pcm_driver = {
.driver = {
.name = AC97C_DMANAME,
.owner = THIS_MODULE,
},
.probe = alchemy_pcm_drvprobe,
.remove = __devexit_p(alchemy_pcm_drvremove),
+};
+static struct platform_driver alchemy_i2spcm_driver = {
.driver = {
.name = I2SC_DMANAME,
.owner = THIS_MODULE,
},
.probe = alchemy_pcm_drvprobe,
.remove = __devexit_p(alchemy_pcm_drvremove),
+};
You shouldn't really have to register two identical drivers for this. If you really want to be able to instantiate the driver with two different names use platform_device_id. But in my opinion it should be enough to just have one generic name, since there is nothing AC97 or I2S specific in this driver.
I need a unique name for the DMA device in soc_dai_link. This was the easiest way. Especially since both ac97 and i2s can be active at runtime.
If you want to instantiate two pcm drivers you can just give the devices different ids. As there is nothing I2C or AC97 specific in the pcm driver it should not matter which one is used for what, if two devices are active at the same time. Right now you need to know which one is which, because you instantiate the driver with either the I2C or AC97 DMA addresses, but if you use snd_soc_dai_get_dma_data as described below and pass the DMA address at runtime this issue will go away.
[...]
+struct platform_device *alchemy_pcm_add(struct platform_device *pdev, int type) +{
struct resource *res, *r;
struct platform_device *pd;
char *pdevname;
int id[2];
int ret;
r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!r)
return NULL;
id[0] = r->start;
r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!r)
return NULL;
id[1] = r->start;
res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL);
if (!res)
return NULL;
res[0].start = res[0].end = id[0];
res[1].start = res[1].end = id[1];
res[0].flags = res[1].flags = IORESOURCE_DMA;
/* "alchemy-pcm-ac97" or "alchemy-pcm-i2s" */
pdevname = (type == 0) ? AC97C_DMANAME : I2SC_DMANAME;
pd = platform_device_alloc(pdevname, -1);
if (!pd)
goto out;
pd->resource = res;
pd->num_resources = 2;
ret = platform_device_add(pd);
if (!ret)
return pd;
platform_device_put(pd);
+out:
kfree(res);
return NULL;
+}
This function looks a bit fishy. The pcm driver should be registered by the platform code file as well. If you need different DMA regions for I2C and AC97 use snd_soc_dai_set_dma_data and snd_soc_dai_get_dma_data to pass them to the PCM driver from the I2S or AC97 driver.
I like to pass the DMA id's along with the ac97/i2s resource information (since they belong together anyway). As an added benefit I get a sensibly named dma device with the correct DMA information, all by simply registering the ac97 platform device.
There is nothing wrong with passing the DMA ids along with the other AC97/I2C resources. At least for the AC97 and I2C driver. But the PCM driver should use snd_soc_dai_get_dma_data to get the DMA addresses at runtime rather then during device instantiation. Take a look at how other platforms handle this.
- Lars -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Fri, Jul 22, 2011 at 9:40 AM, Lars-Peter Clausen lars@metafoo.de wrote:
On 07/22/2011 08:54 AM, Manuel Lauss wrote:
On Fri, Jul 22, 2011 at 1:54 AM, Lars-Peter Clausen lars@metafoo.de wrote:
diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c
- ret = -ENOMEM;
- db1000_asoc97_dev = platform_device_alloc("soc-audio", 0);
New drivers shouldn't user soc-audio anymore, just register a normal platform device driver.
Can you point to an example of the new way?
sound/soc/samsung/speyside.c
Thanks,
diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 0000000..0f7d90a --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,470 @@ [...]
+static struct platform_driver alchemy_ac97pcm_driver = {
- .driver = {
- .name = AC97C_DMANAME,
- .owner = THIS_MODULE,
- },
- .probe = alchemy_pcm_drvprobe,
- .remove = __devexit_p(alchemy_pcm_drvremove),
+};
+static struct platform_driver alchemy_i2spcm_driver = {
- .driver = {
- .name = I2SC_DMANAME,
- .owner = THIS_MODULE,
- },
- .probe = alchemy_pcm_drvprobe,
- .remove = __devexit_p(alchemy_pcm_drvremove),
+};
You shouldn't really have to register two identical drivers for this. If you really want to be able to instantiate the driver with two different names use platform_device_id. But in my opinion it should be enough to just have one generic name, since there is nothing AC97 or I2S specific in this driver.
I need a unique name for the DMA device in soc_dai_link. This was the easiest way. Especially since both ac97 and i2s can be active at runtime.
If you want to instantiate two pcm drivers you can just give the devices different ids. As there is nothing I2C or AC97 specific in the pcm driver it should not matter which one is used for what, if two devices are active at the same time. Right now you need to know which one is which, because you instantiate the driver with either the I2C or AC97 DMA addresses, but if you use snd_soc_dai_get_dma_data as described below and pass the DMA address at runtime this issue will go away.
so instead of "alchemy-pcm-{ac97|i2s}" i'd have "alchemy-pcm.[01]". Not really much clearer in my book. And I still need to know the correct suffix for dai link.
Or am I missing something fundamentally?
[...]
+struct platform_device *alchemy_pcm_add(struct platform_device *pdev, int type) +{
- struct resource *res, *r;
- struct platform_device *pd;
- char *pdevname;
- int id[2];
- int ret;
- r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
- if (!r)
- return NULL;
- id[0] = r->start;
- r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
- if (!r)
- return NULL;
- id[1] = r->start;
- res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL);
- if (!res)
- return NULL;
- res[0].start = res[0].end = id[0];
- res[1].start = res[1].end = id[1];
- res[0].flags = res[1].flags = IORESOURCE_DMA;
- /* "alchemy-pcm-ac97" or "alchemy-pcm-i2s" */
- pdevname = (type == 0) ? AC97C_DMANAME : I2SC_DMANAME;
- pd = platform_device_alloc(pdevname, -1);
- if (!pd)
- goto out;
- pd->resource = res;
- pd->num_resources = 2;
- ret = platform_device_add(pd);
- if (!ret)
- return pd;
- platform_device_put(pd);
+out:
- kfree(res);
- return NULL;
+}
This function looks a bit fishy. The pcm driver should be registered by the platform code file as well. If you need different DMA regions for I2C and AC97 use snd_soc_dai_set_dma_data and snd_soc_dai_get_dma_data to pass them to the PCM driver from the I2S or AC97 driver.
I like to pass the DMA id's along with the ac97/i2s resource information (since they belong together anyway). As an added benefit I get a sensibly named dma device with the correct DMA information, all by simply registering the ac97 platform device.
There is nothing wrong with passing the DMA ids along with the other AC97/I2C resources. At least for the AC97 and I2C driver. But the PCM driver should use snd_soc_dai_get_dma_data to get the DMA addresses at runtime rather then during device instantiation. Take a look at how other platforms handle this.
The ac97/i2s units know they need dma and therefore register the device themselves. Having the board code register an "empty" dma device along with the ac97/i2s devices seems like a waste of code and a source of potential bugs. I'll change my code.
Thank you! Manuel Lauss -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On 07/22/2011 09:56 AM, Manuel Lauss wrote:
On Fri, Jul 22, 2011 at 9:40 AM, Lars-Peter Clausen lars@metafoo.de wrote:
On 07/22/2011 08:54 AM, Manuel Lauss wrote:
On Fri, Jul 22, 2011 at 1:54 AM, Lars-Peter Clausen lars@metafoo.de wrote:
diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 0000000..0f7d90a --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,470 @@ [...]
+static struct platform_driver alchemy_ac97pcm_driver = {
.driver = {
.name = AC97C_DMANAME,
.owner = THIS_MODULE,
},
.probe = alchemy_pcm_drvprobe,
.remove = __devexit_p(alchemy_pcm_drvremove),
+};
+static struct platform_driver alchemy_i2spcm_driver = {
.driver = {
.name = I2SC_DMANAME,
.owner = THIS_MODULE,
},
.probe = alchemy_pcm_drvprobe,
.remove = __devexit_p(alchemy_pcm_drvremove),
+};
You shouldn't really have to register two identical drivers for this. If you really want to be able to instantiate the driver with two different names use platform_device_id. But in my opinion it should be enough to just have one generic name, since there is nothing AC97 or I2S specific in this driver.
I need a unique name for the DMA device in soc_dai_link. This was the easiest way. Especially since both ac97 and i2s can be active at runtime.
If you want to instantiate two pcm drivers you can just give the devices different ids. As there is nothing I2C or AC97 specific in the pcm driver it should not matter which one is used for what, if two devices are active at the same time. Right now you need to know which one is which, because you instantiate the driver with either the I2C or AC97 DMA addresses, but if you use snd_soc_dai_get_dma_data as described below and pass the DMA address at runtime this issue will go away.
so instead of "alchemy-pcm-{ac97|i2s}" i'd have "alchemy-pcm.[01]". Not really much clearer in my book. And I still need to know the correct suffix for dai link.
Since there is nothing I2S or AC97 specific in the PCM driver it doesn't really make much sense to put it in the pcm driver name in my opinion. I would suspect that the common case is, that an board use either AC97 or I2S audio, but not both. So in this case you'd always just have alchemy-pcm.0 in your dai link regardless of whether the board uses AC97 or I2S.
[...]
+struct platform_device *alchemy_pcm_add(struct platform_device *pdev, int type) +{
struct resource *res, *r;
struct platform_device *pd;
char *pdevname;
int id[2];
int ret;
r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!r)
return NULL;
id[0] = r->start;
r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!r)
return NULL;
id[1] = r->start;
res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL);
if (!res)
return NULL;
res[0].start = res[0].end = id[0];
res[1].start = res[1].end = id[1];
res[0].flags = res[1].flags = IORESOURCE_DMA;
/* "alchemy-pcm-ac97" or "alchemy-pcm-i2s" */
pdevname = (type == 0) ? AC97C_DMANAME : I2SC_DMANAME;
pd = platform_device_alloc(pdevname, -1);
if (!pd)
goto out;
pd->resource = res;
pd->num_resources = 2;
ret = platform_device_add(pd);
if (!ret)
return pd;
platform_device_put(pd);
+out:
kfree(res);
return NULL;
+}
This function looks a bit fishy. The pcm driver should be registered by the platform code file as well. If you need different DMA regions for I2C and AC97 use snd_soc_dai_set_dma_data and snd_soc_dai_get_dma_data to pass them to the PCM driver from the I2S or AC97 driver.
I like to pass the DMA id's along with the ac97/i2s resource information (since they belong together anyway). As an added benefit I get a sensibly named dma device with the correct DMA information, all by simply registering the ac97 platform device.
There is nothing wrong with passing the DMA ids along with the other AC97/I2C resources. At least for the AC97 and I2C driver. But the PCM driver should use snd_soc_dai_get_dma_data to get the DMA addresses at runtime rather then during device instantiation. Take a look at how other platforms handle this.
The ac97/i2s units know they need dma and therefore register the device themselves. Having the board code register an "empty" dma device along with the ac97/i2s devices seems like a waste of code and a source of potential bugs. I'll change my code.
If you are worried somebody might forget to register the pcm device when registering the ac97/i2s device provide a helper function which registers both.
- Lars
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Since there's now an ASOC replacement.
Signed-off-by: Manuel Lauss manuel.lauss@googlemail.com --- V2: no changes
sound/mips/Kconfig | 8 - sound/mips/Makefile | 2 - sound/mips/au1x00.c | 695 --------------------------------------------------- 3 files changed, 0 insertions(+), 705 deletions(-) delete mode 100644 sound/mips/au1x00.c
diff --git a/sound/mips/Kconfig b/sound/mips/Kconfig index a9823fa..bb8a4e3 100644 --- a/sound/mips/Kconfig +++ b/sound/mips/Kconfig @@ -22,13 +22,5 @@ config SND_SGI_HAL2 Sound support for the SGI Indy and Indigo2 Workstation.
-config SND_AU1X00 - tristate "Au1x00 AC97 Port Driver" - depends on SOC_AU1000 || SOC_AU1100 || SOC_AU1500 - select SND_PCM - select SND_AC97_CODEC - help - ALSA Sound driver for the Au1x00's AC97 port. - endif # SND_MIPS
diff --git a/sound/mips/Makefile b/sound/mips/Makefile index 861ec0a..b977c44 100644 --- a/sound/mips/Makefile +++ b/sound/mips/Makefile @@ -2,11 +2,9 @@ # Makefile for ALSA #
-snd-au1x00-objs := au1x00.o snd-sgi-o2-objs := sgio2audio.o ad1843.o snd-sgi-hal2-objs := hal2.o
# Toplevel Module Dependency -obj-$(CONFIG_SND_AU1X00) += snd-au1x00.o obj-$(CONFIG_SND_SGI_O2) += snd-sgi-o2.o obj-$(CONFIG_SND_SGI_HAL2) += snd-sgi-hal2.o diff --git a/sound/mips/au1x00.c b/sound/mips/au1x00.c deleted file mode 100644 index 446cf97..0000000 --- a/sound/mips/au1x00.c +++ /dev/null @@ -1,695 +0,0 @@ -/* - * BRIEF MODULE DESCRIPTION - * Driver for AMD Au1000 MIPS Processor, AC'97 Sound Port - * - * Copyright 2004 Cooper Street Innovations Inc. - * Author: Charles Eidsness charles@cooper-street.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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * 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. - * - * History: - * - * 2004-09-09 Charles Eidsness -- Original verion -- based on - * sa11xx-uda1341.c ALSA driver and the - * au1000.c OSS driver. - * 2004-09-09 Matt Porter -- Added support for ALSA 1.0.6 - * - */ - -#include <linux/ioport.h> -#include <linux/interrupt.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <sound/core.h> -#include <sound/initval.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/ac97_codec.h> -#include <asm/mach-au1x00/au1000.h> -#include <asm/mach-au1x00/au1000_dma.h> - -MODULE_AUTHOR("Charles Eidsness charles@cooper-street.com"); -MODULE_DESCRIPTION("Au1000 AC'97 ALSA Driver"); -MODULE_LICENSE("GPL"); -MODULE_SUPPORTED_DEVICE("{{AMD,Au1000 AC'97}}"); - -#define PLAYBACK 0 -#define CAPTURE 1 -#define AC97_SLOT_3 0x01 -#define AC97_SLOT_4 0x02 -#define AC97_SLOT_6 0x08 -#define AC97_CMD_IRQ 31 -#define READ 0 -#define WRITE 1 -#define READ_WAIT 2 -#define RW_DONE 3 - -struct au1000_period -{ - u32 start; - u32 relative_end; /*realtive to start of buffer*/ - struct au1000_period * next; -}; - -/*Au1000 AC97 Port Control Reisters*/ -struct au1000_ac97_reg { - u32 volatile config; - u32 volatile status; - u32 volatile data; - u32 volatile cmd; - u32 volatile cntrl; -}; - -struct audio_stream { - struct snd_pcm_substream *substream; - int dma; - spinlock_t dma_lock; - struct au1000_period * buffer; - unsigned int period_size; - unsigned int periods; -}; - -struct snd_au1000 { - struct snd_card *card; - struct au1000_ac97_reg volatile *ac97_ioport; - - struct resource *ac97_res_port; - spinlock_t ac97_lock; - struct snd_ac97 *ac97; - - struct snd_pcm *pcm; - struct audio_stream *stream[2]; /* playback & capture */ -}; - -/*--------------------------- Local Functions --------------------------------*/ -static void -au1000_set_ac97_xmit_slots(struct snd_au1000 *au1000, long xmit_slots) -{ - u32 volatile ac97_config; - - spin_lock(&au1000->ac97_lock); - ac97_config = au1000->ac97_ioport->config; - ac97_config = ac97_config & ~AC97C_XMIT_SLOTS_MASK; - ac97_config |= (xmit_slots << AC97C_XMIT_SLOTS_BIT); - au1000->ac97_ioport->config = ac97_config; - spin_unlock(&au1000->ac97_lock); -} - -static void -au1000_set_ac97_recv_slots(struct snd_au1000 *au1000, long recv_slots) -{ - u32 volatile ac97_config; - - spin_lock(&au1000->ac97_lock); - ac97_config = au1000->ac97_ioport->config; - ac97_config = ac97_config & ~AC97C_RECV_SLOTS_MASK; - ac97_config |= (recv_slots << AC97C_RECV_SLOTS_BIT); - au1000->ac97_ioport->config = ac97_config; - spin_unlock(&au1000->ac97_lock); -} - - -static void -au1000_release_dma_link(struct audio_stream *stream) -{ - struct au1000_period * pointer; - struct au1000_period * pointer_next; - - stream->period_size = 0; - stream->periods = 0; - pointer = stream->buffer; - if (! pointer) - return; - do { - pointer_next = pointer->next; - kfree(pointer); - pointer = pointer_next; - } while (pointer != stream->buffer); - stream->buffer = NULL; -} - -static int -au1000_setup_dma_link(struct audio_stream *stream, unsigned int period_bytes, - unsigned int periods) -{ - struct snd_pcm_substream *substream = stream->substream; - struct snd_pcm_runtime *runtime = substream->runtime; - struct au1000_period *pointer; - unsigned long dma_start; - int i; - - dma_start = virt_to_phys(runtime->dma_area); - - if (stream->period_size == period_bytes && - stream->periods == periods) - return 0; /* not changed */ - - au1000_release_dma_link(stream); - - stream->period_size = period_bytes; - stream->periods = periods; - - stream->buffer = kmalloc(sizeof(struct au1000_period), GFP_KERNEL); - if (! stream->buffer) - return -ENOMEM; - pointer = stream->buffer; - for (i = 0; i < periods; i++) { - pointer->start = (u32)(dma_start + (i * period_bytes)); - pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1); - if (i < periods - 1) { - pointer->next = kmalloc(sizeof(struct au1000_period), GFP_KERNEL); - if (! pointer->next) { - au1000_release_dma_link(stream); - return -ENOMEM; - } - pointer = pointer->next; - } - } - pointer->next = stream->buffer; - return 0; -} - -static void -au1000_dma_stop(struct audio_stream *stream) -{ - if (snd_BUG_ON(!stream->buffer)) - return; - disable_dma(stream->dma); -} - -static void -au1000_dma_start(struct audio_stream *stream) -{ - if (snd_BUG_ON(!stream->buffer)) - return; - - init_dma(stream->dma); - if (get_dma_active_buffer(stream->dma) == 0) { - clear_dma_done0(stream->dma); - set_dma_addr0(stream->dma, stream->buffer->start); - set_dma_count0(stream->dma, stream->period_size >> 1); - set_dma_addr1(stream->dma, stream->buffer->next->start); - set_dma_count1(stream->dma, stream->period_size >> 1); - } else { - clear_dma_done1(stream->dma); - set_dma_addr1(stream->dma, stream->buffer->start); - set_dma_count1(stream->dma, stream->period_size >> 1); - set_dma_addr0(stream->dma, stream->buffer->next->start); - set_dma_count0(stream->dma, stream->period_size >> 1); - } - enable_dma_buffers(stream->dma); - start_dma(stream->dma); -} - -static irqreturn_t -au1000_dma_interrupt(int irq, void *dev_id) -{ - struct audio_stream *stream = (struct audio_stream *) dev_id; - struct snd_pcm_substream *substream = stream->substream; - - spin_lock(&stream->dma_lock); - switch (get_dma_buffer_done(stream->dma)) { - case DMA_D0: - stream->buffer = stream->buffer->next; - clear_dma_done0(stream->dma); - set_dma_addr0(stream->dma, stream->buffer->next->start); - set_dma_count0(stream->dma, stream->period_size >> 1); - enable_dma_buffer0(stream->dma); - break; - case DMA_D1: - stream->buffer = stream->buffer->next; - clear_dma_done1(stream->dma); - set_dma_addr1(stream->dma, stream->buffer->next->start); - set_dma_count1(stream->dma, stream->period_size >> 1); - enable_dma_buffer1(stream->dma); - break; - case (DMA_D0 | DMA_D1): - printk(KERN_ERR "DMA %d missed interrupt.\n",stream->dma); - au1000_dma_stop(stream); - au1000_dma_start(stream); - break; - case (~DMA_D0 & ~DMA_D1): - printk(KERN_ERR "DMA %d empty irq.\n",stream->dma); - } - spin_unlock(&stream->dma_lock); - snd_pcm_period_elapsed(substream); - return IRQ_HANDLED; -} - -/*-------------------------- PCM Audio Streams -------------------------------*/ - -static unsigned int rates[] = {8000, 11025, 16000, 22050}; -static struct snd_pcm_hw_constraint_list hw_constraints_rates = { - .count = ARRAY_SIZE(rates), - .list = rates, - .mask = 0, -}; - -static struct snd_pcm_hardware snd_au1000_hw = -{ - .info = (SNDRV_PCM_INFO_INTERLEAVED | \ - SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050), - .rate_min = 8000, - .rate_max = 22050, - .channels_min = 1, - .channels_max = 2, - .buffer_bytes_max = 128*1024, - .period_bytes_min = 32, - .period_bytes_max = 16*1024, - .periods_min = 8, - .periods_max = 255, - .fifo_size = 16, -}; - -static int -snd_au1000_playback_open(struct snd_pcm_substream *substream) -{ - struct snd_au1000 *au1000 = substream->pcm->private_data; - - au1000->stream[PLAYBACK]->substream = substream; - au1000->stream[PLAYBACK]->buffer = NULL; - substream->private_data = au1000->stream[PLAYBACK]; - substream->runtime->hw = snd_au1000_hw; - return (snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0); -} - -static int -snd_au1000_capture_open(struct snd_pcm_substream *substream) -{ - struct snd_au1000 *au1000 = substream->pcm->private_data; - - au1000->stream[CAPTURE]->substream = substream; - au1000->stream[CAPTURE]->buffer = NULL; - substream->private_data = au1000->stream[CAPTURE]; - substream->runtime->hw = snd_au1000_hw; - return (snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0); -} - -static int -snd_au1000_playback_close(struct snd_pcm_substream *substream) -{ - struct snd_au1000 *au1000 = substream->pcm->private_data; - - au1000->stream[PLAYBACK]->substream = NULL; - return 0; -} - -static int -snd_au1000_capture_close(struct snd_pcm_substream *substream) -{ - struct snd_au1000 *au1000 = substream->pcm->private_data; - - au1000->stream[CAPTURE]->substream = NULL; - return 0; -} - -static int -snd_au1000_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct audio_stream *stream = substream->private_data; - int err; - - err = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (err < 0) - return err; - return au1000_setup_dma_link(stream, - params_period_bytes(hw_params), - params_periods(hw_params)); -} - -static int -snd_au1000_hw_free(struct snd_pcm_substream *substream) -{ - struct audio_stream *stream = substream->private_data; - au1000_release_dma_link(stream); - return snd_pcm_lib_free_pages(substream); -} - -static int -snd_au1000_playback_prepare(struct snd_pcm_substream *substream) -{ - struct snd_au1000 *au1000 = substream->pcm->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - - if (runtime->channels == 1) - au1000_set_ac97_xmit_slots(au1000, AC97_SLOT_4); - else - au1000_set_ac97_xmit_slots(au1000, AC97_SLOT_3 | AC97_SLOT_4); - snd_ac97_set_rate(au1000->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate); - return 0; -} - -static int -snd_au1000_capture_prepare(struct snd_pcm_substream *substream) -{ - struct snd_au1000 *au1000 = substream->pcm->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - - if (runtime->channels == 1) - au1000_set_ac97_recv_slots(au1000, AC97_SLOT_4); - else - au1000_set_ac97_recv_slots(au1000, AC97_SLOT_3 | AC97_SLOT_4); - snd_ac97_set_rate(au1000->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate); - return 0; -} - -static int -snd_au1000_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct audio_stream *stream = substream->private_data; - int err = 0; - - spin_lock(&stream->dma_lock); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - au1000_dma_start(stream); - break; - case SNDRV_PCM_TRIGGER_STOP: - au1000_dma_stop(stream); - break; - default: - err = -EINVAL; - break; - } - spin_unlock(&stream->dma_lock); - return err; -} - -static snd_pcm_uframes_t -snd_au1000_pointer(struct snd_pcm_substream *substream) -{ - struct audio_stream *stream = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - long location; - - spin_lock(&stream->dma_lock); - location = get_dma_residue(stream->dma); - spin_unlock(&stream->dma_lock); - location = stream->buffer->relative_end - location; - if (location == -1) - location = 0; - return bytes_to_frames(runtime,location); -} - -static struct snd_pcm_ops snd_card_au1000_playback_ops = { - .open = snd_au1000_playback_open, - .close = snd_au1000_playback_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_au1000_hw_params, - .hw_free = snd_au1000_hw_free, - .prepare = snd_au1000_playback_prepare, - .trigger = snd_au1000_trigger, - .pointer = snd_au1000_pointer, -}; - -static struct snd_pcm_ops snd_card_au1000_capture_ops = { - .open = snd_au1000_capture_open, - .close = snd_au1000_capture_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = snd_au1000_hw_params, - .hw_free = snd_au1000_hw_free, - .prepare = snd_au1000_capture_prepare, - .trigger = snd_au1000_trigger, - .pointer = snd_au1000_pointer, -}; - -static int __devinit -snd_au1000_pcm_new(struct snd_au1000 *au1000) -{ - struct snd_pcm *pcm; - int err; - unsigned long flags; - - if ((err = snd_pcm_new(au1000->card, "AU1000 AC97 PCM", 0, 1, 1, &pcm)) < 0) - return err; - - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), 128*1024, 128*1024); - - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, - &snd_card_au1000_playback_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, - &snd_card_au1000_capture_ops); - - pcm->private_data = au1000; - pcm->info_flags = 0; - strcpy(pcm->name, "Au1000 AC97 PCM"); - - spin_lock_init(&au1000->stream[PLAYBACK]->dma_lock); - spin_lock_init(&au1000->stream[CAPTURE]->dma_lock); - - flags = claim_dma_lock(); - if ((au1000->stream[PLAYBACK]->dma = request_au1000_dma(DMA_ID_AC97C_TX, - "AC97 TX", au1000_dma_interrupt, IRQF_DISABLED, - au1000->stream[PLAYBACK])) < 0) { - release_dma_lock(flags); - return -EBUSY; - } - if ((au1000->stream[CAPTURE]->dma = request_au1000_dma(DMA_ID_AC97C_RX, - "AC97 RX", au1000_dma_interrupt, IRQF_DISABLED, - au1000->stream[CAPTURE])) < 0){ - release_dma_lock(flags); - return -EBUSY; - } - /* enable DMA coherency in read/write DMA channels */ - set_dma_mode(au1000->stream[PLAYBACK]->dma, - get_dma_mode(au1000->stream[PLAYBACK]->dma) & ~DMA_NC); - set_dma_mode(au1000->stream[CAPTURE]->dma, - get_dma_mode(au1000->stream[CAPTURE]->dma) & ~DMA_NC); - release_dma_lock(flags); - au1000->pcm = pcm; - return 0; -} - - -/*-------------------------- AC97 CODEC Control ------------------------------*/ - -static unsigned short -snd_au1000_ac97_read(struct snd_ac97 *ac97, unsigned short reg) -{ - struct snd_au1000 *au1000 = ac97->private_data; - u32 volatile cmd; - u16 volatile data; - int i; - - spin_lock(&au1000->ac97_lock); -/* would rather use the interrupt than this polling but it works and I can't -get the interrupt driven case to work efficiently */ - for (i = 0; i < 0x5000; i++) - if (!(au1000->ac97_ioport->status & AC97C_CP)) - break; - if (i == 0x5000) - printk(KERN_ERR "au1000 AC97: AC97 command read timeout\n"); - - cmd = (u32) reg & AC97C_INDEX_MASK; - cmd |= AC97C_READ; - au1000->ac97_ioport->cmd = cmd; - - /* now wait for the data */ - for (i = 0; i < 0x5000; i++) - if (!(au1000->ac97_ioport->status & AC97C_CP)) - break; - if (i == 0x5000) { - printk(KERN_ERR "au1000 AC97: AC97 command read timeout\n"); - spin_unlock(&au1000->ac97_lock); - return 0; - } - - data = au1000->ac97_ioport->cmd & 0xffff; - spin_unlock(&au1000->ac97_lock); - - return data; - -} - - -static void -snd_au1000_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) -{ - struct snd_au1000 *au1000 = ac97->private_data; - u32 cmd; - int i; - - spin_lock(&au1000->ac97_lock); -/* would rather use the interrupt than this polling but it works and I can't -get the interrupt driven case to work efficiently */ - for (i = 0; i < 0x5000; i++) - if (!(au1000->ac97_ioport->status & AC97C_CP)) - break; - if (i == 0x5000) - printk(KERN_ERR "au1000 AC97: AC97 command write timeout\n"); - - cmd = (u32) reg & AC97C_INDEX_MASK; - cmd &= ~AC97C_READ; - cmd |= ((u32) val << AC97C_WD_BIT); - au1000->ac97_ioport->cmd = cmd; - spin_unlock(&au1000->ac97_lock); -} - -static int __devinit -snd_au1000_ac97_new(struct snd_au1000 *au1000) -{ - int err; - struct snd_ac97_bus *pbus; - struct snd_ac97_template ac97; - static struct snd_ac97_bus_ops ops = { - .write = snd_au1000_ac97_write, - .read = snd_au1000_ac97_read, - }; - - if ((au1000->ac97_res_port = request_mem_region(CPHYSADDR(AC97C_CONFIG), - 0x100000, "Au1x00 AC97")) == NULL) { - snd_printk(KERN_ERR "ALSA AC97: can't grap AC97 port\n"); - return -EBUSY; - } - au1000->ac97_ioport = (struct au1000_ac97_reg *) - KSEG1ADDR(au1000->ac97_res_port->start); - - spin_lock_init(&au1000->ac97_lock); - - /* configure pins for AC'97 - TODO: move to board_setup.c */ - au_writel(au_readl(SYS_PINFUNC) & ~0x02, SYS_PINFUNC); - - /* Initialise Au1000's AC'97 Control Block */ - au1000->ac97_ioport->cntrl = AC97C_RS | AC97C_CE; - udelay(10); - au1000->ac97_ioport->cntrl = AC97C_CE; - udelay(10); - - /* Initialise External CODEC -- cold reset */ - au1000->ac97_ioport->config = AC97C_RESET; - udelay(10); - au1000->ac97_ioport->config = 0x0; - mdelay(5); - - /* Initialise AC97 middle-layer */ - if ((err = snd_ac97_bus(au1000->card, 0, &ops, au1000, &pbus)) < 0) - return err; - - memset(&ac97, 0, sizeof(ac97)); - ac97.private_data = au1000; - if ((err = snd_ac97_mixer(pbus, &ac97, &au1000->ac97)) < 0) - return err; - - return 0; -} - -/*------------------------------ Setup / Destroy ----------------------------*/ - -void -snd_au1000_free(struct snd_card *card) -{ - struct snd_au1000 *au1000 = card->private_data; - - if (au1000->ac97_res_port) { - /* put internal AC97 block into reset */ - au1000->ac97_ioport->cntrl = AC97C_RS; - au1000->ac97_ioport = NULL; - release_and_free_resource(au1000->ac97_res_port); - } - - if (au1000->stream[PLAYBACK]) { - if (au1000->stream[PLAYBACK]->dma >= 0) - free_au1000_dma(au1000->stream[PLAYBACK]->dma); - kfree(au1000->stream[PLAYBACK]); - } - - if (au1000->stream[CAPTURE]) { - if (au1000->stream[CAPTURE]->dma >= 0) - free_au1000_dma(au1000->stream[CAPTURE]->dma); - kfree(au1000->stream[CAPTURE]); - } -} - - -static struct snd_card *au1000_card; - -static int __init -au1000_init(void) -{ - int err; - struct snd_card *card; - struct snd_au1000 *au1000; - - err = snd_card_create(-1, "AC97", THIS_MODULE, - sizeof(struct snd_au1000), &card); - if (err < 0) - return err; - - card->private_free = snd_au1000_free; - au1000 = card->private_data; - au1000->card = card; - - au1000->stream[PLAYBACK] = kmalloc(sizeof(struct audio_stream), GFP_KERNEL); - au1000->stream[CAPTURE ] = kmalloc(sizeof(struct audio_stream), GFP_KERNEL); - /* so that snd_au1000_free will work as intended */ - au1000->ac97_res_port = NULL; - if (au1000->stream[PLAYBACK]) - au1000->stream[PLAYBACK]->dma = -1; - if (au1000->stream[CAPTURE ]) - au1000->stream[CAPTURE ]->dma = -1; - - if (au1000->stream[PLAYBACK] == NULL || - au1000->stream[CAPTURE ] == NULL) { - snd_card_free(card); - return -ENOMEM; - } - - if ((err = snd_au1000_ac97_new(au1000)) < 0 ) { - snd_card_free(card); - return err; - } - - if ((err = snd_au1000_pcm_new(au1000)) < 0) { - snd_card_free(card); - return err; - } - - strcpy(card->driver, "Au1000-AC97"); - strcpy(card->shortname, "AMD Au1000-AC97"); - sprintf(card->longname, "AMD Au1000--AC97 ALSA Driver"); - - if ((err = snd_card_register(card)) < 0) { - snd_card_free(card); - return err; - } - - printk(KERN_INFO "ALSA AC97: Driver Initialized\n"); - au1000_card = card; - return 0; -} - -static void __exit au1000_exit(void) -{ - snd_card_free(au1000_card); -} - -module_init(au1000_init); -module_exit(au1000_exit); -
At Thu, 21 Jul 2011 18:34:08 +0200, Manuel Lauss wrote:
Hello,
These 2 patches implement ASoC drivers for the AC97 and I2S controllers found on early Alchemy chips. They are largely based on the old mips/au1x00.c driver which they replace.
In general, dropping an old driver happens a bit later after the new driver comes in. You can make the old one as deprecated at first.
thanks,
Takashi
On Thu, Jul 21, 2011 at 7:08 PM, Takashi Iwai tiwai@suse.de wrote:
At Thu, 21 Jul 2011 18:34:08 +0200, Manuel Lauss wrote:
Hello,
These 2 patches implement ASoC drivers for the AC97 and I2S controllers found on early Alchemy chips. They are largely based on the old mips/au1x00.c driver which they replace.
In general, dropping an old driver happens a bit later after the new driver comes in. You can make the old one as deprecated at first.
Makes sense, just ignore patch 2 then ;)
Thanks, Manuel Lauss
participants (3)
-
Lars-Peter Clausen
-
Manuel Lauss
-
Takashi Iwai