diff options
Diffstat (limited to '')
-rw-r--r-- | sound/soc/cirrus/Kconfig | 60 | ||||
-rw-r--r-- | sound/soc/cirrus/Makefile | 18 | ||||
-rw-r--r-- | sound/soc/cirrus/edb93xx.c | 126 | ||||
-rw-r--r-- | sound/soc/cirrus/ep93xx-ac97.c | 450 | ||||
-rw-r--r-- | sound/soc/cirrus/ep93xx-i2s.c | 528 | ||||
-rw-r--r-- | sound/soc/cirrus/ep93xx-pcm.c | 70 | ||||
-rw-r--r-- | sound/soc/cirrus/ep93xx-pcm.h | 22 | ||||
-rw-r--r-- | sound/soc/cirrus/simone.c | 87 | ||||
-rw-r--r-- | sound/soc/cirrus/snappercl15.c | 136 |
9 files changed, 1497 insertions, 0 deletions
diff --git a/sound/soc/cirrus/Kconfig b/sound/soc/cirrus/Kconfig new file mode 100644 index 000000000..e09199124 --- /dev/null +++ b/sound/soc/cirrus/Kconfig @@ -0,0 +1,60 @@ +config SND_EP93XX_SOC + tristate "SoC Audio support for the Cirrus Logic EP93xx series" + depends on ARCH_EP93XX || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to + the EP93xx I2S or AC97 interfaces. + +config SND_EP93XX_SOC_I2S + tristate + +if SND_EP93XX_SOC_I2S + +config SND_EP93XX_SOC_I2S_WATCHDOG + bool "IRQ based underflow watchdog workaround" + default y + help + I2S controller on EP93xx seems to have undocumented HW issue. + Underflow of internal I2S controller FIFO could confuse the + state machine and the whole stream can be shifted by one byte + until I2S is disabled. This option enables IRQ based watchdog + which disables and re-enables I2S in case of underflow and + fills FIFO with zeroes. + + If you are unsure how to answer this question, answer Y. + +endif # if SND_EP93XX_SOC_I2S + +config SND_EP93XX_SOC_AC97 + tristate + select AC97_BUS + select SND_SOC_AC97_BUS + +config SND_EP93XX_SOC_SNAPPERCL15 + tristate "SoC Audio support for Bluewater Systems Snapper CL15 module" + depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15 && I2C + select SND_EP93XX_SOC_I2S + select SND_SOC_TLV320AIC23_I2C + help + Say Y or M here if you want to add support for I2S audio on the + Bluewater Systems Snapper CL15 module. + +config SND_EP93XX_SOC_SIMONE + tristate "SoC Audio support for Simplemachines Sim.One board" + depends on SND_EP93XX_SOC && MACH_SIM_ONE + select SND_EP93XX_SOC_AC97 + select SND_SOC_AC97_CODEC + help + Say Y or M here if you want to add support for AC97 audio on the + Simplemachines Sim.One board. + +config SND_EP93XX_SOC_EDB93XX + tristate "SoC Audio support for Cirrus Logic EDB93xx boards" + depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A) + select SND_EP93XX_SOC_I2S + select SND_SOC_CS4271_I2C if I2C + select SND_SOC_CS4271_SPI if SPI_MASTER + help + Say Y or M here if you want to add support for I2S audio on the + Cirrus Logic EDB93xx boards. diff --git a/sound/soc/cirrus/Makefile b/sound/soc/cirrus/Makefile new file mode 100644 index 000000000..bfb8dc409 --- /dev/null +++ b/sound/soc/cirrus/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# EP93xx Platform Support +snd-soc-ep93xx-objs := ep93xx-pcm.o +snd-soc-ep93xx-i2s-objs := ep93xx-i2s.o +snd-soc-ep93xx-ac97-objs := ep93xx-ac97.o + +obj-$(CONFIG_SND_EP93XX_SOC) += snd-soc-ep93xx.o +obj-$(CONFIG_SND_EP93XX_SOC_I2S) += snd-soc-ep93xx-i2s.o +obj-$(CONFIG_SND_EP93XX_SOC_AC97) += snd-soc-ep93xx-ac97.o + +# EP93XX Machine Support +snd-soc-snappercl15-objs := snappercl15.o +snd-soc-simone-objs := simone.o +snd-soc-edb93xx-objs := edb93xx.o + +obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o +obj-$(CONFIG_SND_EP93XX_SOC_SIMONE) += snd-soc-simone.o +obj-$(CONFIG_SND_EP93XX_SOC_EDB93XX) += snd-soc-edb93xx.o diff --git a/sound/soc/cirrus/edb93xx.c b/sound/soc/cirrus/edb93xx.c new file mode 100644 index 000000000..3d011abaa --- /dev/null +++ b/sound/soc/cirrus/edb93xx.c @@ -0,0 +1,126 @@ +/* + * SoC audio for EDB93xx + * + * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru> + * + * 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. + * + * This driver support CS4271 codec being master or slave, working + * in control port mode, connected either via SPI or I2C. + * The data format accepted is I2S or left-justified. + * DAPM support not implemented. + */ + +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> + +static int edb93xx_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->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int err; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + + /* + * According to CS4271 datasheet we use MCLK/LRCK=256 for + * rates below 50kHz and 128 for higher sample rates + */ + if (rate < 50000) + mclk_rate = rate * 64 * 4; + else + mclk_rate = rate * 64 * 2; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, + SND_SOC_CLOCK_IN); + if (err) + return err; + + return snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, + SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops edb93xx_ops = { + .hw_params = edb93xx_hw_params, +}; + +static struct snd_soc_dai_link edb93xx_dai = { + .name = "CS4271", + .stream_name = "CS4271 HiFi", + .platform_name = "ep93xx-i2s", + .cpu_dai_name = "ep93xx-i2s", + .codec_name = "spi0.0", + .codec_dai_name = "cs4271-hifi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &edb93xx_ops, +}; + +static struct snd_soc_card snd_soc_edb93xx = { + .name = "EDB93XX", + .owner = THIS_MODULE, + .dai_link = &edb93xx_dai, + .num_links = 1, +}; + +static int edb93xx_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_edb93xx; + int ret; + + ret = ep93xx_i2s_acquire(); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + ep93xx_i2s_release(); + } + + return ret; +} + +static int edb93xx_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + ep93xx_i2s_release(); + + return 0; +} + +static struct platform_driver edb93xx_driver = { + .driver = { + .name = "edb93xx-audio", + }, + .probe = edb93xx_probe, + .remove = edb93xx_remove, +}; + +module_platform_driver(edb93xx_driver); + +MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>"); +MODULE_DESCRIPTION("ALSA SoC EDB93xx"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:edb93xx-audio"); diff --git a/sound/soc/cirrus/ep93xx-ac97.c b/sound/soc/cirrus/ep93xx-ac97.c new file mode 100644 index 000000000..cd5a939ad --- /dev/null +++ b/sound/soc/cirrus/ep93xx-ac97.c @@ -0,0 +1,450 @@ +/* + * ASoC driver for Cirrus Logic EP93xx AC97 controller. + * + * Copyright (c) 2010 Mika Westerberg + * + * Based on s3c-ac97 ASoC driver by Jaswinder Singh. + * + * 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/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/ac97_codec.h> +#include <sound/soc.h> + +#include <linux/platform_data/dma-ep93xx.h> + +#include "ep93xx-pcm.h" + +/* + * Per channel (1-4) registers. + */ +#define AC97CH(n) (((n) - 1) * 0x20) + +#define AC97DR(n) (AC97CH(n) + 0x0000) + +#define AC97RXCR(n) (AC97CH(n) + 0x0004) +#define AC97RXCR_REN BIT(0) +#define AC97RXCR_RX3 BIT(3) +#define AC97RXCR_RX4 BIT(4) +#define AC97RXCR_CM BIT(15) + +#define AC97TXCR(n) (AC97CH(n) + 0x0008) +#define AC97TXCR_TEN BIT(0) +#define AC97TXCR_TX3 BIT(3) +#define AC97TXCR_TX4 BIT(4) +#define AC97TXCR_CM BIT(15) + +#define AC97SR(n) (AC97CH(n) + 0x000c) +#define AC97SR_TXFE BIT(1) +#define AC97SR_TXUE BIT(6) + +#define AC97RISR(n) (AC97CH(n) + 0x0010) +#define AC97ISR(n) (AC97CH(n) + 0x0014) +#define AC97IE(n) (AC97CH(n) + 0x0018) + +/* + * Global AC97 controller registers. + */ +#define AC97S1DATA 0x0080 +#define AC97S2DATA 0x0084 +#define AC97S12DATA 0x0088 + +#define AC97RGIS 0x008c +#define AC97GIS 0x0090 +#define AC97IM 0x0094 +/* + * Common bits for RGIS, GIS and IM registers. + */ +#define AC97_SLOT2RXVALID BIT(1) +#define AC97_CODECREADY BIT(5) +#define AC97_SLOT2TXCOMPLETE BIT(6) + +#define AC97EOI 0x0098 +#define AC97EOI_WINT BIT(0) +#define AC97EOI_CODECREADY BIT(1) + +#define AC97GCR 0x009c +#define AC97GCR_AC97IFE BIT(0) + +#define AC97RESET 0x00a0 +#define AC97RESET_TIMEDRESET BIT(0) + +#define AC97SYNC 0x00a4 +#define AC97SYNC_TIMEDSYNC BIT(0) + +#define AC97_TIMEOUT msecs_to_jiffies(5) + +/** + * struct ep93xx_ac97_info - EP93xx AC97 controller info structure + * @lock: mutex serializing access to the bus (slot 1 & 2 ops) + * @dev: pointer to the platform device dev structure + * @regs: mapped AC97 controller registers + * @done: bus ops wait here for an interrupt + */ +struct ep93xx_ac97_info { + struct mutex lock; + struct device *dev; + void __iomem *regs; + struct completion done; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +/* currently ALSA only supports a single AC97 device */ +static struct ep93xx_ac97_info *ep93xx_ac97_info; + +static struct ep93xx_dma_data ep93xx_ac97_pcm_out = { + .name = "ac97-pcm-out", + .port = EP93XX_DMA_AAC1, + .direction = DMA_MEM_TO_DEV, +}; + +static struct ep93xx_dma_data ep93xx_ac97_pcm_in = { + .name = "ac97-pcm-in", + .port = EP93XX_DMA_AAC1, + .direction = DMA_DEV_TO_MEM, +}; + +static inline unsigned ep93xx_ac97_read_reg(struct ep93xx_ac97_info *info, + unsigned reg) +{ + return __raw_readl(info->regs + reg); +} + +static inline void ep93xx_ac97_write_reg(struct ep93xx_ac97_info *info, + unsigned reg, unsigned val) +{ + __raw_writel(val, info->regs + reg); +} + +static unsigned short ep93xx_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + unsigned short val; + + mutex_lock(&info->lock); + + ep93xx_ac97_write_reg(info, AC97S1DATA, reg); + ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2RXVALID); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) { + dev_warn(info->dev, "timeout reading register %x\n", reg); + mutex_unlock(&info->lock); + return -ETIMEDOUT; + } + val = (unsigned short)ep93xx_ac97_read_reg(info, AC97S2DATA); + + mutex_unlock(&info->lock); + return val; +} + +static void ep93xx_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, + unsigned short val) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + + mutex_lock(&info->lock); + + /* + * Writes to the codec need to be done so that slot 2 is filled in + * before slot 1. + */ + ep93xx_ac97_write_reg(info, AC97S2DATA, val); + ep93xx_ac97_write_reg(info, AC97S1DATA, reg); + + ep93xx_ac97_write_reg(info, AC97IM, AC97_SLOT2TXCOMPLETE); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) + dev_warn(info->dev, "timeout writing register %x\n", reg); + + mutex_unlock(&info->lock); +} + +static void ep93xx_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + + mutex_lock(&info->lock); + + /* + * We are assuming that before this functions gets called, the codec + * BIT_CLK is stopped by forcing the codec into powerdown mode. We can + * control the SYNC signal directly via AC97SYNC register. Using + * TIMEDSYNC the controller will keep the SYNC high > 1us. + */ + ep93xx_ac97_write_reg(info, AC97SYNC, AC97SYNC_TIMEDSYNC); + ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) + dev_warn(info->dev, "codec warm reset timeout\n"); + + mutex_unlock(&info->lock); +} + +static void ep93xx_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct ep93xx_ac97_info *info = ep93xx_ac97_info; + + mutex_lock(&info->lock); + + /* + * For doing cold reset, we disable the AC97 controller interface, clear + * WINT and CODECREADY bits, and finally enable the interface again. + */ + ep93xx_ac97_write_reg(info, AC97GCR, 0); + ep93xx_ac97_write_reg(info, AC97EOI, AC97EOI_CODECREADY | AC97EOI_WINT); + ep93xx_ac97_write_reg(info, AC97GCR, AC97GCR_AC97IFE); + + /* + * Now, assert the reset and wait for the codec to become ready. + */ + ep93xx_ac97_write_reg(info, AC97RESET, AC97RESET_TIMEDRESET); + ep93xx_ac97_write_reg(info, AC97IM, AC97_CODECREADY); + if (!wait_for_completion_timeout(&info->done, AC97_TIMEOUT)) + dev_warn(info->dev, "codec cold reset timeout\n"); + + /* + * Give the codec some time to come fully out from the reset. This way + * we ensure that the subsequent reads/writes will work. + */ + usleep_range(15000, 20000); + + mutex_unlock(&info->lock); +} + +static irqreturn_t ep93xx_ac97_interrupt(int irq, void *dev_id) +{ + struct ep93xx_ac97_info *info = dev_id; + unsigned status, mask; + + /* + * Just mask out the interrupt and wake up the waiting thread. + * Interrupts are cleared via reading/writing to slot 1 & 2 registers by + * the waiting thread. + */ + status = ep93xx_ac97_read_reg(info, AC97GIS); + mask = ep93xx_ac97_read_reg(info, AC97IM); + mask &= ~status; + ep93xx_ac97_write_reg(info, AC97IM, mask); + + complete(&info->done); + return IRQ_HANDLED; +} + +static struct snd_ac97_bus_ops ep93xx_ac97_ops = { + .read = ep93xx_ac97_read, + .write = ep93xx_ac97_write, + .reset = ep93xx_ac97_cold_reset, + .warm_reset = ep93xx_ac97_warm_reset, +}; + +static int ep93xx_ac97_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai); + unsigned v = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * Enable compact mode, TX slots 3 & 4, and the TX FIFO + * itself. + */ + v |= AC97TXCR_CM; + v |= AC97TXCR_TX3 | AC97TXCR_TX4; + v |= AC97TXCR_TEN; + ep93xx_ac97_write_reg(info, AC97TXCR(1), v); + } else { + /* + * Enable compact mode, RX slots 3 & 4, and the RX FIFO + * itself. + */ + v |= AC97RXCR_CM; + v |= AC97RXCR_RX3 | AC97RXCR_RX4; + v |= AC97RXCR_REN; + ep93xx_ac97_write_reg(info, AC97RXCR(1), v); + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * As per Cirrus EP93xx errata described below: + * + * http://www.cirrus.com/en/pubs/errata/ER667E2B.pdf + * + * we will wait for the TX FIFO to be empty before + * clearing the TEN bit. + */ + unsigned long timeout = jiffies + AC97_TIMEOUT; + + do { + v = ep93xx_ac97_read_reg(info, AC97SR(1)); + if (time_after(jiffies, timeout)) { + dev_warn(info->dev, "TX timeout\n"); + break; + } + } while (!(v & (AC97SR_TXFE | AC97SR_TXUE))); + + /* disable the TX FIFO */ + ep93xx_ac97_write_reg(info, AC97TXCR(1), 0); + } else { + /* disable the RX FIFO */ + ep93xx_ac97_write_reg(info, AC97RXCR(1), 0); + } + break; + + default: + dev_warn(info->dev, "unknown command %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int ep93xx_ac97_dai_probe(struct snd_soc_dai *dai) +{ + struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai); + + info->dma_params_tx.filter_data = &ep93xx_ac97_pcm_out; + info->dma_params_rx.filter_data = &ep93xx_ac97_pcm_in; + + dai->playback_dma_data = &info->dma_params_tx; + dai->capture_dma_data = &info->dma_params_rx; + + return 0; +} + +static const struct snd_soc_dai_ops ep93xx_ac97_dai_ops = { + .trigger = ep93xx_ac97_trigger, +}; + +static struct snd_soc_dai_driver ep93xx_ac97_dai = { + .name = "ep93xx-ac97", + .id = 0, + .bus_control = true, + .probe = ep93xx_ac97_dai_probe, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &ep93xx_ac97_dai_ops, +}; + +static const struct snd_soc_component_driver ep93xx_ac97_component = { + .name = "ep93xx-ac97", +}; + +static int ep93xx_ac97_probe(struct platform_device *pdev) +{ + struct ep93xx_ac97_info *info; + struct resource *res; + int irq; + int ret; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq < 0 ? irq : -ENODEV; + + ret = devm_request_irq(&pdev->dev, irq, ep93xx_ac97_interrupt, + IRQF_TRIGGER_HIGH, pdev->name, info); + if (ret) + goto fail; + + dev_set_drvdata(&pdev->dev, info); + + mutex_init(&info->lock); + init_completion(&info->done); + info->dev = &pdev->dev; + + ep93xx_ac97_info = info; + platform_set_drvdata(pdev, info); + + ret = snd_soc_set_ac97_ops(&ep93xx_ac97_ops); + if (ret) + goto fail; + + ret = snd_soc_register_component(&pdev->dev, &ep93xx_ac97_component, + &ep93xx_ac97_dai, 1); + if (ret) + goto fail; + + ret = devm_ep93xx_pcm_platform_register(&pdev->dev); + if (ret) + goto fail_unregister; + + return 0; + +fail_unregister: + snd_soc_unregister_component(&pdev->dev); +fail: + ep93xx_ac97_info = NULL; + snd_soc_set_ac97_ops(NULL); + return ret; +} + +static int ep93xx_ac97_remove(struct platform_device *pdev) +{ + struct ep93xx_ac97_info *info = platform_get_drvdata(pdev); + + snd_soc_unregister_component(&pdev->dev); + + /* disable the AC97 controller */ + ep93xx_ac97_write_reg(info, AC97GCR, 0); + + ep93xx_ac97_info = NULL; + + snd_soc_set_ac97_ops(NULL); + + return 0; +} + +static struct platform_driver ep93xx_ac97_driver = { + .probe = ep93xx_ac97_probe, + .remove = ep93xx_ac97_remove, + .driver = { + .name = "ep93xx-ac97", + }, +}; + +module_platform_driver(ep93xx_ac97_driver); + +MODULE_DESCRIPTION("EP93xx AC97 ASoC Driver"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ep93xx-ac97"); diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c new file mode 100644 index 000000000..0918c5da5 --- /dev/null +++ b/sound/soc/cirrus/ep93xx-i2s.c @@ -0,0 +1,528 @@ +/* + * linux/sound/soc/ep93xx-i2s.c + * EP93xx I2S driver + * + * Copyright (C) 2010 Ryan Mallon + * + * Based on the original driver by: + * Copyright (C) 2007 Chase Douglas <chasedouglas@gmail> + * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + * + * 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/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/hardware.h> +#include <mach/ep93xx-regs.h> +#include <linux/platform_data/dma-ep93xx.h> + +#include "ep93xx-pcm.h" + +#define EP93XX_I2S_TXCLKCFG 0x00 +#define EP93XX_I2S_RXCLKCFG 0x04 +#define EP93XX_I2S_GLSTS 0x08 +#define EP93XX_I2S_GLCTRL 0x0C + +#define EP93XX_I2S_I2STX0LFT 0x10 +#define EP93XX_I2S_I2STX0RT 0x14 + +#define EP93XX_I2S_TXLINCTRLDATA 0x28 +#define EP93XX_I2S_TXCTRL 0x2C +#define EP93XX_I2S_TXWRDLEN 0x30 +#define EP93XX_I2S_TX0EN 0x34 + +#define EP93XX_I2S_RXLINCTRLDATA 0x58 +#define EP93XX_I2S_RXCTRL 0x5C +#define EP93XX_I2S_RXWRDLEN 0x60 +#define EP93XX_I2S_RX0EN 0x64 + +#define EP93XX_I2S_WRDLEN_16 (0 << 0) +#define EP93XX_I2S_WRDLEN_24 (1 << 0) +#define EP93XX_I2S_WRDLEN_32 (2 << 0) + +#define EP93XX_I2S_RXLINCTRLDATA_R_JUST BIT(1) /* Right justify */ + +#define EP93XX_I2S_TXLINCTRLDATA_R_JUST BIT(2) /* Right justify */ + +/* + * Transmit empty interrupt level select: + * 0 - Generate interrupt when FIFO is half empty + * 1 - Generate interrupt when FIFO is empty + */ +#define EP93XX_I2S_TXCTRL_TXEMPTY_LVL BIT(0) +#define EP93XX_I2S_TXCTRL_TXUFIE BIT(1) /* Transmit interrupt enable */ + +#define EP93XX_I2S_CLKCFG_LRS (1 << 0) /* lrclk polarity */ +#define EP93XX_I2S_CLKCFG_CKP (1 << 1) /* Bit clock polarity */ +#define EP93XX_I2S_CLKCFG_REL (1 << 2) /* First bit transition */ +#define EP93XX_I2S_CLKCFG_MASTER (1 << 3) /* Master mode */ +#define EP93XX_I2S_CLKCFG_NBCG (1 << 4) /* Not bit clock gating */ + +#define EP93XX_I2S_GLSTS_TX0_FIFO_FULL BIT(12) + +struct ep93xx_i2s_info { + struct clk *mclk; + struct clk *sclk; + struct clk *lrclk; + void __iomem *regs; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +static struct ep93xx_dma_data ep93xx_i2s_dma_data[] = { + [SNDRV_PCM_STREAM_PLAYBACK] = { + .name = "i2s-pcm-out", + .port = EP93XX_DMA_I2S1, + .direction = DMA_MEM_TO_DEV, + }, + [SNDRV_PCM_STREAM_CAPTURE] = { + .name = "i2s-pcm-in", + .port = EP93XX_DMA_I2S1, + .direction = DMA_DEV_TO_MEM, + }, +}; + +static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info, + unsigned reg, unsigned val) +{ + __raw_writel(val, info->regs + reg); +} + +static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info, + unsigned reg) +{ + return __raw_readl(info->regs + reg); +} + +static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Enable clocks */ + clk_enable(info->mclk); + clk_enable(info->sclk); + clk_enable(info->lrclk); + + /* Enable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1); + } + + /* Enable fifo */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + ep93xx_i2s_write_reg(info, base_reg, 1); + + /* Enable TX IRQs (FIFO empty or underflow) */ + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) && + stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL, + EP93XX_I2S_TXCTRL_TXEMPTY_LVL | + EP93XX_I2S_TXCTRL_TXUFIE); +} + +static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + + /* Disable IRQs */ + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) && + stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL, 0); + + /* Disable fifo */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + ep93xx_i2s_write_reg(info, base_reg, 0); + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Disable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0); + + /* Disable clocks */ + clk_disable(info->lrclk); + clk_disable(info->sclk); + clk_disable(info->mclk); + } +} + +/* + * According to documentation I2S controller can handle underflow conditions + * just fine, but in reality the state machine is sometimes confused so that + * the whole stream is shifted by one byte. The watchdog below disables the TX + * FIFO, fills the buffer with zeroes and re-enables the FIFO. State machine + * is being reset and by filling the buffer we get some time before next + * underflow happens. + */ +static irqreturn_t ep93xx_i2s_interrupt(int irq, void *dev_id) +{ + struct ep93xx_i2s_info *info = dev_id; + + /* Disable FIFO */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 0); + /* + * Fill TX FIFO with zeroes, this way we can defer next IRQs as much as + * possible and get more time for DMA to catch up. Actually there are + * only 8 samples in this FIFO, so even on 8kHz maximum deferral here is + * 1ms. + */ + while (!(ep93xx_i2s_read_reg(info, EP93XX_I2S_GLSTS) & + EP93XX_I2S_GLSTS_TX0_FIFO_FULL)) { + ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0LFT, 0); + ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0RT, 0); + } + /* Re-enable FIFO */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 1); + + return IRQ_HANDLED; +} + +static int ep93xx_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + info->dma_params_tx.filter_data = + &ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + info->dma_params_rx.filter_data = + &ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_CAPTURE]; + + dai->playback_dma_data = &info->dma_params_tx; + dai->capture_dma_data = &info->dma_params_rx; + + return 0; +} + +static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + ep93xx_i2s_disable(info, substream->stream); +} + +static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai); + unsigned int clk_cfg; + unsigned int txlin_ctrl = 0; + unsigned int rxlin_ctrl = 0; + + clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + clk_cfg |= EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_LEFT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_RIGHT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + rxlin_ctrl |= EP93XX_I2S_RXLINCTRLDATA_R_JUST; + txlin_ctrl |= EP93XX_I2S_TXLINCTRLDATA_R_JUST; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + clk_cfg |= EP93XX_I2S_CLKCFG_MASTER; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* Codec is master */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Negative bit clock, lrclk low on left word */ + clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS); + break; + + case SND_SOC_DAIFMT_NB_IF: + /* Negative bit clock, lrclk low on right word */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP; + clk_cfg |= EP93XX_I2S_CLKCFG_LRS; + break; + + case SND_SOC_DAIFMT_IB_NF: + /* Positive bit clock, lrclk low on left word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP; + clk_cfg &= ~EP93XX_I2S_CLKCFG_LRS; + break; + + case SND_SOC_DAIFMT_IB_IF: + /* Positive bit clock, lrclk low on right word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS; + break; + } + + /* Write new register values */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, rxlin_ctrl); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, txlin_ctrl); + return 0; +} + +static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + unsigned word_len, div, sdiv, lrdiv; + int err; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = EP93XX_I2S_WRDLEN_16; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + word_len = EP93XX_I2S_WRDLEN_24; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + word_len = EP93XX_I2S_WRDLEN_32; + break; + + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len); + else + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len); + + /* + * EP93xx I2S module can be setup so SCLK / LRCLK value can be + * 32, 64, 128. MCLK / SCLK value can be 2 and 4. + * We set LRCLK equal to `rate' and minimum SCLK / LRCLK + * value is 64, because our sample size is 32 bit * 2 channels. + * I2S standard permits us to transmit more bits than + * the codec uses. + */ + div = clk_get_rate(info->mclk) / params_rate(params); + sdiv = 4; + if (div > (256 + 512) / 2) { + lrdiv = 128; + } else { + lrdiv = 64; + if (div < (128 + 256) / 2) + sdiv = 2; + } + + err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv); + if (err) + return err; + + err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv); + if (err) + return err; + + ep93xx_i2s_enable(info, substream->stream); + return 0; +} + +static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai); + + if (dir == SND_SOC_CLOCK_IN || clk_id != 0) + return -EINVAL; + + return clk_set_rate(info->mclk, freq); +} + +#ifdef CONFIG_PM +static int ep93xx_i2s_suspend(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + if (!dai->active) + return 0; + + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; +} + +static int ep93xx_i2s_resume(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + if (!dai->active) + return 0; + + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; +} +#else +#define ep93xx_i2s_suspend NULL +#define ep93xx_i2s_resume NULL +#endif + +static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .shutdown = ep93xx_i2s_shutdown, + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, +}; + +#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver ep93xx_i2s_dai = { + .symmetric_rates= 1, + .probe = ep93xx_i2s_dai_probe, + .suspend = ep93xx_i2s_suspend, + .resume = ep93xx_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = EP93XX_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = EP93XX_I2S_FORMATS, + }, + .ops = &ep93xx_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver ep93xx_i2s_component = { + .name = "ep93xx-i2s", +}; + +static int ep93xx_i2s_probe(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info; + struct resource *res; + int err; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG)) { + int irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq < 0 ? irq : -ENODEV; + + err = devm_request_irq(&pdev->dev, irq, ep93xx_i2s_interrupt, 0, + pdev->name, info); + if (err) + return err; + } + + info->mclk = clk_get(&pdev->dev, "mclk"); + if (IS_ERR(info->mclk)) { + err = PTR_ERR(info->mclk); + goto fail; + } + + info->sclk = clk_get(&pdev->dev, "sclk"); + if (IS_ERR(info->sclk)) { + err = PTR_ERR(info->sclk); + goto fail_put_mclk; + } + + info->lrclk = clk_get(&pdev->dev, "lrclk"); + if (IS_ERR(info->lrclk)) { + err = PTR_ERR(info->lrclk); + goto fail_put_sclk; + } + + dev_set_drvdata(&pdev->dev, info); + + err = snd_soc_register_component(&pdev->dev, &ep93xx_i2s_component, + &ep93xx_i2s_dai, 1); + if (err) + goto fail_put_lrclk; + + err = devm_ep93xx_pcm_platform_register(&pdev->dev); + if (err) + goto fail_unregister; + + return 0; + +fail_unregister: + snd_soc_unregister_component(&pdev->dev); +fail_put_lrclk: + clk_put(info->lrclk); +fail_put_sclk: + clk_put(info->sclk); +fail_put_mclk: + clk_put(info->mclk); +fail: + return err; +} + +static int ep93xx_i2s_remove(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + clk_put(info->lrclk); + clk_put(info->sclk); + clk_put(info->mclk); + return 0; +} + +static struct platform_driver ep93xx_i2s_driver = { + .probe = ep93xx_i2s_probe, + .remove = ep93xx_i2s_remove, + .driver = { + .name = "ep93xx-i2s", + }, +}; + +module_platform_driver(ep93xx_i2s_driver); + +MODULE_ALIAS("platform:ep93xx-i2s"); +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("EP93XX I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/cirrus/ep93xx-pcm.c b/sound/soc/cirrus/ep93xx-pcm.c new file mode 100644 index 000000000..67a73330d --- /dev/null +++ b/sound/soc/cirrus/ep93xx-pcm.c @@ -0,0 +1,70 @@ +/* + * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface + * + * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + * Copyright (C) 2006 Applied Data Systems + * + * Rewritten for the SoC audio subsystem (Based on PXA2xx code): + * Copyright (c) 2008 Ryan Mallon + * + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/dmaengine.h> + +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include <linux/platform_data/dma-ep93xx.h> + +#include "ep93xx-pcm.h" + +static const struct snd_pcm_hardware ep93xx_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .buffer_bytes_max = 131072, + .period_bytes_min = 32, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 32, +}; + +static bool ep93xx_pcm_dma_filter(struct dma_chan *chan, void *filter_param) +{ + struct ep93xx_dma_data *data = filter_param; + + if (data->direction == ep93xx_dma_chan_direction(chan)) { + chan->private = data; + return true; + } + + return false; +} + +static const struct snd_dmaengine_pcm_config ep93xx_dmaengine_pcm_config = { + .pcm_hardware = &ep93xx_pcm_hardware, + .compat_filter_fn = ep93xx_pcm_dma_filter, + .prealloc_buffer_size = 131072, +}; + +int devm_ep93xx_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, + &ep93xx_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_NO_DT | + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(devm_ep93xx_pcm_platform_register); + +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("EP93xx ALSA PCM interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/cirrus/ep93xx-pcm.h b/sound/soc/cirrus/ep93xx-pcm.h new file mode 100644 index 000000000..b7a12a2fa --- /dev/null +++ b/sound/soc/cirrus/ep93xx-pcm.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __EP93XX_PCM_H__ +#define __EP93XX_PCM_H__ + +int devm_ep93xx_pcm_platform_register(struct device *dev); + +#endif diff --git a/sound/soc/cirrus/simone.c b/sound/soc/cirrus/simone.c new file mode 100644 index 000000000..1ec661834 --- /dev/null +++ b/sound/soc/cirrus/simone.c @@ -0,0 +1,87 @@ +/* + * simone.c -- ASoC audio for Simplemachines Sim.One board + * + * Copyright (c) 2010 Mika Westerberg + * + * Based on snappercl15 machine driver by Ryan Mallon. + * + * 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/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +static struct snd_soc_dai_link simone_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "ep93xx-ac97", + .codec_dai_name = "ac97-hifi", + .codec_name = "ac97-codec", + .platform_name = "ep93xx-ac97", +}; + +static struct snd_soc_card snd_soc_simone = { + .name = "Sim.One", + .owner = THIS_MODULE, + .dai_link = &simone_dai, + .num_links = 1, +}; + +static struct platform_device *simone_snd_ac97_device; + +static int simone_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_simone; + int ret; + + simone_snd_ac97_device = platform_device_register_simple("ac97-codec", + -1, NULL, 0); + if (IS_ERR(simone_snd_ac97_device)) + return PTR_ERR(simone_snd_ac97_device); + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + platform_device_unregister(simone_snd_ac97_device); + } + + return ret; +} + +static int simone_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + platform_device_unregister(simone_snd_ac97_device); + + return 0; +} + +static struct platform_driver simone_driver = { + .driver = { + .name = "simone-audio", + }, + .probe = simone_probe, + .remove = simone_remove, +}; + +module_platform_driver(simone_driver); + +MODULE_DESCRIPTION("ALSA SoC Simplemachines Sim.One"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:simone-audio"); diff --git a/sound/soc/cirrus/snappercl15.c b/sound/soc/cirrus/snappercl15.c new file mode 100644 index 000000000..11ff7b267 --- /dev/null +++ b/sound/soc/cirrus/snappercl15.c @@ -0,0 +1,136 @@ +/* + * snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module + * + * Copyright (C) 2008 Bluewater Systems Ltd + * Author: Ryan Mallon + * + * 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/platform_device.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "../codecs/tlv320aic23.h" + +#define CODEC_CLOCK 5644800 + +static int snappercl15_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->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int err; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_IN); + if (err) + return err; + + err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_OUT); + if (err) + return err; + + return 0; +} + +static const struct snd_soc_ops snappercl15_ops = { + .hw_params = snappercl15_hw_params, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static struct snd_soc_dai_link snappercl15_dai = { + .name = "tlv320aic23", + .stream_name = "AIC23", + .cpu_dai_name = "ep93xx-i2s", + .codec_dai_name = "tlv320aic23-hifi", + .codec_name = "tlv320aic23-codec.0-001a", + .platform_name = "ep93xx-i2s", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &snappercl15_ops, +}; + +static struct snd_soc_card snd_soc_snappercl15 = { + .name = "Snapper CL15", + .owner = THIS_MODULE, + .dai_link = &snappercl15_dai, + .num_links = 1, + + .dapm_widgets = tlv320aic23_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int snappercl15_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_snappercl15; + int ret; + + ret = ep93xx_i2s_acquire(); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + ep93xx_i2s_release(); + } + + return ret; +} + +static int snappercl15_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + ep93xx_i2s_release(); + + return 0; +} + +static struct platform_driver snappercl15_driver = { + .driver = { + .name = "snappercl15-audio", + }, + .probe = snappercl15_probe, + .remove = snappercl15_remove, +}; + +module_platform_driver(snappercl15_driver); + +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("ALSA SoC Snapper CL15"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:snappercl15-audio"); |