[alsa-devel] [PATCH v2 1/3] ALSA SoC: Add OpenFirmware helper for matching bus and codec drivers
From: Grant Likely grant.likely@secretlab.ca
Simple utility layer for creating ASoC machine instances based on data in the OpenFirmware device tree. OF aware platform drivers and codec drivers register themselves with this framework and the framework automatically instantiates a machine driver.
This is most likely temporary glue code to work around limitations in the ASoC v1 framework. I expect ASoC v2 won't need this. ---
include/sound/soc-of.h | 21 ++++++ sound/soc/Kconfig | 3 + sound/soc/Makefile | 1 sound/soc/soc-of.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 0 deletions(-)
diff --git a/include/sound/soc-of.h b/include/sound/soc-of.h new file mode 100644 index 0000000..a963032 --- /dev/null +++ b/include/sound/soc-of.h @@ -0,0 +1,21 @@ +/* + * OF helpers for ALSA SoC + * + * Copyright (C) 2008, Secret Lab Technologies Ltd. + */ + +#ifndef _INCLUDE_SOC_OF_H_ +#define _INCLUDE_SOC_OF_H_ + +#include <linux/of.h> +#include <sound/soc.h> + +int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev, + void *codec_data, struct snd_soc_codec_dai *dai, + struct device_node *node); + +int of_snd_soc_register_platform(struct snd_soc_platform *platform, + struct device_node *node, + struct snd_soc_cpu_dai *cpu_dai); + +#endif /* _INCLUDE_SOC_OF_H_ */ diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 18f28ac..99118ed 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -23,6 +23,9 @@ config SND_SOC This ASoC audio support can also be built as a module. If so, the module will be called snd-soc-core.
+config SND_SOC_OF + tristate + # All the supported Soc's source "sound/soc/at91/Kconfig" source "sound/soc/pxa/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 782db21..191c2e5 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -2,3 +2,4 @@ snd-soc-core-objs := soc-core.o soc-dapm.o
obj-$(CONFIG_SND_SOC) += snd-soc-core.o obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ omap/ +obj-$(CONFIG_SND_SOC_OF) += soc-of.o diff --git a/sound/soc/soc-of.c b/sound/soc/soc-of.c new file mode 100644 index 0000000..0c855df --- /dev/null +++ b/sound/soc/soc-of.c @@ -0,0 +1,171 @@ +/* + * OF helpers for ALSA SoC Layer + * + * Copyright (C) 2008, Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-of.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALSA SoC OpenFirmware bindings"); + +static DEFINE_MUTEX(of_snd_soc_mutex); +static LIST_HEAD(of_snd_soc_device_list); +static int of_snd_soc_next_index; + +struct of_snd_soc_device { + int id; + struct list_head list; + struct snd_soc_device device; + struct snd_soc_machine machine; + struct snd_soc_dai_link dai_link; + struct platform_device *pdev; + struct device_node *platform_node; + struct device_node *codec_node; +}; + +static struct snd_soc_ops of_snd_soc_ops = { +}; + +static struct of_snd_soc_device * +of_snd_soc_get_device(struct device_node *codec_node) +{ + struct of_snd_soc_device *of_soc; + + list_for_each_entry(of_soc, &of_snd_soc_device_list, list) { + if (of_soc->codec_node == codec_node) + return of_soc; + } + + of_soc = kzalloc(sizeof(struct of_snd_soc_device), GFP_KERNEL); + if (!of_soc) + return NULL; + + /* Initialize the structure and add it to the global list */ + of_soc->codec_node = codec_node; + of_soc->id = of_snd_soc_next_index++; + of_soc->machine.dai_link = &of_soc->dai_link; + of_soc->machine.num_links = 1; + of_soc->device.machine = &of_soc->machine; + of_soc->dai_link.ops = &of_snd_soc_ops; + list_add(&of_soc->list, &of_snd_soc_device_list); + + return of_soc; +} + +static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc) +{ + struct platform_device *pdev; + int rc; + + /* Only register the device if both the codec and platform have + * been registered */ + if ((!of_soc->device.codec_data) || (!of_soc->platform_node)) + return; + + pr_info("platform<-->codec match achieved; registering machine\n"); + + pdev = platform_device_alloc("soc-audio", of_soc->id); + if (!pdev) { + pr_err("of_soc: platform_device_alloc() failed\n"); + return; + } + + pdev->dev.platform_data = of_soc; + platform_set_drvdata(pdev, &of_soc->device); + of_soc->device.dev = &pdev->dev; + + /* The ASoC device is complete; register it */ + rc = platform_device_add(pdev); + if (rc) { + pr_err("of_soc: platform_device_add() failed\n"); + return; + } + +} + +int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev, + void *codec_data, struct snd_soc_codec_dai *dai, + struct device_node *node) +{ + struct of_snd_soc_device *of_soc; + int rc = 0; + + pr_info("registering ASoC codec driver: %s\n", node->full_name); + + mutex_lock(&of_snd_soc_mutex); + of_soc = of_snd_soc_get_device(node); + if (!of_soc) { + rc = -ENOMEM; + goto out; + } + + /* Store the codec data */ + of_soc->device.codec_data = codec_data; + of_soc->device.codec_dev = codec_dev; + of_soc->dai_link.name = node->name; + of_soc->dai_link.stream_name = node->name; + of_soc->dai_link.codec_dai = dai; + + /* Now try to register the SoC device */ + of_snd_soc_register_device(of_soc); + + out: + mutex_unlock(&of_snd_soc_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(of_snd_soc_register_codec); + +int of_snd_soc_register_platform(struct snd_soc_platform *platform, + struct device_node *node, + struct snd_soc_cpu_dai *cpu_dai) +{ + struct of_snd_soc_device *of_soc; + struct device_node *codec_node; + const phandle *handle; + int len, rc = 0; + + pr_info("registering ASoC platform driver: %s\n", node->full_name); + + handle = of_get_property(node, "codec-handle", &len); + if (!handle || len < sizeof(handle)) + return -ENODEV; + codec_node = of_find_node_by_phandle(*handle); + if (!codec_node) + return -ENODEV; + pr_info("looking for codec: %s\n", codec_node->full_name); + + mutex_lock(&of_snd_soc_mutex); + of_soc = of_snd_soc_get_device(codec_node); + if (!of_soc) { + rc = -ENOMEM; + goto out; + } + + of_soc->platform_node = node; + of_soc->dai_link.cpu_dai = cpu_dai; + of_soc->device.platform = platform; + of_soc->machine.name = of_soc->dai_link.cpu_dai->name; + + /* Now try to register the SoC device */ + of_snd_soc_register_device(of_soc); + + out: + mutex_unlock(&of_snd_soc_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(of_snd_soc_register_platform);
From: Grant Likely grant.likely@secretlab.ca
This is an I2S bus driver for the MPC5200 PSC device. It is probably will not be merged as-is because it uses v1 of the ASoC API, but I want to get it out there for comments. ---
include/asm-powerpc/mpc52xx_psc.h | 32 + sound/soc/fsl/Kconfig | 7 sound/soc/fsl/Makefile | 2 sound/soc/fsl/mpc5200_psc_i2s.c | 896 +++++++++++++++++++++++++++++++++++++ 4 files changed, 936 insertions(+), 1 deletions(-)
diff --git a/include/asm-powerpc/mpc52xx_psc.h b/include/asm-powerpc/mpc52xx_psc.h index 710c5d3..0985dc8 100644 --- a/include/asm-powerpc/mpc52xx_psc.h +++ b/include/asm-powerpc/mpc52xx_psc.h @@ -60,10 +60,12 @@ #define MPC52xx_PSC_RXTX_FIFO_ALARM 0x0002 #define MPC52xx_PSC_RXTX_FIFO_EMPTY 0x0001
-/* PSC interrupt mask bits */ +/* PSC interrupt status/mask bits */ #define MPC52xx_PSC_IMR_TXRDY 0x0100 #define MPC52xx_PSC_IMR_RXRDY 0x0200 #define MPC52xx_PSC_IMR_DB 0x0400 +#define MPC52xx_PSC_IMR_TXEMP 0x0800 +#define MPC52xx_PSC_IMR_ORERR 0x1000 #define MPC52xx_PSC_IMR_IPC 0x8000
/* PSC input port change bit */ @@ -92,6 +94,34 @@
#define MPC52xx_PSC_RFNUM_MASK 0x01ff
+#define MPC52xx_PSC_SICR_DTS1 (1 << 29) +#define MPC52xx_PSC_SICR_SHDR (1 << 28) +#define MPC52xx_PSC_SICR_SIM_MASK (0xf << 24) +#define MPC52xx_PSC_SICR_SIM_UART (0x0 << 24) +#define MPC52xx_PSC_SICR_SIM_UART_DCD (0x8 << 24) +#define MPC52xx_PSC_SICR_SIM_CODEC_8 (0x1 << 24) +#define MPC52xx_PSC_SICR_SIM_CODEC_16 (0x2 << 24) +#define MPC52xx_PSC_SICR_SIM_AC97 (0x3 << 24) +#define MPC52xx_PSC_SICR_SIM_SIR (0x8 << 24) +#define MPC52xx_PSC_SICR_SIM_SIR_DCD (0xc << 24) +#define MPC52xx_PSC_SICR_SIM_MIR (0x5 << 24) +#define MPC52xx_PSC_SICR_SIM_FIR (0x6 << 24) +#define MPC52xx_PSC_SICR_SIM_CODEC_24 (0x7 << 24) +#define MPC52xx_PSC_SICR_SIM_CODEC_32 (0xf << 24) +#define MPC52xx_PSC_SICR_GENCLK (1 << 23) +#define MPC52xx_PSC_SICR_I2S (1 << 22) +#define MPC52xx_PSC_SICR_CLKPOL (1 << 21) +#define MPC52xx_PSC_SICR_SYNCPOL (1 << 20) +#define MPC52xx_PSC_SICR_CELLSLAVE (1 << 19) +#define MPC52xx_PSC_SICR_CELL2XCLK (1 << 18) +#define MPC52xx_PSC_SICR_ESAI (1 << 17) +#define MPC52xx_PSC_SICR_ENAC97 (1 << 16) +#define MPC52xx_PSC_SICR_SPI (1 << 15) +#define MPC52xx_PSC_SICR_MSTR (1 << 14) +#define MPC52xx_PSC_SICR_CPOL (1 << 13) +#define MPC52xx_PSC_SICR_CPHA (1 << 12) +#define MPC52xx_PSC_SICR_USEEOF (1 << 11) +#define MPC52xx_PSC_SICR_DISABLEEOF (1 << 10)
/* Structure of the hardware registers */ struct mpc52xx_psc { diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 257101f..9ac970e 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -17,4 +17,11 @@ config SND_SOC_MPC8610_HPCD help Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
+config SND_SOC_MPC5200_I2S + tristate "Freescale MPC5200 PSC in I2S mode driver" + select SND_SOC_OF + depends on SND_SOC && PPC_MPC52xx + help + Say Y here to support the MPC5200 PSCs in I2S mode. + endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 62f680a..98729a1 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -4,3 +4,5 @@ obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o # MPC8610 Platform Support obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o
+obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o + diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c new file mode 100644 index 0000000..cdaae72 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -0,0 +1,896 @@ +/* + * Freescale MPC5200 PSC in I2S mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-of.h> + +#include <sysdev/bestcomm/bestcomm.h> +#include <sysdev/bestcomm/gen_bd.h> +#include <asm/mpc52xx_psc.h> + +MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); +MODULE_LICENSE("GPL"); + +/** + * PSC_I2S_RATES: sample rates supported by the I2S + * + * This driver currently only supports the PSC running in I2S slave mode, + * which means the codec determines the sample rate. Therefore, we tell + * ALSA that we support all rates and let the codec driver decide what rates + * are really supported. + */ +#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \ + SNDRV_PCM_RATE_CONTINUOUS) + +/** + * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode + */ +#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_S32_BE) + +/** + * psc_i2s_stream - Data specific to a single stream (playback or capture) + * @active: flag indicating if the stream is active + * @psc_i2s: pointer back to parent psc_i2s data structure + * @bcom_task: bestcomm task structure + * @irq: irq number for bestcomm task + * @period_start: physical address of start of DMA region + * @period_end: physical address of end of DMA region + * @period_next_pt: physical address of next DMA buffer to enqueue + * @period_bytes: size of DMA period in bytes + */ +struct psc_i2s_stream { + int active; + struct psc_i2s *psc_i2s; + struct bcom_task *bcom_task; + int irq; + struct snd_pcm_substream *stream; + dma_addr_t period_start; + dma_addr_t period_end; + dma_addr_t period_next_pt; + dma_addr_t period_current_pt; + int period_bytes; +}; + +/** + * psc_i2s - Private driver data + * @name: short name for this device ("PSC0", "PSC1", etc) + * @psc_regs: pointer to the PSC's registers + * @fifo_regs: pointer to the PSC's FIFO registers + * @irq: IRQ of this PSC + * @dev: struct device pointer + * @playback: the number of playback streams opened + * @capture: the number of capture streams opened + * @dai: the CPU DAI for this device + * @playback_stream: Playback stream context data + * @capture_stream: Capture stream context data + */ +struct psc_i2s { + char name[32]; + struct mpc52xx_psc __iomem *psc_regs; + struct mpc52xx_psc_fifo __iomem *fifo_regs; + unsigned int irq; + struct device *dev; + struct snd_soc_cpu_dai dai; + spinlock_t lock; + + /* per-stream data */ + struct psc_i2s_stream playback_stream; + struct psc_i2s_stream capture_stream; + + /* Statistics */ + struct { + int overrun_count; + int underrun_count; + } stats; +}; + +/* + * Interrupt handlers + */ +static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) +{ + struct psc_i2s *psc_i2s = _psc_i2s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 imr; + u16 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + imr = in_be16(®s->mpc52xx_psc_imr); + + /* Playback underrun error */ + if (isr & imr & MPC52xx_PSC_IMR_TXEMP) + psc_i2s->stats.underrun_count++; + + /* Capture overrun error */ + if (isr & imr & MPC52xx_PSC_IMR_ORERR) + psc_i2s->stats.overrun_count++; + + out_8(®s->command, 4 << 4); /* reset the error status */ + + return IRQ_HANDLED; +} + +/** + * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer + * @s: pointer to stream private data structure + * + * Enqueues another audio period buffer into the bestcomm queue. + * + * Note: The routine must only be called when there is space available in + * the queue. Otherwise the enqueue will fail and the audio ring buffer + * will get out of sync + */ +static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) +{ + struct bcom_bd *bd; + + /* Prepare and enqueue the next buffer descriptor */ + bd = bcom_prepare_next_buffer(s->bcom_task); + bd->status = s->period_bytes; + bd->data[0] = s->period_next_pt; + bcom_submit_next_buffer(s->bcom_task, NULL); + + /* Update for next period */ + s->period_next_pt += s->period_bytes; + if (s->period_next_pt >= s->period_end) + s->period_next_pt = s->period_start; +} + +/* Bestcomm DMA irq handler */ +static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) +{ + struct psc_i2s_stream *s = _psc_i2s_stream; + + //spin_lock(&s->psc_i2s->lock); + + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + s->period_current_pt += s->period_bytes; + if (s->period_current_pt >= s->period_end) + s->period_current_pt = s->period_start; + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + } + + //spin_unlock(&s->psc_i2s->lock); + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +/** + * psc_i2s_startup: create a new substream + * + * This is the first function called when a stream is opened. + * + * If this is the first stream open, then grab the IRQ and program most of + * the PSC registers. + */ +static int psc_i2s_startup(struct snd_pcm_substream *substream) +{ + int playback_irq, capture_irq, rc; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + struct mpc52xx_psc_fifo __iomem *fiforegs = psc_i2s->fifo_regs; + + dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); + + /* Disable all interrupts and reset the PSC */ + out_be16(®s->mpc52xx_psc_imr, 0); + out_8(®s->command, 3 << 4); /* reset transmitter */ + out_8(®s->command, 2 << 4); /* reset receiver */ + out_8(®s->command, 1 << 4); /* reset mode */ + out_8(®s->command, 4 << 4); /* reset error */ + + /* Default to CODEC8 mode */ + out_be32(®s->sicr, + MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + MPC52xx_PSC_SICR_CLKPOL | MPC52xx_PSC_SICR_SIM_CODEC_8); + + /* First write: RxRdy (FIFO Alarm) generates receive FIFO interrupt */ + /* Second write to mode: register Normal mode for non loopback */ + out_8(®s->mode, 0); + out_8(®s->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&fiforegs->rfalarm, 0x100); /* set RFALARM level */ + out_8(&fiforegs->rfcntl, 0x4); /* set RFGRAN level (bytes) */ + out_be16(&fiforegs->tfalarm, 0x100); /* set TFALARM level */ + out_8(&fiforegs->tfcntl, 0x7); /* set TFGRAN level (bytes*4) */ + + /* Setup the IRQs */ + playback_irq = bcom_get_task_irq(psc_i2s->playback_stream.bcom_task); + capture_irq = bcom_get_task_irq(psc_i2s->capture_stream.bcom_task); + rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, + "psc-i2s-status", psc_i2s); + rc |= request_irq(capture_irq, &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-capture", &psc_i2s->capture_stream); + rc |= request_irq(playback_irq, &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-playback", &psc_i2s->playback_stream); + if (rc) { + free_irq(psc_i2s->irq, psc_i2s); + free_irq(capture_irq, &psc_i2s->capture_stream); + free_irq(playback_irq, &psc_i2s->playback_stream); + return -ENODEV; + } + + return 0; +} + +static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + u32 sicr; + + dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i\n", + __FUNCTION__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params)); + + sicr = MPC52xx_PSC_SICR_DTS1 | + MPC52xx_PSC_SICR_I2S | MPC52xx_PSC_SICR_CLKPOL; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_16; + break; + case SNDRV_PCM_FORMAT_S24_BE: + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_24; + break; + case SNDRV_PCM_FORMAT_S32_BE: + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_32; + break; + default: + dev_dbg(psc_i2s->dev, "invalid format\n"); + return -EINVAL; + } + out_be32(&psc_i2s->psc_regs->sicr, sicr); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int psc_i2s_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +/** + * psc_i2s_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + */ +static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct psc_i2s_stream *s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 imr; + u8 psc_cmd; + long flags; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture_stream; + else + s = &psc_i2s->playback_stream; + + dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" + " stream_id=%i\n", + substream, cmd, substream->pstr->stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->period_bytes = frames_to_bytes(runtime, + runtime->period_size); + s->period_start = virt_to_phys(runtime->dma_area); + s->period_end = s->period_start + + (s->period_bytes * runtime->periods); + s->period_next_pt = s->period_start; + s->period_current_pt = s->period_start; + s->active = 1; + + /* First; reset everything */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + out_8(®s->command, MPC52xx_PSC_RST_RX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } else { + out_8(®s->command, MPC52xx_PSC_RST_TX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } + + /* Next, fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + while (!bcom_queue_full(s->bcom_task)) + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + + /* Update interrupt enable settings. This must be done + * before the PSC is enabled so that TX underrun events + * are not missed. */ + imr = 0; + if (psc_i2s->playback_stream.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_i2s->capture_stream.active) + imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, imr); + + /* Due to errata in the i2s mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + spin_lock_irqsave(&psc_i2s->lock, flags); + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0); + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0); + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + psc_cmd = MPC52xx_PSC_RX_ENABLE; + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) + psc_cmd |= MPC52xx_PSC_TX_ENABLE; + out_8(®s->command, psc_cmd); + spin_unlock_irqrestore(&psc_i2s->lock, flags); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* Turn off the PSC */ + s->active = 0; + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (!psc_i2s->playback_stream.active) { + out_8(®s->command, 2 << 4); /* reset rx */ + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + } + } else { + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + if (!psc_i2s->capture_stream.active) + out_8(®s->command, 2 << 4); /* reset rx */ + } + + bcom_disable(s->bcom_task); + while (!bcom_queue_empty(s->bcom_task)) + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + break; + + default: + dev_dbg(psc_i2s->dev, "invalid command\n"); + return -EINVAL; + } + + /* Update interrupt enable settings */ + imr = 0; + if (psc_i2s->playback_stream.active) imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_i2s->capture_stream.active) imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, imr); + + return 0; +} + +/** + * psc_i2s_shutdown: shutdown the data transfer on a stream + * + * Shutdown the PSC if there are no other substreams open. + */ +static void psc_i2s_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + + dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); + + /* + * If this is the last active substream, disable the PSC and release + * the IRQ. + */ + if (!psc_i2s->playback_stream.active && + !psc_i2s->capture_stream.active) { + /* TODO: shut off channels */ + free_irq(psc_i2s->irq, psc_i2s); + free_irq(bcom_get_task_irq(psc_i2s->capture_stream.bcom_task), + &psc_i2s->capture_stream); + free_irq(bcom_get_task_irq(psc_i2s->playback_stream.bcom_task), + &psc_i2s->playback_stream); + } +} + +/** + * psc_i2s_set_sysclk: set the clock frequency and direction + * + * This function is called by the machine driver to tell us what the clock + * frequency and direction are. + * + * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN), + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) + */ +static int psc_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct psc_i2s *psc_i2s = cpu_dai->private_data; + dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", + cpu_dai, dir); + return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; +} + +/** + * psc_i2s_set_fmt: set the serial format. + * + * This function is called by the machine driver to tell us what serial + * format to use. + * + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * + * @format: one of SND_SOC_DAIFMT_xxx + */ +static int psc_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format) +{ + struct psc_i2s *psc_i2s = cpu_dai->private_data; + dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", + cpu_dai, format); + return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_i2s_dai_template: template CPU Digital Audio Interface + */ +static struct snd_soc_cpu_dai psc_i2s_dai_template = { + .type = SND_SOC_DAI_I2S, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .ops = { + .startup = psc_i2s_startup, + .hw_params = psc_i2s_hw_params, + .hw_free = psc_i2s_hw_free, + .shutdown = psc_i2s_shutdown, + .trigger = psc_i2s_trigger, + }, + .dai_ops = { + .set_sysclk = psc_i2s_set_sysclk, + .set_fmt = psc_i2s_set_fmt, + }, +}; + +/* --------------------------------------------------------------------- + * The PSC I2S 'ASoC platform' driver + * + * Can be referenced by an 'ASoC machine' driver + * This driver only deals with the audio bus; it doesn't have any + * interaction with the attached codec + */ + +static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S8 |SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .period_bytes_max = 1024 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 1024 * 1024, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 0, +}; + +static unsigned int psc_i2s_fixed_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, +}; + +static struct snd_pcm_hw_constraint_list psc_i2s_constraints_rates = { + .count = ARRAY_SIZE(psc_i2s_fixed_rates), + .list = psc_i2s_fixed_rates, + .mask = 0, +}; + +static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + int rc; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture_stream; + else + s = &psc_i2s->playback_stream; + + snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); + + rc = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (rc < 0) { + dev_err(psc_i2s->dev, "invalid buffer size\n"); + return rc; + } + rc = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &psc_i2s_constraints_rates); + if (rc < 0) { + dev_err(psc_i2s->dev, "invalid rate\n"); + return rc; + } + + s->stream = substream; + return 0; +} + +static int psc_i2s_pcm_close(struct snd_pcm_substream * substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture_stream; + else + s = &psc_i2s->playback_stream; + + s->stream = NULL; + return 0; +} + +static snd_pcm_uframes_t +psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + dma_addr_t count; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture_stream; + else + s = &psc_i2s->playback_stream; + + count = s->period_current_pt - s->period_start; + + return bytes_to_frames(substream->runtime, count); +} + +static struct snd_pcm_ops psc_i2s_pcm_ops = { + .open = psc_i2s_pcm_open, + .close = psc_i2s_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .pointer = psc_i2s_pcm_pointer, +}; + +static u64 psc_i2s_pcm_dmamask = 0xffffffff; +static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; + int rc = 0; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n", + card, dai, pcm); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &psc_i2s_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (pcm->streams[0].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[0].substream->dma_buffer); + if (rc) { + dev_err(card->dev, "Cannot alloc playback buffer\n"); + return -ENOMEM; + } + } + + if (pcm->streams[1].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[1].substream->dma_buffer); + if (rc) { + if (pcm->streams[0].substream) + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + dev_err(card->dev, "Cannot allocate capture buffer\n"); + return -ENOMEM; + } + } + + return 0; +} + +static void psc_i2s_pcm_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_pcm_substream *substream; + int stream; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +struct snd_soc_platform psc_i2s_pcm_soc_platform = { + .name = "mpc5200-psc-audio", + .pcm_ops = &psc_i2s_pcm_ops, + .pcm_new = &psc_i2s_pcm_new, + .pcm_free = &psc_i2s_pcm_free, +}; + +/* --------------------------------------------------------------------- + * Sysfs attributes for debugging + */ + +static ssize_t psc_i2s_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + + return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x tfnum=%i tfstat=0x%.4x\n", + in_be16(&psc_i2s->psc_regs->sr_csr.status), + in_be32(&psc_i2s->psc_regs->sicr), + in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff, + in_be16(&psc_i2s->fifo_regs->rfstat), + in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff, + in_be16(&psc_i2s->fifo_regs->tfstat)); +} + +static int * psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, + const char *name) +{ + if (strcmp(name, "playback_underrun") == 0) + return &psc_i2s->stats.underrun_count; + if (strcmp(name, "capture_overrun") == 0) + return &psc_i2s->stats.overrun_count; + + return NULL; +} + +static ssize_t psc_i2s_stat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + int *attrib; + + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + if (!attrib) + return 0; + + return sprintf(buf, "%i\n", *attrib); +} + +static ssize_t psc_i2s_stat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + int *attrib; + + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + if (!attrib) + return 0; + + *attrib = simple_strtoul(buf, NULL, 0); + return count; +} + +DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL); +DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show,psc_i2s_stat_store); +DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store); + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int __devinit psc_i2s_of_probe(struct of_device *op, + const struct of_device_id *match) +{ + phys_addr_t fifo; + struct psc_i2s *psc_i2s; + struct resource res; + int size, psc_id, irq, rc; + const __be32 *prop; + void __iomem *regs; + + dev_dbg(&op->dev, "probing psc i2s device\n"); + + /* Get the PSC ID */ + prop = of_get_property(op->node, "cell-index", &size); + if (!prop || size < sizeof *prop) + return -ENODEV; + psc_id = be32_to_cpu(*prop); + + /* Fetch the registers and IRQ of the PSC */ + irq = irq_of_parse_and_map(op->node, 0); + if (of_address_to_resource(op->node, 0, &res)) { + dev_err(&op->dev, "Missing reg property\n"); + return -ENODEV; + } + regs = ioremap(res.start, 1 + res.end - res.start); + if (!regs) { + dev_err(&op->dev, "Could not map registers\n"); + return -ENODEV; + } + + /* Allocate and initialize the driver private data */ + psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL); + if (!psc_i2s) { + iounmap(regs); + return -ENOMEM; + } + spin_lock_init(&psc_i2s->lock); + psc_i2s->irq = irq; + psc_i2s->psc_regs = regs; + psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs; + psc_i2s->dev = &op->dev; + psc_i2s->playback_stream.psc_i2s = psc_i2s; + psc_i2s->capture_stream.psc_i2s = psc_i2s; + snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1); + + /* Fill out the CPU DAI structure */ + memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai); + psc_i2s->dai.private_data = psc_i2s; + psc_i2s->dai.name = psc_i2s->name; + psc_i2s->dai.id = psc_id; + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_i2s->capture_stream.bcom_task = + bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512); + psc_i2s->playback_stream.bcom_task = + bcom_psc_gen_bd_tx_init(psc_id, 10, fifo); + if (!psc_i2s->capture_stream.bcom_task || + !psc_i2s->playback_stream.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + iounmap(regs); + kfree(psc_i2s); + return -ENODEV; + } + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_i2s); + + /* Register the SYSFS files */ + rc = device_create_file(psc_i2s->dev, &dev_attr_status); + rc = device_create_file(psc_i2s->dev, &dev_attr_capture_overrun); + rc = device_create_file(psc_i2s->dev, &dev_attr_playback_underrun); + if (rc) + dev_info(psc_i2s->dev, "error creating sysfs files\n"); + + /* Tell the ASoC OF helpers about it */ + of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node, + &psc_i2s->dai); + + return 0; +} + +static int __devexit psc_i2s_of_remove(struct of_device *op) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "psc_i2s_remove()\n"); + + bcom_gen_bd_rx_release(psc_i2s->capture_stream.bcom_task); + bcom_gen_bd_tx_release(psc_i2s->playback_stream.bcom_task); + + iounmap(psc_i2s->psc_regs); + iounmap(psc_i2s->fifo_regs); + kfree(psc_i2s); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} + +/* Match table for of_platform binding */ +static struct of_device_id psc_i2s_match[] __devinitdata = { + { .compatible = "fsl,mpc5200-psc-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_i2s_match); + +static struct of_platform_driver psc_i2s_driver = { + .match_table = psc_i2s_match, + .probe = psc_i2s_of_probe, + .remove = __devexit_p(psc_i2s_of_remove), + .driver = { + .name = "mpc5200-psc-i2s", + .owner = THIS_MODULE, + }, +}; + +/* --------------------------------------------------------------------- + * Module setup and teardown; simply register the of_platform driver + * for the PSC in I2S mode. + */ +static int __init psc_i2s_init(void) +{ + return of_register_platform_driver(&psc_i2s_driver); +} +module_init(psc_i2s_init); + +static void __exit psc_i2s_exit(void) +{ + of_unregister_platform_driver(&psc_i2s_driver); +} +module_exit(psc_i2s_exit); + +
On Sat, Jul 12, 2008 at 02:39:34AM -0600, Grant Likely wrote:
This is an I2S bus driver for the MPC5200 PSC device. It is probably will not be merged as-is because it uses v1 of the ASoC API, but I want to get it out there for comments.
Looks basically good. A few things below, plus you probably want to run checkpatch over it.
+/* Bestcomm DMA irq handler */ +static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) +{
- struct psc_i2s_stream *s = _psc_i2s_stream;
- //spin_lock(&s->psc_i2s->lock);
Either the lock is needed or it isn't :) .
- If this is the first stream open, then grab the IRQ and program most of
- the PSC registers.
+static int psc_i2s_startup(struct snd_pcm_substream *substream) +{
The comment here doesn't seem to reflect what the function does - it looks to just unconditionally reinitialise the controller with things like this:
- /* Disable all interrupts and reset the PSC */
- out_be16(®s->mpc52xx_psc_imr, 0);
- out_8(®s->command, 3 << 4); /* reset transmitter */
- out_8(®s->command, 2 << 4); /* reset receiver */
- out_8(®s->command, 1 << 4); /* reset mode */
- out_8(®s->command, 4 << 4); /* reset error */
which I'd imagine might upset running streams?
+static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_STOP:
- default:
dev_dbg(psc_i2s->dev, "invalid command\n");
return -EINVAL;
- }
This doesn't handle the pause, suspend, resume or pause_release triggers. If there's really nothing to do for those it should ignore them, especially given the default: behaviour.
From: Grant Likely grant.likely@secretlab.ca
ASoC Codec driver for the TLV320AIC26 device. This driver uses the ASoC v1 API, so I don't expect it to get merged as-is, but I want to get it out there for review. ---
sound/soc/codecs/Kconfig | 4 sound/soc/codecs/Makefile | 2 sound/soc/codecs/tlv320aic26.c | 546 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tlv320aic26.h | 103 ++++++++ 4 files changed, 655 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3903ab7..96c7bfe 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -41,6 +41,10 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270
+config SND_SOC_TLV320AIC26 + tristate "TI TLB320AIC26 Codec support" + depends on SND_SOC && SPI + config SND_SOC_TLV320AIC3X tristate depends on SND_SOC && I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4e1314c..ec0cd93 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -5,6 +5,7 @@ snd-soc-wm8753-objs := wm8753.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-cs4270-objs := cs4270.o +snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o @@ -14,4 +15,5 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c new file mode 100644 index 0000000..3ebfa23 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.c @@ -0,0 +1,546 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * ALSA SoC CODEC driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/soc-of.h> +#include <sound/initval.h> + +#include "tlv320aic26.h" + +MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver"); +MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_LICENSE("GPL"); + +/* AIC26 driver private data */ +struct aic26 { + struct spi_device *spi; + struct snd_soc_codec codec; + u16 reg_cache[AIC26_REG_CACHE_SIZE]; /* shadow registers */ + int master; + int datfm; + int mclk; + + /* Keyclick parameters */ + int keyclick_amplitude; + int keyclick_freq; + int keyclick_len; +}; + +/* --------------------------------------------------------------------- + * Register access routines + */ +static unsigned int aic26_reg_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd, value; + u8 buffer[2]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + /* Do SPI transfer; first 16bits are command; remaining is + * register contents */ + cmd = AIC26_READ_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + value = (buffer[0] << 8) | buffer[1]; + + /* Update the cache before returning with the value */ + if (AIC26_REG_IS_CACHED(reg)) + cache[AIC26_REG_CACHE_ADDR(reg)] = value; + return value; +} + +static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if ((reg < 0) || (reg >= AIC26_NUM_REGS)) { + WARN_ON_ONCE(1); + return 0; + } + + if (AIC26_REG_IS_CACHED(reg)) + return cache[AIC26_REG_CACHE_ADDR(reg)]; + + return aic26_reg_read(codec, reg); +} + +static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd; + u8 buffer[4]; + int rc; + + if ((reg < 0) || (reg >= AIC26_NUM_REGS)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + /* Do SPI transfer; first 16bits are command; remaining is data + * to write into register */ + cmd = AIC26_WRITE_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + buffer[2] = value >> 8; + buffer[3] = value; + rc = spi_write(aic26->spi, buffer, 4); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + + /* update cache before returning */ + if (AIC26_REG_IS_CACHED(reg)) + cache[AIC26_REG_CACHE_ADDR(reg)] = value; + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Operations + */ +static int aic26_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct aic26 *aic26 = codec->private_data; + int fsref, divisor, wlen, pval, jval, dval, qval; + u16 reg; + + dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n", + substream, params); + dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params), + params_format(params)); + + switch (params_rate(params)) { + case 8000: fsref = 48000; divisor = AIC26_DIV_6; break; + case 11025: fsref = 44100; divisor = AIC26_DIV_4; break; + case 12000: fsref = 48000; divisor = AIC26_DIV_4; break; + case 16000: fsref = 48000; divisor = AIC26_DIV_3; break; + case 22050: fsref = 44100; divisor = AIC26_DIV_2; break; + case 24000: fsref = 48000; divisor = AIC26_DIV_2; break; + case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break; + case 44100: fsref = 44100; divisor = AIC26_DIV_1; break; + case 48000: fsref = 48000; divisor = AIC26_DIV_1; break; + default: dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL; + } + + /* select data word length */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break; + case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break; + default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + /* Configure PLL */ + pval = 1; + jval = (fsref == 44100) ? 7 : 8; + dval = (fsref == 44100) ? 5264 : 1920; + qval = 0; + reg = 0x8000 | qval << 11 | pval << 8 | jval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg); + reg = dval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg); + + /* Power up CODEC */ + aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + if (aic26->master) + reg |= 0x0800; + if (fsref == 48000) + reg |= 0x2000; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Audio Control 1 (FSref divisor) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1); + reg &= ~0x0fff; + reg |= wlen | aic26->datfm | (divisor << 3) | divisor; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg); + + return 0; +} + +/** + * aic26_mute - Mute control to reduce noise when changing audio format + */ +static int aic26_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic26 *aic26 = codec->private_data; + u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN); + + dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", + dai, mute); + + if (mute) + reg |= 0x8080; + else + reg &= ~0x8080; + aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg); + + return 0; +} + +static int aic26_set_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," + " freq=%i, dir=%i)\n", + codec_dai, clk_id, freq, dir); + + /* MCLK needs to fall between 2MHz and 50 MHz */ + if ((freq < 2000000) || (freq > 50000000)) + return -EINVAL; + + aic26->mclk = freq; + return 0; +} + +static int aic26_set_fmt(struct snd_soc_codec_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", + codec_dai, fmt); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break; + case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break; + default: dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break; + case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break; + case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break; + case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break; + default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Definition + */ +struct snd_soc_codec_dai aic26_dai = { + .name = "tlv320aic26", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .ops = { + .hw_params = aic26_hw_params, + }, + .dai_ops = { + .digital_mute = aic26_mute, + .set_sysclk = aic26_set_sysclk, + .set_fmt = aic26_set_fmt, + }, +}; +EXPORT_SYMBOL_GPL(aic26_dai); + +/* --------------------------------------------------------------------- + * ALSA controls + */ +static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static const struct soc_enum aic26_capture_src_enum = + SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12,2, aic26_capture_src_text); + +static const struct snd_kcontrol_new aic26_snd_controls[] = { + /* Output */ + SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1), + SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1), + SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0), + SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1), + SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0), + SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0), + SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0), + SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0), + SOC_ENUM("Capture Source", aic26_capture_src_enum), +}; + +/* --------------------------------------------------------------------- + * SoC CODEC portion of driver: probe and release routines + */ +static int aic26_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct snd_kcontrol *kcontrol; + struct aic26 *aic26; + int i, ret, err; + + dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n"); + dev_dbg(&pdev->dev, "socdev=%p\n", socdev); + dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data); + + /* Fetch the relevant aic26 private data here (it's already been + * stored in the .codec pointer) */ + aic26 = socdev->codec_data; + if (aic26 == NULL) { + dev_err(&pdev->dev, "aic26: missing codec pointer\n"); + return -ENODEV; + } + codec = &aic26->codec; + socdev->codec = codec; + + dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n", + &pdev->dev, socdev->dev); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to create pcms\n"); + return -ENODEV; + } + + /* register controls */ + dev_dbg(&pdev->dev, "Registering controls\n"); + for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) { + kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL); + err = snd_ctl_add(codec->card, kcontrol); + WARN_ON(err < 0); + } + + /* CODEC is setup, we can register the card now */ + dev_dbg(&pdev->dev, "Registering card\n"); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to register card\n"); + goto card_err; + } + return 0; + + card_err: + snd_soc_free_pcms(socdev); + return ret; +} + +static int aic26_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + snd_soc_free_pcms(socdev); + return 0; +} + +struct snd_soc_codec_device aic26_soc_codec_dev = { + .probe = aic26_probe, + .remove = aic26_remove, +}; + +/* --------------------------------------------------------------------- + * SPI device portion of driver: sysfs files for debugging + */ + +static ssize_t aic26_regs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + char *idx = buf; + int cache_flag, addr, page, i, reg; + + cache_flag = (strcmp(attr->attr.name, "regs_cache") == 0); + + for (page = 0; page < 3; page++) { + for (i = 0; i < 0x20; i++) { + addr = AIC26_PAGE_ADDR(page, i); + if (i % 8 == 0) + idx += sprintf(idx, "%i:%.2i:", page,i); + if (cache_flag) + reg = aic26_reg_read_cache(&aic26->codec, addr); + else + reg = aic26_reg_read(&aic26->codec, addr); + idx += sprintf(idx, " %.4x", reg); + if (i % 8 == 7) + idx += sprintf(idx, "\n"); + } + } + return idx - buf; +} + +static ssize_t aic26_keyclick_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val, amp, freq, len; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + amp = (val >> 12) & 0x7; + freq = (125 << ((val >> 8) & 0x7)) >> 1; + len = 2 * (1 +((val >> 4) & 0xf)); + + return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len); +} + +/* Any write to the keyclick attribute will trigger the keyclick */ +static ssize_t aic26_keyclick_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + val |= 0x8000; + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val); + + return count; +} + +DEVICE_ATTR(regs, 0644, aic26_regs_show, NULL); +DEVICE_ATTR(regs_cache, 0644, aic26_regs_show, NULL); +DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: probe and release routines and SPI + * driver registration. + */ +static int aic26_spi_probe(struct spi_device *spi) +{ + struct aic26 *aic26; + int rc, i, reg; + + dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n"); + + /* Allocate driver data */ + aic26 = kzalloc(sizeof *aic26, GFP_KERNEL); + if (!aic26) + return -ENOMEM; + + /* Initialize the driver data */ + aic26->spi = spi; + dev_set_drvdata(&spi->dev, aic26); + + /* Setup what we can in the codec structure so that the register + * access functions will work as expected. More will be filled + * out when it is probed by the SoC CODEC part of this driver */ + aic26->codec.private_data = aic26; + aic26->codec.name = "aic26"; + aic26->codec.owner = THIS_MODULE; + aic26->codec.dai = &aic26_dai; + aic26->codec.num_dai = 1; + aic26->codec.read = aic26_reg_read; + aic26->codec.write = aic26_reg_write; + aic26->master = 1; + mutex_init(&aic26->codec.mutex); + INIT_LIST_HEAD(&aic26->codec.dapm_widgets); + INIT_LIST_HEAD(&aic26->codec.dapm_paths); + aic26->codec.reg_cache_size = sizeof(aic26->reg_cache); + aic26->codec.reg_cache = aic26->reg_cache; + + /* Reset the codec to power on defaults */ + aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00); + + /* Power up CODEC */ + aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + reg |= 0x0800; /* set master mode */ + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Fill page 2 register cache */ + for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++) + aic26_reg_read(&aic26->codec, AIC26_PAGE_ADDR(2, i)); + + /* Register the sysfs files for debugging */ + /* Create SysFS files */ + rc = device_create_file(&spi->dev, &dev_attr_regs); + rc |= device_create_file(&spi->dev, &dev_attr_regs_cache); + rc |= device_create_file(&spi->dev, &dev_attr_keyclick); + if (rc) + dev_info(&spi->dev, "error creating sysfs files\n"); + +#if defined(CONFIG_SND_SOC_OF) + /* Tell the of_soc helper about this codec */ + of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai, + spi->dev.archdata.of_node); +#endif + + dev_dbg(&spi->dev, "SPI device initialized\n"); + return 0; +} + +static int aic26_spi_remove(struct spi_device *spi) +{ + struct aic26 *aic26 = dev_get_drvdata(&spi->dev); + + kfree(aic26); + + return 0; +} + +static struct spi_driver aic26_spi = { + .driver = { + .name = "tlv320aic26", + .owner = THIS_MODULE, + }, + .probe = aic26_spi_probe, + .remove = aic26_spi_remove, +}; + +static int __init aic26_init(void) +{ + return spi_register_driver(&aic26_spi); +} +module_init(aic26_init); + +static void __exit aic26_exit(void) +{ + spi_unregister_driver(&aic26_spi); +} +module_exit(aic26_exit); diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h new file mode 100644 index 0000000..9edb8e9 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.h @@ -0,0 +1,103 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * register definitions + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#ifndef _TLV320AIC16_H_ +#define _TLV320AIC16_H_ + +/* AIC26 Registers */ +#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5)) +#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5)) +#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset) +#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0) +#define AIC26_REG_CACHE_SIZE (0x20) /* only page 2 cached */ +#define AIC26_REG_IS_CACHED(addr) ((addr & ~0x1f) == (2 << 6)) +#define AIC26_REG_CACHE_ADDR(addr) (addr & 0x1f) + +/* Page 0: Auxillary data registers */ +#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05) +#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06) +#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07) +#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09) +#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A) + +/* Page 1: Auxillary control registers */ +#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00) +#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01) +#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03) +#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04) + +/* Page 2: Audio control registers */ +#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00) +#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01) +#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02) +#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03) +#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04) +#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05) +#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06) + +#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07) +#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08) +#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09) +#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A) +#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B) +#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C) +#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D) +#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E) +#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F) +#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10) +#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11) +#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12) +#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13) +#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14) +#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15) +#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16) +#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17) +#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18) +#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19) +#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A) + +#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B) +#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C) +#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D) +#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E) + +#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) +#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +/* fsref dividers; used in register 'Audio Control 1' */ +enum aic26_divisors { + AIC26_DIV_1 = 0, + AIC26_DIV_1_5 = 1, + AIC26_DIV_2 = 2, + AIC26_DIV_3 = 3, + AIC26_DIV_4 = 4, + AIC26_DIV_5 = 5, + AIC26_DIV_5_5 = 6, + AIC26_DIV_6 = 7, +}; + +/* Digital data format */ +enum aic26_datfm { + AIC26_DATFM_I2S = 0 << 8, + AIC26_DATFM_DSP = 1 << 8, + AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */ + AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */ +}; + +/* Sample word length in bits; used in register 'Audio Control 1' */ +enum aic26_wlen { + AIC26_WLEN_16 = 0 << 10, + AIC26_WLEN_20 = 1 << 10, + AIC26_WLEN_24 = 2 << 10, + AIC26_WLEN_32 = 3 << 10, +}; + +#endif /* _TLV320AIC16_H_ */
On Sat, Jul 12, 2008 at 02:39:39AM -0600, Grant Likely wrote:
ASoC Codec driver for the TLV320AIC26 device. This driver uses the ASoC v1 API, so I don't expect it to get merged as-is, but I want to get it out there for review.
I've not reviewed this revision of these drivers yet but I just wanted to point out that there's absoluely no problem with merging v1 drivers - so long as a driver uses the existing merged APIs there's no issue from that point of view.
On Sat, Jul 12, 2008 at 12:10 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Sat, Jul 12, 2008 at 02:39:39AM -0600, Grant Likely wrote:
ASoC Codec driver for the TLV320AIC26 device. This driver uses the ASoC v1 API, so I don't expect it to get merged as-is, but I want to get it out there for review.
I've not reviewed this revision of these drivers yet but I just wanted to point out that there's absoluely no problem with merging v1 drivers - so long as a driver uses the existing merged APIs there's no issue from that point of view.
Oops, I forgot to update my commit messages. I'll probably repost v3 of the series this evening and I'll fix this.
Cheers, g.
On Sat, Jul 12, 2008 at 02:39:39AM -0600, Grant Likely wrote:
ASoC Codec driver for the TLV320AIC26 device. This driver uses the ASoC v1 API, so I don't expect it to get merged as-is, but I want to get it out there for review.
This looks basically good - most of the issues below are nitpicky.
- /* Configure PLL */
- pval = 1;
- jval = (fsref == 44100) ? 7 : 8;
- dval = (fsref == 44100) ? 5264 : 1920;
- qval = 0;
- reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
- aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
- reg = dval << 2;
- aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
Does the PLL configuration not depend on the input clock frequency? You have a set_sysclk() method to configure the MCLK supplied but the driver never appears to reference the value anywhere.
- /* Power up CODEC */
- aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0);
As discussed this should probably just be removed from hw_params().
+static int aic26_set_fmt(struct snd_soc_codec_dai *codec_dai, unsigned int fmt) +{
- /* interface format */
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break;
- case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break;
- case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break;
- case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break;
- default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
- }
I'm having a hard time liking this indentation style. It's not an obstacle to merging but it doesn't really help legibility - there's not enough white space and once you have a non-standard line like the default: one.
+static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static const struct soc_enum aic26_capture_src_enum =
- SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12,2, aic26_capture_src_text);
checkpatch complains about the 12,2 here and a bunch of other stuff - ALSA is generally very strict about that.
- SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0),
- SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0),
- SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0),
- SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0),
This configuration is also exposed via a sysfs file, including some of the configurability. Exposing the information via sysfs isn't a particular problem but allowing it to be written to without going through ALSA seems wrong.
+static ssize_t aic26_regs_show(struct device *dev,
struct device_attribute *attr, char *buf)
+{
As discussed this is replicating the existing codec register display sysfs file.
+#if defined(CONFIG_SND_SOC_OF)
- /* Tell the of_soc helper about this codec */
- of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai,
spi->dev.archdata.of_node);
+#endif
CONFIG_SND_SOC_OF could be a module - this needs to also check for it being a module.
+#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
SNDRV_PCM_RATE_48000)
+#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
These are usually kept in the driver code.
On Mon, Jul 14, 2008 at 12:45:55PM +0100, Mark Brown wrote:
On Sat, Jul 12, 2008 at 02:39:39AM -0600, Grant Likely wrote:
ASoC Codec driver for the TLV320AIC26 device. This driver uses the ASoC v1 API, so I don't expect it to get merged as-is, but I want to get it out there for review.
This looks basically good - most of the issues below are nitpicky.
- /* Configure PLL */
- pval = 1;
- jval = (fsref == 44100) ? 7 : 8;
- dval = (fsref == 44100) ? 5264 : 1920;
- qval = 0;
- reg = 0x8000 | qval << 11 | pval << 8 | jval << 2;
- aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg);
- reg = dval << 2;
- aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg);
Does the PLL configuration not depend on the input clock frequency? You have a set_sysclk() method to configure the MCLK supplied but the driver never appears to reference the value anywhere.
Yes, this should be done better. I'm working on making this better, but I'm hoping it is okay to have that as follow-on patches.
- /* Power up CODEC */
- aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0);
As discussed this should probably just be removed from hw_params().
done
+static int aic26_set_fmt(struct snd_soc_codec_dai *codec_dai, unsigned int fmt) +{
- /* interface format */
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break;
- case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break;
- case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break;
- case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break;
- default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL;
- }
I'm having a hard time liking this indentation style. It's not an obstacle to merging but it doesn't really help legibility - there's not enough white space and once you have a non-standard line like the default: one.
Cleaned up.
+static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static const struct soc_enum aic26_capture_src_enum =
- SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12,2, aic26_capture_src_text);
checkpatch complains about the 12,2 here and a bunch of other stuff - ALSA is generally very strict about that.
Originally, that had been to keep the line under 80 chars, but some of the names have changed since then to make it no longer necessary. I'll clean up the checkpatch stuff.
- SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0),
- SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0),
- SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0),
- SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0),
This configuration is also exposed via a sysfs file, including some of the configurability. Exposing the information via sysfs isn't a particular problem but allowing it to be written to without going through ALSA seems wrong.
All the written bit does is trigger the keyclick event. It doesn't adjust the parameters in any way.
+static ssize_t aic26_regs_show(struct device *dev,
struct device_attribute *attr, char *buf)
+{
As discussed this is replicating the existing codec register display sysfs file.
removed
+#if defined(CONFIG_SND_SOC_OF)
- /* Tell the of_soc helper about this codec */
- of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai,
spi->dev.archdata.of_node);
+#endif
CONFIG_SND_SOC_OF could be a module - this needs to also check for it being a module.
Doesn't #if defined() match on both static and module selection?
+#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
SNDRV_PCM_RATE_48000)
+#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
These are usually kept in the driver code.
fixed.
Thanks, g.
On Fri, Jul 18, 2008 at 12:29:37AM -0600, Grant Likely wrote:
On Mon, Jul 14, 2008 at 12:45:55PM +0100, Mark Brown wrote:
On Sat, Jul 12, 2008 at 02:39:39AM -0600, Grant Likely wrote:
This configuration is also exposed via a sysfs file, including some of the configurability. Exposing the information via sysfs isn't a particular problem but allowing it to be written to without going through ALSA seems wrong.
All the written bit does is trigger the keyclick event. It doesn't adjust the parameters in any way.
That should be OK for now but we ought to work out a way of doing this via an ALSA control so it can be standardised over the devices that support it. I can't see anything suitable currently, though.
+#if defined(CONFIG_SND_SOC_OF)
- /* Tell the of_soc helper about this codec */
- of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai,
spi->dev.archdata.of_node);
+#endif
CONFIG_SND_SOC_OF could be a module - this needs to also check for it being a module.
Doesn't #if defined() match on both static and module selection?
Sadly not.
Hi I have one query about this soc driver. I want to know when u will merge it with kernel then where and by which name this device will be available in /dev directory of your file system.
As i am following the same structure for my driver so please help me. I am ot able to recognise the device in the file system.
-----Original Message----- From: Grant Likely grant.likely@secretlab.ca To: linuxppc-dev@ozlabs.org, alsa-devel@alsa-project.org, liam.girdwood@wolfsonmicro.com, jonsmirl@gmail.com Subject: [alsa-devel] [PATCH v2 3/3] ALSA SoC: Add Texas Instruments TLV320AIC26 codec driver Date: Sat, 12 Jul 2008 02:39:39 -0600
From: Grant Likely grant.likely@secretlab.ca
ASoC Codec driver for the TLV320AIC26 device. This driver uses the ASoC v1 API, so I don't expect it to get merged as-is, but I want to get it out there for review. ---
sound/soc/codecs/Kconfig | 4 sound/soc/codecs/Makefile | 2 sound/soc/codecs/tlv320aic26.c | 546 ++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tlv320aic26.h | 103 ++++++++ 4 files changed, 655 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3903ab7..96c7bfe 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -41,6 +41,10 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270
+config SND_SOC_TLV320AIC26 + tristate "TI TLB320AIC26 Codec support" + depends on SND_SOC && SPI + config SND_SOC_TLV320AIC3X tristate depends on SND_SOC && I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4e1314c..ec0cd93 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -5,6 +5,7 @@ snd-soc-wm8753-objs := wm8753.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-cs4270-objs := cs4270.o +snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o @@ -14,4 +15,5 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c new file mode 100644 index 0000000..3ebfa23 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.c @@ -0,0 +1,546 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * ALSA SoC CODEC driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/soc-of.h> +#include <sound/initval.h> + +#include "tlv320aic26.h" + +MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver"); +MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_LICENSE("GPL"); + +/* AIC26 driver private data */ +struct aic26 { + struct spi_device *spi; + struct snd_soc_codec codec; + u16 reg_cache[AIC26_REG_CACHE_SIZE]; /* shadow registers */ + int master; + int datfm; + int mclk; + + /* Keyclick parameters */ + int keyclick_amplitude; + int keyclick_freq; + int keyclick_len; +}; + +/* --------------------------------------------------------------------- + * Register access routines + */ +static unsigned int aic26_reg_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd, value; + u8 buffer[2]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + /* Do SPI transfer; first 16bits are command; remaining is + * register contents */ + cmd = AIC26_READ_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + value = (buffer[0] << 8) | buffer[1]; + + /* Update the cache before returning with the value */ + if (AIC26_REG_IS_CACHED(reg)) + cache[AIC26_REG_CACHE_ADDR(reg)] = value; + return value; +} + +static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if ((reg < 0) || (reg >= AIC26_NUM_REGS)) { + WARN_ON_ONCE(1); + return 0; + } + + if (AIC26_REG_IS_CACHED(reg)) + return cache[AIC26_REG_CACHE_ADDR(reg)]; + + return aic26_reg_read(codec, reg); +} + +static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd; + u8 buffer[4]; + int rc; + + if ((reg < 0) || (reg >= AIC26_NUM_REGS)) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + /* Do SPI transfer; first 16bits are command; remaining is data + * to write into register */ + cmd = AIC26_WRITE_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + buffer[2] = value >> 8; + buffer[3] = value; + rc = spi_write(aic26->spi, buffer, 4); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + + /* update cache before returning */ + if (AIC26_REG_IS_CACHED(reg)) + cache[AIC26_REG_CACHE_ADDR(reg)] = value; + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Operations + */ +static int aic26_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct aic26 *aic26 = codec->private_data; + int fsref, divisor, wlen, pval, jval, dval, qval; + u16 reg; + + dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n", + substream, params); + dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params), + params_format(params)); + + switch (params_rate(params)) { + case 8000: fsref = 48000; divisor = AIC26_DIV_6; break; + case 11025: fsref = 44100; divisor = AIC26_DIV_4; break; + case 12000: fsref = 48000; divisor = AIC26_DIV_4; break; + case 16000: fsref = 48000; divisor = AIC26_DIV_3; break; + case 22050: fsref = 44100; divisor = AIC26_DIV_2; break; + case 24000: fsref = 48000; divisor = AIC26_DIV_2; break; + case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break; + case 44100: fsref = 44100; divisor = AIC26_DIV_1; break; + case 48000: fsref = 48000; divisor = AIC26_DIV_1; break; + default: dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL; + } + + /* select data word length */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break; + case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break; + default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + /* Configure PLL */ + pval = 1; + jval = (fsref == 44100) ? 7 : 8; + dval = (fsref == 44100) ? 5264 : 1920; + qval = 0; + reg = 0x8000 | qval << 11 | pval << 8 | jval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg); + reg = dval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg); + + /* Power up CODEC */ + aic26_reg_write(codec, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + if (aic26->master) + reg |= 0x0800; + if (fsref == 48000) + reg |= 0x2000; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Audio Control 1 (FSref divisor) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1); + reg &= ~0x0fff; + reg |= wlen | aic26->datfm | (divisor << 3) | divisor; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg); + + return 0; +} + +/** + * aic26_mute - Mute control to reduce noise when changing audio format + */ +static int aic26_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic26 *aic26 = codec->private_data; + u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN); + + dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", + dai, mute); + + if (mute) + reg |= 0x8080; + else + reg &= ~0x8080; + aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg); + + return 0; +} + +static int aic26_set_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," + " freq=%i, dir=%i)\n", + codec_dai, clk_id, freq, dir); + + /* MCLK needs to fall between 2MHz and 50 MHz */ + if ((freq < 2000000) || (freq > 50000000)) + return -EINVAL; + + aic26->mclk = freq; + return 0; +} + +static int aic26_set_fmt(struct snd_soc_codec_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", + codec_dai, fmt); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break; + case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break; + default: dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break; + case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break; + case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break; + case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break; + default: dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Definition + */ +struct snd_soc_codec_dai aic26_dai = { + .name = "tlv320aic26", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .ops = { + .hw_params = aic26_hw_params, + }, + .dai_ops = { + .digital_mute = aic26_mute, + .set_sysclk = aic26_set_sysclk, + .set_fmt = aic26_set_fmt, + }, +}; +EXPORT_SYMBOL_GPL(aic26_dai); + +/* --------------------------------------------------------------------- + * ALSA controls + */ +static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static const struct soc_enum aic26_capture_src_enum = + SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12,2, aic26_capture_src_text); + +static const struct snd_kcontrol_new aic26_snd_controls[] = { + /* Output */ + SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1), + SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1), + SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0), + SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1), + SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0), + SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0), + SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0), + SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0), + SOC_ENUM("Capture Source", aic26_capture_src_enum), +}; + +/* --------------------------------------------------------------------- + * SoC CODEC portion of driver: probe and release routines + */ +static int aic26_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct snd_kcontrol *kcontrol; + struct aic26 *aic26; + int i, ret, err; + + dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n"); + dev_dbg(&pdev->dev, "socdev=%p\n", socdev); + dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data); + + /* Fetch the relevant aic26 private data here (it's already been + * stored in the .codec pointer) */ + aic26 = socdev->codec_data; + if (aic26 == NULL) { + dev_err(&pdev->dev, "aic26: missing codec pointer\n"); + return -ENODEV; + } + codec = &aic26->codec; + socdev->codec = codec; + + dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n", + &pdev->dev, socdev->dev); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to create pcms\n"); + return -ENODEV; + } + + /* register controls */ + dev_dbg(&pdev->dev, "Registering controls\n"); + for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) { + kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL); + err = snd_ctl_add(codec->card, kcontrol); + WARN_ON(err < 0); + } + + /* CODEC is setup, we can register the card now */ + dev_dbg(&pdev->dev, "Registering card\n"); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to register card\n"); + goto card_err; + } + return 0; + + card_err: + snd_soc_free_pcms(socdev); + return ret; +} + +static int aic26_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + snd_soc_free_pcms(socdev); + return 0; +} + +struct snd_soc_codec_device aic26_soc_codec_dev = { + .probe = aic26_probe, + .remove = aic26_remove, +}; + +/* --------------------------------------------------------------------- + * SPI device portion of driver: sysfs files for debugging + */ + +static ssize_t aic26_regs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + char *idx = buf; + int cache_flag, addr, page, i, reg; + + cache_flag = (strcmp(attr->attr.name, "regs_cache") == 0); + + for (page = 0; page < 3; page++) { + for (i = 0; i < 0x20; i++) { + addr = AIC26_PAGE_ADDR(page, i); + if (i % 8 == 0) + idx += sprintf(idx, "%i:%.2i:", page,i); + if (cache_flag) + reg = aic26_reg_read_cache(&aic26->codec, addr); + else + reg = aic26_reg_read(&aic26->codec, addr); + idx += sprintf(idx, " %.4x", reg); + if (i % 8 == 7) + idx += sprintf(idx, "\n"); + } + } + return idx - buf; +} + +static ssize_t aic26_keyclick_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val, amp, freq, len; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + amp = (val >> 12) & 0x7; + freq = (125 << ((val >> 8) & 0x7)) >> 1; + len = 2 * (1 +((val >> 4) & 0xf)); + + return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len); +} + +/* Any write to the keyclick attribute will trigger the keyclick */ +static ssize_t aic26_keyclick_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + val |= 0x8000; + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val); + + return count; +} + +DEVICE_ATTR(regs, 0644, aic26_regs_show, NULL); +DEVICE_ATTR(regs_cache, 0644, aic26_regs_show, NULL); +DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: probe and release routines and SPI + * driver registration. + */ +static int aic26_spi_probe(struct spi_device *spi) +{ + struct aic26 *aic26; + int rc, i, reg; + + dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n"); + + /* Allocate driver data */ + aic26 = kzalloc(sizeof *aic26, GFP_KERNEL); + if (!aic26) + return -ENOMEM; + + /* Initialize the driver data */ + aic26->spi = spi; + dev_set_drvdata(&spi->dev, aic26); + + /* Setup what we can in the codec structure so that the register + * access functions will work as expected. More will be filled + * out when it is probed by the SoC CODEC part of this driver */ + aic26->codec.private_data = aic26; + aic26->codec.name = "aic26"; + aic26->codec.owner = THIS_MODULE; + aic26->codec.dai = &aic26_dai; + aic26->codec.num_dai = 1; + aic26->codec.read = aic26_reg_read; + aic26->codec.write = aic26_reg_write; + aic26->master = 1; + mutex_init(&aic26->codec.mutex); + INIT_LIST_HEAD(&aic26->codec.dapm_widgets); + INIT_LIST_HEAD(&aic26->codec.dapm_paths); + aic26->codec.reg_cache_size = sizeof(aic26->reg_cache); + aic26->codec.reg_cache = aic26->reg_cache; + + /* Reset the codec to power on defaults */ + aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00); + + /* Power up CODEC */ + aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + reg |= 0x0800; /* set master mode */ + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Fill page 2 register cache */ + for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++) + aic26_reg_read(&aic26->codec, AIC26_PAGE_ADDR(2, i)); + + /* Register the sysfs files for debugging */ + /* Create SysFS files */ + rc = device_create_file(&spi->dev, &dev_attr_regs); + rc |= device_create_file(&spi->dev, &dev_attr_regs_cache); + rc |= device_create_file(&spi->dev, &dev_attr_keyclick); + if (rc) + dev_info(&spi->dev, "error creating sysfs files\n"); + +#if defined(CONFIG_SND_SOC_OF) + /* Tell the of_soc helper about this codec */ + of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai, + spi->dev.archdata.of_node); +#endif + + dev_dbg(&spi->dev, "SPI device initialized\n"); + return 0; +} + +static int aic26_spi_remove(struct spi_device *spi) +{ + struct aic26 *aic26 = dev_get_drvdata(&spi->dev); + + kfree(aic26); + + return 0; +} + +static struct spi_driver aic26_spi = { + .driver = { + .name = "tlv320aic26", + .owner = THIS_MODULE, + }, + .probe = aic26_spi_probe, + .remove = aic26_spi_remove, +}; + +static int __init aic26_init(void) +{ + return spi_register_driver(&aic26_spi); +} +module_init(aic26_init); + +static void __exit aic26_exit(void) +{ + spi_unregister_driver(&aic26_spi); +} +module_exit(aic26_exit); diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h new file mode 100644 index 0000000..9edb8e9 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.h @@ -0,0 +1,103 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * register definitions + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#ifndef _TLV320AIC16_H_ +#define _TLV320AIC16_H_ + +/* AIC26 Registers */ +#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5)) +#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5)) +#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset) +#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0) +#define AIC26_REG_CACHE_SIZE (0x20) /* only page 2 cached */ +#define AIC26_REG_IS_CACHED(addr) ((addr & ~0x1f) == (2 << 6)) +#define AIC26_REG_CACHE_ADDR(addr) (addr & 0x1f) + +/* Page 0: Auxillary data registers */ +#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05) +#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06) +#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07) +#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09) +#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A) + +/* Page 1: Auxillary control registers */ +#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00) +#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01) +#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03) +#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04) + +/* Page 2: Audio control registers */ +#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00) +#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01) +#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02) +#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03) +#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04) +#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05) +#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06) + +#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07) +#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08) +#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09) +#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A) +#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B) +#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C) +#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D) +#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E) +#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F) +#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10) +#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11) +#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12) +#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13) +#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14) +#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15) +#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16) +#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17) +#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18) +#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19) +#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A) + +#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B) +#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C) +#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D) +#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E) + +#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) +#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +/* fsref dividers; used in register 'Audio Control 1' */ +enum aic26_divisors { + AIC26_DIV_1 = 0, + AIC26_DIV_1_5 = 1, + AIC26_DIV_2 = 2, + AIC26_DIV_3 = 3, + AIC26_DIV_4 = 4, + AIC26_DIV_5 = 5, + AIC26_DIV_5_5 = 6, + AIC26_DIV_6 = 7, +}; + +/* Digital data format */ +enum aic26_datfm { + AIC26_DATFM_I2S = 0 << 8, + AIC26_DATFM_DSP = 1 << 8, + AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */ + AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */ +}; + +/* Sample word length in bits; used in register 'Audio Control 1' */ +enum aic26_wlen { + AIC26_WLEN_16 = 0 << 10, + AIC26_WLEN_20 = 1 << 10, + AIC26_WLEN_24 = 2 << 10, + AIC26_WLEN_32 = 3 << 10, +}; + +#endif /* _TLV320AIC16_H_ */
_______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Jul 15, 2008 at 01:27:52PM +0530, dinesh wrote:
I have one query about this soc driver. I want to know when u will merge it with kernel then where and by which name this device will be available in /dev directory of your file system.
As i am following the same structure for my driver so please help me. I am ot able to recognise the device in the file system.
It will appear via the standard ALSA interfaces (and OSS interfaces if you enable OSS compatibility). The standard location for ALSA device files is under /dev/snd where you'll see several files per device.
thanks for your response but there is no /dev/snd directory in my file structure is there any special method to create it please tell in detail.
-----Original Message----- From: Mark Brown broonie@opensource.wolfsonmicro.com To: dinesh dinesh.dua@coraltele.com Cc: Grant Likely grant.likely@secretlab.ca, linuxppc-dev@ozlabs.org, alsa-devel@alsa-project.org, liam.girdwood@wolfsonmicro.com Subject: Re: [alsa-devel] [PATCH v2 3/3] ALSA SoC: Add Texas Instruments TLV320AIC26 codec driver Date: Tue, 15 Jul 2008 11:33:34 +0100
On Tue, Jul 15, 2008 at 01:27:52PM +0530, dinesh wrote:
I have one query about this soc driver. I want to know when u will merge it with kernel then where and by which name this device will be available in /dev directory of your file system.
As i am following the same structure for my driver so please help me. I am ot able to recognise the device in the file system.
It will appear via the standard ALSA interfaces (and OSS interfaces if you enable OSS compatibility). The standard location for ALSA device files is under /dev/snd where you'll see several files per device. _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Jul 15, 2008 at 06:08:19PM +0530, dinesh wrote:
thanks for your response but there is no /dev/snd directory in my file structure is there any special method to create it please tell in detail.
The devices appear via the standard kernel interfaces so if you are using udev or mdev they should be created automatically. Otherwise you'll need to create them statically using whatever method you usually use (eg, a MAKEDEV script supplied by your distribution or manually by reference to the device numbers exposed in /sys/class/sound/*/dev).
The ALSA devices use the standard Linux mechanisms to create their devices so whatever you normally use to create devices should work for ALSA too.
Hi, I am writing a soc sound driver for MPC8323 board linux 2.6.24 in which i want to do data transfer to and from device myself using BUFFER DESCRIPTOR not with the usual DMA transfer. Please help me.
Hi dinesh,
If you are working on ARM, see the ARM AACi code, in sound/arm/aaci.c
Thanks Nobin Mathew.
On 7/16/08, dinesh dinesh.dua@coraltele.com wrote:
Hi, I am writing a soc sound driver for MPC8323 board linux 2.6.24 in which i want to do data transfer to and from device myself using BUFFER DESCRIPTOR not with the usual DMA transfer. Please help me. _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
no i am working on powerpc. MPC8323
-----Original Message----- From: Nobin Mathew nobin.mathew@gmail.com To: dinesh dinesh.dua@coraltele.com Cc: Grant Likely grant.likely@secretlab.ca, linuxppc-dev@ozlabs.org, alsa-devel@alsa-project.org, liam.girdwood@wolfsonmicro.com Subject: Re: [alsa-devel] WRITING AN SOC DRIVER WITHOUT DMA Date: Wed, 16 Jul 2008 15:37:54 +0530
Hi dinesh,
If you are working on ARM, see the ARM AACi code, in sound/arm/aaci.c
Thanks Nobin Mathew.
On 7/16/08, dinesh dinesh.dua@coraltele.com wrote:
Hi, I am writing a soc sound driver for MPC8323 board linux 2.6.24 in which i want to do data transfer to and from device myself using BUFFER DESCRIPTOR not with the usual DMA transfer. Please help me. _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
_______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Hi, I think no one is able to understand my problem, let me break it to low level. What i want is that i have a buffer in driver code which is also handled by some other application i want that this buffer data is to be used for capture and playback stream fills data to another buffer which i can passover to my other application. If someone can help me please. Regards, Dinesh
-----Original Message----- From: dinesh dinesh.dua@coraltele.com To: Nobin Mathew nobin.mathew@gmail.com Cc: Grant Likely grant.likely@secretlab.ca, linuxppc-dev@ozlabs.org, alsa-devel@alsa-project.org, liam.girdwood@wolfsonmicro.com Subject: Re: [alsa-devel] WRITING AN SOC DRIVER WITHOUT DMA Date: Wed, 16 Jul 2008 15:43:26 +0530
no i am working on powerpc. MPC8323
-----Original Message----- From: Nobin Mathew nobin.mathew@gmail.com To: dinesh dinesh.dua@coraltele.com Cc: Grant Likely grant.likely@secretlab.ca, linuxppc-dev@ozlabs.org, alsa-devel@alsa-project.org, liam.girdwood@wolfsonmicro.com Subject: Re: [alsa-devel] WRITING AN SOC DRIVER WITHOUT DMA Date: Wed, 16 Jul 2008 15:37:54 +0530
Hi dinesh,
If you are working on ARM, see the ARM AACi code, in sound/arm/aaci.c
Thanks Nobin Mathew.
On 7/16/08, dinesh dinesh.dua@coraltele.com wrote:
Hi, I am writing a soc sound driver for MPC8323 board linux 2.6.24 in which i want to do data transfer to and from device myself using BUFFER DESCRIPTOR not with the usual DMA transfer. Please help me. _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
_______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Thu, Jul 17, 2008 at 11:33:31AM +0530, dinesh wrote:
What i want is that i have a buffer in driver code which is also handled by some other application i want that this buffer data is to be used for capture and playback stream fills data to another buffer which i can passover to my other application.
Depending on what exactly you're doing here you may find that this is best implemented in user space with an ALSA plugin rather than doing it as part of a driver. If you do want to do this in kernel space then the parts of an ASoC driver which transfer audio data are just the same as those for any other ALSA driver so things like sound/arm/aaci.c may provide useful examples.
Hi Dinesh,
If that is your requirement then go and see the actual code sound/arm/aaci.c. They are not using DMA. It is programmed IO.
Thanks Nobin Mathew
On 7/17/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Thu, Jul 17, 2008 at 11:33:31AM +0530, dinesh wrote:
What i want is that i have a buffer in driver code which is also handled by some other application i want that this buffer data is to be used for capture and playback stream fills data to another buffer which i can passover to my other application.
Depending on what exactly you're doing here you may find that this is best implemented in user space with an ALSA plugin rather than doing it as part of a driver. If you do want to do this in kernel space then the parts of an ASoC driver which transfer audio data are just the same as those for any other ALSA driver so things like sound/arm/aaci.c may provide useful examples.
On 7/17/08, Nobin Mathew nobin.mathew@gmail.com wrote:
Hi Dinesh,
If that is your requirement then go and see the actual code sound/arm/aaci.c. They are not using DMA. It is programmed IO.
Search around the list archives, the first version of the Efika/mpc5200 ac97 driver was programmed IO. The next version updated it to DMA.
Thanks Nobin Mathew
On 7/17/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Thu, Jul 17, 2008 at 11:33:31AM +0530, dinesh wrote:
What i want is that i have a buffer in driver code which is also handled by some other application i want that this buffer data is to be used for capture and playback stream fills data to another buffer which i can passover to my other application.
Depending on what exactly you're doing here you may find that this is best implemented in user space with an ALSA plugin rather than doing it as part of a driver. If you do want to do this in kernel space then the parts of an ASoC driver which transfer audio data are just the same as those for any other ALSA driver so things like sound/arm/aaci.c may provide useful examples.
Linuxppc-dev mailing list Linuxppc-dev@ozlabs.org https://ozlabs.org/mailman/listinfo/linuxppc-dev
Grant Likely wrote:
- aic26->codec.reg_cache_size = sizeof(aic26->reg_cache);
- aic26->codec.reg_cache = aic26->reg_cache;
...
- /* Register the sysfs files for debugging */
- /* Create SysFS files */
- rc = device_create_file(&spi->dev, &dev_attr_regs);
- rc |= device_create_file(&spi->dev, &dev_attr_regs_cache);
- rc |= device_create_file(&spi->dev, &dev_attr_keyclick);
- if (rc)
dev_info(&spi->dev, "error creating sysfs files\n");
I believe ASoC creates sysfs entries if you use reg_cache and reg_cache_size, so you should not be creating your own sysfs entries if you use these variables.
+#if defined(CONFIG_SND_SOC_OF)
- /* Tell the of_soc helper about this codec */
- of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai,
spi->dev.archdata.of_node);
+#endif
I haven't paid any attention to the of_soc helper, because it's for ASoC V1 only. However, I don't understand why you need to reference it in the codec driver. My CS4270 codec driver knows nothing about OF and works fine in arch/powerpc.
On Sat, Jul 12, 2008 at 02:39:29AM -0600, Grant Likely wrote:
Simple utility layer for creating ASoC machine instances based on data in the OpenFirmware device tree. OF aware platform drivers and codec drivers register themselves with this framework and the framework automatically instantiates a machine driver.
Ideally someone from the PowerPC community would sign off on this - given the nature and volume of discussion people obviously have very strong opinions about how machine drivers should be done so I'd really like to see some community buy in for something like this. From an ASoC point of view if this works it's fine.
This is most likely temporary glue code to work around limitations in the ASoC v1 framework. I expect ASoC v2 won't need this.
It will need some way of providing a machine driver, generic (like this one) or otherwise.
+static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc) +{
- struct platform_device *pdev;
- int rc;
- /* Only register the device if both the codec and platform have
* been registered */
- if ((!of_soc->device.codec_data) || (!of_soc->platform_node))
return;
- pr_info("platform<-->codec match achieved; registering machine\n");
So what this does is add an extremely simple machine driver which matches up the first DAI on a single codec and platform with no facility for setting up any configurable clocking or anything? This is fine in so far as it goes but it's going to work for very few systems as it is - most codecs will need some additional configuration. This could be added later, though.
Given this it might be worth renaming it to something less generic and perhaps pushing it down into an of directory below the main ASoC directory to parallel existing machine drivers. I'm happy with the code from an ASoC point of view.
On 7/14/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Sat, Jul 12, 2008 at 02:39:29AM -0600, Grant Likely wrote:
Simple utility layer for creating ASoC machine instances based on data in the OpenFirmware device tree. OF aware platform drivers and codec drivers register themselves with this framework and the framework automatically instantiates a machine driver.
Ideally someone from the PowerPC community would sign off on this - given the nature and volume of discussion people obviously have very strong opinions about how machine drivers should be done so I'd really like to see some community buy in for something like this. From an ASoC point of view if this works it's fine.
Grant is one of the core PowerPC developers. There's no big disagreement on the driver model, there just isn't a clean solution for building a PowerPC ASoC driver under ASoC v1. The ASoC v1 driver model is simply not compatible with the PowerPC device tree model. That's why all of this glue code is needed.
Hopefully we can get the driver model sorted out in v2. If the ASoC driver model is fixed all of this glue code disappears.
The PowerPC side isn't without fault too. PowerPC still doesn't have a good way to load the fabric/machine driver.
Which are we going to call it, fabric or machine? I had been working on the Apple code in sound/aoa. It is called fabric in that code. The equivalent driver is called machine in ASoC v1.
This is most likely temporary glue code to work around limitations in the ASoC v1 framework. I expect ASoC v2 won't need this.
It will need some way of providing a machine driver, generic (like this one) or otherwise.
+static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc) +{
struct platform_device *pdev;
int rc;
/* Only register the device if both the codec and platform have
* been registered */
if ((!of_soc->device.codec_data) || (!of_soc->platform_node))
return;
pr_info("platform<-->codec match achieved; registering machine\n");
So what this does is add an extremely simple machine driver which matches up the first DAI on a single codec and platform with no facility for setting up any configurable clocking or anything? This is fine in so far as it goes but it's going to work for very few systems as it is - most codecs will need some additional configuration. This could be added later, though.
Given this it might be worth renaming it to something less generic and perhaps pushing it down into an of directory below the main ASoC directory to parallel existing machine drivers. I'm happy with the code from an ASoC point of view.
On Mon, Jul 14, 2008 at 10:13:14AM -0400, Jon Smirl wrote:
On 7/14/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
Ideally someone from the PowerPC community would sign off on this - given the nature and volume of discussion people obviously have very
Grant is one of the core PowerPC developers. There's no big
OK, fair enough...
Hopefully we can get the driver model sorted out in v2. If the ASoC driver model is fixed all of this glue code disappears.
The PowerPC side isn't without fault too. PowerPC still doesn't have a good way to load the fabric/machine driver.
I'm finding it difficult to square these two statements - from an ASoC point of view the main thing this patch is doing is adding a machine driver and that's not something that's going to go away. With version 2 you will get the wait for all components to come on-line logic that's implemented here from the ASoC core but that doesn't remove the need for a machine driver to tell the core what to wait for and arrange any machine specific things like clocking. It's this debate about machine drivers that makes me nervous here.
Like I say, from an ASoC point of view it's not an issue and the current approach is fine.
Which are we going to call it, fabric or machine? I had been working on the Apple code in sound/aoa. It is called fabric in that code. The equivalent driver is called machine in ASoC v1.
ASoC has always called it a machine driver.
This is most likely temporary glue code to work around limitations in the ASoC v1 framework. I expect ASoC v2 won't need this.
It will need some way of providing a machine driver, generic (like this one) or otherwise.
[BTW, it'd be helpful if you could delete unreferenced quotes when you reply - it makes things much easier to read, especially when reviewing lengthy patches. Unfortunately the GMail UI encourages doing this :(]
Mark Brown wrote:
The PowerPC side isn't without fault too. PowerPC still doesn't have a good way to load the fabric/machine driver.
I'm finding it difficult to square these two statements - from an ASoC point of view the main thing this patch is doing is adding a machine driver and that's not something that's going to go away.
Jon's concern is that there is no straightforward way to build a kernel with multiple fabric drivers and have the right one chosen via the device tree. This is just a limitation of the device tree model, and no one has come up with a good solution yet.
The problem still exists in ASoC V2. However, it's not anything that ASoC itself needs to be concerned with. It's purely a PowerPC problem.
Which are we going to call it, fabric or machine? I had been working on the Apple code in sound/aoa. It is called fabric in that code. The equivalent driver is called machine in ASoC v1.
ASoC has always called it a machine driver.
Wait, I thought it's supposed to be called a fabric driver now? On PowerPC, it should be called a fabric driver because we already have machine drivers.
On Mon, Jul 14, 2008 at 10:14 AM, Timur Tabi timur@freescale.com wrote:
Mark Brown wrote:
The PowerPC side isn't without fault too. PowerPC still doesn't have a good way to load the fabric/machine driver.
I'm finding it difficult to square these two statements - from an ASoC point of view the main thing this patch is doing is adding a machine driver and that's not something that's going to go away.
Jon's concern is that there is no straightforward way to build a kernel with multiple fabric drivers and have the right one chosen via the device tree. This is just a limitation of the device tree model, and no one has come up with a good solution yet.
The problem still exists in ASoC V2. However, it's not anything that ASoC itself needs to be concerned with. It's purely a PowerPC problem.
It's purely an *OF Device Tree* problem. PowerPC isn't the only platform using the device tree. :-)
g.
On Mon, Jul 14, 2008 at 11:14:41AM -0500, Timur Tabi wrote:
Mark Brown wrote:
I'm finding it difficult to square these two statements - from an ASoC point of view the main thing this patch is doing is adding a machine driver and that's not something that's going to go away.
Jon's concern is that there is no straightforward way to build a kernel with multiple fabric drivers and have the right one chosen via the device tree. This is just a limitation of the device tree model, and no one has come up with a good solution yet.
Indeed - I understand what the problem you guys have is, I just want to make sure that there is a reasonable consensus among the PowerPC people that this approach is OK to go in and won't create ructions. The lack of resolution on this issue makes me nervous about any proposed solution where I haven't seen any explicit indication that the community is OK with it.
Incidentally, nobody ever really commented on my suggestion to do something DMI-like - you've already got the board type information present in the device trees (in the model and compatible information in the root nodes), all that's needed is an API to allow matching on it.
The problem still exists in ASoC V2. However, it's not anything that ASoC itself needs to be concerned with. It's purely a PowerPC problem.
Right, I just want to be clear that you guys all understand what this code does and that there won't be too many complaints after the fact.
ASoC has always called it a machine driver.
Wait, I thought it's supposed to be called a fabric driver now? On PowerPC, it should be called a fabric driver because we already have machine drivers.
I don't mind - you can call it what you like inside PowerPC-specific code.
On Mon, Jul 14, 2008 at 10:53 AM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Mon, Jul 14, 2008 at 11:14:41AM -0500, Timur Tabi wrote:
Mark Brown wrote:
I'm finding it difficult to square these two statements - from an ASoC point of view the main thing this patch is doing is adding a machine driver and that's not something that's going to go away.
Jon's concern is that there is no straightforward way to build a kernel with multiple fabric drivers and have the right one chosen via the device tree. This is just a limitation of the device tree model, and no one has come up with a good solution yet.
Indeed - I understand what the problem you guys have is, I just want to make sure that there is a reasonable consensus among the PowerPC people that this approach is OK to go in and won't create ructions. The lack of resolution on this issue makes me nervous about any proposed solution where I haven't seen any explicit indication that the community is OK with it.
Incidentally, nobody ever really commented on my suggestion to do something DMI-like
I'm feeling stupid; what does "DMI" stand for?
- you've already got the board type information
present in the device trees (in the model and compatible information in the root nodes), all that's needed is an API to allow matching on it.
Yes, we have APIs for matching against device trees. Personally, I'm leaning towards having the powerpc platform code (arch/powerpc/platforms/* stuff; not ASoC platform stuff) register a platform device for the machine driver and let as many machine drivers as needed be written. Hopefully we'll be able to do at least one generic machine driver that will be usable by most PowerPC boards, but I don't think it is a requirement or even realistic to shoehorn all powerpc sound circuits into a single driver.
The problem still exists in ASoC V2. However, it's not anything that ASoC itself needs to be concerned with. It's purely a PowerPC problem.
Right, I just want to be clear that you guys all understand what this code does and that there won't be too many complaints after the fact.
Shouldn't be. I'm certainly not pushing this as the end-all be-all powerpc sound machine driver.
ASoC has always called it a machine driver.
Wait, I thought it's supposed to be called a fabric driver now? On PowerPC, it should be called a fabric driver because we already have machine drivers.
I don't mind - you can call it what you like inside PowerPC-specific code.
Oh help! Don't tell us that! Otherwise we'll always be talking across purposes. When ambiguous, let's just be sure to always refer to them as "ASoC machine drivers". :-)
Cheers, g.
On Mon, Jul 14, 2008 at 11:21:12AM -0600, Grant Likely wrote:
On Mon, Jul 14, 2008 at 10:53 AM, Mark Brown
Incidentally, nobody ever really commented on my suggestion to do something DMI-like
I'm feeling stupid; what does "DMI" stand for?
Desktop Management Interface, a standard BIOS interface for getting system data on x86 class hardware. Of particular interest here is the fact that it contains various ID strings for things like motherboard and chassis - on Linux drivers can be automatically loaded based on these strings. See drivers/misc/thinkpad_acpi.c for an example of a driver that does this.
Note that it's *not* binding a driver, it just provides the hooks to enable the modules to be automatically loaded and to let drivers query information in DMI.
Yes, we have APIs for matching against device trees. Personally, I'm leaning towards having the powerpc platform code (arch/powerpc/platforms/* stuff; not ASoC platform stuff) register a platform device for the machine driver and let as many machine drivers as needed be written. Hopefully we'll be able to do at least one
That would be what I'd expected initially - it is, after all, what other platforms are doing here.
generic machine driver that will be usable by most PowerPC boards, but I don't think it is a requirement or even realistic to shoehorn all powerpc sound circuits into a single driver.
Yes, that'd be completely unrealistic.
I don't mind - you can call it what you like inside PowerPC-specific code.
Oh help! Don't tell us that! Otherwise we'll always be talking across purposes. When ambiguous, let's just be sure to always refer to them as "ASoC machine drivers". :-)
Feh, from now on I shall me naming all new concepts with pwgen :) .
Mark Brown wrote:
Desktop Management Interface, a standard BIOS interface for getting system data on x86 class hardware. Of particular interest here is the fact that it contains various ID strings for things like motherboard and chassis - on Linux drivers can be automatically loaded based on these strings. See drivers/misc/thinkpad_acpi.c for an example of a driver that does this.
The only problem with this is that the OF probing code in the kernel binds drivers to device tree nodes. So when a driver claims a node, no other driver will be probed with it.
So we can't have generic nodes that classify the motherboard and just let everyone get probed on it.
On Mon, Jul 14, 2008 at 01:40:24PM -0500, Timur Tabi wrote:
Mark Brown wrote:
Desktop Management Interface, a standard BIOS interface for getting system data on x86 class hardware. Of particular interest here is the fact that it contains various ID strings for things like motherboard and chassis - on Linux drivers can be automatically loaded based on these strings. See drivers/misc/thinkpad_acpi.c for an example of a driver that does this.
The only problem with this is that the OF probing code in the kernel binds drivers to device tree nodes. So when a driver claims a node, no other driver will be probed with it.
So we can't have generic nodes that classify the motherboard and just let everyone get probed on it.
My suggestion is that you change this for the root node. It's already got the information required in there, it's just there's no way to use it to load modules at the minute. You could presumably read the information out of the device tree using existing APIs to check you're running on the right board once code is loaded?
Mark Brown wrote:
The only problem with this is that the OF probing code in the kernel binds drivers to device tree nodes. So when a driver claims a node, no other driver will be probed with it.
So we can't have generic nodes that classify the motherboard and just let everyone get probed on it.
My suggestion is that you change this for the root node.
That's an interesting idea.
It's already got the information required in there, it's just there's no way to use it to load modules at the minute.
Correct.
You could presumably read the information out of the device tree using existing APIs to check you're running on the right board once code is loaded?
Yes. The driver <-> node binding is only for probing. Any driver can scan the entire tree at any time.
On Mon, Jul 14, 2008 at 01:40:24PM -0500, Timur Tabi wrote:
Mark Brown wrote:
Desktop Management Interface, a standard BIOS interface for getting system data on x86 class hardware. Of particular interest here is the fact that it contains various ID strings for things like motherboard and chassis - on Linux drivers can be automatically loaded based on these strings. See drivers/misc/thinkpad_acpi.c for an example of a driver that does this.
The only problem with this is that the OF probing code in the kernel binds drivers to device tree nodes. So when a driver claims a node, no other driver will be probed with it.
Yes, but that is only for the of_platform bus which is just one way to setup device drivers from the device tree. Setting it up from the platform code is just as valid an option. Or, each ASoC machine driver could read the device tree itself in the module_init hook to decide whether or not to instantiate itself.
Cheers, g.
On 7/14/08, Timur Tabi timur@freescale.com wrote:
Mark Brown wrote:
Desktop Management Interface, a standard BIOS interface for getting system data on x86 class hardware. Of particular interest here is the fact that it contains various ID strings for things like motherboard and chassis - on Linux drivers can be automatically loaded based on these strings. See drivers/misc/thinkpad_acpi.c for an example of a driver that does this.
The only problem with this is that the OF probing code in the kernel binds drivers to device tree nodes. So when a driver claims a node, no other driver will be probed with it.
So we can't have generic nodes that classify the motherboard and just let everyone get probed on it.
Allowing multiple binds at the root causes the problem of something like compatible="lite5200b,mpc5200-simple". Both platforms would bind and that's not what you want.
I'm not a fan of each platform creating a platform device in arch/powerpc/platforms/*. That implies that each platform would cause another source file to be added each containing pretty much identical code. It also makes the mpc5200-simple platform pointless. Of course this scheme works and I'm doing it right now, but it's clearly not an optimal solution.
Another scheme would be to add kernel code to always create virtual OF devices like "lite5200b-fabric" that are derived off from the machine name that achieved a bind.
A third scheme would be to convert the powerpc machine drivers themselves to device drivers and move them out of arch/powerpc/platforms/* into drivers/whatever. Then add the ASoC fabric code to them. I don't know if you can load device drivers early enough to load a powerpc machine driver from initrd.
A fourth scheme is to do it at compile time. But that means no universal firmware images that support multiple hardware revisions. We have this one today too.
On Mon, Jul 14, 2008 at 07:45:46PM -0400, Jon Smirl wrote:
On 7/14/08, Timur Tabi timur@freescale.com wrote:
Mark Brown wrote:
chassis - on Linux drivers can be automatically loaded based on these strings. See drivers/misc/thinkpad_acpi.c for an example of a driver that does this.
Allowing multiple binds at the root causes the problem of something like compatible="lite5200b,mpc5200-simple". Both platforms would bind and that's not what you want.
Binding isn't the issue here - it's loading the driver in the first place. Once the drivers are loaded they can (hopefully) figure out if they are running on appropriate hardware.
Another scheme would be to add kernel code to always create virtual OF devices like "lite5200b-fabric" that are derived off from the machine name that achieved a bind.
This is what I'm suggesting, modulo the fact that I'm suggesting *not* creating virtual devices but rather providing a mechanism for drivers to load without binding to anything. It strikes me that you're going to run into similar situations with other hardware at some point - either for undocumented extras that you happen to know exist on the system (like much of the DMI usage on x86) or for other things where you've got on-board hardware structured like sound hardware tends to be.
On 7/15/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Mon, Jul 14, 2008 at 07:45:46PM -0400, Jon Smirl wrote:
On 7/14/08, Timur Tabi timur@freescale.com wrote:
Mark Brown wrote:
chassis - on Linux drivers can be automatically loaded based on these strings. See drivers/misc/thinkpad_acpi.c for an example of a driver that does this.
Allowing multiple binds at the root causes the problem of something like compatible="lite5200b,mpc5200-simple". Both platforms would bind and that's not what you want.
Binding isn't the issue here - it's loading the driver in the first place. Once the drivers are loaded they can (hopefully) figure out if they are running on appropriate hardware.
In this case we would end up with two core PowerPC machine drivers bound, not ALSA ones. We have machine drivers in the PowerPC core, that's one reason why it is confusing to call the ALSA drivers machine drivers too.
If we allow multiple bindings both lite5200b and mpc5200-simple would bind. The compatible list is a priority list from most specific to most general. First you check for the specific driver lite5200b, if it's not found then load the generic one mpc5200-simple.
Another scheme would be to add kernel code to always create virtual OF devices like "lite5200b-fabric" that are derived off from the machine name that achieved a bind.
This is what I'm suggesting, modulo the fact that I'm suggesting *not* creating virtual devices but rather providing a mechanism for drivers to load without binding to anything. It strikes me that you're going to
If you have drivers for four different hardware releases compiled into your kernel, the only kernel mechanism I know of to trigger only one of these to initialize is to create a device and then let the probe mechanism sort it out. This is even more true when the drivers are on initrd and need to be dynamically loaded. The module load code will search through the alias lists in the module .ko files.
It has been pointed out that a lite5200b-fabric device isn't really a virtual devices. It's a device that represents the traces on the PCB wiring the generic chip drivers together.
run into similar situations with other hardware at some point - either for undocumented extras that you happen to know exist on the system (like much of the DMI usage on x86) or for other things where you've got on-board hardware structured like sound hardware tends to be.
This makes sense, it is possible that other areas we aren't familiar with will need fabric drivers too. The problem is easily exposed in audio hardware since we use external clock and amp chips.
On Tue, Jul 15, 2008 at 09:08:28AM -0400, Jon Smirl wrote:
On 7/15/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
Binding isn't the issue here - it's loading the driver in the first place. Once the drivers are loaded they can (hopefully) figure out if they are running on appropriate hardware.
In this case we would end up with two core PowerPC machine drivers bound, not ALSA ones. We have machine drivers in the PowerPC core, that's one reason why it is confusing to call the ALSA drivers machine drivers too.
Again, I'm not talking about binding anything.
This is what I'm suggesting, modulo the fact that I'm suggesting *not* creating virtual devices but rather providing a mechanism for drivers to load without binding to anything. It strikes me that you're going to
If you have drivers for four different hardware releases compiled into your kernel, the only kernel mechanism I know of to trigger only one of these to initialize is to create a device and then let the probe mechanism sort it out. This is even more true when the drivers are on initrd and need to be dynamically loaded. The module load code will search through the alias lists in the module .ko files.
This can be handled in the module initialisation (rather than driver probe) - providing something can arrange for the driver to get loaded then the drivers can check the system they're running on when that happens and fail if it's inappropriate. The ARM ASoC drivers and x86 DMI based stuff do things this way, for example (ARM doesn't have the automatic module load stuff implemented, though).
The infrastructure for automatically loading modules is usable separately from device registration, though normally the two do go hand in hand.
It has been pointed out that a lite5200b-fabric device isn't really a virtual devices. It's a device that represents the traces on the PCB wiring the generic chip drivers together.
This was brought up very early on when you guys first started working on it. :/
On Mon, Jul 14, 2008 at 7:49 AM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Sat, Jul 12, 2008 at 02:39:29AM -0600, Grant Likely wrote:
+static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc) +{
struct platform_device *pdev;
int rc;
/* Only register the device if both the codec and platform have
* been registered */
if ((!of_soc->device.codec_data) || (!of_soc->platform_node))
return;
pr_info("platform<-->codec match achieved; registering machine\n");
So what this does is add an extremely simple machine driver which matches up the first DAI on a single codec and platform with no facility for setting up any configurable clocking or anything? This is fine in so far as it goes but it's going to work for very few systems as it is - most codecs will need some additional configuration. This could be added later, though.
Yes, that is pretty much exactly what it is. When I wrote this my goal was just to get something working that had some semblance of cleanliness. Since it is now looking like something that could get merged, I'll clean up the documentation describing exactly what it is and what the limitations are. I fully expect that if others find it useful then they will need to extend it to extract additional configuration information out of the device tree.
That being said, I expect this code to be completely rewritten when ASoC v2 hits mainline. Most of the code is devoted to matching platforms with codecs. The mechanism will be completely different when the ASoC layer handles making sure all devices are present before setting up the machine driver. I expect that some machines can be handled with some form of generic of-asoc driver, while more complex ones will need custom code.
Given this it might be worth renaming it to something less generic and perhaps pushing it down into an of directory below the main ASoC directory to parallel existing machine drivers. I'm happy with the code from an ASoC point of view.
I'm okay with that. How about fsl/mpc5200-of-machine.c for now? (only the mpc5200 i2s driver uses it at the moment). It can always be renamed if other folks want to use it for other chips.
Cheers, g.
On Mon, Jul 14, 2008 at 11:06:34AM -0600, Grant Likely wrote:
I'm okay with that. How about fsl/mpc5200-of-machine.c for now? (only the mpc5200 i2s driver uses it at the moment). It can always be renamed if other folks want to use it for other chips.
That seems reasonable so long as you're happy with it, though it does seem to be going to the other extreme in terms of broadness :)
On Mon, Jul 14, 2008 at 11:16 AM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Mon, Jul 14, 2008 at 11:06:34AM -0600, Grant Likely wrote:
I'm okay with that. How about fsl/mpc5200-of-machine.c for now? (only the mpc5200 i2s driver uses it at the moment). It can always be renamed if other folks want to use it for other chips.
That seems reasonable so long as you're happy with it, though it does seem to be going to the other extreme in terms of broadness :)
heh; I don't care. This stuff is all going to be in flux anyway, so change is inevitable whatever is chosen now. :-)
g.
On Mon, Jul 14, 2008 at 06:16:33PM +0100, Mark Brown wrote:
On Mon, Jul 14, 2008 at 11:06:34AM -0600, Grant Likely wrote:
I'm okay with that. How about fsl/mpc5200-of-machine.c for now? (only the mpc5200 i2s driver uses it at the moment). It can always be renamed if other folks want to use it for other chips.
That seems reasonable so long as you're happy with it, though it does seem to be going to the other extreme in terms of broadness :)
Okay, I've changed my mind. :-) I'll back off a bit from this extreme and call it:
sound/soc/fsl/soc-of-simple.c
Does that sound okay? If non-freescale chips decide to use it then it can be moved out of the freescale director.
Cheers, g.
On Fri, Jul 18, 2008 at 01:17:20AM -0600, Grant Likely wrote:
Okay, I've changed my mind. :-) I'll back off a bit from this extreme and call it:
sound/soc/fsl/soc-of-simple.c
Does that sound okay? If non-freescale chips decide to use it then it can be moved out of the freescale director.
Fine by me - you could add an of directory but I don't mind either way so long as the people actually using the code are happy.
Grant Likely wrote:
Okay, I've changed my mind. :-) I'll back off a bit from this extreme and call it:
sound/soc/fsl/soc-of-simple.c
That works for me.
And please don't forget to CC: me on any discussion involving sound/soc/fsl.
On Sat, Jul 12, 2008 at 02:39:29AM -0600, Grant Likely wrote:
From: Grant Likely grant.likely@secretlab.ca
Simple utility layer for creating ASoC machine instances based on data in the OpenFirmware device tree. OF aware platform drivers and codec drivers register themselves with this framework and the framework automatically instantiates a machine driver.
This is most likely temporary glue code to work around limitations in the ASoC v1 framework. I expect ASoC v2 won't need this.
[...]
--- /dev/null +++ b/sound/soc/soc-of.c
It's quite inconvenient to spread the firmware-specific bits over the whole kernel source tree. So, can we place this in driver/of/, just as we did for i2c, spi and gpio?
@@ -0,0 +1,171 @@ +/*
- OF helpers for ALSA SoC Layer
- Copyright (C) 2008, Secret Lab Technologies Ltd.
- */
+#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-of.h> +#include <sound/initval.h>
+MODULE_AUTHOR("Grant Likely grant.likely@secretlab.ca"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALSA SoC OpenFirmware bindings");
+static DEFINE_MUTEX(of_snd_soc_mutex); +static LIST_HEAD(of_snd_soc_device_list); +static int of_snd_soc_next_index;
+struct of_snd_soc_device {
- int id;
- struct list_head list;
- struct snd_soc_device device;
- struct snd_soc_machine machine;
- struct snd_soc_dai_link dai_link;
- struct platform_device *pdev;
- struct device_node *platform_node;
- struct device_node *codec_node;
+};
+static struct snd_soc_ops of_snd_soc_ops = { +};
+static struct of_snd_soc_device * +of_snd_soc_get_device(struct device_node *codec_node) +{
- struct of_snd_soc_device *of_soc;
- list_for_each_entry(of_soc, &of_snd_soc_device_list, list) {
if (of_soc->codec_node == codec_node)
return of_soc;
- }
- of_soc = kzalloc(sizeof(struct of_snd_soc_device), GFP_KERNEL);
- if (!of_soc)
return NULL;
- /* Initialize the structure and add it to the global list */
- of_soc->codec_node = codec_node;
- of_soc->id = of_snd_soc_next_index++;
- of_soc->machine.dai_link = &of_soc->dai_link;
- of_soc->machine.num_links = 1;
- of_soc->device.machine = &of_soc->machine;
- of_soc->dai_link.ops = &of_snd_soc_ops;
- list_add(&of_soc->list, &of_snd_soc_device_list);
- return of_soc;
+}
+static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc) +{
- struct platform_device *pdev;
- int rc;
- /* Only register the device if both the codec and platform have
* been registered */
- if ((!of_soc->device.codec_data) || (!of_soc->platform_node))
return;
- pr_info("platform<-->codec match achieved; registering machine\n");
- pdev = platform_device_alloc("soc-audio", of_soc->id);
- if (!pdev) {
pr_err("of_soc: platform_device_alloc() failed\n");
return;
- }
- pdev->dev.platform_data = of_soc;
- platform_set_drvdata(pdev, &of_soc->device);
- of_soc->device.dev = &pdev->dev;
- /* The ASoC device is complete; register it */
- rc = platform_device_add(pdev);
- if (rc) {
pr_err("of_soc: platform_device_add() failed\n");
return;
- }
+}
+int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev,
void *codec_data, struct snd_soc_codec_dai *dai,
struct device_node *node)
+{
- struct of_snd_soc_device *of_soc;
- int rc = 0;
- pr_info("registering ASoC codec driver: %s\n", node->full_name);
- mutex_lock(&of_snd_soc_mutex);
- of_soc = of_snd_soc_get_device(node);
- if (!of_soc) {
rc = -ENOMEM;
goto out;
- }
- /* Store the codec data */
- of_soc->device.codec_data = codec_data;
- of_soc->device.codec_dev = codec_dev;
- of_soc->dai_link.name = node->name;
- of_soc->dai_link.stream_name = node->name;
- of_soc->dai_link.codec_dai = dai;
- /* Now try to register the SoC device */
- of_snd_soc_register_device(of_soc);
- out:
- mutex_unlock(&of_snd_soc_mutex);
- return rc;
+} +EXPORT_SYMBOL_GPL(of_snd_soc_register_codec);
+int of_snd_soc_register_platform(struct snd_soc_platform *platform,
struct device_node *node,
struct snd_soc_cpu_dai *cpu_dai)
+{
- struct of_snd_soc_device *of_soc;
- struct device_node *codec_node;
- const phandle *handle;
- int len, rc = 0;
- pr_info("registering ASoC platform driver: %s\n", node->full_name);
- handle = of_get_property(node, "codec-handle", &len);
- if (!handle || len < sizeof(handle))
return -ENODEV;
- codec_node = of_find_node_by_phandle(*handle);
- if (!codec_node)
return -ENODEV;
- pr_info("looking for codec: %s\n", codec_node->full_name);
- mutex_lock(&of_snd_soc_mutex);
- of_soc = of_snd_soc_get_device(codec_node);
- if (!of_soc) {
rc = -ENOMEM;
goto out;
- }
- of_soc->platform_node = node;
- of_soc->dai_link.cpu_dai = cpu_dai;
- of_soc->device.platform = platform;
- of_soc->machine.name = of_soc->dai_link.cpu_dai->name;
- /* Now try to register the SoC device */
- of_snd_soc_register_device(of_soc);
- out:
- mutex_unlock(&of_snd_soc_mutex);
- return rc;
+} +EXPORT_SYMBOL_GPL(of_snd_soc_register_platform);
Thanks,
On Mon, Jul 14, 2008 at 8:16 AM, Anton Vorontsov avorontsov@ru.mvista.com wrote:
On Sat, Jul 12, 2008 at 02:39:29AM -0600, Grant Likely wrote:
--- /dev/null +++ b/sound/soc/soc-of.c
It's quite inconvenient to spread the firmware-specific bits over the whole kernel source tree. So, can we place this in driver/of/, just as we did for i2c, spi and gpio?
This isn't any more specific than say how device drivers are specific for particular devices. This is an ASoC machine driver and should live with all the other ASoC machine drivers.
g.
participants (7)
-
Anton Vorontsov
-
dinesh
-
Grant Likely
-
Jon Smirl
-
Mark Brown
-
Nobin Mathew
-
Timur Tabi