diff options
author | Ingo Molnar <mingo@elte.hu> | 2008-10-15 13:46:29 +0200 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-10-15 13:46:29 +0200 |
commit | b2aaf8f74cdc84a9182f6cabf198b7763bcb9d40 (patch) | |
tree | 53ccb1c2c14751fe69cf93102e76e97021f6df07 /sound/soc | |
parent | 4f962d4d65923d7b722192e729840cfb79af0a5a (diff) | |
parent | 278429cff8809958d25415ba0ed32b59866ab1a8 (diff) |
Merge branch 'linus' into stackprotector
Conflicts:
arch/x86/kernel/Makefile
include/asm-x86/pda.h
Diffstat (limited to 'sound/soc')
135 files changed, 26489 insertions, 2911 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 18f28ac4bfe8..4dfda6674bec 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -2,16 +2,10 @@ # SoC audio configuration # -menu "System on Chip audio support" - depends on SND!=n - -config SND_SOC_AC97_BUS - bool - -config SND_SOC +menuconfig SND_SOC tristate "ALSA for SoC audio support" - depends on SND select SND_PCM + select AC97_BUS if SND_SOC_AC97_BUS ---help--- If you want ASoC support, you should say Y here and also to the @@ -23,16 +17,25 @@ config SND_SOC This ASoC audio support can also be built as a module. If so, the module will be called snd-soc-core. +if SND_SOC + +config SND_SOC_AC97_BUS + bool + # All the supported Soc's +source "sound/soc/at32/Kconfig" source "sound/soc/at91/Kconfig" +source "sound/soc/au1x/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/omap/Kconfig" +source "sound/soc/blackfin/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" -endmenu +endif # SND_SOC + diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 782db2127108..d849349f2c66 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,4 +1,5 @@ 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) += codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ +obj-$(CONFIG_SND_SOC) += omap/ au1x/ blackfin/ diff --git a/sound/soc/at32/Kconfig b/sound/soc/at32/Kconfig new file mode 100644 index 000000000000..b0765e86c085 --- /dev/null +++ b/sound/soc/at32/Kconfig @@ -0,0 +1,34 @@ +config SND_AT32_SOC + tristate "SoC Audio for the Atmel AT32 System-on-a-Chip" + depends on AVR32 && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the AT32 SSC interface. You will also need to + to select the audio interfaces to support below. + + +config SND_AT32_SOC_SSC + tristate + + + +config SND_AT32_SOC_PLAYPAQ + tristate "SoC Audio support for PlayPaq with WM8510" + depends on SND_AT32_SOC && BOARD_PLAYPAQ + select SND_AT32_SOC_SSC + select SND_SOC_WM8510 + help + Say Y or M here if you want to add support for SoC audio + on the LRS PlayPaq. + + + +config SND_AT32_SOC_PLAYPAQ_SLAVE + bool "Run CODEC on PlayPaq in slave mode" + depends on SND_AT32_SOC_PLAYPAQ + default n + help + Say Y if you want to run with the AT32 SSC generating the BCLK + and FRAME signals on the PlayPaq. Unless you want to play + with the AT32 as the SSC master, you probably want to say N here, + as this will give you better sound quality. diff --git a/sound/soc/at32/Makefile b/sound/soc/at32/Makefile new file mode 100644 index 000000000000..c03e55ececeb --- /dev/null +++ b/sound/soc/at32/Makefile @@ -0,0 +1,11 @@ +# AT32 Platform Support +snd-soc-at32-objs := at32-pcm.o +snd-soc-at32-ssc-objs := at32-ssc.o + +obj-$(CONFIG_SND_AT32_SOC) += snd-soc-at32.o +obj-$(CONFIG_SND_AT32_SOC_SSC) += snd-soc-at32-ssc.o + +# AT32 Machine Support +snd-soc-playpaq-objs := playpaq_wm8510.o + +obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o diff --git a/sound/soc/at32/at32-pcm.c b/sound/soc/at32/at32-pcm.c new file mode 100644 index 000000000000..c83584f989a9 --- /dev/null +++ b/sound/soc/at32/at32-pcm.c @@ -0,0 +1,492 @@ +/* sound/soc/at32/at32-pcm.c + * ASoC PCM interface for Atmel AT32 SoC + * + * Copyright (C) 2008 Long Range Systems + * Geoffrey Wossum <gwossum@acm.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Note that this is basically a port of the sound/soc/at91-pcm.c to + * the AVR32 kernel. Thanks to Frank Mandarino for that code. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/atmel_pdc.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "at32-pcm.h" + + + +/*--------------------------------------------------------------------------*\ + * Hardware definition +\*--------------------------------------------------------------------------*/ +/* TODO: These values were taken from the AT91 platform driver, check + * them against real values for AT32 + */ +static const struct snd_pcm_hardware at32_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE), + + .formats = SNDRV_PCM_FMTBIT_S16, + .period_bytes_min = 32, + .period_bytes_max = 8192, /* 512 frames * 16 bytes / frame */ + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 32 * 1024, +}; + + + +/*--------------------------------------------------------------------------*\ + * Data types +\*--------------------------------------------------------------------------*/ +struct at32_runtime_data { + struct at32_pcm_dma_params *params; + dma_addr_t dma_buffer; /* physical address of DMA buffer */ + dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ + size_t period_size; + + dma_addr_t period_ptr; /* physical address of next period */ + int periods; /* period index of period_ptr */ + + /* Save PDC registers (for power management) */ + u32 pdc_xpr_save; + u32 pdc_xcr_save; + u32 pdc_xnpr_save; + u32 pdc_xncr_save; +}; + + + +/*--------------------------------------------------------------------------*\ + * Helper functions +\*--------------------------------------------------------------------------*/ +static int at32_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *dmabuf = &substream->dma_buffer; + size_t size = at32_pcm_hardware.buffer_bytes_max; + + dmabuf->dev.type = SNDRV_DMA_TYPE_DEV; + dmabuf->dev.dev = pcm->card->dev; + dmabuf->private_data = NULL; + dmabuf->area = dma_alloc_coherent(pcm->card->dev, size, + &dmabuf->addr, GFP_KERNEL); + pr_debug("at32_pcm: preallocate_dma_buffer: " + "area=%p, addr=%p, size=%ld\n", + (void *)dmabuf->area, (void *)dmabuf->addr, size); + + if (!dmabuf->area) + return -ENOMEM; + + dmabuf->bytes = size; + return 0; +} + + + +/*--------------------------------------------------------------------------*\ + * ISR +\*--------------------------------------------------------------------------*/ +static void at32_pcm_dma_irq(u32 ssc_sr, struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *rtd = substream->runtime; + struct at32_runtime_data *prtd = rtd->private_data; + struct at32_pcm_dma_params *params = prtd->params; + static int count; + + count++; + if (ssc_sr & params->mask->ssc_endbuf) { + pr_warning("at32-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n", + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "underrun" : "overrun", params->name, ssc_sr, count); + + /* re-start the PDC */ + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) + prtd->period_ptr = prtd->dma_buffer; + + + ssc_writex(params->ssc->regs, params->pdc->xpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_enable); + } + + + if (ssc_sr & params->mask->ssc_endx) { + /* Load the PDC next pointer and counter registers */ + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) + prtd->period_ptr = prtd->dma_buffer; + ssc_writex(params->ssc->regs, params->pdc->xnpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + } + + + snd_pcm_period_elapsed(substream); +} + + + +/*--------------------------------------------------------------------------*\ + * PCM operations +\*--------------------------------------------------------------------------*/ +static int at32_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct at32_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* this may get called several times by oss emulation + * with different params + */ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->params = rtd->dai->cpu_dai->dma_data; + prtd->params->dma_intr_handler = at32_pcm_dma_irq; + + prtd->dma_buffer = runtime->dma_addr; + prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes; + prtd->period_size = params_period_bytes(params); + + pr_debug("hw_params: DMA for %s initialized " + "(dma_bytes=%ld, period_size=%ld)\n", + prtd->params->name, runtime->dma_bytes, prtd->period_size); + + return 0; +} + + + +static int at32_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct at32_runtime_data *prtd = substream->runtime->private_data; + struct at32_pcm_dma_params *params = prtd->params; + + if (params != NULL) { + ssc_writex(params->ssc->regs, SSC_PDC_PTCR, + params->mask->pdc_disable); + prtd->params->dma_intr_handler = NULL; + } + + return 0; +} + + + +static int at32_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct at32_runtime_data *prtd = substream->runtime->private_data; + struct at32_pcm_dma_params *params = prtd->params; + + ssc_writex(params->ssc->regs, SSC_IDR, + params->mask->ssc_endx | params->mask->ssc_endbuf); + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + + return 0; +} + + +static int at32_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *rtd = substream->runtime; + struct at32_runtime_data *prtd = rtd->private_data; + struct at32_pcm_dma_params *params = prtd->params; + int ret = 0; + + pr_debug("at32_pcm_trigger: buffer_size = %ld, " + "dma_area = %p, dma_bytes = %ld\n", + rtd->buffer_size, rtd->dma_area, rtd->dma_bytes); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->period_ptr = prtd->dma_buffer; + + ssc_writex(params->ssc->regs, params->pdc->xpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + + prtd->period_ptr += prtd->period_size; + ssc_writex(params->ssc->regs, params->pdc->xnpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + + pr_debug("trigger: period_ptr=%lx, xpr=%x, " + "xcr=%d, xnpr=%x, xncr=%d\n", + (unsigned long)prtd->period_ptr, + ssc_readx(params->ssc->regs, params->pdc->xpr), + ssc_readx(params->ssc->regs, params->pdc->xcr), + ssc_readx(params->ssc->regs, params->pdc->xnpr), + ssc_readx(params->ssc->regs, params->pdc->xncr)); + + ssc_writex(params->ssc->regs, SSC_IER, + params->mask->ssc_endx | params->mask->ssc_endbuf); + ssc_writex(params->ssc->regs, SSC_PDC_PTCR, + params->mask->pdc_enable); + + pr_debug("sr=%x, imr=%x\n", + ssc_readx(params->ssc->regs, SSC_SR), + ssc_readx(params->ssc->regs, SSC_IER)); + break; /* SNDRV_PCM_TRIGGER_START */ + + + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + break; + + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_enable); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + + + +static snd_pcm_uframes_t at32_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct at32_runtime_data *prtd = runtime->private_data; + struct at32_pcm_dma_params *params = prtd->params; + dma_addr_t ptr; + snd_pcm_uframes_t x; + + ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr); + x = bytes_to_frames(runtime, ptr - prtd->dma_buffer); + + if (x == runtime->buffer_size) + x = 0; + + return x; +} + + + +static int at32_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct at32_runtime_data *prtd; + int ret = 0; + + snd_soc_set_runtime_hwparams(substream, &at32_pcm_hardware); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + goto out; + } + runtime->private_data = prtd; + + +out: + return ret; +} + + + +static int at32_pcm_close(struct snd_pcm_substream *substream) +{ + struct at32_runtime_data *prtd = substream->runtime->private_data; + + kfree(prtd); + return 0; +} + + +static int at32_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + + + +static struct snd_pcm_ops at32_pcm_ops = { + .open = at32_pcm_open, + .close = at32_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = at32_pcm_hw_params, + .hw_free = at32_pcm_hw_free, + .prepare = at32_pcm_prepare, + .trigger = at32_pcm_trigger, + .pointer = at32_pcm_pointer, + .mmap = at32_pcm_mmap, +}; + + + +/*--------------------------------------------------------------------------*\ + * ASoC platform driver +\*--------------------------------------------------------------------------*/ +static u64 at32_pcm_dmamask = 0xffffffff; + +static int at32_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &at32_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = at32_pcm_preallocate_dma_buffer( + pcm, SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + pr_debug("at32-pcm: Allocating PCM capture DMA buffer\n"); + ret = at32_pcm_preallocate_dma_buffer( + pcm, SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + + +out: + return ret; +} + + + +static void at32_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream == NULL) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + + + +#ifdef CONFIG_PM +static int at32_pcm_suspend(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct at32_runtime_data *prtd; + struct at32_pcm_dma_params *params; + + if (runtime == NULL) + return 0; + prtd = runtime->private_data; + params = prtd->params; + + /* Disable the PDC and save the PDC registers */ + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + + prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr); + prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr); + prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr); + prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr); + + return 0; +} + + + +static int at32_pcm_resume(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct at32_runtime_data *prtd; + struct at32_pcm_dma_params *params; + + if (runtime == NULL) + return 0; + prtd = runtime->private_data; + params = prtd->params; + + /* Restore the PDC registers and enable the PDC */ + ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save); + ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save); + ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save); + ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save); + + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, params->mask->pdc_enable); + return 0; +} +#else /* CONFIG_PM */ +# define at32_pcm_suspend NULL +# define at32_pcm_resume NULL +#endif /* CONFIG_PM */ + + + +struct snd_soc_platform at32_soc_platform = { + .name = "at32-audio", + .pcm_ops = &at32_pcm_ops, + .pcm_new = at32_pcm_new, + .pcm_free = at32_pcm_free_dma_buffers, + .suspend = at32_pcm_suspend, + .resume = at32_pcm_resume, +}; +EXPORT_SYMBOL_GPL(at32_soc_platform); + + + +MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>"); +MODULE_DESCRIPTION("Atmel AT32 PCM module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/at32/at32-pcm.h b/sound/soc/at32/at32-pcm.h new file mode 100644 index 000000000000..2a52430417da --- /dev/null +++ b/sound/soc/at32/at32-pcm.h @@ -0,0 +1,79 @@ +/* sound/soc/at32/at32-pcm.h + * ASoC PCM interface for Atmel AT32 SoC + * + * Copyright (C) 2008 Long Range Systems + * Geoffrey Wossum <gwossum@acm.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SOUND_SOC_AT32_AT32_PCM_H +#define __SOUND_SOC_AT32_AT32_PCM_H __FILE__ + +#include <linux/atmel-ssc.h> + + +/* + * Registers and status bits that are required by the PCM driver + * TODO: Is ptcr really used? + */ +struct at32_pdc_regs { + u32 xpr; /* PDC RX/TX pointer */ + u32 xcr; /* PDC RX/TX counter */ + u32 xnpr; /* PDC next RX/TX pointer */ + u32 xncr; /* PDC next RX/TX counter */ + u32 ptcr; /* PDC transfer control */ +}; + + + +/* + * SSC mask info + */ +struct at32_ssc_mask { + u32 ssc_enable; /* SSC RX/TX enable */ + u32 ssc_disable; /* SSC RX/TX disable */ + u32 ssc_endx; /* SSC ENDTX or ENDRX */ + u32 ssc_endbuf; /* SSC TXBUFF or RXBUFF */ + u32 pdc_enable; /* PDC RX/TX enable */ + u32 pdc_disable; /* PDC RX/TX disable */ +}; + + + +/* + * This structure, shared between the PCM driver and the interface, + * contains all information required by the PCM driver to perform the + * PDC DMA operation. All fields except dma_intr_handler() are initialized + * by the interface. The dms_intr_handler() pointer is set by the PCM + * driver and called by the interface SSC interrupt handler if it is + * non-NULL. + */ +struct at32_pcm_dma_params { + char *name; /* stream identifier */ + int pdc_xfer_size; /* PDC counter increment in bytes */ + struct ssc_device *ssc; /* SSC device for stream */ + struct at32_pdc_regs *pdc; /* PDC register info */ + struct at32_ssc_mask *mask; /* SSC mask info */ + struct snd_pcm_substream *substream; + void (*dma_intr_handler) (u32, struct snd_pcm_substream *); +}; + + + +/* + * The AT32 ASoC platform driver + */ +extern struct snd_soc_platform at32_soc_platform; + + + +/* + * SSC register access (since ssc_writel() / ssc_readl() require literal name) + */ +#define ssc_readx(base, reg) (__raw_readl((base) + (reg))) +#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg)) + +#endif /* __SOUND_SOC_AT32_AT32_PCM_H */ diff --git a/sound/soc/at32/at32-ssc.c b/sound/soc/at32/at32-ssc.c new file mode 100644 index 000000000000..4ef6492c902e --- /dev/null +++ b/sound/soc/at32/at32-ssc.c @@ -0,0 +1,849 @@ +/* sound/soc/at32/at32-ssc.c + * ASoC platform driver for AT32 using SSC as DAI + * + * Copyright (C) 2008 Long Range Systems + * Geoffrey Wossum <gwossum@acm.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Note that this is basically a port of the sound/soc/at91-ssc.c to + * the AVR32 kernel. Thanks to Frank Mandarino for that code. + */ + +/* #define DEBUG */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/atmel_pdc.h> +#include <linux/atmel-ssc.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "at32-pcm.h" +#include "at32-ssc.h" + + + +/*-------------------------------------------------------------------------*\ + * Constants +\*-------------------------------------------------------------------------*/ +#define NUM_SSC_DEVICES 3 + +/* + * SSC direction masks + */ +#define SSC_DIR_MASK_UNUSED 0 +#define SSC_DIR_MASK_PLAYBACK 1 +#define SSC_DIR_MASK_CAPTURE 2 + +/* + * SSC register values that Atmel left out of <linux/atmel-ssc.h>. These + * are expected to be used with SSC_BF + */ +/* START bit field values */ +#define SSC_START_CONTINUOUS 0 +#define SSC_START_TX_RX 1 +#define SSC_START_LOW_RF 2 +#define SSC_START_HIGH_RF 3 +#define SSC_START_FALLING_RF 4 +#define SSC_START_RISING_RF 5 +#define SSC_START_LEVEL_RF 6 +#define SSC_START_EDGE_RF 7 +#define SSS_START_COMPARE_0 8 + +/* CKI bit field values */ +#define SSC_CKI_FALLING 0 +#define SSC_CKI_RISING 1 + +/* CKO bit field values */ +#define SSC_CKO_NONE 0 +#define SSC_CKO_CONTINUOUS 1 +#define SSC_CKO_TRANSFER 2 + +/* CKS bit field values */ +#define SSC_CKS_DIV 0 +#define SSC_CKS_CLOCK 1 +#define SSC_CKS_PIN 2 + +/* FSEDGE bit field values */ +#define SSC_FSEDGE_POSITIVE 0 +#define SSC_FSEDGE_NEGATIVE 1 + +/* FSOS bit field values */ +#define SSC_FSOS_NONE 0 +#define SSC_FSOS_NEGATIVE 1 +#define SSC_FSOS_POSITIVE 2 +#define SSC_FSOS_LOW 3 +#define SSC_FSOS_HIGH 4 +#define SSC_FSOS_TOGGLE 5 + +#define START_DELAY 1 + + + +/*-------------------------------------------------------------------------*\ + * Module data +\*-------------------------------------------------------------------------*/ +/* + * SSC PDC registered required by the PCM DMA engine + */ +static struct at32_pdc_regs pdc_tx_reg = { + .xpr = SSC_PDC_TPR, + .xcr = SSC_PDC_TCR, + .xnpr = SSC_PDC_TNPR, + .xncr = SSC_PDC_TNCR, +}; + + + +static struct at32_pdc_regs pdc_rx_reg = { + .xpr = SSC_PDC_RPR, + .xcr = SSC_PDC_RCR, + .xnpr = SSC_PDC_RNPR, + .xncr = SSC_PDC_RNCR, +}; + + + +/* + * SSC and PDC status bits for transmit and receive + */ +static struct at32_ssc_mask ssc_tx_mask = { + .ssc_enable = SSC_BIT(CR_TXEN), + .ssc_disable = SSC_BIT(CR_TXDIS), + .ssc_endx = SSC_BIT(SR_ENDTX), + .ssc_endbuf = SSC_BIT(SR_TXBUFE), + .pdc_enable = SSC_BIT(PDC_PTCR_TXTEN), + .pdc_disable = SSC_BIT(PDC_PTCR_TXTDIS), +}; + + + +static struct at32_ssc_mask ssc_rx_mask = { + .ssc_enable = SSC_BIT(CR_RXEN), + .ssc_disable = SSC_BIT(CR_RXDIS), + .ssc_endx = SSC_BIT(SR_ENDRX), + .ssc_endbuf = SSC_BIT(SR_RXBUFF), + .pdc_enable = SSC_BIT(PDC_PTCR_RXTEN), + .pdc_disable = SSC_BIT(PDC_PTCR_RXTDIS), +}; + + + +/* + * DMA parameters for each SSC + */ +static struct at32_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = { + { + { + .name = "SSC0 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC0 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + }, + }, + { + { + .name = "SSC1 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC1 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + }, + }, + { + { + .name = "SSC2 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC2 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + }, + }, +}; + + + +static struct at32_ssc_info ssc_info[NUM_SSC_DEVICES] = { + { + .name = "ssc0", + .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock), + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, + { + .name = "ssc1", + .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock), + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, + { + .name = "ssc2", + .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock), + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, +}; + + + + +/*-------------------------------------------------------------------------*\ + * ISR +\*-------------------------------------------------------------------------*/ +/* + * SSC interrupt handler. Passes PDC interrupts to the DMA interrupt + * handler in the PCM driver. + */ +static irqreturn_t at32_ssc_interrupt(int irq, void *dev_id) +{ + struct at32_ssc_info *ssc_p = dev_id; + struct at32_pcm_dma_params *dma_params; + u32 ssc_sr; + u32 ssc_substream_mask; + int i; + + ssc_sr = (ssc_readl(ssc_p->ssc->regs, SR) & + ssc_readl(ssc_p->ssc->regs, IMR)); + + /* + * Loop through substreams attached to this SSC. If a DMA-related + * interrupt occured on that substream, call the DMA interrupt + * handler function, if one has been registered in the dma_param + * structure by the PCM driver. + */ + for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) { + dma_params = ssc_p->dma_params[i]; + + if ((dma_params != NULL) && + (dma_params->dma_intr_handler != NULL)) { + ssc_substream_mask = (dma_params->mask->ssc_endx | + dma_params->mask->ssc_endbuf); + if (ssc_sr & ssc_substream_mask) { + dma_params->dma_intr_handler(ssc_sr, + dma_params-> + substream); + } + } + } + + + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*\ + * DAI functions +\*-------------------------------------------------------------------------*/ +/* + * Startup. Only that one substream allowed in each direction. + */ +static int at32_ssc_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct at32_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; + int dir_mask; + + dir_mask = ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + SSC_DIR_MASK_PLAYBACK : SSC_DIR_MASK_CAPTURE); + + spin_lock_irq(&ssc_p->lock); + if (ssc_p->dir_mask & dir_mask) { + spin_unlock_irq(&ssc_p->lock); + return -EBUSY; + } + ssc_p->dir_mask |= dir_mask; + spin_unlock_irq(&ssc_p->lock); + + return 0; +} + + + +/* + * Shutdown. Clear DMA parameters and shutdown the SSC if there + * are no other substreams open. + */ +static void at32_ssc_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct at32_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; + struct at32_pcm_dma_params *dma_params; + int dir_mask; + + dma_params = ssc_p->dma_params[substream->stream]; + + if (dma_params != NULL) { + ssc_writel(dma_params->ssc->regs, CR, + dma_params->mask->ssc_disable); + pr_debug("%s disabled SSC_SR=0x%08x\n", + (substream->stream ? "receiver" : "transmit"), + ssc_readl(ssc_p->ssc->regs, SR)); + + dma_params->ssc = NULL; + dma_params->substream = NULL; + ssc_p->dma_params[substream->stream] = NULL; + } + + + dir_mask = 1 << substream->stream; + spin_lock_irq(&ssc_p->lock); + ssc_p->dir_mask &= ~dir_mask; + if (!ssc_p->dir_mask) { + /* Shutdown the SSC clock */ + pr_debug("at32-ssc: Stopping user %d clock\n", + ssc_p->ssc->user); + clk_disable(ssc_p->ssc->clk); + + if (ssc_p->initialized) { + free_irq(ssc_p->ssc->irq, ssc_p); + ssc_p->initialized = 0; + } + + /* Reset the SSC */ + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST)); + + /* clear the SSC dividers */ + ssc_p->cmr_div = 0; + ssc_p->tcmr_period = 0; + ssc_p->rcmr_period = 0; + } + spin_unlock_irq(&ssc_p->lock); +} + + + +/* + * Set the SSC system clock rate + */ +static int at32_ssc_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + /* TODO: What the heck do I do here? */ + return 0; +} + + + +/* + * Record DAI format for use by hw_params() + */ +static int at32_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct at32_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; + + ssc_p->daifmt = fmt; + return 0; +} + + + +/* + * Record SSC clock dividers for use in hw_params() + */ +static int at32_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct at32_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; + + switch (div_id) { + case AT32_SSC_CMR_DIV: + /* + * The same master clock divider is used for both + * transmit and receive, so if a value has already + * been set, it must match this value + */ + if (ssc_p->cmr_div == 0) + ssc_p->cmr_div = div; + else if (div != ssc_p->cmr_div) + return -EBUSY; + break; + + case AT32_SSC_TCMR_PERIOD: + ssc_p->tcmr_period = div; + break; + + case AT32_SSC_RCMR_PERIOD: + ssc_p->rcmr_period = div; + break; + + default: + return -EINVAL; + } + + return 0; +} + + + +/* + * Configure the SSC + */ +static int at32_ssc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int id = rtd->dai->cpu_dai->id; + struct at32_ssc_info *ssc_p = &ssc_info[id]; + struct at32_pcm_dma_params *dma_params; + int channels, bits; + u32 tfmr, rfmr, tcmr, rcmr; + int start_event; + int ret; + + + /* + * Currently, there is only one set of dma_params for each direction. + * If more are added, this code will have to be changed to select + * the proper set + */ + dma_params = &ssc_dma_params[id][substream->stream]; + dma_params->ssc = ssc_p->ssc; + dma_params->substream = substream; + + ssc_p->dma_params[substream->stream] = dma_params; + + + /* + * The cpu_dai->dma_data field is only used to communicate the + * appropriate DMA parameters to the PCM driver's hw_params() + * function. It should not be used for other purposes as it + * is common to all substreams. + */ + rtd->dai->cpu_dai->dma_data = dma_params; + + channels = params_channels(params); + + + /* + * Determine sample size in bits and the PDC increment + */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + bits = 8; + dma_params->pdc_xfer_size = 1; + break; + + case SNDRV_PCM_FORMAT_S16: + bits = 16; + dma_params->pdc_xfer_size = 2; + break; + + case SNDRV_PCM_FORMAT_S24: + bits = 24; + dma_params->pdc_xfer_size = 4; + break; + + case SNDRV_PCM_FORMAT_S32: + bits = 32; + dma_params->pdc_xfer_size = 4; + break; + + default: + pr_warning("at32-ssc: Unsupported PCM format %d", + params_format(params)); + return -EINVAL; + } + pr_debug("at32-ssc: bits = %d, pdc_xfer_size = %d, channels = %d\n", + bits, dma_params->pdc_xfer_size, channels); + + + /* + * The SSC only supports up to 16-bit samples in I2S format, due + * to the size of the Frame Mode Register FSLEN field. + */ + if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S) + if (bits > 16) { + pr_warning("at32-ssc: " + "sample size %d is too large for I2S\n", + bits); + return -EINVAL; + } + + + /* + * Compute the SSC register settings + */ + switch (ssc_p->daifmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_MASTER_MASK)) { + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS: + /* + * I2S format, SSC provides BCLK and LRS clocks. + * + * The SSC transmit and receive clocks are generated from the + * MCK divider, and the BCLK signal is output on the SSC TK line + */ + pr_debug("at32-ssc: SSC mode is I2S BCLK / FRAME master\n"); + rcmr = (SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period) | + SSC_BF(RCMR_STTDLY, START_DELAY) | + SSC_BF(RCMR_START, SSC_START_FALLING_RF) | + SSC_BF(RCMR_CKI, SSC_CKI_RISING) | + SSC_BF(RCMR_CKO, SSC_CKO_NONE) | + SSC_BF(RCMR_CKS, SSC_CKS_DIV)); + + rfmr = (SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | + SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE) | + SSC_BF(RFMR_FSLEN, bits - 1) | + SSC_BF(RFMR_DATNB, channels - 1) | + SSC_BIT(RFMR_MSBF) | SSC_BF(RFMR_DATLEN, bits - 1)); + + tcmr = (SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period) | + SSC_BF(TCMR_STTDLY, START_DELAY) | + SSC_BF(TCMR_START, SSC_START_FALLING_RF) | + SSC_BF(TCMR_CKI, SSC_CKI_FALLING) | + SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS) | + SSC_BF(TCMR_CKS, SSC_CKS_DIV)); + + tfmr = (SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | + SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE) | + SSC_BF(TFMR_FSLEN, bits - 1) | + SSC_BF(TFMR_DATNB, channels - 1) | SSC_BIT(TFMR_MSBF) | + SSC_BF(TFMR_DATLEN, bits - 1)); + break; + + + case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM: + /* + * I2S format, CODEC supplies BCLK and LRC clock. + * + * The SSC transmit clock is obtained from the BCLK signal + * on the TK line, and the SSC receive clock is generated from + * the transmit clock. + * + * For single channel data, one sample is transferred on the + * falling edge of the LRC clock. For two channel data, one + * sample is transferred on both edges of the LRC clock. + */ + pr_debug("at32-ssc: SSC mode is I2S BCLK / FRAME slave\n"); + start_event = ((channels == 1) ? + SSC_START_FALLING_RF : SSC_START_EDGE_RF); + + rcmr = (SSC_BF(RCMR_STTDLY, START_DELAY) | + SSC_BF(RCMR_START, start_event) | + SSC_BF(RCMR_CKI, SSC_CKI_RISING) | + SSC_BF(RCMR_CKO, SSC_CKO_NONE) | + SSC_BF(RCMR_CKS, SSC_CKS_CLOCK)); + + rfmr = (SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | + SSC_BF(RFMR_FSOS, SSC_FSOS_NONE) | + SSC_BIT(RFMR_MSBF) | SSC_BF(RFMR_DATLEN, bits - 1)); + + tcmr = (SSC_BF(TCMR_STTDLY, START_DELAY) | + SSC_BF(TCMR_START, start_event) | + SSC_BF(TCMR_CKI, SSC_CKI_FALLING) | + SSC_BF(TCMR_CKO, SSC_CKO_NONE) | + SSC_BF(TCMR_CKS, SSC_CKS_PIN)); + + tfmr = (SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | + SSC_BF(TFMR_FSOS, SSC_FSOS_NONE) | + SSC_BIT(TFMR_MSBF) | SSC_BF(TFMR_DATLEN, bits - 1)); + break; + + + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS: + /* + * DSP/PCM Mode A format, SSC provides BCLK and LRC clocks. + * + * The SSC transmit and receive clocks are generated from the + * MCK divider, and the BCLK signal is output on the SSC TK line + */ + pr_debug("at32-ssc: SSC mode is DSP A BCLK / FRAME master\n"); + rcmr = (SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period) | + SSC_BF(RCMR_STTDLY, 1) | + SSC_BF(RCMR_START, SSC_START_RISING_RF) | + SSC_BF(RCMR_CKI, SSC_CKI_RISING) | + SSC_BF(RCMR_CKO, SSC_CKO_NONE) | + SSC_BF(RCMR_CKS, SSC_CKS_DIV)); + + rfmr = (SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | + SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE) | + SSC_BF(RFMR_DATNB, channels - 1) | + SSC_BIT(RFMR_MSBF) | SSC_BF(RFMR_DATLEN, bits - 1)); + + tcmr = (SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period) | + SSC_BF(TCMR_STTDLY, 1) | + SSC_BF(TCMR_START, SSC_START_RISING_RF) | + SSC_BF(TCMR_CKI, SSC_CKI_RISING) | + SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS) | + SSC_BF(TCMR_CKS, SSC_CKS_DIV)); + + tfmr = (SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) | + SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE) | + SSC_BF(TFMR_DATNB, channels - 1) | + SSC_BIT(TFMR_MSBF) | SSC_BF(TFMR_DATLEN, bits - 1)); + break; + + + case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM: + default: + pr_warning("at32-ssc: unsupported DAI format 0x%x\n", + ssc_p->daifmt); + return -EINVAL; + break; + } + pr_debug("at32-ssc: RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n", + rcmr, rfmr, tcmr, tfmr); + + + if (!ssc_p->initialized) { + /* enable peripheral clock */ + pr_debug("at32-ssc: Starting clock\n"); + clk_enable(ssc_p->ssc->clk); + + /* Reset the SSC and its PDC registers */ + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST)); + + ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0); + + ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0); + + ret = request_irq(ssc_p->ssc->irq, at32_ssc_interrupt, 0, + ssc_p->name, ssc_p); + if (ret < 0) { + pr_warning("at32-ssc: request irq failed (%d)\n", ret); + pr_debug("at32-ssc: Stopping clock\n"); + clk_disable(ssc_p->ssc->clk); + return ret; + } + + ssc_p->initialized = 1; + } + + /* Set SSC clock mode register */ + ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div); + + /* set receive clock mode and format */ + ssc_writel(ssc_p->ssc->regs, RCMR, rcmr); + ssc_writel(ssc_p->ssc->regs, RFMR, rfmr); + + /* set transmit clock mode and format */ + ssc_writel(ssc_p->ssc->regs, TCMR, tcmr); + ssc_writel(ssc_p->ssc->regs, TFMR, tfmr); + + pr_debug("at32-ssc: SSC initialized\n"); + return 0; +} + + + +static int at32_ssc_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct at32_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; + struct at32_pcm_dma_params *dma_params; + + dma_params = ssc_p->dma_params[substream->stream]; + + ssc_writel(dma_params->ssc->regs, CR, dma_params->mask->ssc_enable); + + return 0; +} + + + +#ifdef CONFIG_PM +static int at32_ssc_suspend(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + struct at32_ssc_info *ssc_p; + + if (!cpu_dai->active) + return 0; + + ssc_p = &ssc_info[cpu_dai->id]; + + /* Save the status register before disabling transmit and receive */ + ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR); + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS)); + + /* Save the current interrupt mask, then disable unmasked interrupts */ + ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR); + ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr); + + ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR); + ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR); + ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR); + ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR); + ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR); + + return 0; +} + + + +static int at32_ssc_resume(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + struct at32_ssc_info *ssc_p; + u32 cr; + + if (!cpu_dai->active) + return 0; + + ssc_p = &ssc_info[cpu_dai->id]; + + /* restore SSC register settings */ + ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr); + ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr); + ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr); + ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr); + ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr); + + /* re-enable interrupts */ + ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr); + + /* Re-enable recieve and transmit as appropriate */ + cr = 0; + cr |= + (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0; + cr |= + (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0; + ssc_writel(ssc_p->ssc->regs, CR, cr); + + return 0; +} +#else /* CONFIG_PM */ +# define at32_ssc_suspend NULL +# define at32_ssc_resume NULL +#endif /* CONFIG_PM */ + + +#define AT32_SSC_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 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + + +#define AT32_SSC_FORMATS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16 | \ + SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_S32) + + +struct snd_soc_dai at32_ssc_dai[NUM_SSC_DEVICES] = { + { + .name = "at32-ssc0", + .id = 0, + .type = SND_SOC_DAI_PCM, + .suspend = at32_ssc_suspend, + .resume = at32_ssc_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = AT32_SSC_RATES, + .formats = AT32_SSC_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = AT32_SSC_RATES, + .formats = AT32_SSC_FORMATS, + }, + .ops = { + .startup = at32_ssc_startup, + .shutdown = at32_ssc_shutdown, + .prepare = at32_ssc_prepare, + .hw_params = at32_ssc_hw_params, + }, + .dai_ops = { + .set_sysclk = at32_ssc_set_dai_sysclk, + .set_fmt = at32_ssc_set_dai_fmt, + .set_clkdiv = at32_ssc_set_dai_clkdiv, + }, + .private_data = &ssc_info[0], + }, + { + .name = "at32-ssc1", + .id = 1, + .type = SND_SOC_DAI_PCM, + .suspend = at32_ssc_suspend, + .resume = at32_ssc_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = AT32_SSC_RATES, + .formats = AT32_SSC_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = AT32_SSC_RATES, + .formats = AT32_SSC_FORMATS, + }, + .ops = { + .startup = at32_ssc_startup, + .shutdown = at32_ssc_shutdown, + .prepare = at32_ssc_prepare, + .hw_params = at32_ssc_hw_params, + }, + .dai_ops = { + .set_sysclk = at32_ssc_set_dai_sysclk, + .set_fmt = at32_ssc_set_dai_fmt, + .set_clkdiv = at32_ssc_set_dai_clkdiv, + }, + .private_data = &ssc_info[1], + }, + { + .name = "at32-ssc2", + .id = 2, + .type = SND_SOC_DAI_PCM, + .suspend = at32_ssc_suspend, + .resume = at32_ssc_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = AT32_SSC_RATES, + .formats = AT32_SSC_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = AT32_SSC_RATES, + .formats = AT32_SSC_FORMATS, + }, + .ops = { + .startup = at32_ssc_startup, + .shutdown = at32_ssc_shutdown, + .prepare = at32_ssc_prepare, + .hw_params = at32_ssc_hw_params, + }, + .dai_ops = { + .set_sysclk = at32_ssc_set_dai_sysclk, + .set_fmt = at32_ssc_set_dai_fmt, + .set_clkdiv = at32_ssc_set_dai_clkdiv, + }, + .private_data = &ssc_info[2], + }, +}; +EXPORT_SYMBOL_GPL(at32_ssc_dai); + + +MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>"); +MODULE_DESCRIPTION("AT32 SSC ASoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/at32/at32-ssc.h b/sound/soc/at32/at32-ssc.h new file mode 100644 index 000000000000..3c052dbbe460 --- /dev/null +++ b/sound/soc/at32/at32-ssc.h @@ -0,0 +1,59 @@ +/* sound/soc/at32/at32-ssc.h + * ASoC SSC interface for Atmel AT32 SoC + * + * Copyright (C) 2008 Long Range Systems + * Geoffrey Wossum <gwossum@acm.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SOUND_SOC_AT32_AT32_SSC_H +#define __SOUND_SOC_AT32_AT32_SSC_H __FILE__ + +#include <linux/types.h> +#include <linux/atmel-ssc.h> + +#include "at32-pcm.h" + + + +struct at32_ssc_state { + u32 ssc_cmr; + u32 ssc_rcmr; + u32 ssc_rfmr; + u32 ssc_tcmr; + u32 ssc_tfmr; + u32 ssc_sr; + u32 ssc_imr; +}; + + + +struct at32_ssc_info { + char *name; + struct ssc_device *ssc; + spinlock_t lock; /* lock for dir_mask */ + unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */ + unsigned short initialized; /* true if SSC has been initialized */ + unsigned short daifmt; + unsigned short cmr_div; + unsigned short tcmr_period; + unsigned short rcmr_period; + struct at32_pcm_dma_params *dma_params[2]; + struct at32_ssc_state ssc_state; +}; + + +/* SSC divider ids */ +#define AT32_SSC_CMR_DIV 0 /* MCK divider for BCLK */ +#define AT32_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */ +#define AT32_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */ + + +extern struct snd_soc_dai at32_ssc_dai[]; + + + +#endif /* __SOUND_SOC_AT32_AT32_SSC_H */ diff --git a/sound/soc/at32/playpaq_wm8510.c b/sound/soc/at32/playpaq_wm8510.c new file mode 100644 index 000000000000..98a2d5826a85 --- /dev/null +++ b/sound/soc/at32/playpaq_wm8510.c @@ -0,0 +1,519 @@ +/* sound/soc/at32/playpaq_wm8510.c + * ASoC machine driver for PlayPaq using WM8510 codec + * + * Copyright (C) 2008 Long Range Systems + * Geoffrey Wossum <gwossum@acm.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c + * + * NOTE: If you don't have the AT32 enhanced portmux configured (which + * isn't currently in the mainline or Atmel patched kernel), you will + * need to set the MCLK pin (PA30) to peripheral A in your board initialization + * code. Something like: + * at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0); + * + */ + +/* #define DEBUG */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <mach/at32ap700x.h> +#include <mach/portmux.h> + +#include "../codecs/wm8510.h" +#include "at32-pcm.h" +#include "at32-ssc.h" + + +/*-------------------------------------------------------------------------*\ + * constants +\*-------------------------------------------------------------------------*/ +#define MCLK_PIN GPIO_PIN_PA(30) +#define MCLK_PERIPH GPIO_PERIPH_A + + +/*-------------------------------------------------------------------------*\ + * data types +\*-------------------------------------------------------------------------*/ +/* SSC clocking data */ +struct ssc_clock_data { + /* CMR div */ + unsigned int cmr_div; + + /* Frame period (as needed by xCMR.PERIOD) */ + unsigned int period; + + /* The SSC clock rate these settings where calculated for */ + unsigned long ssc_rate; +}; + + +/*-------------------------------------------------------------------------*\ + * module data +\*-------------------------------------------------------------------------*/ +static struct clk *_gclk0; +static struct clk *_pll0; + +#define CODEC_CLK (_gclk0) + + +/*-------------------------------------------------------------------------*\ + * Sound SOC operations +\*-------------------------------------------------------------------------*/ +#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE +static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock( + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct at32_ssc_info *ssc_p = cpu_dai->private_data; + struct ssc_device *ssc = ssc_p->ssc; + struct ssc_clock_data cd; + unsigned int rate, width_bits, channels; + unsigned int bitrate, ssc_div; + unsigned actual_rate; + + + /* + * Figure out required bitrate + */ + rate = params_rate(params); + channels = params_channels(params); + width_bits = snd_pcm_format_physical_width(params_format(params)); + bitrate = rate * width_bits * channels; + + + /* + * Figure out required SSC divider and period for required bitrate + */ + cd.ssc_rate = clk_get_rate(ssc->clk); + ssc_div = cd.ssc_rate / bitrate; + cd.cmr_div = ssc_div / 2; + if (ssc_div & 1) { + /* round cmr_div up */ + cd.cmr_div++; + } + cd.period = width_bits - 1; + + + /* + * Find actual rate, compare to requested rate + */ + actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1)); + pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n", + rate, actual_rate); + + + return cd; +} +#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ + + + +static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct at32_ssc_info *ssc_p = cpu_dai->private_data; + struct ssc_device *ssc = ssc_p->ssc; + unsigned int pll_out = 0, bclk = 0, mclk_div = 0; + int ret; + + + /* Due to difficulties with getting the correct clocks from the AT32's + * PLL0, we're going to let the CODEC be in charge of all the clocks + */ +#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE + const unsigned int fmt = (SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); +#else + struct ssc_clock_data cd; + const unsigned int fmt = (SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); +#endif + + if (ssc == NULL) { + pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n"); + return -EINVAL; + } + + + /* + * Figure out PLL and BCLK dividers for WM8510 + */ + switch (params_rate(params)) { + case 48000: + pll_out = 12288000; + mclk_div = WM8510_MCLKDIV_1; + bclk = WM8510_BCLKDIV_8; + break; + + case 44100: + pll_out = 11289600; + mclk_div = WM8510_MCLKDIV_1; + bclk = WM8510_BCLKDIV_8; + break; + + case 22050: + pll_out = 11289600; + mclk_div = WM8510_MCLKDIV_2; + bclk = WM8510_BCLKDIV_8; + break; + + case 16000: + pll_out = 12288000; + mclk_div = WM8510_MCLKDIV_3; + bclk = WM8510_BCLKDIV_8; + break; + + case 11025: + pll_out = 11289600; + mclk_div = WM8510_MCLKDIV_4; + bclk = WM8510_BCLKDIV_8; + break; + + case 8000: + pll_out = 12288000; + mclk_div = WM8510_MCLKDIV_6; + bclk = WM8510_BCLKDIV_8; + break; + + default: + pr_warning("playpaq_wm8510: Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + + + /* + * set CPU and CODEC DAI configuration + */ + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + pr_warning("playpaq_wm8510: " + "Failed to set CODEC DAI format (%d)\n", + ret); + return ret; + } + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) { + pr_warning("playpaq_wm8510: " + "Failed to set CPU DAI format (%d)\n", + ret); + return ret; + } + + + /* + * Set CPU clock configuration + */ +#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE + cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai); + pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n", + cd.cmr_div, cd.period); + ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div); + if (ret < 0) { + pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n", + ret); + return ret; + } + ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD, + cd.period); + if (ret < 0) { + pr_warning("playpaq_wm8510: " + "Failed to set CPU transmit period (%d)\n", + ret); + return ret; + } +#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ + + + /* + * Set CODEC clock configuration + */ + pr_debug("playpaq_wm8510: " + "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n", + clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div); + + +#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk); + if (ret < 0) { + pr_warning + ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n", + ret); + return ret; + } +#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ + + + ret = snd_soc_dai_set_pll(codec_dai, 0, + clk_get_rate(CODEC_CLK), pll_out); + if (ret < 0) { + pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n", + ret); + return ret; + } + + + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div); + if (ret < 0) { + pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n", + ret); + return ret; + } + + + return 0; +} + + + +static struct snd_soc_ops playpaq_wm8510_ops = { + .hw_params = playpaq_wm8510_hw_params, +}; + + + +static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + + + +static const char *intercon[][3] = { + /* speaker connected to SPKOUT */ + {"Ext Spk", NULL, "SPKOUTP"}, + {"Ext Spk", NULL, "SPKOUTN"}, + + {"Mic Bias", NULL, "Int Mic"}, + {"MICN", NULL, "Mic Bias"}, + {"MICP", NULL, "Mic Bias"}, + + /* Terminator */ + {NULL, NULL, NULL}, +}; + + + +static int playpaq_wm8510_init(struct snd_soc_codec *codec) +{ + int i; + + /* + * Add DAPM widgets + */ + for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]); + + + + /* + * Setup audio path interconnects + */ + for (i = 0; intercon[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, + intercon[i][0], + intercon[i][1], intercon[i][2]); + } + + + /* always connected pins */ + snd_soc_dapm_enable_pin(codec, "Int Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + snd_soc_dapm_sync(codec); + + + + /* Make CSB show PLL rate */ + snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV, + WM8510_OPCLKDIV_1 | 4); + + return 0; +} + + + +static struct snd_soc_dai_link playpaq_wm8510_dai = { + .name = "WM8510", + .stream_name = "WM8510 PCM", + .cpu_dai = &at32_ssc_dai[0], + .codec_dai = &wm8510_dai, + .init = playpaq_wm8510_init, + .ops = &playpaq_wm8510_ops, +}; + + + +static struct snd_soc_machine snd_soc_machine_playpaq = { + .name = "LRS_PlayPaq_WM8510", + .dai_link = &playpaq_wm8510_dai, + .num_links = 1, +}; + + + +static struct wm8510_setup_data playpaq_wm8510_setup = { + .i2c_bus = 0, + .i2c_address = 0x1a, +}; + + + +static struct snd_soc_device playpaq_wm8510_snd_devdata = { + .machine = &snd_soc_machine_playpaq, + .platform = &at32_soc_platform, + .codec_dev = &soc_codec_dev_wm8510, + .codec_data = &playpaq_wm8510_setup, +}; + +static struct platform_device *playpaq_snd_device; + + +static int __init playpaq_asoc_init(void) +{ + int ret = 0; + struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data; + struct ssc_device *ssc = NULL; + + + /* + * Request SSC device + */ + ssc = ssc_request(0); + if (IS_ERR(ssc)) { + ret = PTR_ERR(ssc); + goto err_ssc; + } + ssc_p->ssc = ssc; + + + /* + * Configure MCLK for WM8510 + */ + _gclk0 = clk_get(NULL, "gclk0"); + if (IS_ERR(_gclk0)) { + _gclk0 = NULL; + goto err_gclk0; + } + _pll0 = clk_get(NULL, "pll0"); + if (IS_ERR(_pll0)) { + _pll0 = NULL; + goto err_pll0; + } + if (clk_set_parent(_gclk0, _pll0)) { + pr_warning("snd-soc-playpaq: " + "Failed to set PLL0 as parent for DAC clock\n"); + goto err_set_clk; + } + clk_set_rate(CODEC_CLK, 12000000); + clk_enable(CODEC_CLK); + +#if defined CONFIG_AT32_ENHANCED_PORTMUX + at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0); +#endif + + + /* + * Create and register platform device + */ + playpaq_snd_device = platform_device_alloc("soc-audio", 0); + if (playpaq_snd_device == NULL) { + ret = -ENOMEM; + goto err_device_alloc; + } + + platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata); + playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev; + + ret = platform_device_add(playpaq_snd_device); + if (ret) { + pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n", + ret); + goto err_device_add; + } + + return 0; + + +err_device_add: + if (playpaq_snd_device != NULL) { + platform_device_put(playpaq_snd_device); + playpaq_snd_device = NULL; + } +err_device_alloc: +err_set_clk: + if (_pll0 != NULL) { + clk_put(_pll0); + _pll0 = NULL; + } +err_pll0: + if (_gclk0 != NULL) { + clk_put(_gclk0); + _gclk0 = NULL; + } +err_gclk0: + ssc_free(ssc); +err_ssc: + return ret; +} + + +static void __exit playpaq_asoc_exit(void) +{ + struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data; + struct ssc_device *ssc; + + if (ssc_p != NULL) { + ssc = ssc_p->ssc; + if (ssc != NULL) + ssc_free(ssc); + ssc_p->ssc = NULL; + } + + if (_gclk0 != NULL) { + clk_put(_gclk0); + _gclk0 = NULL; + } + if (_pll0 != NULL) { + clk_put(_pll0); + _pll0 = NULL; + } + +#if defined CONFIG_AT32_ENHANCED_PORTMUX + at32_free_pin(MCLK_PIN); +#endif + + platform_device_unregister(playpaq_snd_device); + playpaq_snd_device = NULL; +} + +module_init(playpaq_asoc_init); +module_exit(playpaq_asoc_exit); + +MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>"); +MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/at91/Kconfig b/sound/soc/at91/Kconfig index 5cb93fd3a407..85a883299c2e 100644 --- a/sound/soc/at91/Kconfig +++ b/sound/soc/at91/Kconfig @@ -1,6 +1,6 @@ config SND_AT91_SOC tristate "SoC Audio for the Atmel AT91 System-on-Chip" - depends on ARCH_AT91 && SND_SOC + depends on ARCH_AT91 help Say Y or M if you want to add support for codecs attached to the AT91 SSC interface. You will also need @@ -8,20 +8,3 @@ config SND_AT91_SOC config SND_AT91_SOC_SSC tristate - -config SND_AT91_SOC_ETI_B1_WM8731 - tristate "SoC Audio support for WM8731-based Endrelia ETI-B1 boards" - depends on SND_AT91_SOC && (MACH_ETI_B1 || MACH_ETI_C1) - select SND_AT91_SOC_SSC - select SND_SOC_WM8731 - help - Say Y if you want to add support for SoC audio on WM8731-based - Endrelia Technologies Inc ETI-B1 or ETI-C1 boards. - -config SND_AT91_SOC_ETI_SLAVE - bool "Run codec in slave Mode on Endrelia boards" - depends on SND_AT91_SOC_ETI_B1_WM8731 - default n - help - Say Y if you want to run with the AT91 SSC generating the BCLK - and LRC signals on Endrelia boards. diff --git a/sound/soc/at91/Makefile b/sound/soc/at91/Makefile index f23da17cc328..b817f11df286 100644 --- a/sound/soc/at91/Makefile +++ b/sound/soc/at91/Makefile @@ -4,8 +4,3 @@ snd-soc-at91-ssc-objs := at91-ssc.o obj-$(CONFIG_SND_AT91_SOC) += snd-soc-at91.o obj-$(CONFIG_SND_AT91_SOC_SSC) += snd-soc-at91-ssc.o - -# AT91 Machine Support -snd-soc-eti-b1-wm8731-objs := eti_b1_wm8731.o - -obj-$(CONFIG_SND_AT91_SOC_ETI_B1_WM8731) += snd-soc-eti-b1-wm8731.o diff --git a/sound/soc/at91/at91-pcm.c b/sound/soc/at91/at91-pcm.c index ccac6bd2889c..7ab48bd25e4c 100644 --- a/sound/soc/at91/at91-pcm.c +++ b/sound/soc/at91/at91-pcm.c @@ -28,8 +28,8 @@ #include <sound/pcm_params.h> #include <sound/soc.h> -#include <asm/arch/hardware.h> -#include <asm/arch/at91_ssc.h> +#include <mach/hardware.h> +#include <mach/at91_ssc.h> #include "at91-pcm.h" @@ -318,7 +318,7 @@ static int at91_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, static u64 at91_pcm_dmamask = 0xffffffff; static int at91_pcm_new(struct snd_card *card, - struct snd_soc_codec_dai *dai, struct snd_pcm *pcm) + struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; @@ -367,7 +367,7 @@ static void at91_pcm_free_dma_buffers(struct snd_pcm *pcm) #ifdef CONFIG_PM static int at91_pcm_suspend(struct platform_device *pdev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = dai->runtime; struct at91_runtime_data *prtd; @@ -392,7 +392,7 @@ static int at91_pcm_suspend(struct platform_device *pdev, } static int at91_pcm_resume(struct platform_device *pdev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = dai->runtime; struct at91_runtime_data *prtd; diff --git a/sound/soc/at91/at91-pcm.h b/sound/soc/at91/at91-pcm.h index 58d0f00a07b2..e5aada2cb102 100644 --- a/sound/soc/at91/at91-pcm.h +++ b/sound/soc/at91/at91-pcm.h @@ -19,7 +19,7 @@ #ifndef _AT91_PCM_H #define _AT91_PCM_H -#include <asm/arch/hardware.h> +#include <mach/hardware.h> struct at91_ssc_periph { void __iomem *base; diff --git a/sound/soc/at91/at91-ssc.c b/sound/soc/at91/at91-ssc.c index bc35d00a38f8..1b61cc461261 100644 --- a/sound/soc/at91/at91-ssc.c +++ b/sound/soc/at91/at91-ssc.c @@ -5,7 +5,7 @@ * Endrelia Technologies Inc. * * Based on pxa2xx Platform drivers by - * Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -28,9 +28,9 @@ #include <sound/initval.h> #include <sound/soc.h> -#include <asm/arch/hardware.h> -#include <asm/arch/at91_pmc.h> -#include <asm/arch/at91_ssc.h> +#include <mach/hardware.h> +#include <mach/at91_pmc.h> +#include <mach/at91_ssc.h> #include "at91-pcm.h" #include "at91-ssc.h" @@ -41,7 +41,7 @@ #define DBG(x...) #endif -#if defined(CONFIG_ARCH_AT91SAM9260) +#if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20) #define NUM_SSC_DEVICES 1 #else #define NUM_SSC_DEVICES 3 @@ -281,7 +281,7 @@ static void at91_ssc_shutdown(struct snd_pcm_substream *substream) /* * Record the SSC system clock rate. */ -static int at91_ssc_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, +static int at91_ssc_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { /* @@ -303,7 +303,7 @@ static int at91_ssc_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, /* * Record the DAI format for use in hw_params(). */ -static int at91_ssc_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, +static int at91_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; @@ -315,7 +315,7 @@ static int at91_ssc_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, /* * Record SSC clock dividers for use in hw_params(). */ -static int at91_ssc_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai, +static int at91_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int div) { struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; @@ -408,7 +408,7 @@ static int at91_ssc_hw_params(struct snd_pcm_substream *substream, dma_params->pdc_xfer_size = 4; break; default: - printk(KERN_WARNING "at91-ssc: unsupported PCM format"); + printk(KERN_WARNING "at91-ssc: unsupported PCM format\n"); return -EINVAL; } @@ -634,7 +634,7 @@ static int at91_ssc_prepare(struct snd_pcm_substream *substream) #ifdef CONFIG_PM static int at91_ssc_suspend(struct platform_device *pdev, - struct snd_soc_cpu_dai *cpu_dai) + struct snd_soc_dai *cpu_dai) { struct at91_ssc_info *ssc_p; @@ -662,7 +662,7 @@ static int at91_ssc_suspend(struct platform_device *pdev, } static int at91_ssc_resume(struct platform_device *pdev, - struct snd_soc_cpu_dai *cpu_dai) + struct snd_soc_dai *cpu_dai) { struct at91_ssc_info *ssc_p; @@ -700,7 +700,7 @@ static int at91_ssc_resume(struct platform_device *pdev, #define AT91_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) -struct snd_soc_cpu_dai at91_ssc_dai[NUM_SSC_DEVICES] = { +struct snd_soc_dai at91_ssc_dai[NUM_SSC_DEVICES] = { { .name = "at91-ssc0", .id = 0, .type = SND_SOC_DAI_PCM, diff --git a/sound/soc/at91/at91-ssc.h b/sound/soc/at91/at91-ssc.h index b188f973df9f..6b7bf382d06f 100644 --- a/sound/soc/at91/at91-ssc.h +++ b/sound/soc/at91/at91-ssc.h @@ -21,7 +21,7 @@ #define AT91SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */ #define AT91SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */ -extern struct snd_soc_cpu_dai at91_ssc_dai[]; +extern struct snd_soc_dai at91_ssc_dai[]; #endif /* _AT91_SSC_H */ diff --git a/sound/soc/at91/eti_b1_wm8731.c b/sound/soc/at91/eti_b1_wm8731.c deleted file mode 100644 index 1347dcf3f80b..000000000000 --- a/sound/soc/at91/eti_b1_wm8731.c +++ /dev/null @@ -1,358 +0,0 @@ -/* - * eti_b1_wm8731 -- SoC audio for AT91RM9200-based Endrelia ETI_B1 board. - * - * Author: Frank Mandarino <fmandarino@endrelia.com> - * Endrelia Technologies Inc. - * Created: Mar 29, 2006 - * - * Based on corgi.c by: - * - * Copyright 2005 Wolfson Microelectronics PLC. - * Copyright 2005 Openedhand Ltd. - * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> - * Richard Purdie <richard@openedhand.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/version.h> -#include <linux/kernel.h> -#include <linux/clk.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> -#include <sound/soc-dapm.h> - -#include <asm/hardware.h> -#include <asm/arch/gpio.h> - -#include "../codecs/wm8731.h" -#include "at91-pcm.h" -#include "at91-ssc.h" - -#if 0 -#define DBG(x...) printk(KERN_INFO "eti_b1_wm8731: " x) -#else -#define DBG(x...) -#endif - -static struct clk *pck1_clk; -static struct clk *pllb_clk; - - -static int eti_b1_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; - int ret; - - /* cpu clock is the AT91 master clock sent to the SSC */ - ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, AT91_SYSCLK_MCK, - 60000000, SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* codec system clock is supplied by PCK1, set to 12MHz */ - ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8731_SYSCLK, - 12000000, SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* Start PCK1 clock. */ - clk_enable(pck1_clk); - DBG("pck1 started\n"); - - return 0; -} - -static void eti_b1_shutdown(struct snd_pcm_substream *substream) -{ - /* Stop PCK1 clock. */ - clk_disable(pck1_clk); - DBG("pck1 stopped\n"); -} - -static int eti_b1_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_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; - int ret; - -#ifdef CONFIG_SND_AT91_SOC_ETI_SLAVE - unsigned int rate; - int cmr_div, period; - - /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); - if (ret < 0) - return ret; - - /* set cpu DAI configuration */ - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); - if (ret < 0) - return ret; - - /* - * The SSC clock dividers depend on the sample rate. The CMR.DIV - * field divides the system master clock MCK to drive the SSC TK - * signal which provides the codec BCLK. The TCMR.PERIOD and - * RCMR.PERIOD fields further divide the BCLK signal to drive - * the SSC TF and RF signals which provide the codec DACLRC and - * ADCLRC clocks. - * - * The dividers were determined through trial and error, where a - * CMR.DIV value is chosen such that the resulting BCLK value is - * divisible, or almost divisible, by (2 * sample rate), and then - * the TCMR.PERIOD or RCMR.PERIOD is BCLK / (2 * sample rate) - 1. - */ - rate = params_rate(params); - - switch (rate) { - case 8000: - cmr_div = 25; /* BCLK = 60MHz/(2*25) = 1.2MHz */ - period = 74; /* LRC = BCLK/(2*(74+1)) = 8000Hz */ - break; - case 32000: - cmr_div = 7; /* BCLK = 60MHz/(2*7) ~= 4.28571428MHz */ - period = 66; /* LRC = BCLK/(2*(66+1)) = 31982.942Hz */ - break; - case 48000: - cmr_div = 13; /* BCLK = 60MHz/(2*13) ~= 2.3076923MHz */ - period = 23; /* LRC = BCLK/(2*(23+1)) = 48076.923Hz */ - break; - default: - printk(KERN_WARNING "unsupported rate %d on ETI-B1 board\n", rate); - return -EINVAL; - } - - /* set the MCK divider for BCLK */ - ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, AT91SSC_CMR_DIV, cmr_div); - if (ret < 0) - return ret; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - /* set the BCLK divider for DACLRC */ - ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, - AT91SSC_TCMR_PERIOD, period); - } else { - /* set the BCLK divider for ADCLRC */ - ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, - AT91SSC_RCMR_PERIOD, period); - } - if (ret < 0) - return ret; - -#else /* CONFIG_SND_AT91_SOC_ETI_SLAVE */ - /* - * Codec in Master Mode. - */ - - /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set cpu DAI configuration */ - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - -#endif /* CONFIG_SND_AT91_SOC_ETI_SLAVE */ - - return 0; -} - -static struct snd_soc_ops eti_b1_ops = { - .startup = eti_b1_startup, - .hw_params = eti_b1_hw_params, - .shutdown = eti_b1_shutdown, -}; - - -static const struct snd_soc_dapm_widget eti_b1_dapm_widgets[] = { - SND_SOC_DAPM_MIC("Int Mic", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), -}; - -static const char *intercon[][3] = { - - /* speaker connected to LHPOUT */ - {"Ext Spk", NULL, "LHPOUT"}, - - /* mic is connected to Mic Jack, with WM8731 Mic Bias */ - {"MICIN", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Int Mic"}, - - /* terminator */ - {NULL, NULL, NULL}, -}; - -/* - * Logic for a wm8731 as connected on a Endrelia ETI-B1 board. - */ -static int eti_b1_wm8731_init(struct snd_soc_codec *codec) -{ - int i; - - DBG("eti_b1_wm8731_init() called\n"); - - /* Add specific widgets */ - for(i = 0; i < ARRAY_SIZE(eti_b1_dapm_widgets); i++) { - snd_soc_dapm_new_control(codec, &eti_b1_dapm_widgets[i]); - } - - /* Set up specific audio path interconnects */ - for(i = 0; intercon[i][0] != NULL; i++) { - snd_soc_dapm_connect_input(codec, intercon[i][0], - intercon[i][1], intercon[i][2]); - } - - /* not connected */ - snd_soc_dapm_set_endpoint(codec, "RLINEIN", 0); - snd_soc_dapm_set_endpoint(codec, "LLINEIN", 0); - - /* always connected */ - snd_soc_dapm_set_endpoint(codec, "Int Mic", 1); - snd_soc_dapm_set_endpoint(codec, "Ext Spk", 1); - - snd_soc_dapm_sync_endpoints(codec); - - return 0; -} - -static struct snd_soc_dai_link eti_b1_dai = { - .name = "WM8731", - .stream_name = "WM8731 PCM", - .cpu_dai = &at91_ssc_dai[1], - .codec_dai = &wm8731_dai, - .init = eti_b1_wm8731_init, - .ops = &eti_b1_ops, -}; - -static struct snd_soc_machine snd_soc_machine_eti_b1 = { - .name = "ETI_B1_WM8731", - .dai_link = &eti_b1_dai, - .num_links = 1, -}; - -static struct wm8731_setup_data eti_b1_wm8731_setup = { - .i2c_address = 0x1a, -}; - -static struct snd_soc_device eti_b1_snd_devdata = { - .machine = &snd_soc_machine_eti_b1, - .platform = &at91_soc_platform, - .codec_dev = &soc_codec_dev_wm8731, - .codec_data = &eti_b1_wm8731_setup, -}; - -static struct platform_device *eti_b1_snd_device; - -static int __init eti_b1_init(void) -{ - int ret; - struct at91_ssc_periph *ssc = eti_b1_dai.cpu_dai->private_data; - - if (!request_mem_region(AT91RM9200_BASE_SSC1, SZ_16K, "soc-audio")) { - DBG("SSC1 memory region is busy\n"); - return -EBUSY; - } - - ssc->base = ioremap(AT91RM9200_BASE_SSC1, SZ_16K); - if (!ssc->base) { - DBG("SSC1 memory ioremap failed\n"); - ret = -ENOMEM; - goto fail_release_mem; - } - - ssc->pid = AT91RM9200_ID_SSC1; - - eti_b1_snd_device = platform_device_alloc("soc-audio", -1); - if (!eti_b1_snd_device) { - DBG("platform device allocation failed\n"); - ret = -ENOMEM; - goto fail_io_unmap; - } - - platform_set_drvdata(eti_b1_snd_device, &eti_b1_snd_devdata); - eti_b1_snd_devdata.dev = &eti_b1_snd_device->dev; - - ret = platform_device_add(eti_b1_snd_device); - if (ret) { - DBG("platform device add failed\n"); - platform_device_put(eti_b1_snd_device); - goto fail_io_unmap; - } - - at91_set_A_periph(AT91_PIN_PB6, 0); /* TF1 */ - at91_set_A_periph(AT91_PIN_PB7, 0); /* TK1 */ - at91_set_A_periph(AT91_PIN_PB8, 0); /* TD1 */ - at91_set_A_periph(AT91_PIN_PB9, 0); /* RD1 */ -/* at91_set_A_periph(AT91_PIN_PB10, 0);*/ /* RK1 */ - at91_set_A_periph(AT91_PIN_PB11, 0); /* RF1 */ - - /* - * Set PCK1 parent to PLLB and its rate to 12 Mhz. - */ - pllb_clk = clk_get(NULL, "pllb"); - pck1_clk = clk_get(NULL, "pck1"); - - clk_set_parent(pck1_clk, pllb_clk); - clk_set_rate(pck1_clk, 12000000); - - DBG("MCLK rate %luHz\n", clk_get_rate(pck1_clk)); - - /* assign the GPIO pin to PCK1 */ - at91_set_B_periph(AT91_PIN_PA24, 0); - -#ifdef CONFIG_SND_AT91_SOC_ETI_SLAVE - printk(KERN_INFO "eti_b1_wm8731: Codec in Slave Mode\n"); -#else - printk(KERN_INFO "eti_b1_wm8731: Codec in Master Mode\n"); -#endif - return ret; - -fail_io_unmap: - iounmap(ssc->base); -fail_release_mem: - release_mem_region(AT91RM9200_BASE_SSC1, SZ_16K); - return ret; -} - -static void __exit eti_b1_exit(void) -{ - struct at91_ssc_periph *ssc = eti_b1_dai.cpu_dai->private_data; - - clk_put(pck1_clk); - clk_put(pllb_clk); - - platform_device_unregister(eti_b1_snd_device); - - iounmap(ssc->base); - release_mem_region(AT91RM9200_BASE_SSC1, SZ_16K); -} - -module_init(eti_b1_init); -module_exit(eti_b1_exit); - -/* Module information */ -MODULE_AUTHOR("Frank Mandarino <fmandarino@endrelia.com>"); -MODULE_DESCRIPTION("ALSA SoC ETI-B1-WM8731"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig new file mode 100644 index 000000000000..410a893aa66b --- /dev/null +++ b/sound/soc/au1x/Kconfig @@ -0,0 +1,32 @@ +## +## Au1200/Au1550 PSC + DBDMA +## +config SND_SOC_AU1XPSC + tristate "SoC Audio for Au1200/Au1250/Au1550" + depends on SOC_AU1200 || SOC_AU1550 + help + This option enables support for the Programmable Serial + Controllers in AC97 and I2S mode, and the Descriptor-Based DMA + Controller (DBDMA) as found on the Au1200/Au1250/Au1550 SoC. + +config SND_SOC_AU1XPSC_I2S + tristate + +config SND_SOC_AU1XPSC_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + + +## +## Boards +## +config SND_SOC_SAMPLE_PSC_AC97 + tristate "Sample Au12x0/Au1550 PSC AC97 sound machine" + depends on SND_SOC_AU1XPSC + select SND_SOC_AU1XPSC_AC97 + select SND_SOC_AC97_CODEC + help + This is a sample AC97 sound machine for use in Au12x0/Au1550 + based systems which have audio on PSC1 (e.g. Db1200 demoboard). diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile new file mode 100644 index 000000000000..6c6950b8003a --- /dev/null +++ b/sound/soc/au1x/Makefile @@ -0,0 +1,13 @@ +# Au1200/Au1550 PSC audio +snd-soc-au1xpsc-dbdma-objs := dbdma2.o +snd-soc-au1xpsc-i2s-objs := psc-i2s.o +snd-soc-au1xpsc-ac97-objs := psc-ac97.o + +obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o +obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o +obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o + +# Boards +snd-soc-sample-ac97-objs := sample-ac97.o + +obj-$(CONFIG_SND_SOC_SAMPLE_PSC_AC97) += snd-soc-sample-ac97.o diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c new file mode 100644 index 000000000000..1466d9328800 --- /dev/null +++ b/sound/soc/au1x/dbdma2.c @@ -0,0 +1,421 @@ +/* + * Au12x0/Au1550 PSC ALSA ASoC audio support. + * + * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * Manuel Lauss <mano@roarinelk.homelinux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * DMA glue for Au1x-PSC audio. + * + * NOTE: all of these drivers can only work with a SINGLE instance + * of a PSC. Multiple independent audio devices are impossible + * with ASoC v1. + */ + + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1xxx_dbdma.h> +#include <asm/mach-au1x00/au1xxx_psc.h> + +#include "psc.h" + +/*#define PCM_DEBUG*/ + +#define MSG(x...) printk(KERN_INFO "au1xpsc_pcm: " x) +#ifdef PCM_DEBUG +#define DBG MSG +#else +#define DBG(x...) do {} while (0) +#endif + +struct au1xpsc_audio_dmadata { + /* DDMA control data */ + unsigned int ddma_id; /* DDMA direction ID for this PSC */ + u32 ddma_chan; /* DDMA context */ + + /* PCM context (for irq handlers) */ + struct snd_pcm_substream *substream; + unsigned long curr_period; /* current segment DDMA is working on */ + unsigned long q_period; /* queue period(s) */ + unsigned long dma_area; /* address of queued DMA area */ + unsigned long dma_area_s; /* start address of DMA area */ + unsigned long pos; /* current byte position being played */ + unsigned long periods; /* number of SG segments in total */ + unsigned long period_bytes; /* size in bytes of one SG segment */ + + /* runtime data */ + int msbits; +}; + +/* instance data. There can be only one, MacLeod!!!! */ +static struct au1xpsc_audio_dmadata *au1xpsc_audio_pcmdma[2]; + +/* + * These settings are somewhat okay, at least on my machine audio plays + * almost skip-free. Especially the 64kB buffer seems to help a LOT. + */ +#define AU1XPSC_PERIOD_MIN_BYTES 1024 +#define AU1XPSC_BUFFER_MIN_BYTES 65536 + +#define AU1XPSC_PCM_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | \ + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE | \ + 0) + +/* PCM hardware DMA capabilities - platform specific */ +static const struct snd_pcm_hardware au1xpsc_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = AU1XPSC_PCM_FMTS, + .period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES, + .period_bytes_max = 4096 * 1024 - 1, + .periods_min = 2, + .periods_max = 4096, /* 2 to as-much-as-you-like */ + .buffer_bytes_max = 4096 * 1024 - 1, + .fifo_size = 16, /* fifo entries of AC97/I2S PSC */ +}; + +static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd) +{ + au1xxx_dbdma_put_source_flags(cd->ddma_chan, + (void *)phys_to_virt(cd->dma_area), + cd->period_bytes, DDMA_FLAGS_IE); + + /* update next-to-queue period */ + ++cd->q_period; + cd->dma_area += cd->period_bytes; + if (cd->q_period >= cd->periods) { + cd->q_period = 0; + cd->dma_area = cd->dma_area_s; + } +} + +static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd) +{ + au1xxx_dbdma_put_dest_flags(cd->ddma_chan, + (void *)phys_to_virt(cd->dma_area), + cd->period_bytes, DDMA_FLAGS_IE); + + /* update next-to-queue period */ + ++cd->q_period; + cd->dma_area += cd->period_bytes; + if (cd->q_period >= cd->periods) { + cd->q_period = 0; + cd->dma_area = cd->dma_area_s; + } +} + +static void au1x_pcm_dmatx_cb(int irq, void *dev_id) +{ + struct au1xpsc_audio_dmadata *cd = dev_id; + + cd->pos += cd->period_bytes; + if (++cd->curr_period >= cd->periods) { + cd->pos = 0; + cd->curr_period = 0; + } + snd_pcm_period_elapsed(cd->substream); + au1x_pcm_queue_tx(cd); +} + +static void au1x_pcm_dmarx_cb(int irq, void *dev_id) +{ + struct au1xpsc_audio_dmadata *cd = dev_id; + + cd->pos += cd->period_bytes; + if (++cd->curr_period >= cd->periods) { + cd->pos = 0; + cd->curr_period = 0; + } + snd_pcm_period_elapsed(cd->substream); + au1x_pcm_queue_rx(cd); +} + +static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd) +{ + if (pcd->ddma_chan) { + au1xxx_dbdma_stop(pcd->ddma_chan); + au1xxx_dbdma_reset(pcd->ddma_chan); + au1xxx_dbdma_chan_free(pcd->ddma_chan); + pcd->ddma_chan = 0; + pcd->msbits = 0; + } +} + +/* in case of missing DMA ring or changed TX-source / RX-dest bit widths, + * allocate (or reallocate) a 2-descriptor DMA ring with bit depth according + * to ALSA-supplied sample depth. This is due to limitations in the dbdma api + * (cannot adjust source/dest widths of already allocated descriptor ring). + */ +static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd, + int stype, int msbits) +{ + /* DMA only in 8/16/32 bit widths */ + if (msbits == 24) + msbits = 32; + + /* check current config: correct bits and descriptors allocated? */ + if ((pcd->ddma_chan) && (msbits == pcd->msbits)) + goto out; /* all ok! */ + + au1x_pcm_dbdma_free(pcd); + + if (stype == PCM_RX) + pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id, + DSCR_CMD0_ALWAYS, + au1x_pcm_dmarx_cb, (void *)pcd); + else + pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS, + pcd->ddma_id, + au1x_pcm_dmatx_cb, (void *)pcd); + + if (!pcd->ddma_chan) + return -ENOMEM;; + + au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits); + au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2); + + pcd->msbits = msbits; + + au1xxx_dbdma_stop(pcd->ddma_chan); + au1xxx_dbdma_reset(pcd->ddma_chan); + +out: + return 0; +} + +static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct au1xpsc_audio_dmadata *pcd; + int stype, ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + goto out; + + stype = SUBSTREAM_TYPE(substream); + pcd = au1xpsc_audio_pcmdma[stype]; + + DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d " + "runtime->min_align %d\n", + (unsigned long)runtime->dma_area, + (unsigned long)runtime->dma_addr, runtime->dma_bytes, + runtime->min_align); + + DBG("bits %d frags %d frag_bytes %d is_rx %d\n", params->msbits, + params_periods(params), params_period_bytes(params), stype); + + ret = au1x_pcm_dbdma_realloc(pcd, stype, params->msbits); + if (ret) { + MSG("DDMA channel (re)alloc failed!\n"); + goto out; + } + + pcd->substream = substream; + pcd->period_bytes = params_period_bytes(params); + pcd->periods = params_periods(params); + pcd->dma_area_s = pcd->dma_area = (unsigned long)runtime->dma_addr; + pcd->q_period = 0; + pcd->curr_period = 0; + pcd->pos = 0; + + ret = 0; +out: + return ret; +} + +static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct au1xpsc_audio_dmadata *pcd = + au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]; + + au1xxx_dbdma_reset(pcd->ddma_chan); + + if (SUBSTREAM_TYPE(substream) == PCM_RX) { + au1x_pcm_queue_rx(pcd); + au1x_pcm_queue_rx(pcd); + } else { + au1x_pcm_queue_tx(pcd); + au1x_pcm_queue_tx(pcd); + } + + return 0; +} + +static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + u32 c = au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->ddma_chan; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + au1xxx_dbdma_start(c); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + au1xxx_dbdma_stop(c); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t +au1xpsc_pcm_pointer(struct snd_pcm_substream *substream) +{ + return bytes_to_frames(substream->runtime, + au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]->pos); +} + +static int au1xpsc_pcm_open(struct snd_pcm_substream *substream) +{ + snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware); + return 0; +} + +static int au1xpsc_pcm_close(struct snd_pcm_substream *substream) +{ + au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[SUBSTREAM_TYPE(substream)]); + return 0; +} + +struct snd_pcm_ops au1xpsc_pcm_ops = { + .open = au1xpsc_pcm_open, + .close = au1xpsc_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = au1xpsc_pcm_hw_params, + .hw_free = au1xpsc_pcm_hw_free, + .prepare = au1xpsc_pcm_prepare, + .trigger = au1xpsc_pcm_trigger, + .pointer = au1xpsc_pcm_pointer, +}; + +static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int au1xpsc_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1); + + return 0; +} + +static int au1xpsc_pcm_probe(struct platform_device *pdev) +{ + struct resource *r; + int ret; + + if (au1xpsc_audio_pcmdma[PCM_TX] || au1xpsc_audio_pcmdma[PCM_RX]) + return -EBUSY; + + /* TX DMA */ + au1xpsc_audio_pcmdma[PCM_TX] + = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL); + if (!au1xpsc_audio_pcmdma[PCM_TX]) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!r) { + ret = -ENODEV; + goto out1; + } + (au1xpsc_audio_pcmdma[PCM_TX])->ddma_id = r->start; + + /* RX DMA */ + au1xpsc_audio_pcmdma[PCM_RX] + = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL); + if (!au1xpsc_audio_pcmdma[PCM_RX]) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!r) { + ret = -ENODEV; + goto out2; + } + (au1xpsc_audio_pcmdma[PCM_RX])->ddma_id = r->start; + + return 0; + +out2: + kfree(au1xpsc_audio_pcmdma[PCM_RX]); + au1xpsc_audio_pcmdma[PCM_RX] = NULL; +out1: + kfree(au1xpsc_audio_pcmdma[PCM_TX]); + au1xpsc_audio_pcmdma[PCM_TX] = NULL; + return ret; +} + +static int au1xpsc_pcm_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < 2; i++) { + if (au1xpsc_audio_pcmdma[i]) { + au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[i]); + kfree(au1xpsc_audio_pcmdma[i]); + au1xpsc_audio_pcmdma[i] = NULL; + } + } + + return 0; +} + +/* au1xpsc audio platform */ +struct snd_soc_platform au1xpsc_soc_platform = { + .name = "au1xpsc-pcm-dbdma", + .probe = au1xpsc_pcm_probe, + .remove = au1xpsc_pcm_remove, + .pcm_ops = &au1xpsc_pcm_ops, + .pcm_new = au1xpsc_pcm_new, + .pcm_free = au1xpsc_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(au1xpsc_soc_platform); + +static int __init au1xpsc_audio_dbdma_init(void) +{ + au1xpsc_audio_pcmdma[PCM_TX] = NULL; + au1xpsc_audio_pcmdma[PCM_RX] = NULL; + return 0; +} + +static void __exit au1xpsc_audio_dbdma_exit(void) +{ +} + +module_init(au1xpsc_audio_dbdma_init); +module_exit(au1xpsc_audio_dbdma_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver"); +MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c new file mode 100644 index 000000000000..57facbad6825 --- /dev/null +++ b/sound/soc/au1x/psc-ac97.c @@ -0,0 +1,387 @@ +/* + * Au12x0/Au1550 PSC ALSA ASoC audio support. + * + * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * Manuel Lauss <mano@roarinelk.homelinux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Au1xxx-PSC AC97 glue. + * + * NOTE: all of these drivers can only work with a SINGLE instance + * of a PSC. Multiple independent audio devices are impossible + * with ASoC v1. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1xxx_psc.h> + +#include "psc.h" + +#define AC97_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_48000 + +#define AC97_FMTS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE) + +#define AC97PCR_START(stype) \ + ((stype) == PCM_TX ? PSC_AC97PCR_TS : PSC_AC97PCR_RS) +#define AC97PCR_STOP(stype) \ + ((stype) == PCM_TX ? PSC_AC97PCR_TP : PSC_AC97PCR_RP) +#define AC97PCR_CLRFIFO(stype) \ + ((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC) + +/* instance data. There can be only one, MacLeod!!!! */ +static struct au1xpsc_audio_data *au1xpsc_ac97_workdata; + +/* AC97 controller reads codec register */ +static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + /* FIXME */ + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; + unsigned short data, tmo; + + au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg), AC97_CDC(pscdata)); + au_sync(); + + tmo = 1000; + while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo) + udelay(2); + + if (!tmo) + data = 0xffff; + else + data = au_readl(AC97_CDC(pscdata)) & 0xffff; + + au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); + au_sync(); + + return data; +} + +/* AC97 controller writes to codec register */ +static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + /* FIXME */ + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; + unsigned int tmo; + + au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff), AC97_CDC(pscdata)); + au_sync(); + tmo = 1000; + while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo) + au_sync(); + + au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); + au_sync(); +} + +/* AC97 controller asserts a warm reset */ +static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + /* FIXME */ + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; + + au_writel(PSC_AC97RST_SNC, AC97_RST(pscdata)); + au_sync(); + msleep(10); + au_writel(0, AC97_RST(pscdata)); + au_sync(); +} + +static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + /* FIXME */ + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; + int i; + + /* disable PSC during cold reset */ + au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); + au_sync(); + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata)); + au_sync(); + + /* issue cold reset */ + au_writel(PSC_AC97RST_RST, AC97_RST(pscdata)); + au_sync(); + msleep(500); + au_writel(0, AC97_RST(pscdata)); + au_sync(); + + /* enable PSC */ + au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata)); + au_sync(); + + /* wait for PSC to indicate it's ready */ + i = 100000; + while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i)) + au_sync(); + + if (i == 0) { + printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n"); + return; + } + + /* enable the ac97 function */ + au_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); + au_sync(); + + /* wait for AC97 core to become ready */ + i = 100000; + while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i)) + au_sync(); + if (i == 0) + printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n"); +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = au1xpsc_ac97_read, + .write = au1xpsc_ac97_write, + .reset = au1xpsc_ac97_cold_reset, + .warm_reset = au1xpsc_ac97_warm_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + /* FIXME */ + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; + unsigned long r, stat; + int chans, stype = SUBSTREAM_TYPE(substream); + + chans = params_channels(params); + + r = au_readl(AC97_CFG(pscdata)); + stat = au_readl(AC97_STAT(pscdata)); + + /* already active? */ + if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) { + /* reject parameters not currently set up */ + if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) || + (pscdata->rate != params_rate(params))) + return -EINVAL; + } else { + /* disable AC97 device controller first */ + au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); + au_sync(); + + /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */ + r &= ~PSC_AC97CFG_LEN_MASK; + r |= PSC_AC97CFG_SET_LEN(params->msbits); + + /* channels: enable slots for front L/R channel */ + if (stype == PCM_TX) { + r &= ~PSC_AC97CFG_TXSLOT_MASK; + r |= PSC_AC97CFG_TXSLOT_ENA(3); + r |= PSC_AC97CFG_TXSLOT_ENA(4); + } else { + r &= ~PSC_AC97CFG_RXSLOT_MASK; + r |= PSC_AC97CFG_RXSLOT_ENA(3); + r |= PSC_AC97CFG_RXSLOT_ENA(4); + } + + /* finally enable the AC97 controller again */ + au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); + au_sync(); + + pscdata->cfg = r; + pscdata->rate = params_rate(params); + } + + return 0; +} + +static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + /* FIXME */ + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; + int ret, stype = SUBSTREAM_TYPE(substream); + + ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + au_writel(AC97PCR_START(stype), AC97_PCR(pscdata)); + au_sync(); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata)); + au_sync(); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static int au1xpsc_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret; + struct resource *r; + unsigned long sel; + + if (au1xpsc_ac97_workdata) + return -EBUSY; + + au1xpsc_ac97_workdata = + kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL); + if (!au1xpsc_ac97_workdata) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENODEV; + goto out0; + } + + ret = -EBUSY; + au1xpsc_ac97_workdata->ioarea = + request_mem_region(r->start, r->end - r->start + 1, + "au1xpsc_ac97"); + if (!au1xpsc_ac97_workdata->ioarea) + goto out0; + + au1xpsc_ac97_workdata->mmio = ioremap(r->start, 0xffff); + if (!au1xpsc_ac97_workdata->mmio) + goto out1; + + /* configuration: max dma trigger threshold, enable ac97 */ + au1xpsc_ac97_workdata->cfg = PSC_AC97CFG_RT_FIFO8 | + PSC_AC97CFG_TT_FIFO8 | + PSC_AC97CFG_DE_ENABLE; + + /* preserve PSC clock source set up by platform (dev.platform_data + * is already occupied by soc layer) + */ + sel = au_readl(PSC_SEL(au1xpsc_ac97_workdata)) & PSC_SEL_CLK_MASK; + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); + au_sync(); + au_writel(0, PSC_SEL(au1xpsc_ac97_workdata)); + au_sync(); + au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(au1xpsc_ac97_workdata)); + au_sync(); + /* next up: cold reset. Dont check for PSC-ready now since + * there may not be any codec clock yet. + */ + + return 0; + +out1: + release_resource(au1xpsc_ac97_workdata->ioarea); + kfree(au1xpsc_ac97_workdata->ioarea); +out0: + kfree(au1xpsc_ac97_workdata); + au1xpsc_ac97_workdata = NULL; + return ret; +} + +static void au1xpsc_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + /* disable PSC completely */ + au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); + au_sync(); + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); + au_sync(); + + iounmap(au1xpsc_ac97_workdata->mmio); + release_resource(au1xpsc_ac97_workdata->ioarea); + kfree(au1xpsc_ac97_workdata->ioarea); + kfree(au1xpsc_ac97_workdata); + au1xpsc_ac97_workdata = NULL; +} + +static int au1xpsc_ac97_suspend(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + /* save interesting registers and disable PSC */ + au1xpsc_ac97_workdata->pm[0] = + au_readl(PSC_SEL(au1xpsc_ac97_workdata)); + + au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); + au_sync(); + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); + au_sync(); + + return 0; +} + +static int au1xpsc_ac97_resume(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + /* restore PSC clock config */ + au_writel(au1xpsc_ac97_workdata->pm[0] | PSC_SEL_PS_AC97MODE, + PSC_SEL(au1xpsc_ac97_workdata)); + au_sync(); + + /* after this point the ac97 core will cold-reset the codec. + * During cold-reset the PSC is reinitialized and the last + * configuration set up in hw_params() is restored. + */ + return 0; +} + +struct snd_soc_dai au1xpsc_ac97_dai = { + .name = "au1xpsc_ac97", + .type = SND_SOC_DAI_AC97, + .probe = au1xpsc_ac97_probe, + .remove = au1xpsc_ac97_remove, + .suspend = au1xpsc_ac97_suspend, + .resume = au1xpsc_ac97_resume, + .playback = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .rates = AC97_RATES, + .formats = AC97_FMTS, + .channels_min = 2, + .channels_max = 2, + }, + .ops = { + .trigger = au1xpsc_ac97_trigger, + .hw_params = au1xpsc_ac97_hw_params, + }, +}; +EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai); + +static int __init au1xpsc_ac97_init(void) +{ + au1xpsc_ac97_workdata = NULL; + return 0; +} + +static void __exit au1xpsc_ac97_exit(void) +{ +} + +module_init(au1xpsc_ac97_init); +module_exit(au1xpsc_ac97_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver"); +MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c new file mode 100644 index 000000000000..9384702c7ebd --- /dev/null +++ b/sound/soc/au1x/psc-i2s.c @@ -0,0 +1,414 @@ +/* + * Au12x0/Au1550 PSC ALSA ASoC audio support. + * + * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * Manuel Lauss <mano@roarinelk.homelinux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Au1xxx-PSC I2S glue. + * + * NOTE: all of these drivers can only work with a SINGLE instance + * of a PSC. Multiple independent audio devices are impossible + * with ASoC v1. + * NOTE: so far only PSC slave mode (bit- and frameclock) is supported. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1xxx_psc.h> + +#include "psc.h" + +/* supported I2S DAI hardware formats */ +#define AU1XPSC_I2S_DAIFMT \ + (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \ + SND_SOC_DAIFMT_NB_NF) + +/* supported I2S direction */ +#define AU1XPSC_I2S_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AU1XPSC_I2S_RATES \ + SNDRV_PCM_RATE_8000_192000 + +#define AU1XPSC_I2S_FMTS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +#define I2SSTAT_BUSY(stype) \ + ((stype) == PCM_TX ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB) +#define I2SPCR_START(stype) \ + ((stype) == PCM_TX ? PSC_I2SPCR_TS : PSC_I2SPCR_RS) +#define I2SPCR_STOP(stype) \ + ((stype) == PCM_TX ? PSC_I2SPCR_TP : PSC_I2SPCR_RP) +#define I2SPCR_CLRFIFO(stype) \ + ((stype) == PCM_TX ? PSC_I2SPCR_TC : PSC_I2SPCR_RC) + + +/* instance data. There can be only one, MacLeod!!!! */ +static struct au1xpsc_audio_data *au1xpsc_i2s_workdata; + +static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata; + unsigned long ct; + int ret; + + ret = -EINVAL; + + ct = pscdata->cfg; + + ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ); /* left-justified */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ct |= PSC_I2SCFG_XM; /* enable I2S mode */ + break; + case SND_SOC_DAIFMT_MSB: + break; + case SND_SOC_DAIFMT_LSB: + ct |= PSC_I2SCFG_MLJ; /* LSB (right-) justified */ + break; + default: + goto out; + } + + ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI); /* IB-IF */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI; + break; + case SND_SOC_DAIFMT_NB_IF: + ct |= PSC_I2SCFG_BI; + break; + case SND_SOC_DAIFMT_IB_NF: + ct |= PSC_I2SCFG_WI; + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + goto out; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */ + ct |= PSC_I2SCFG_MS; /* PSC I2S slave mode */ + break; + case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ + ct &= ~PSC_I2SCFG_MS; /* PSC I2S Master mode */ + break; + default: + goto out; + } + + pscdata->cfg = ct; + ret = 0; +out: + return ret; +} + +static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata; + + int cfgbits; + unsigned long stat; + + /* check if the PSC is already streaming data */ + stat = au_readl(I2S_STAT(pscdata)); + if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) { + /* reject parameters not currently set up in hardware */ + cfgbits = au_readl(I2S_CFG(pscdata)); + if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) || + (params_rate(params) != pscdata->rate)) + return -EINVAL; + } else { + /* set sample bitdepth */ + pscdata->cfg &= ~(0x1f << 4); + pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits); + /* remember current rate for other stream */ + pscdata->rate = params_rate(params); + } + return 0; +} + +/* Configure PSC late: on my devel systems the codec is I2S master and + * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit. ASoC + * uses aggressive PM and switches the codec off when it is not in use + * which also means the PSC unit doesn't get any clocks and is therefore + * dead. That's why this chunk here gets called from the trigger callback + * because I can be reasonably certain the codec is driving the clocks. + */ +static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata) +{ + unsigned long tmo; + + /* bring PSC out of sleep, and configure I2S unit */ + au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata)); + au_sync(); + + tmo = 1000000; + while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo) + tmo--; + + if (!tmo) + goto psc_err; + + au_writel(0, I2S_CFG(pscdata)); + au_sync(); + au_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata)); + au_sync(); + + /* wait for I2S controller to become ready */ + tmo = 1000000; + while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo) + tmo--; + + if (tmo) + return 0; + +psc_err: + au_writel(0, I2S_CFG(pscdata)); + au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata)); + au_sync(); + return -ETIMEDOUT; +} + +static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype) +{ + unsigned long tmo, stat; + int ret; + + ret = 0; + + /* if both TX and RX are idle, configure the PSC */ + stat = au_readl(I2S_STAT(pscdata)); + if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) { + ret = au1xpsc_i2s_configure(pscdata); + if (ret) + goto out; + } + + au_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata)); + au_sync(); + au_writel(I2SPCR_START(stype), I2S_PCR(pscdata)); + au_sync(); + + /* wait for start confirmation */ + tmo = 1000000; + while (!(au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo) + tmo--; + + if (!tmo) { + au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata)); + au_sync(); + ret = -ETIMEDOUT; + } +out: + return ret; +} + +static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype) +{ + unsigned long tmo, stat; + + au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata)); + au_sync(); + + /* wait for stop confirmation */ + tmo = 1000000; + while ((au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo) + tmo--; + + /* if both TX and RX are idle, disable PSC */ + stat = au_readl(I2S_STAT(pscdata)); + if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) { + au_writel(0, I2S_CFG(pscdata)); + au_sync(); + au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata)); + au_sync(); + } + return 0; +} + +static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata; + int ret, stype = SUBSTREAM_TYPE(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + ret = au1xpsc_i2s_start(pscdata, stype); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + ret = au1xpsc_i2s_stop(pscdata, stype); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static int au1xpsc_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct resource *r; + unsigned long sel; + int ret; + + if (au1xpsc_i2s_workdata) + return -EBUSY; + + au1xpsc_i2s_workdata = + kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL); + if (!au1xpsc_i2s_workdata) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENODEV; + goto out0; + } + + ret = -EBUSY; + au1xpsc_i2s_workdata->ioarea = + request_mem_region(r->start, r->end - r->start + 1, + "au1xpsc_i2s"); + if (!au1xpsc_i2s_workdata->ioarea) + goto out0; + + au1xpsc_i2s_workdata->mmio = ioremap(r->start, 0xffff); + if (!au1xpsc_i2s_workdata->mmio) + goto out1; + + /* preserve PSC clock source set up by platform (dev.platform_data + * is already occupied by soc layer) + */ + sel = au_readl(PSC_SEL(au1xpsc_i2s_workdata)) & PSC_SEL_CLK_MASK; + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); + au_sync(); + au_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(au1xpsc_i2s_workdata)); + au_writel(0, I2S_CFG(au1xpsc_i2s_workdata)); + au_sync(); + + /* preconfigure: set max rx/tx fifo depths */ + au1xpsc_i2s_workdata->cfg |= + PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8; + + /* don't wait for I2S core to become ready now; clocks may not + * be running yet; depending on clock input for PSC a wait might + * time out. + */ + + return 0; + +out1: + release_resource(au1xpsc_i2s_workdata->ioarea); + kfree(au1xpsc_i2s_workdata->ioarea); +out0: + kfree(au1xpsc_i2s_workdata); + au1xpsc_i2s_workdata = NULL; + return ret; +} + +static void au1xpsc_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + au_writel(0, I2S_CFG(au1xpsc_i2s_workdata)); + au_sync(); + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); + au_sync(); + + iounmap(au1xpsc_i2s_workdata->mmio); + release_resource(au1xpsc_i2s_workdata->ioarea); + kfree(au1xpsc_i2s_workdata->ioarea); + kfree(au1xpsc_i2s_workdata); + au1xpsc_i2s_workdata = NULL; +} + +static int au1xpsc_i2s_suspend(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + /* save interesting register and disable PSC */ + au1xpsc_i2s_workdata->pm[0] = + au_readl(PSC_SEL(au1xpsc_i2s_workdata)); + + au_writel(0, I2S_CFG(au1xpsc_i2s_workdata)); + au_sync(); + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); + au_sync(); + + return 0; +} + +static int au1xpsc_i2s_resume(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + /* select I2S mode and PSC clock */ + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); + au_sync(); + au_writel(0, PSC_SEL(au1xpsc_i2s_workdata)); + au_sync(); + au_writel(au1xpsc_i2s_workdata->pm[0], + PSC_SEL(au1xpsc_i2s_workdata)); + au_sync(); + + return 0; +} + +struct snd_soc_dai au1xpsc_i2s_dai = { + .name = "au1xpsc_i2s", + .type = SND_SOC_DAI_I2S, + .probe = au1xpsc_i2s_probe, + .remove = au1xpsc_i2s_remove, + .suspend = au1xpsc_i2s_suspend, + .resume = au1xpsc_i2s_resume, + .playback = { + .rates = AU1XPSC_I2S_RATES, + .formats = AU1XPSC_I2S_FMTS, + .channels_min = 2, + .channels_max = 8, /* 2 without external help */ + }, + .capture = { + .rates = AU1XPSC_I2S_RATES, + .formats = AU1XPSC_I2S_FMTS, + .channels_min = 2, + .channels_max = 8, /* 2 without external help */ + }, + .ops = { + .trigger = au1xpsc_i2s_trigger, + .hw_params = au1xpsc_i2s_hw_params, + }, + .dai_ops = { + .set_fmt = au1xpsc_i2s_set_fmt, + }, +}; +EXPORT_SYMBOL(au1xpsc_i2s_dai); + +static int __init au1xpsc_i2s_init(void) +{ + au1xpsc_i2s_workdata = NULL; + return 0; +} + +static void __exit au1xpsc_i2s_exit(void) +{ +} + +module_init(au1xpsc_i2s_init); +module_exit(au1xpsc_i2s_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver"); +MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h new file mode 100644 index 000000000000..8fdb1a04a07b --- /dev/null +++ b/sound/soc/au1x/psc.h @@ -0,0 +1,53 @@ +/* + * Au12x0/Au1550 PSC ALSA ASoC audio support. + * + * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * Manuel Lauss <mano@roarinelk.homelinux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * NOTE: all of these drivers can only work with a SINGLE instance + * of a PSC. Multiple independent audio devices are impossible + * with ASoC v1. + */ + +#ifndef _AU1X_PCM_H +#define _AU1X_PCM_H + +extern struct snd_soc_dai au1xpsc_ac97_dai; +extern struct snd_soc_dai au1xpsc_i2s_dai; +extern struct snd_soc_platform au1xpsc_soc_platform; +extern struct snd_ac97_bus_ops soc_ac97_ops; + +struct au1xpsc_audio_data { + void __iomem *mmio; + + unsigned long cfg; + unsigned long rate; + + unsigned long pm[2]; + struct resource *ioarea; +}; + +#define PCM_TX 0 +#define PCM_RX 1 + +#define SUBSTREAM_TYPE(substream) \ + ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX) + +/* easy access macros */ +#define PSC_CTRL(x) ((unsigned long)((x)->mmio) + PSC_CTRL_OFFSET) +#define PSC_SEL(x) ((unsigned long)((x)->mmio) + PSC_SEL_OFFSET) +#define I2S_STAT(x) ((unsigned long)((x)->mmio) + PSC_I2SSTAT_OFFSET) +#define I2S_CFG(x) ((unsigned long)((x)->mmio) + PSC_I2SCFG_OFFSET) +#define I2S_PCR(x) ((unsigned long)((x)->mmio) + PSC_I2SPCR_OFFSET) +#define AC97_CFG(x) ((unsigned long)((x)->mmio) + PSC_AC97CFG_OFFSET) +#define AC97_CDC(x) ((unsigned long)((x)->mmio) + PSC_AC97CDC_OFFSET) +#define AC97_EVNT(x) ((unsigned long)((x)->mmio) + PSC_AC97EVNT_OFFSET) +#define AC97_PCR(x) ((unsigned long)((x)->mmio) + PSC_AC97PCR_OFFSET) +#define AC97_RST(x) ((unsigned long)((x)->mmio) + PSC_AC97RST_OFFSET) +#define AC97_STAT(x) ((unsigned long)((x)->mmio) + PSC_AC97STAT_OFFSET) + +#endif diff --git a/sound/soc/au1x/sample-ac97.c b/sound/soc/au1x/sample-ac97.c new file mode 100644 index 000000000000..f75ae7f62c3d --- /dev/null +++ b/sound/soc/au1x/sample-ac97.c @@ -0,0 +1,144 @@ +/* + * Sample Au12x0/Au1550 PSC AC97 sound machine. + * + * Copyright (c) 2007-2008 Manuel Lauss <mano@roarinelk.homelinux.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms outlined in the file COPYING at the root of this + * source archive. + * + * This is a very generic AC97 sound machine driver for boards which + * have (AC97) audio at PSC1 (e.g. DB1200 demoboards). + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1xxx_psc.h> +#include <asm/mach-au1x00/au1xxx_dbdma.h> + +#include "../codecs/ac97.h" +#include "psc.h" + +static int au1xpsc_sample_ac97_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_sync(codec); + return 0; +} + +static struct snd_soc_dai_link au1xpsc_sample_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &au1xpsc_ac97_dai, /* see psc-ac97.c */ + .codec_dai = &ac97_dai, /* see codecs/ac97.c */ + .init = au1xpsc_sample_ac97_init, + .ops = NULL, +}; + +static struct snd_soc_machine au1xpsc_sample_ac97_machine = { + .name = "Au1xxx PSC AC97 Audio", + .dai_link = &au1xpsc_sample_ac97_dai, + .num_links = 1, +}; + +static struct snd_soc_device au1xpsc_sample_ac97_devdata = { + .machine = &au1xpsc_sample_ac97_machine, + .platform = &au1xpsc_soc_platform, /* see dbdma2.c */ + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct resource au1xpsc_psc1_res[] = { + [0] = { + .start = CPHYSADDR(PSC1_BASE_ADDR), + .end = CPHYSADDR(PSC1_BASE_ADDR) + 0x000fffff, + .flags = IORESOURCE_MEM, + }, + [1] = { +#ifdef CONFIG_SOC_AU1200 + .start = AU1200_PSC1_INT, + .end = AU1200_PSC1_INT, +#elif defined(CONFIG_SOC_AU1550) + .start = AU1550_PSC1_INT, + .end = AU1550_PSC1_INT, +#endif + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = DSCR_CMD0_PSC1_TX, + .end = DSCR_CMD0_PSC1_TX, + .flags = IORESOURCE_DMA, + }, + [3] = { + .start = DSCR_CMD0_PSC1_RX, + .end = DSCR_CMD0_PSC1_RX, + .flags = IORESOURCE_DMA, + }, +}; + +static struct platform_device *au1xpsc_sample_ac97_dev; + +static int __init au1xpsc_sample_ac97_load(void) +{ + int ret; + +#ifdef CONFIG_SOC_AU1200 + unsigned long io; + + /* modify sys_pinfunc for AC97 on PSC1 */ + io = au_readl(SYS_PINFUNC); + io |= SYS_PINFUNC_P1C; + io &= ~(SYS_PINFUNC_P1A | SYS_PINFUNC_P1B); + au_writel(io, SYS_PINFUNC); + au_sync(); +#endif + + ret = -ENOMEM; + + /* setup PSC clock source for AC97 part: external clock provided + * by codec. The psc-ac97.c driver depends on this setting! + */ + au_writel(PSC_SEL_CLK_SERCLK, PSC1_BASE_ADDR + PSC_SEL_OFFSET); + au_sync(); + + au1xpsc_sample_ac97_dev = platform_device_alloc("soc-audio", -1); + if (!au1xpsc_sample_ac97_dev) + goto out; + + au1xpsc_sample_ac97_dev->resource = + kmemdup(au1xpsc_psc1_res, sizeof(struct resource) * + ARRAY_SIZE(au1xpsc_psc1_res), GFP_KERNEL); + au1xpsc_sample_ac97_dev->num_resources = ARRAY_SIZE(au1xpsc_psc1_res); + au1xpsc_sample_ac97_dev->id = 1; + + platform_set_drvdata(au1xpsc_sample_ac97_dev, + &au1xpsc_sample_ac97_devdata); + au1xpsc_sample_ac97_devdata.dev = &au1xpsc_sample_ac97_dev->dev; + ret = platform_device_add(au1xpsc_sample_ac97_dev); + + if (ret) { + platform_device_put(au1xpsc_sample_ac97_dev); + au1xpsc_sample_ac97_dev = NULL; + } + +out: + return ret; +} + +static void __exit au1xpsc_sample_ac97_exit(void) +{ + platform_device_unregister(au1xpsc_sample_ac97_dev); +} + +module_init(au1xpsc_sample_ac97_load); +module_exit(au1xpsc_sample_ac97_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1xxx PSC sample AC97 machine"); +MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig new file mode 100644 index 000000000000..dc006206f622 --- /dev/null +++ b/sound/soc/blackfin/Kconfig @@ -0,0 +1,101 @@ +config SND_BF5XX_I2S + tristate "SoC I2S Audio for the ADI BF5xx chip" + depends on BLACKFIN && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the Blackfin SPORT (synchronous serial ports) interface in I2S + mode (supports single stereo In/Out). + You will also need to select the audio interfaces to support below. + +config SND_BF5XX_SOC_SSM2602 + tristate "SoC SSM2602 Audio support for BF52x ezkit" + depends on SND_BF5XX_I2S + select SND_BF5XX_SOC_I2S + select SND_SOC_SSM2602 + select I2C + select I2C_BLACKFIN_TWI + help + Say Y if you want to add support for SoC audio on BF527-EZKIT. + +config SND_BF5XX_SOC_AD73311 + tristate "SoC AD73311 Audio support for Blackfin" + depends on SND_BF5XX_I2S + select SND_BF5XX_SOC_I2S + select SND_SOC_AD73311 + help + Say Y if you want to add support for AD73311 codec on Blackfin. + +config SND_BFIN_AD73311_SE + int "PF pin for AD73311L Chip Select" + depends on SND_BF5XX_SOC_AD73311 + default 4 + help + Enter the GPIO used to control AD73311's SE pin. Acceptable + values are 0 to 7 + +config SND_BF5XX_AC97 + tristate "SoC AC97 Audio for the ADI BF5xx chip" + depends on BLACKFIN && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the Blackfin SPORT (synchronous serial ports) interface in slot 16 + mode (pseudo AC97 interface). + You will also need to select the audio interfaces to support below. + + Note: + AC97 codecs which do not implment the slot-16 mode will not function + properly with this driver. This driver is known to work with the + Analog Devices line of AC97 codecs. + +config SND_MMAP_SUPPORT + bool "Enable MMAP Support" + depends on SND_BF5XX_AC97 + default y + help + Say y if you want AC97 driver to support mmap mode. + We introduce an intermediate buffer to simulate mmap. + +config SND_BF5XX_SOC_SPORT + tristate + +config SND_BF5XX_SOC_I2S + tristate + select SND_BF5XX_SOC_SPORT + +config SND_BF5XX_SOC_AC97 + tristate + select AC97_BUS + select SND_SOC_AC97_BUS + select SND_BF5XX_SOC_SPORT + +config SND_BF5XX_SOC_AD1980 + tristate "SoC AD1980/1 Audio support for BF5xx" + depends on SND_BF5XX_AC97 + select SND_BF5XX_SOC_AC97 + select SND_SOC_AD1980 + help + Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT. + +config SND_BF5XX_SPORT_NUM + int "Set a SPORT for Sound chip" + depends on (SND_BF5XX_I2S || SND_BF5XX_AC97) + range 0 3 if BF54x + range 0 1 if (BF53x || BF561) + default 0 + help + Set the correct SPORT for sound chip. + +config SND_BF5XX_HAVE_COLD_RESET + bool "BOARD has COLD Reset GPIO" + depends on SND_BF5XX_AC97 + default y if BFIN548_EZKIT + default n if !BFIN548_EZKIT + +config SND_BF5XX_RESET_GPIO_NUM + int "Set a GPIO for cold reset" + depends on SND_BF5XX_HAVE_COLD_RESET + range 0 159 + default 19 if BFIN548_EZKIT + default 5 if BFIN537_STAMP + help + Set the correct GPIO for RESET the sound chip. diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile new file mode 100644 index 000000000000..97bb37a6359c --- /dev/null +++ b/sound/soc/blackfin/Makefile @@ -0,0 +1,21 @@ +# Blackfin Platform Support +snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o +snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o +snd-soc-bf5xx-sport-objs := bf5xx-sport.o +snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o +snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o + +obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o +obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o +obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o +obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o +obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o + +# Blackfin Machine Support +snd-ad1980-objs := bf5xx-ad1980.o +snd-ssm2602-objs := bf5xx-ssm2602.o +snd-ad73311-objs := bf5xx-ad73311.o + +obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o +obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o +obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.c b/sound/soc/blackfin/bf5xx-ac97-pcm.c new file mode 100644 index 000000000000..25e50d2ea1ec --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97-pcm.c @@ -0,0 +1,457 @@ +/* + * File: sound/soc/blackfin/bf5xx-ac97-pcm.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: DMA Driver for AC97 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> + +#include "bf5xx-ac97-pcm.h" +#include "bf5xx-ac97.h" +#include "bf5xx-sport.h" + +#if defined(CONFIG_SND_MMAP_SUPPORT) +static void bf5xx_mmap_copy(struct snd_pcm_substream *substream, + snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + bf5xx_pcm_to_ac97( + (struct ac97_frame *)sport->tx_dma_buf + sport->tx_pos, + (__u32 *)runtime->dma_area + sport->tx_pos, count); + sport->tx_pos += runtime->period_size; + if (sport->tx_pos >= runtime->buffer_size) + sport->tx_pos %= runtime->buffer_size; + sport->tx_delay_pos = sport->tx_pos; + } else { + bf5xx_ac97_to_pcm( + (struct ac97_frame *)sport->rx_dma_buf + sport->rx_pos, + (__u32 *)runtime->dma_area + sport->rx_pos, count); + sport->rx_pos += runtime->period_size; + if (sport->rx_pos >= runtime->buffer_size) + sport->rx_pos %= runtime->buffer_size; + } +} +#endif + +static void bf5xx_dma_irq(void *data) +{ + struct snd_pcm_substream *pcm = data; +#if defined(CONFIG_SND_MMAP_SUPPORT) + struct snd_pcm_runtime *runtime = pcm->runtime; + struct sport_device *sport = runtime->private_data; + bf5xx_mmap_copy(pcm, runtime->period_size); + if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (sport->once == 0) { + snd_pcm_period_elapsed(pcm); + bf5xx_mmap_copy(pcm, runtime->period_size); + sport->once = 1; + } + } +#endif + snd_pcm_period_elapsed(pcm); +} + +/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes. + * The total rx/tx buffer is for ac97 frame to hold all pcm data + * is 0x20000 * sizeof(struct ac97_frame) / 4. + */ +#ifdef CONFIG_SND_MMAP_SUPPORT +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, +#else +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, +#endif + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 32, + .period_bytes_max = 0x10000, + .periods_min = 1, + .periods_max = PAGE_SIZE/32, + .buffer_bytes_max = 0x20000, /* 128 kbytes */ + .fifo_size = 16, +}; + +static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + size_t size = bf5xx_pcm_hardware.buffer_bytes_max + * sizeof(struct ac97_frame) / 4; + + snd_pcm_lib_malloc_pages(substream, size); + + return 0; +} + +static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + memset(runtime->dma_area, 0, runtime->buffer_size); + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + + /* An intermediate buffer is introduced for implementing mmap for + * SPORT working in TMD mode(include AC97). + */ +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } +#else + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, runtime->dma_area, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, runtime->dma_area, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } +#endif + return 0; +} + +static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int ret = 0; + + pr_debug("%s enter\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + bf5xx_mmap_copy(substream, runtime->period_size); + snd_pcm_period_elapsed(substream); + sport->tx_delay_pos = 0; + sport_tx_start(sport); + } + else + sport_rx_start(sport); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +#if defined(CONFIG_SND_MMAP_SUPPORT) + sport->tx_pos = 0; +#endif + sport_tx_stop(sport); + } else { +#if defined(CONFIG_SND_MMAP_SUPPORT) + sport->rx_pos = 0; +#endif + sport_rx_stop(sport); + } + break; + default: + ret = -EINVAL; + } + return ret; +} + +static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + unsigned int curr; + +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + curr = sport->tx_delay_pos; + else + curr = sport->rx_pos; +#else + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame); + else + curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame); + +#endif + return curr; +} + +static int bf5xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + pr_debug("%s enter\n", __func__); + snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + if (sport_handle != NULL) + runtime->private_data = sport_handle; + else { + pr_err("sport_handle is NULL\n"); + return -1; + } + return 0; + + out: + return ret; +} + +static int bf5xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + + pr_debug("%s enter\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport->once = 0; + memset(sport->tx_dma_buf, 0, runtime->buffer_size * sizeof(struct ac97_frame)); + } else + memset(sport->rx_dma_buf, 0, runtime->buffer_size * sizeof(struct ac97_frame)); + + return 0; +} + +#ifdef CONFIG_SND_MMAP_SUPPORT +static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + size_t size = vma->vm_end - vma->vm_start; + vma->vm_start = (unsigned long)runtime->dma_area; + vma->vm_end = vma->vm_start + size; + vma->vm_flags |= VM_SHARED; + return 0 ; +} +#else +static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + pr_debug("%s copy pos:0x%lx count:0x%lx\n", + substream->stream ? "Capture" : "Playback", pos, count); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + bf5xx_pcm_to_ac97( + (struct ac97_frame *)runtime->dma_area + pos, + buf, count); + else + bf5xx_ac97_to_pcm( + (struct ac97_frame *)runtime->dma_area + pos, + buf, count); + return 0; +} +#endif + +struct snd_pcm_ops bf5xx_pcm_ac97_ops = { + .open = bf5xx_pcm_open, + .close = bf5xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = bf5xx_pcm_hw_params, + .hw_free = bf5xx_pcm_hw_free, + .prepare = bf5xx_pcm_prepare, + .trigger = bf5xx_pcm_trigger, + .pointer = bf5xx_pcm_pointer, +#ifdef CONFIG_SND_MMAP_SUPPORT + .mmap = bf5xx_pcm_mmap, +#else + .copy = bf5xx_pcm_copy, +#endif +}; + +static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = bf5xx_pcm_hardware.buffer_bytes_max + * sizeof(struct ac97_frame) / 4; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + pr_err("Failed to allocate dma memory\n"); + pr_err("Please increase uncached DMA memory region\n"); + return -ENOMEM; + } + buf->bytes = size; + + pr_debug("%s, area:%p, size:0x%08lx\n", __func__, + buf->area, buf->bytes); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_handle->tx_buf = buf->area; + else + sport_handle->rx_buf = buf->area; + +/* + * Need to allocate local buffer when enable + * MMAP for SPORT working in TMD mode (include AC97). + */ +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!sport_handle->tx_dma_buf) { + sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \ + size, &sport_handle->tx_dma_phy, GFP_KERNEL); + if (!sport_handle->tx_dma_buf) { + pr_err("Failed to allocate memory for tx dma \ + buf - Please increase uncached DMA \ + memory region\n"); + return -ENOMEM; + } else + memset(sport_handle->tx_dma_buf, 0, size); + } else + memset(sport_handle->tx_dma_buf, 0, size); + } else { + if (!sport_handle->rx_dma_buf) { + sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \ + size, &sport_handle->rx_dma_phy, GFP_KERNEL); + if (!sport_handle->rx_dma_buf) { + pr_err("Failed to allocate memory for rx dma \ + buf - Please increase uncached DMA \ + memory region\n"); + return -ENOMEM; + } else + memset(sport_handle->rx_dma_buf, 0, size); + } else + memset(sport_handle->rx_dma_buf, 0, size); + } +#endif + return 0; +} + +static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; +#if defined(CONFIG_SND_MMAP_SUPPORT) + size_t size = bf5xx_pcm_hardware.buffer_bytes_max * + sizeof(struct ac97_frame) / 4; +#endif + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(NULL, buf->bytes, buf->area, 0); + buf->area = NULL; +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (sport_handle->tx_dma_buf) + dma_free_coherent(NULL, size, \ + sport_handle->tx_dma_buf, 0); + sport_handle->tx_dma_buf = NULL; + } else { + + if (sport_handle->rx_dma_buf) + dma_free_coherent(NULL, size, \ + sport_handle->rx_dma_buf, 0); + sport_handle->rx_dma_buf = NULL; + } +#endif + } + if (sport_handle) + sport_done(sport_handle); +} + +static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; + +int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + pr_debug("%s enter\n", __func__); + if (!card->dev->dma_mask) + card->dev->dma_mask = &bf5xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (dai->playback.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform bf5xx_ac97_soc_platform = { + .name = "bf5xx-audio", + .pcm_ops = &bf5xx_pcm_ac97_ops, + .pcm_new = bf5xx_pcm_ac97_new, + .pcm_free = bf5xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(bf5xx_ac97_soc_platform); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.h b/sound/soc/blackfin/bf5xx-ac97-pcm.h new file mode 100644 index 000000000000..350125a0ae21 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97-pcm.h @@ -0,0 +1,29 @@ +/* + * linux/sound/arm/bf5xx-ac97-pcm.h -- ALSA PCM interface for the Blackfin + * + * Copyright 2007 Analog Device Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BF5XX_AC97_PCM_H +#define _BF5XX_AC97_PCM_H + +struct bf5xx_pcm_dma_params { + char *name; /* stream identifier */ +}; + +struct bf5xx_gpio { + u32 sys; + u32 rx; + u32 tx; + u32 clk; + u32 frm; +}; + +/* platform data */ +extern struct snd_soc_platform bf5xx_ac97_soc_platform; + +#endif diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c new file mode 100644 index 000000000000..5e5aafb6485f --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -0,0 +1,406 @@ +/* + * bf5xx-ac97.c -- AC97 support for the ADI blackfin chip. + * + * Author: Roy Huang + * Created: 11th. June 2007 + * Copyright: Analog Device Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/irq.h> +#include <asm/portmux.h> +#include <linux/mutex.h> +#include <linux/gpio.h> + +#include "bf5xx-sport.h" +#include "bf5xx-ac97.h" + +#if defined(CONFIG_BF54x) +#define PIN_REQ_SPORT_0 {P_SPORT0_TFS, P_SPORT0_DTPRI, P_SPORT0_TSCLK, \ + P_SPORT0_RFS, P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0} + +#define PIN_REQ_SPORT_1 {P_SPORT1_TFS, P_SPORT1_DTPRI, P_SPORT1_TSCLK, \ + P_SPORT1_RFS, P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0} + +#define PIN_REQ_SPORT_2 {P_SPORT2_TFS, P_SPORT2_DTPRI, P_SPORT2_TSCLK, \ + P_SPORT2_RFS, P_SPORT2_DRPRI, P_SPORT2_RSCLK, 0} + +#define PIN_REQ_SPORT_3 {P_SPORT3_TFS, P_SPORT3_DTPRI, P_SPORT3_TSCLK, \ + P_SPORT3_RFS, P_SPORT3_DRPRI, P_SPORT3_RSCLK, 0} +#else +#define PIN_REQ_SPORT_0 {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, \ + P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0} + +#define PIN_REQ_SPORT_1 {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, \ + P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0} +#endif + +static int *cmd_count; +static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; + +#if defined(CONFIG_BF54x) +static struct sport_param sport_params[4] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERR, + .regs = (struct sport_register *)SPORT1_TCR1, + }, + { + .dma_rx_chan = CH_SPORT2_RX, + .dma_tx_chan = CH_SPORT2_TX, + .err_irq = IRQ_SPORT2_ERR, + .regs = (struct sport_register *)SPORT2_TCR1, + }, + { + .dma_rx_chan = CH_SPORT3_RX, + .dma_tx_chan = CH_SPORT3_TX, + .err_irq = IRQ_SPORT3_ERR, + .regs = (struct sport_register *)SPORT3_TCR1, + } +}; +#else +static struct sport_param sport_params[2] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERROR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERROR, + .regs = (struct sport_register *)SPORT1_TCR1, + } +}; +#endif + +void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u32 *src, \ + size_t count) +{ + while (count--) { + dst->ac97_tag = TAG_VALID | TAG_PCM; + (dst++)->ac97_pcm = *src++; + } +} +EXPORT_SYMBOL(bf5xx_pcm_to_ac97); + +void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u32 *dst, \ + size_t count) +{ + while (count--) + *(dst++) = (src++)->ac97_pcm; +} +EXPORT_SYMBOL(bf5xx_ac97_to_pcm); + +static unsigned int sport_tx_curr_frag(struct sport_device *sport) +{ + return sport->tx_curr_frag = sport_curr_offset_tx(sport) / \ + sport->tx_fragsize; +} + +static void enqueue_cmd(struct snd_ac97 *ac97, __u16 addr, __u16 data) +{ + struct sport_device *sport = sport_handle; + int nextfrag = sport_tx_curr_frag(sport); + struct ac97_frame *nextwrite; + + sport_incfrag(sport, &nextfrag, 1); + + nextwrite = (struct ac97_frame *)(sport->tx_buf + \ + nextfrag * sport->tx_fragsize); + pr_debug("sport->tx_buf:%p, nextfrag:0x%x nextwrite:%p, cmd_count:%d\n", + sport->tx_buf, nextfrag, nextwrite, cmd_count[nextfrag]); + nextwrite[cmd_count[nextfrag]].ac97_tag |= TAG_CMD; + nextwrite[cmd_count[nextfrag]].ac97_addr = addr; + nextwrite[cmd_count[nextfrag]].ac97_data = data; + ++cmd_count[nextfrag]; + pr_debug("ac97_sport: Inserting %02x/%04x into fragment %d\n", + addr >> 8, data, nextfrag); +} + +static unsigned short bf5xx_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct ac97_frame out_frame[2], in_frame[2]; + + pr_debug("%s enter 0x%x\n", __func__, reg); + + /* When dma descriptor is enabled, the register should not be read */ + if (sport_handle->tx_run || sport_handle->rx_run) { + pr_err("Could you send a mail to cliff.cai@analog.com " + "to report this?\n"); + return -EFAULT; + } + + memset(&out_frame, 0, 2 * sizeof(struct ac97_frame)); + memset(&in_frame, 0, 2 * sizeof(struct ac97_frame)); + out_frame[0].ac97_tag = TAG_VALID | TAG_CMD; + out_frame[0].ac97_addr = ((reg << 8) | 0x8000); + sport_send_and_recv(sport_handle, (unsigned char *)&out_frame, + (unsigned char *)&in_frame, + 2 * sizeof(struct ac97_frame)); + return in_frame[1].ac97_data; +} + +void bf5xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + pr_debug("%s enter 0x%x:0x%04x\n", __func__, reg, val); + + if (sport_handle->tx_run) { + enqueue_cmd(ac97, (reg << 8), val); /* write */ + enqueue_cmd(ac97, (reg << 8) | 0x8000, 0); /* read back */ + } else { + struct ac97_frame frame; + memset(&frame, 0, sizeof(struct ac97_frame)); + frame.ac97_tag = TAG_VALID | TAG_CMD; + frame.ac97_addr = (reg << 8); + frame.ac97_data = val; + sport_send_and_recv(sport_handle, (unsigned char *)&frame, \ + NULL, sizeof(struct ac97_frame)); + } +} + +static void bf5xx_ac97_warm_reset(struct snd_ac97 *ac97) +{ +#if defined(CONFIG_BF54x) || defined(CONFIG_BF561) || \ + (defined(BF537_FAMILY) && (CONFIG_SND_BF5XX_SPORT_NUM == 1)) + +#define CONCAT(a, b, c) a ## b ## c +#define BFIN_SPORT_RFS(x) CONCAT(P_SPORT, x, _RFS) + + u16 per = BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM); + u16 gpio = P_IDENT(BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM)); + + pr_debug("%s enter\n", __func__); + + peripheral_free(per); + gpio_request(gpio, "bf5xx-ac97"); + gpio_direction_output(gpio, 1); + udelay(2); + gpio_set_value(gpio, 0); + udelay(1); + gpio_free(gpio); + peripheral_request(per, "soc-audio"); +#else + pr_info("%s: Not implemented\n", __func__); +#endif +} + +static void bf5xx_ac97_cold_reset(struct snd_ac97 *ac97) +{ +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + pr_debug("%s enter\n", __func__); + + /* It is specified for bf548-ezkit */ + gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 0); + /* Keep reset pin low for 1 ms */ + mdelay(1); + gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1); + /* Wait for bit clock recover */ + mdelay(1); +#else + pr_info("%s: Not implemented\n", __func__); +#endif +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = bf5xx_ac97_read, + .write = bf5xx_ac97_write, + .warm_reset = bf5xx_ac97_warm_reset, + .reset = bf5xx_ac97_cold_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +#ifdef CONFIG_PM +static int bf5xx_ac97_suspend(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + if (dai->capture.active) + sport_rx_stop(sport); + if (dai->playback.active) + sport_tx_stop(sport); + return 0; +} + +static int bf5xx_ac97_resume(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret; + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + + ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + if (dai->capture.active) + sport_rx_start(sport); + if (dai->playback.active) + sport_tx_start(sport); + return 0; +} + +#else +#define bf5xx_ac97_suspend NULL +#define bf5xx_ac97_resume NULL +#endif + +static int bf5xx_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret; +#if defined(CONFIG_BF54x) + u16 sport_req[][7] = {PIN_REQ_SPORT_0, PIN_REQ_SPORT_1, + PIN_REQ_SPORT_2, PIN_REQ_SPORT_3}; +#else + u16 sport_req[][7] = {PIN_REQ_SPORT_0, PIN_REQ_SPORT_1}; +#endif + cmd_count = (int *)get_zeroed_page(GFP_KERNEL); + if (cmd_count == NULL) + return -ENOMEM; + + if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { + pr_err("Requesting Peripherals failed\n"); + return -EFAULT; + } + +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + /* Request PB3 as reset pin */ + if (gpio_request(CONFIG_SND_BF5XX_RESET_GPIO_NUM, "SND_AD198x RESET")) { + pr_err("Failed to request GPIO_%d for reset\n", + CONFIG_SND_BF5XX_RESET_GPIO_NUM); + peripheral_free_list(&sport_req[sport_num][0]); + return -1; + } + gpio_direction_output(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1); +#endif + sport_handle = sport_init(&sport_params[sport_num], 2, \ + sizeof(struct ac97_frame), NULL); + if (!sport_handle) { + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -ENODEV; + } + /*SPORT works in TDM mode to simulate AC97 transfers*/ + ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + kfree(sport_handle); + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -EBUSY; + } + + ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + kfree(sport_handle); + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + kfree(sport_handle); + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -EBUSY; + } + return 0; +} + +static void bf5xx_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + free_page((unsigned long)cmd_count); + cmd_count = NULL; +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif +} + +struct snd_soc_dai bfin_ac97_dai = { + .name = "bf5xx-ac97", + .id = 0, + .type = SND_SOC_DAI_AC97, + .probe = bf5xx_ac97_probe, + .remove = bf5xx_ac97_remove, + .suspend = bf5xx_ac97_suspend, + .resume = bf5xx_ac97_resume, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(bfin_ac97_dai); + +MODULE_AUTHOR("Roy Huang"); +MODULE_DESCRIPTION("AC97 driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-ac97.h b/sound/soc/blackfin/bf5xx-ac97.h new file mode 100644 index 000000000000..3f77cc558dc0 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97.h @@ -0,0 +1,36 @@ +/* + * linux/sound/arm/bf5xx-ac97.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BF5XX_AC97_H +#define _BF5XX_AC97_H + +extern struct snd_ac97_bus_ops bf5xx_ac97_ops; +extern struct snd_ac97 *ac97; +/* Frame format in memory, only support stereo currently */ +struct ac97_frame { + u16 ac97_tag; /* slot 0 */ + u16 ac97_addr; /* slot 1 */ + u16 ac97_data; /* slot 2 */ + u32 ac97_pcm; /* slot 3 and 4: left and right pcm data */ +} __attribute__ ((packed)); + +#define TAG_VALID 0x8000 +#define TAG_CMD 0x6000 +#define TAG_PCM_LEFT 0x1000 +#define TAG_PCM_RIGHT 0x0800 +#define TAG_PCM (TAG_PCM_LEFT | TAG_PCM_RIGHT) + +extern struct snd_soc_dai bfin_ac97_dai; + +void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u32 *src, \ + size_t count); + +void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u32 *dst, \ + size_t count); + +#endif diff --git a/sound/soc/blackfin/bf5xx-ad1980.c b/sound/soc/blackfin/bf5xx-ad1980.c new file mode 100644 index 000000000000..124425d22320 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad1980.c @@ -0,0 +1,113 @@ +/* + * File: sound/soc/blackfin/bf5xx-ad1980.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: Board driver for AD1980/1 audio codec + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <asm/dma.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <linux/gpio.h> +#include <asm/portmux.h> + +#include "../codecs/ad1980.h" +#include "bf5xx-sport.h" +#include "bf5xx-ac97-pcm.h" +#include "bf5xx-ac97.h" + +static struct snd_soc_machine bf5xx_board; + +static int bf5xx_board_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + pr_debug("%s enter\n", __func__); + cpu_dai->private_data = sport_handle; + return 0; +} + +static struct snd_soc_ops bf5xx_board_ops = { + .startup = bf5xx_board_startup, +}; + +static struct snd_soc_dai_link bf5xx_board_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &bfin_ac97_dai, + .codec_dai = &ad1980_dai, + .ops = &bf5xx_board_ops, +}; + +static struct snd_soc_machine bf5xx_board = { + .name = "bf5xx-board", + .dai_link = &bf5xx_board_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_board_snd_devdata = { + .machine = &bf5xx_board, + .platform = &bf5xx_ac97_soc_platform, + .codec_dev = &soc_codec_dev_ad1980, +}; + +static struct platform_device *bf5xx_board_snd_device; + +static int __init bf5xx_board_init(void) +{ + int ret; + + bf5xx_board_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf5xx_board_snd_device) + return -ENOMEM; + + platform_set_drvdata(bf5xx_board_snd_device, &bf5xx_board_snd_devdata); + bf5xx_board_snd_devdata.dev = &bf5xx_board_snd_device->dev; + ret = platform_device_add(bf5xx_board_snd_device); + + if (ret) + platform_device_put(bf5xx_board_snd_device); + + return ret; +} + +static void __exit bf5xx_board_exit(void) +{ + platform_device_unregister(bf5xx_board_snd_device); +} + +module_init(bf5xx_board_init); +module_exit(bf5xx_board_exit); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ALSA SoC AD1980/1 BF5xx board"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-ad73311.c b/sound/soc/blackfin/bf5xx-ad73311.c new file mode 100644 index 000000000000..622c9b909532 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad73311.c @@ -0,0 +1,240 @@ +/* + * File: sound/soc/blackfin/bf5xx-ad73311.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Thur Sep 25 2008 + * Description: Board driver for ad73311 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> + +#include <asm/blackfin.h> +#include <asm/cacheflush.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/portmux.h> + +#include "../codecs/ad73311.h" +#include "bf5xx-sport.h" +#include "bf5xx-i2s-pcm.h" +#include "bf5xx-i2s.h" + +#if CONFIG_SND_BF5XX_SPORT_NUM == 0 +#define bfin_write_SPORT_TCR1 bfin_write_SPORT0_TCR1 +#define bfin_read_SPORT_TCR1 bfin_read_SPORT0_TCR1 +#define bfin_write_SPORT_TCR2 bfin_write_SPORT0_TCR2 +#define bfin_write_SPORT_TX16 bfin_write_SPORT0_TX16 +#define bfin_read_SPORT_STAT bfin_read_SPORT0_STAT +#else +#define bfin_write_SPORT_TCR1 bfin_write_SPORT1_TCR1 +#define bfin_read_SPORT_TCR1 bfin_read_SPORT1_TCR1 +#define bfin_write_SPORT_TCR2 bfin_write_SPORT1_TCR2 +#define bfin_write_SPORT_TX16 bfin_write_SPORT1_TX16 +#define bfin_read_SPORT_STAT bfin_read_SPORT1_STAT +#endif + +#define GPIO_SE CONFIG_SND_BFIN_AD73311_SE + +static struct snd_soc_machine bf5xx_ad73311; + +static int snd_ad73311_startup(void) +{ + pr_debug("%s enter\n", __func__); + + /* Pull up SE pin on AD73311L */ + gpio_set_value(GPIO_SE, 1); + return 0; +} + +static int snd_ad73311_configure(void) +{ + unsigned short ctrl_regs[6]; + unsigned short status = 0; + int count = 0; + + /* DMCLK = MCLK = 16.384 MHz + * SCLK = DMCLK/8 = 2.048 MHz + * Sample Rate = DMCLK/2048 = 8 KHz + */ + ctrl_regs[0] = AD_CONTROL | AD_WRITE | CTRL_REG_B | REGB_MCDIV(0) | \ + REGB_SCDIV(0) | REGB_DIRATE(0); + ctrl_regs[1] = AD_CONTROL | AD_WRITE | CTRL_REG_C | REGC_PUDEV | \ + REGC_PUADC | REGC_PUDAC | REGC_PUREF | REGC_REFUSE ; + ctrl_regs[2] = AD_CONTROL | AD_WRITE | CTRL_REG_D | REGD_OGS(2) | \ + REGD_IGS(2); + ctrl_regs[3] = AD_CONTROL | AD_WRITE | CTRL_REG_E | REGE_DA(0x1f); + ctrl_regs[4] = AD_CONTROL | AD_WRITE | CTRL_REG_F | REGF_SEEN ; + ctrl_regs[5] = AD_CONTROL | AD_WRITE | CTRL_REG_A | REGA_MODE_DATA; + + local_irq_disable(); + snd_ad73311_startup(); + udelay(1); + + bfin_write_SPORT_TCR1(TFSR); + bfin_write_SPORT_TCR2(0xF); + SSYNC(); + + /* SPORT Tx Register is a 8 x 16 FIFO, all the data can be put to + * FIFO before enable SPORT to transfer the data + */ + for (count = 0; count < 6; count++) + bfin_write_SPORT_TX16(ctrl_regs[count]); + SSYNC(); + bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() | TSPEN); + SSYNC(); + + /* When TUVF is set, the data is already send out */ + while (!(status & TUVF) && count++ < 10000) { + udelay(1); + status = bfin_read_SPORT_STAT(); + SSYNC(); + } + bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() & ~TSPEN); + SSYNC(); + local_irq_enable(); + + if (count == 10000) { + printk(KERN_ERR "ad73311: failed to configure codec\n"); + return -1; + } + return 0; +} + +static int bf5xx_probe(struct platform_device *pdev) +{ + int err; + if (gpio_request(GPIO_SE, "AD73311_SE")) { + printk(KERN_ERR "%s: Failed ro request GPIO_%d\n", __func__, GPIO_SE); + return -EBUSY; + } + + gpio_direction_output(GPIO_SE, 0); + + err = snd_ad73311_configure(); + if (err < 0) + return -EFAULT; + + return 0; +} + +static int bf5xx_ad73311_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + pr_debug("%s enter\n", __func__); + cpu_dai->private_data = sport_handle; + return 0; +} + +static int bf5xx_ad73311_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + pr_debug("%s rate %d format %x\n", __func__, params_rate(params), + params_format(params)); + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + return 0; +} + + +static struct snd_soc_ops bf5xx_ad73311_ops = { + .startup = bf5xx_ad73311_startup, + .hw_params = bf5xx_ad73311_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_ad73311_dai = { + .name = "ad73311", + .stream_name = "AD73311", + .cpu_dai = &bf5xx_i2s_dai, + .codec_dai = &ad73311_dai, + .ops = &bf5xx_ad73311_ops, +}; + +static struct snd_soc_machine bf5xx_ad73311 = { + .name = "bf5xx_ad73311", + .probe = bf5xx_probe, + .dai_link = &bf5xx_ad73311_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_ad73311_snd_devdata = { + .machine = &bf5xx_ad73311, + .platform = &bf5xx_i2s_soc_platform, + .codec_dev = &soc_codec_dev_ad73311, +}; + +static struct platform_device *bf52x_ad73311_snd_device; + +static int __init bf5xx_ad73311_init(void) +{ + int ret; + + pr_debug("%s enter\n", __func__); + bf52x_ad73311_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf52x_ad73311_snd_device) + return -ENOMEM; + + platform_set_drvdata(bf52x_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); + bf5xx_ad73311_snd_devdata.dev = &bf52x_ad73311_snd_device->dev; + ret = platform_device_add(bf52x_ad73311_snd_device); + + if (ret) + platform_device_put(bf52x_ad73311_snd_device); + + return ret; +} + +static void __exit bf5xx_ad73311_exit(void) +{ + pr_debug("%s enter\n", __func__); + platform_device_unregister(bf52x_ad73311_snd_device); +} + +module_init(bf5xx_ad73311_init); +module_exit(bf5xx_ad73311_exit); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ALSA SoC AD73311 Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c new file mode 100644 index 000000000000..61fccf925192 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c @@ -0,0 +1,288 @@ +/* + * File: sound/soc/blackfin/bf5xx-i2s-pcm.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: DMA driver for i2s codec + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> + +#include "bf5xx-i2s-pcm.h" +#include "bf5xx-i2s.h" +#include "bf5xx-sport.h" + +static void bf5xx_dma_irq(void *data) +{ + struct snd_pcm_substream *pcm = data; + snd_pcm_period_elapsed(pcm); +} + +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 32, + .period_bytes_max = 0x10000, + .periods_min = 1, + .periods_max = PAGE_SIZE/32, + .buffer_bytes_max = 0x20000, /* 128 kbytes */ + .fifo_size = 16, +}; + +static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + size_t size = bf5xx_pcm_hardware.buffer_bytes_max; + snd_pcm_lib_malloc_pages(substream, size); + + return 0; +} + +static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int period_bytes = frames_to_bytes(runtime, runtime->period_size); + + pr_debug("%s enter\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, runtime->dma_area, + runtime->periods, period_bytes); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, runtime->dma_area, + runtime->periods, period_bytes); + } + + return 0; +} + +static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int ret = 0; + + pr_debug("%s enter\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_start(sport); + else + sport_rx_start(sport); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_stop(sport); + else + sport_rx_stop(sport); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + unsigned int diff; + snd_pcm_uframes_t frames; + pr_debug("%s enter\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + diff = sport_curr_offset_tx(sport); + frames = bytes_to_frames(substream->runtime, diff); + } else { + diff = sport_curr_offset_rx(sport); + frames = bytes_to_frames(substream->runtime, diff); + } + return frames; +} + +static int bf5xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + pr_debug("%s enter\n", __func__); + snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, \ + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + if (sport_handle != NULL) + runtime->private_data = sport_handle; + else { + pr_err("sport_handle is NULL\n"); + return -1; + } + return 0; + + out: + return ret; +} + +static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + size_t size = vma->vm_end - vma->vm_start; + vma->vm_start = (unsigned long)runtime->dma_area; + vma->vm_end = vma->vm_start + size; + vma->vm_flags |= VM_SHARED; + + return 0 ; +} + +struct snd_pcm_ops bf5xx_pcm_i2s_ops = { + .open = bf5xx_pcm_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = bf5xx_pcm_hw_params, + .hw_free = bf5xx_pcm_hw_free, + .prepare = bf5xx_pcm_prepare, + .trigger = bf5xx_pcm_trigger, + .pointer = bf5xx_pcm_pointer, + .mmap = bf5xx_pcm_mmap, +}; + +static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = bf5xx_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + pr_err("Failed to allocate dma memory \ + Please increase uncached DMA memory region\n"); + return -ENOMEM; + } + buf->bytes = size; + + pr_debug("%s, area:%p, size:0x%08lx\n", __func__, + buf->area, buf->bytes); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_handle->tx_buf = buf->area; + else + sport_handle->rx_buf = buf->area; + + return 0; +} + +static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(NULL, buf->bytes, buf->area, 0); + buf->area = NULL; + } + if (sport_handle) + sport_done(sport_handle); +} + +static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; + +int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + pr_debug("%s enter\n", __func__); + if (!card->dev->dma_mask) + card->dev->dma_mask = &bf5xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (dai->playback.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform bf5xx_i2s_soc_platform = { + .name = "bf5xx-audio", + .pcm_ops = &bf5xx_pcm_i2s_ops, + .pcm_new = bf5xx_pcm_i2s_new, + .pcm_free = bf5xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(bf5xx_i2s_soc_platform); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.h b/sound/soc/blackfin/bf5xx-i2s-pcm.h new file mode 100644 index 000000000000..4d4609a97c59 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.h @@ -0,0 +1,29 @@ +/* + * linux/sound/arm/bf5xx-i2s-pcm.h -- ALSA PCM interface for the Blackfin + * + * Copyright 2007 Analog Device Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BF5XX_I2S_PCM_H +#define _BF5XX_I2S_PCM_H + +struct bf5xx_pcm_dma_params { + char *name; /* stream identifier */ +}; + +struct bf5xx_gpio { + u32 sys; + u32 rx; + u32 tx; + u32 clk; + u32 frm; +}; + +/* platform data */ +extern struct snd_soc_platform bf5xx_i2s_soc_platform; + +#endif diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c new file mode 100644 index 000000000000..827587f08180 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s.c @@ -0,0 +1,311 @@ +/* + * File: sound/soc/blackfin/bf5xx-i2s.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: Blackfin I2S CPU DAI driver + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/irq.h> +#include <asm/portmux.h> +#include <linux/mutex.h> +#include <linux/gpio.h> + +#include "bf5xx-sport.h" +#include "bf5xx-i2s.h" + +struct bf5xx_i2s_port { + u16 tcr1; + u16 rcr1; + u16 tcr2; + u16 rcr2; + int counter; +}; + +static struct bf5xx_i2s_port bf5xx_i2s; +static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; + +static struct sport_param sport_params[2] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERROR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERROR, + .regs = (struct sport_register *)SPORT1_TCR1, + } +}; + +static u16 sport_req[][7] = { + { P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, + P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0}, + { P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, + P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0}, +}; + +static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + int ret = 0; + + /* interface format:support I2S,slave mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + bf5xx_i2s.tcr1 |= TFSR | TCKFE; + bf5xx_i2s.rcr1 |= RFSR | RCKFE; + bf5xx_i2s.tcr2 |= TSFSE; + bf5xx_i2s.rcr2 |= RSFSE; + break; + case SND_SOC_DAIFMT_DSP_A: + bf5xx_i2s.tcr1 |= TFSR; + bf5xx_i2s.rcr1 |= RFSR; + break; + case SND_SOC_DAIFMT_LEFT_J: + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBM_CFS: + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int bf5xx_i2s_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s enter\n", __func__); + + /*this counter is used for counting how many pcm streams are opened*/ + bf5xx_i2s.counter++; + return 0; +} + +static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + + bf5xx_i2s.tcr2 &= ~0x1f; + bf5xx_i2s.rcr2 &= ~0x1f; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bf5xx_i2s.tcr2 |= 15; + bf5xx_i2s.rcr2 |= 15; + sport_handle->wdsize = 2; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bf5xx_i2s.tcr2 |= 23; + bf5xx_i2s.rcr2 |= 23; + sport_handle->wdsize = 3; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bf5xx_i2s.tcr2 |= 31; + bf5xx_i2s.rcr2 |= 31; + sport_handle->wdsize = 4; + break; + } + + if (bf5xx_i2s.counter == 1) { + /* + * TX and RX are not independent,they are enabled at the + * same time, even if only one side is running. So, we + * need to configure both of them at the time when the first + * stream is opened. + * + * CPU DAI:slave mode. + */ + ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, + bf5xx_i2s.rcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1, + bf5xx_i2s.tcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + } + + return 0; +} + +static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s enter\n", __func__); + bf5xx_i2s.counter--; +} + +static int bf5xx_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + pr_debug("%s enter\n", __func__); + if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { + pr_err("Requesting Peripherals failed\n"); + return -EFAULT; + } + + /* request DMA for SPORT */ + sport_handle = sport_init(&sport_params[sport_num], 4, \ + 2 * sizeof(u32), NULL); + if (!sport_handle) { + peripheral_free_list(&sport_req[sport_num][0]); + return -ENODEV; + } + + return 0; +} + +static void bf5xx_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + pr_debug("%s enter\n", __func__); + peripheral_free_list(&sport_req[sport_num][0]); +} + +#ifdef CONFIG_PM +static int bf5xx_i2s_suspend(struct platform_device *dev, + struct snd_soc_dai *dai) +{ + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + if (dai->capture.active) + sport_rx_stop(sport); + if (dai->playback.active) + sport_tx_stop(sport); + return 0; +} + +static int bf5xx_i2s_resume(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret; + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + + ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + if (dai->capture.active) + sport_rx_start(sport); + if (dai->playback.active) + sport_tx_start(sport); + return 0; +} + +#else +#define bf5xx_i2s_suspend NULL +#define bf5xx_i2s_resume NULL +#endif + +#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai bf5xx_i2s_dai = { + .name = "bf5xx-i2s", + .id = 0, + .type = SND_SOC_DAI_I2S, + .probe = bf5xx_i2s_probe, + .remove = bf5xx_i2s_remove, + .suspend = bf5xx_i2s_suspend, + .resume = bf5xx_i2s_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = BF5XX_I2S_RATES, + .formats = BF5XX_I2S_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = BF5XX_I2S_RATES, + .formats = BF5XX_I2S_FORMATS,}, + .ops = { + .startup = bf5xx_i2s_startup, + .shutdown = bf5xx_i2s_shutdown, + .hw_params = bf5xx_i2s_hw_params,}, + .dai_ops = { + .set_fmt = bf5xx_i2s_set_dai_fmt, + }, +}; +EXPORT_SYMBOL_GPL(bf5xx_i2s_dai); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("I2S driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-i2s.h b/sound/soc/blackfin/bf5xx-i2s.h new file mode 100644 index 000000000000..7107d1a0b06b --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s.h @@ -0,0 +1,14 @@ +/* + * linux/sound/arm/bf5xx-i2s.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BF5XX_I2S_H +#define _BF5XX_I2S_H + +extern struct snd_soc_dai bf5xx_i2s_dai; + +#endif diff --git a/sound/soc/blackfin/bf5xx-sport.c b/sound/soc/blackfin/bf5xx-sport.c new file mode 100644 index 000000000000..3b99e484d555 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-sport.c @@ -0,0 +1,1032 @@ +/* + * File: bf5xx_sport.c + * Based on: + * Author: Roy Huang <roy.huang@analog.com> + * + * Created: Tue Sep 21 10:52:42 CEST 2004 + * Description: + * Blackfin SPORT Driver + * + * Copyright 2004-2007 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/gpio.h> +#include <linux/bug.h> +#include <asm/portmux.h> +#include <asm/dma.h> +#include <asm/blackfin.h> +#include <asm/cacheflush.h> + +#include "bf5xx-sport.h" +/* delay between frame sync pulse and first data bit in multichannel mode */ +#define FRAME_DELAY (1<<12) + +struct sport_device *sport_handle; +EXPORT_SYMBOL(sport_handle); +/* note: multichannel is in units of 8 channels, + * tdm_count is # channels NOT / 8 ! */ +int sport_set_multichannel(struct sport_device *sport, + int tdm_count, u32 mask, int packed) +{ + pr_debug("%s tdm_count=%d mask:0x%08x packed=%d\n", __func__, + tdm_count, mask, packed); + + if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN)) + return -EBUSY; + + if (tdm_count & 0x7) + return -EINVAL; + + if (tdm_count > 32) + return -EINVAL; /* Only support less than 32 channels now */ + + if (tdm_count) { + sport->regs->mcmc1 = ((tdm_count>>3)-1) << 12; + sport->regs->mcmc2 = FRAME_DELAY | MCMEN | \ + (packed ? (MCDTXPE|MCDRXPE) : 0); + + sport->regs->mtcs0 = mask; + sport->regs->mrcs0 = mask; + sport->regs->mtcs1 = 0; + sport->regs->mrcs1 = 0; + sport->regs->mtcs2 = 0; + sport->regs->mrcs2 = 0; + sport->regs->mtcs3 = 0; + sport->regs->mrcs3 = 0; + } else { + sport->regs->mcmc1 = 0; + sport->regs->mcmc2 = 0; + + sport->regs->mtcs0 = 0; + sport->regs->mrcs0 = 0; + } + + sport->regs->mtcs1 = 0; sport->regs->mtcs2 = 0; sport->regs->mtcs3 = 0; + sport->regs->mrcs1 = 0; sport->regs->mrcs2 = 0; sport->regs->mrcs3 = 0; + + SSYNC(); + + return 0; +} +EXPORT_SYMBOL(sport_set_multichannel); + +int sport_config_rx(struct sport_device *sport, unsigned int rcr1, + unsigned int rcr2, unsigned int clkdiv, unsigned int fsdiv) +{ + if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN)) + return -EBUSY; + + sport->regs->rcr1 = rcr1; + sport->regs->rcr2 = rcr2; + sport->regs->rclkdiv = clkdiv; + sport->regs->rfsdiv = fsdiv; + + SSYNC(); + + return 0; +} +EXPORT_SYMBOL(sport_config_rx); + +int sport_config_tx(struct sport_device *sport, unsigned int tcr1, + unsigned int tcr2, unsigned int clkdiv, unsigned int fsdiv) +{ + if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN)) + return -EBUSY; + + sport->regs->tcr1 = tcr1; + sport->regs->tcr2 = tcr2; + sport->regs->tclkdiv = clkdiv; + sport->regs->tfsdiv = fsdiv; + + SSYNC(); + + return 0; +} +EXPORT_SYMBOL(sport_config_tx); + +static void setup_desc(struct dmasg *desc, void *buf, int fragcount, + size_t fragsize, unsigned int cfg, + unsigned int x_count, unsigned int ycount, size_t wdsize) +{ + + int i; + + for (i = 0; i < fragcount; ++i) { + desc[i].next_desc_addr = (unsigned long)&(desc[i + 1]); + desc[i].start_addr = (unsigned long)buf + i*fragsize; + desc[i].cfg = cfg; + desc[i].x_count = x_count; + desc[i].x_modify = wdsize; + desc[i].y_count = ycount; + desc[i].y_modify = wdsize; + } + + /* make circular */ + desc[fragcount-1].next_desc_addr = (unsigned long)desc; + + pr_debug("setup desc: desc0=%p, next0=%lx, desc1=%p," + "next1=%lx\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n", + &(desc[0]), desc[0].next_desc_addr, + &(desc[1]), desc[1].next_desc_addr, + desc[0].x_count, desc[0].y_count, + desc[0].start_addr, desc[0].cfg); +} + +static int sport_start(struct sport_device *sport) +{ + enable_dma(sport->dma_rx_chan); + enable_dma(sport->dma_tx_chan); + sport->regs->rcr1 |= RSPEN; + sport->regs->tcr1 |= TSPEN; + SSYNC(); + + return 0; +} + +static int sport_stop(struct sport_device *sport) +{ + sport->regs->tcr1 &= ~TSPEN; + sport->regs->rcr1 &= ~RSPEN; + SSYNC(); + + disable_dma(sport->dma_rx_chan); + disable_dma(sport->dma_tx_chan); + return 0; +} + +static inline int sport_hook_rx_dummy(struct sport_device *sport) +{ + struct dmasg *desc, temp_desc; + unsigned long flags; + + BUG_ON(sport->dummy_rx_desc == NULL); + BUG_ON(sport->curr_rx_desc == sport->dummy_rx_desc); + + /* Maybe the dummy buffer descriptor ring is damaged */ + sport->dummy_rx_desc->next_desc_addr = \ + (unsigned long)(sport->dummy_rx_desc+1); + + local_irq_save(flags); + desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_rx_chan); + /* Copy the descriptor which will be damaged to backup */ + temp_desc = *desc; + desc->x_count = 0xa; + desc->y_count = 0; + desc->next_desc_addr = (unsigned long)(sport->dummy_rx_desc); + local_irq_restore(flags); + /* Waiting for dummy buffer descriptor is already hooked*/ + while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) - + sizeof(struct dmasg)) != + (unsigned long)sport->dummy_rx_desc) + ; + sport->curr_rx_desc = sport->dummy_rx_desc; + /* Restore the damaged descriptor */ + *desc = temp_desc; + + return 0; +} + +static inline int sport_rx_dma_start(struct sport_device *sport, int dummy) +{ + if (dummy) { + sport->dummy_rx_desc->next_desc_addr = \ + (unsigned long) sport->dummy_rx_desc; + sport->curr_rx_desc = sport->dummy_rx_desc; + } else + sport->curr_rx_desc = sport->dma_rx_desc; + + set_dma_next_desc_addr(sport->dma_rx_chan, \ + (unsigned long)(sport->curr_rx_desc)); + set_dma_x_count(sport->dma_rx_chan, 0); + set_dma_x_modify(sport->dma_rx_chan, 0); + set_dma_config(sport->dma_rx_chan, (DMAFLOW_LARGE | NDSIZE_9 | \ + WDSIZE_32 | WNR)); + set_dma_curr_addr(sport->dma_rx_chan, sport->curr_rx_desc->start_addr); + SSYNC(); + + return 0; +} + +static inline int sport_tx_dma_start(struct sport_device *sport, int dummy) +{ + if (dummy) { + sport->dummy_tx_desc->next_desc_addr = \ + (unsigned long) sport->dummy_tx_desc; + sport->curr_tx_desc = sport->dummy_tx_desc; + } else + sport->curr_tx_desc = sport->dma_tx_desc; + + set_dma_next_desc_addr(sport->dma_tx_chan, \ + (unsigned long)(sport->curr_tx_desc)); + set_dma_x_count(sport->dma_tx_chan, 0); + set_dma_x_modify(sport->dma_tx_chan, 0); + set_dma_config(sport->dma_tx_chan, + (DMAFLOW_LARGE | NDSIZE_9 | WDSIZE_32)); + set_dma_curr_addr(sport->dma_tx_chan, sport->curr_tx_desc->start_addr); + SSYNC(); + + return 0; +} + +int sport_rx_start(struct sport_device *sport) +{ + unsigned long flags; + pr_debug("%s enter\n", __func__); + if (sport->rx_run) + return -EBUSY; + if (sport->tx_run) { + /* tx is running, rx is not running */ + BUG_ON(sport->dma_rx_desc == NULL); + BUG_ON(sport->curr_rx_desc != sport->dummy_rx_desc); + local_irq_save(flags); + while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) - + sizeof(struct dmasg)) != + (unsigned long)sport->dummy_rx_desc) + ; + sport->dummy_rx_desc->next_desc_addr = + (unsigned long)(sport->dma_rx_desc); + local_irq_restore(flags); + sport->curr_rx_desc = sport->dma_rx_desc; + } else { + sport_tx_dma_start(sport, 1); + sport_rx_dma_start(sport, 0); + sport_start(sport); + } + + sport->rx_run = 1; + + return 0; +} +EXPORT_SYMBOL(sport_rx_start); + +int sport_rx_stop(struct sport_device *sport) +{ + pr_debug("%s enter\n", __func__); + + if (!sport->rx_run) + return 0; + if (sport->tx_run) { + /* TX dma is still running, hook the dummy buffer */ + sport_hook_rx_dummy(sport); + } else { + /* Both rx and tx dma will be stopped */ + sport_stop(sport); + sport->curr_rx_desc = NULL; + sport->curr_tx_desc = NULL; + } + + sport->rx_run = 0; + + return 0; +} +EXPORT_SYMBOL(sport_rx_stop); + +static inline int sport_hook_tx_dummy(struct sport_device *sport) +{ + struct dmasg *desc, temp_desc; + unsigned long flags; + + BUG_ON(sport->dummy_tx_desc == NULL); + BUG_ON(sport->curr_tx_desc == sport->dummy_tx_desc); + + sport->dummy_tx_desc->next_desc_addr = \ + (unsigned long)(sport->dummy_tx_desc+1); + + /* Shorten the time on last normal descriptor */ + local_irq_save(flags); + desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_tx_chan); + /* Store the descriptor which will be damaged */ + temp_desc = *desc; + desc->x_count = 0xa; + desc->y_count = 0; + desc->next_desc_addr = (unsigned long)(sport->dummy_tx_desc); + local_irq_restore(flags); + /* Waiting for dummy buffer descriptor is already hooked*/ + while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - \ + sizeof(struct dmasg)) != \ + (unsigned long)sport->dummy_tx_desc) + ; + sport->curr_tx_desc = sport->dummy_tx_desc; + /* Restore the damaged descriptor */ + *desc = temp_desc; + + return 0; +} + +int sport_tx_start(struct sport_device *sport) +{ + unsigned flags; + pr_debug("%s: tx_run:%d, rx_run:%d\n", __func__, + sport->tx_run, sport->rx_run); + if (sport->tx_run) + return -EBUSY; + if (sport->rx_run) { + BUG_ON(sport->dma_tx_desc == NULL); + BUG_ON(sport->curr_tx_desc != sport->dummy_tx_desc); + /* Hook the normal buffer descriptor */ + local_irq_save(flags); + while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - + sizeof(struct dmasg)) != + (unsigned long)sport->dummy_tx_desc) + ; + sport->dummy_tx_desc->next_desc_addr = + (unsigned long)(sport->dma_tx_desc); + local_irq_restore(flags); + sport->curr_tx_desc = sport->dma_tx_desc; + } else { + + sport_tx_dma_start(sport, 0); + /* Let rx dma run the dummy buffer */ + sport_rx_dma_start(sport, 1); + sport_start(sport); + } + sport->tx_run = 1; + return 0; +} +EXPORT_SYMBOL(sport_tx_start); + +int sport_tx_stop(struct sport_device *sport) +{ + if (!sport->tx_run) + return 0; + if (sport->rx_run) { + /* RX is still running, hook the dummy buffer */ + sport_hook_tx_dummy(sport); + } else { + /* Both rx and tx dma stopped */ + sport_stop(sport); + sport->curr_rx_desc = NULL; + sport->curr_tx_desc = NULL; + } + + sport->tx_run = 0; + + return 0; +} +EXPORT_SYMBOL(sport_tx_stop); + +static inline int compute_wdsize(size_t wdsize) +{ + switch (wdsize) { + case 1: + return WDSIZE_8; + case 2: + return WDSIZE_16; + case 4: + default: + return WDSIZE_32; + } +} + +int sport_config_rx_dma(struct sport_device *sport, void *buf, + int fragcount, size_t fragsize) +{ + unsigned int x_count; + unsigned int y_count; + unsigned int cfg; + dma_addr_t addr; + + pr_debug("%s buf:%p, frag:%d, fragsize:0x%lx\n", __func__, \ + buf, fragcount, fragsize); + + x_count = fragsize / sport->wdsize; + y_count = 0; + + /* for fragments larger than 64k words we use 2d dma, + * denote fragecount as two numbers' mutliply and both of them + * are less than 64k.*/ + if (x_count >= 0x10000) { + int i, count = x_count; + + for (i = 16; i > 0; i--) { + x_count = 1 << i; + if ((count & (x_count - 1)) == 0) { + y_count = count >> i; + if (y_count < 0x10000) + break; + } + } + if (i == 0) + return -EINVAL; + } + pr_debug("%s(x_count:0x%x, y_count:0x%x)\n", __func__, + x_count, y_count); + + if (sport->dma_rx_desc) + dma_free_coherent(NULL, sport->rx_desc_bytes, + sport->dma_rx_desc, 0); + + /* Allocate a new descritor ring as current one. */ + sport->dma_rx_desc = dma_alloc_coherent(NULL, \ + fragcount * sizeof(struct dmasg), &addr, 0); + sport->rx_desc_bytes = fragcount * sizeof(struct dmasg); + + if (!sport->dma_rx_desc) { + pr_err("Failed to allocate memory for rx desc\n"); + return -ENOMEM; + } + + sport->rx_buf = buf; + sport->rx_fragsize = fragsize; + sport->rx_frags = fragcount; + + cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | WNR | \ + (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */ + + if (y_count != 0) + cfg |= DMA2D; + + setup_desc(sport->dma_rx_desc, buf, fragcount, fragsize, + cfg|DMAEN, x_count, y_count, sport->wdsize); + + return 0; +} +EXPORT_SYMBOL(sport_config_rx_dma); + +int sport_config_tx_dma(struct sport_device *sport, void *buf, \ + int fragcount, size_t fragsize) +{ + unsigned int x_count; + unsigned int y_count; + unsigned int cfg; + dma_addr_t addr; + + pr_debug("%s buf:%p, fragcount:%d, fragsize:0x%lx\n", + __func__, buf, fragcount, fragsize); + + x_count = fragsize/sport->wdsize; + y_count = 0; + + /* for fragments larger than 64k words we use 2d dma, + * denote fragecount as two numbers' mutliply and both of them + * are less than 64k.*/ + if (x_count >= 0x10000) { + int i, count = x_count; + + for (i = 16; i > 0; i--) { + x_count = 1 << i; + if ((count & (x_count - 1)) == 0) { + y_count = count >> i; + if (y_count < 0x10000) + break; + } + } + if (i == 0) + return -EINVAL; + } + pr_debug("%s x_count:0x%x, y_count:0x%x\n", __func__, + x_count, y_count); + + + if (sport->dma_tx_desc) { + dma_free_coherent(NULL, sport->tx_desc_bytes, \ + sport->dma_tx_desc, 0); + } + + sport->dma_tx_desc = dma_alloc_coherent(NULL, \ + fragcount * sizeof(struct dmasg), &addr, 0); + sport->tx_desc_bytes = fragcount * sizeof(struct dmasg); + if (!sport->dma_tx_desc) { + pr_err("Failed to allocate memory for tx desc\n"); + return -ENOMEM; + } + + sport->tx_buf = buf; + sport->tx_fragsize = fragsize; + sport->tx_frags = fragcount; + cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | \ + (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */ + + if (y_count != 0) + cfg |= DMA2D; + + setup_desc(sport->dma_tx_desc, buf, fragcount, fragsize, + cfg|DMAEN, x_count, y_count, sport->wdsize); + + return 0; +} +EXPORT_SYMBOL(sport_config_tx_dma); + +/* setup dummy dma descriptor ring, which don't generate interrupts, + * the x_modify is set to 0 */ +static int sport_config_rx_dummy(struct sport_device *sport) +{ + struct dmasg *desc; + unsigned config; + + pr_debug("%s entered\n", __func__); +#if L1_DATA_A_LENGTH != 0 + desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc)); +#else + { + dma_addr_t addr; + desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0); + } +#endif + if (desc == NULL) { + pr_err("Failed to allocate memory for dummy rx desc\n"); + return -ENOMEM; + } + memset(desc, 0, 2 * sizeof(*desc)); + sport->dummy_rx_desc = desc; + desc->start_addr = (unsigned long)sport->dummy_buf; + config = DMAFLOW_LARGE | NDSIZE_9 | compute_wdsize(sport->wdsize) + | WNR | DMAEN; + desc->cfg = config; + desc->x_count = sport->dummy_count/sport->wdsize; + desc->x_modify = sport->wdsize; + desc->y_count = 0; + desc->y_modify = 0; + memcpy(desc+1, desc, sizeof(*desc)); + desc->next_desc_addr = (unsigned long)(desc+1); + desc[1].next_desc_addr = (unsigned long)desc; + return 0; +} + +static int sport_config_tx_dummy(struct sport_device *sport) +{ + struct dmasg *desc; + unsigned int config; + + pr_debug("%s entered\n", __func__); + +#if L1_DATA_A_LENGTH != 0 + desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc)); +#else + { + dma_addr_t addr; + desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0); + } +#endif + if (!desc) { + pr_err("Failed to allocate memory for dummy tx desc\n"); + return -ENOMEM; + } + memset(desc, 0, 2 * sizeof(*desc)); + sport->dummy_tx_desc = desc; + desc->start_addr = (unsigned long)sport->dummy_buf + \ + sport->dummy_count; + config = DMAFLOW_LARGE | NDSIZE_9 | + compute_wdsize(sport->wdsize) | DMAEN; + desc->cfg = config; + desc->x_count = sport->dummy_count/sport->wdsize; + desc->x_modify = sport->wdsize; + desc->y_count = 0; + desc->y_modify = 0; + memcpy(desc+1, desc, sizeof(*desc)); + desc->next_desc_addr = (unsigned long)(desc+1); + desc[1].next_desc_addr = (unsigned long)desc; + return 0; +} + +unsigned long sport_curr_offset_rx(struct sport_device *sport) +{ + unsigned long curr = get_dma_curr_addr(sport->dma_rx_chan); + + return (unsigned char *)curr - sport->rx_buf; +} +EXPORT_SYMBOL(sport_curr_offset_rx); + +unsigned long sport_curr_offset_tx(struct sport_device *sport) +{ + unsigned long curr = get_dma_curr_addr(sport->dma_tx_chan); + + return (unsigned char *)curr - sport->tx_buf; +} +EXPORT_SYMBOL(sport_curr_offset_tx); + +void sport_incfrag(struct sport_device *sport, int *frag, int tx) +{ + ++(*frag); + if (tx == 1 && *frag == sport->tx_frags) + *frag = 0; + + if (tx == 0 && *frag == sport->rx_frags) + *frag = 0; +} +EXPORT_SYMBOL(sport_incfrag); + +void sport_decfrag(struct sport_device *sport, int *frag, int tx) +{ + --(*frag); + if (tx == 1 && *frag == 0) + *frag = sport->tx_frags; + + if (tx == 0 && *frag == 0) + *frag = sport->rx_frags; +} +EXPORT_SYMBOL(sport_decfrag); + +static int sport_check_status(struct sport_device *sport, + unsigned int *sport_stat, + unsigned int *rx_stat, + unsigned int *tx_stat) +{ + int status = 0; + + if (sport_stat) { + SSYNC(); + status = sport->regs->stat; + if (status & (TOVF|TUVF|ROVF|RUVF)) + sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF)); + SSYNC(); + *sport_stat = status; + } + + if (rx_stat) { + SSYNC(); + status = get_dma_curr_irqstat(sport->dma_rx_chan); + if (status & (DMA_DONE|DMA_ERR)) + clear_dma_irqstat(sport->dma_rx_chan); + SSYNC(); + *rx_stat = status; + } + + if (tx_stat) { + SSYNC(); + status = get_dma_curr_irqstat(sport->dma_tx_chan); + if (status & (DMA_DONE|DMA_ERR)) + clear_dma_irqstat(sport->dma_tx_chan); + SSYNC(); + *tx_stat = status; + } + + return 0; +} + +int sport_dump_stat(struct sport_device *sport, char *buf, size_t len) +{ + int ret; + + ret = snprintf(buf, len, + "sts: 0x%04x\n" + "rx dma %d sts: 0x%04x tx dma %d sts: 0x%04x\n", + sport->regs->stat, + sport->dma_rx_chan, + get_dma_curr_irqstat(sport->dma_rx_chan), + sport->dma_tx_chan, + get_dma_curr_irqstat(sport->dma_tx_chan)); + buf += ret; + len -= ret; + + ret += snprintf(buf, len, + "curr_rx_desc:0x%p, curr_tx_desc:0x%p\n" + "dma_rx_desc:0x%p, dma_tx_desc:0x%p\n" + "dummy_rx_desc:0x%p, dummy_tx_desc:0x%p\n", + sport->curr_rx_desc, sport->curr_tx_desc, + sport->dma_rx_desc, sport->dma_tx_desc, + sport->dummy_rx_desc, sport->dummy_tx_desc); + + return ret; +} + +static irqreturn_t rx_handler(int irq, void *dev_id) +{ + unsigned int rx_stat; + struct sport_device *sport = dev_id; + + pr_debug("%s enter\n", __func__); + sport_check_status(sport, NULL, &rx_stat, NULL); + if (!(rx_stat & DMA_DONE)) + pr_err("rx dma is already stopped\n"); + + if (sport->rx_callback) { + sport->rx_callback(sport->rx_data); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t tx_handler(int irq, void *dev_id) +{ + unsigned int tx_stat; + struct sport_device *sport = dev_id; + pr_debug("%s enter\n", __func__); + sport_check_status(sport, NULL, NULL, &tx_stat); + if (!(tx_stat & DMA_DONE)) { + pr_err("tx dma is already stopped\n"); + return IRQ_HANDLED; + } + if (sport->tx_callback) { + sport->tx_callback(sport->tx_data); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t err_handler(int irq, void *dev_id) +{ + unsigned int status = 0; + struct sport_device *sport = dev_id; + + pr_debug("%s\n", __func__); + if (sport_check_status(sport, &status, NULL, NULL)) { + pr_err("error checking status ??"); + return IRQ_NONE; + } + + if (status & (TOVF|TUVF|ROVF|RUVF)) { + pr_info("sport status error:%s%s%s%s\n", + status & TOVF ? " TOVF" : "", + status & TUVF ? " TUVF" : "", + status & ROVF ? " ROVF" : "", + status & RUVF ? " RUVF" : ""); + if (status & TOVF || status & TUVF) { + disable_dma(sport->dma_tx_chan); + if (sport->tx_run) + sport_tx_dma_start(sport, 0); + else + sport_tx_dma_start(sport, 1); + enable_dma(sport->dma_tx_chan); + } else { + disable_dma(sport->dma_rx_chan); + if (sport->rx_run) + sport_rx_dma_start(sport, 0); + else + sport_rx_dma_start(sport, 1); + enable_dma(sport->dma_rx_chan); + } + } + status = sport->regs->stat; + if (status & (TOVF|TUVF|ROVF|RUVF)) + sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF)); + SSYNC(); + + if (sport->err_callback) + sport->err_callback(sport->err_data); + + return IRQ_HANDLED; +} + +int sport_set_rx_callback(struct sport_device *sport, + void (*rx_callback)(void *), void *rx_data) +{ + BUG_ON(rx_callback == NULL); + sport->rx_callback = rx_callback; + sport->rx_data = rx_data; + + return 0; +} +EXPORT_SYMBOL(sport_set_rx_callback); + +int sport_set_tx_callback(struct sport_device *sport, + void (*tx_callback)(void *), void *tx_data) +{ + BUG_ON(tx_callback == NULL); + sport->tx_callback = tx_callback; + sport->tx_data = tx_data; + + return 0; +} +EXPORT_SYMBOL(sport_set_tx_callback); + +int sport_set_err_callback(struct sport_device *sport, + void (*err_callback)(void *), void *err_data) +{ + BUG_ON(err_callback == NULL); + sport->err_callback = err_callback; + sport->err_data = err_data; + + return 0; +} +EXPORT_SYMBOL(sport_set_err_callback); + +struct sport_device *sport_init(struct sport_param *param, unsigned wdsize, + unsigned dummy_count, void *private_data) +{ + int ret; + struct sport_device *sport; + pr_debug("%s enter\n", __func__); + BUG_ON(param == NULL); + BUG_ON(wdsize == 0 || dummy_count == 0); + sport = kmalloc(sizeof(struct sport_device), GFP_KERNEL); + if (!sport) { + pr_err("Failed to allocate for sport device\n"); + return NULL; + } + + memset(sport, 0, sizeof(struct sport_device)); + sport->dma_rx_chan = param->dma_rx_chan; + sport->dma_tx_chan = param->dma_tx_chan; + sport->err_irq = param->err_irq; + sport->regs = param->regs; + sport->private_data = private_data; + + if (request_dma(sport->dma_rx_chan, "SPORT RX Data") == -EBUSY) { + pr_err("Failed to request RX dma %d\n", \ + sport->dma_rx_chan); + goto __init_err1; + } + if (set_dma_callback(sport->dma_rx_chan, rx_handler, sport) != 0) { + pr_err("Failed to request RX irq %d\n", \ + sport->dma_rx_chan); + goto __init_err2; + } + + if (request_dma(sport->dma_tx_chan, "SPORT TX Data") == -EBUSY) { + pr_err("Failed to request TX dma %d\n", \ + sport->dma_tx_chan); + goto __init_err2; + } + + if (set_dma_callback(sport->dma_tx_chan, tx_handler, sport) != 0) { + pr_err("Failed to request TX irq %d\n", \ + sport->dma_tx_chan); + goto __init_err3; + } + + if (request_irq(sport->err_irq, err_handler, IRQF_SHARED, "SPORT err", + sport) < 0) { + pr_err("Failed to request err irq:%d\n", \ + sport->err_irq); + goto __init_err3; + } + + pr_err("dma rx:%d tx:%d, err irq:%d, regs:%p\n", + sport->dma_rx_chan, sport->dma_tx_chan, + sport->err_irq, sport->regs); + + sport->wdsize = wdsize; + sport->dummy_count = dummy_count; + +#if L1_DATA_A_LENGTH != 0 + sport->dummy_buf = l1_data_sram_alloc(dummy_count * 2); +#else + sport->dummy_buf = kmalloc(dummy_count * 2, GFP_KERNEL); +#endif + if (sport->dummy_buf == NULL) { + pr_err("Failed to allocate dummy buffer\n"); + goto __error; + } + + memset(sport->dummy_buf, 0, dummy_count * 2); + ret = sport_config_rx_dummy(sport); + if (ret) { + pr_err("Failed to config rx dummy ring\n"); + goto __error; + } + ret = sport_config_tx_dummy(sport); + if (ret) { + pr_err("Failed to config tx dummy ring\n"); + goto __error; + } + + return sport; +__error: + free_irq(sport->err_irq, sport); +__init_err3: + free_dma(sport->dma_tx_chan); +__init_err2: + free_dma(sport->dma_rx_chan); +__init_err1: + kfree(sport); + return NULL; +} +EXPORT_SYMBOL(sport_init); + +void sport_done(struct sport_device *sport) +{ + if (sport == NULL) + return; + + sport_stop(sport); + if (sport->dma_rx_desc) + dma_free_coherent(NULL, sport->rx_desc_bytes, + sport->dma_rx_desc, 0); + if (sport->dma_tx_desc) + dma_free_coherent(NULL, sport->tx_desc_bytes, + sport->dma_tx_desc, 0); + +#if L1_DATA_A_LENGTH != 0 + l1_data_sram_free(sport->dummy_rx_desc); + l1_data_sram_free(sport->dummy_tx_desc); + l1_data_sram_free(sport->dummy_buf); +#else + dma_free_coherent(NULL, 2*sizeof(struct dmasg), + sport->dummy_rx_desc, 0); + dma_free_coherent(NULL, 2*sizeof(struct dmasg), + sport->dummy_tx_desc, 0); + kfree(sport->dummy_buf); +#endif + free_dma(sport->dma_rx_chan); + free_dma(sport->dma_tx_chan); + free_irq(sport->err_irq, sport); + + kfree(sport); + sport = NULL; +} +EXPORT_SYMBOL(sport_done); +/* +* It is only used to send several bytes when dma is not enabled + * sport controller is configured but not enabled. + * Multichannel cannot works with pio mode */ +/* Used by ac97 to write and read codec register */ +int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \ + u8 *in_data, int len) +{ + unsigned short dma_config; + unsigned short status; + unsigned long flags; + unsigned long wait = 0; + + pr_debug("%s enter, out_data:%p, in_data:%p len:%d\n", \ + __func__, out_data, in_data, len); + pr_debug("tcr1:0x%04x, tcr2:0x%04x, tclkdiv:0x%04x, tfsdiv:0x%04x\n" + "mcmc1:0x%04x, mcmc2:0x%04x\n", + sport->regs->tcr1, sport->regs->tcr2, + sport->regs->tclkdiv, sport->regs->tfsdiv, + sport->regs->mcmc1, sport->regs->mcmc2); + flush_dcache_range((unsigned)out_data, (unsigned)(out_data + len)); + + /* Enable tx dma */ + dma_config = (RESTART | WDSIZE_16 | DI_EN); + set_dma_start_addr(sport->dma_tx_chan, (unsigned long)out_data); + set_dma_x_count(sport->dma_tx_chan, len/2); + set_dma_x_modify(sport->dma_tx_chan, 2); + set_dma_config(sport->dma_tx_chan, dma_config); + enable_dma(sport->dma_tx_chan); + + if (in_data != NULL) { + invalidate_dcache_range((unsigned)in_data, \ + (unsigned)(in_data + len)); + /* Enable rx dma */ + dma_config = (RESTART | WDSIZE_16 | WNR | DI_EN); + set_dma_start_addr(sport->dma_rx_chan, (unsigned long)in_data); + set_dma_x_count(sport->dma_rx_chan, len/2); + set_dma_x_modify(sport->dma_rx_chan, 2); + set_dma_config(sport->dma_rx_chan, dma_config); + enable_dma(sport->dma_rx_chan); + } + + local_irq_save(flags); + sport->regs->tcr1 |= TSPEN; + sport->regs->rcr1 |= RSPEN; + SSYNC(); + + status = get_dma_curr_irqstat(sport->dma_tx_chan); + while (status & DMA_RUN) { + udelay(1); + status = get_dma_curr_irqstat(sport->dma_tx_chan); + pr_debug("DMA status:0x%04x\n", status); + if (wait++ > 100) + goto __over; + } + status = sport->regs->stat; + wait = 0; + + while (!(status & TXHRE)) { + pr_debug("sport status:0x%04x\n", status); + udelay(1); + status = *(unsigned short *)&sport->regs->stat; + if (wait++ > 1000) + goto __over; + } + /* Wait for the last byte sent out */ + udelay(20); + pr_debug("sport status:0x%04x\n", status); + +__over: + sport->regs->tcr1 &= ~TSPEN; + sport->regs->rcr1 &= ~RSPEN; + SSYNC(); + disable_dma(sport->dma_tx_chan); + /* Clear the status */ + clear_dma_irqstat(sport->dma_tx_chan); + if (in_data != NULL) { + disable_dma(sport->dma_rx_chan); + clear_dma_irqstat(sport->dma_rx_chan); + } + SSYNC(); + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL(sport_send_and_recv); + +MODULE_AUTHOR("Roy Huang"); +MODULE_DESCRIPTION("SPORT driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-sport.h b/sound/soc/blackfin/bf5xx-sport.h new file mode 100644 index 000000000000..fcadcc081f7f --- /dev/null +++ b/sound/soc/blackfin/bf5xx-sport.h @@ -0,0 +1,194 @@ +/* + * File: bf5xx_ac97_sport.h + * Based on: + * Author: Roy Huang <roy.huang@analog.com> + * + * Created: + * Description: + * + * Copyright 2004-2007 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef __BF5XX_SPORT_H__ +#define __BF5XX_SPORT_H__ + +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <asm/dma.h> + +struct sport_register { + u16 tcr1; u16 reserved0; + u16 tcr2; u16 reserved1; + u16 tclkdiv; u16 reserved2; + u16 tfsdiv; u16 reserved3; + u32 tx; + u32 reserved_l0; + u32 rx; + u32 reserved_l1; + u16 rcr1; u16 reserved4; + u16 rcr2; u16 reserved5; + u16 rclkdiv; u16 reserved6; + u16 rfsdiv; u16 reserved7; + u16 stat; u16 reserved8; + u16 chnl; u16 reserved9; + u16 mcmc1; u16 reserved10; + u16 mcmc2; u16 reserved11; + u32 mtcs0; + u32 mtcs1; + u32 mtcs2; + u32 mtcs3; + u32 mrcs0; + u32 mrcs1; + u32 mrcs2; + u32 mrcs3; +}; + +#define DESC_ELEMENT_COUNT 9 + +struct sport_device { + int dma_rx_chan; + int dma_tx_chan; + int err_irq; + struct sport_register *regs; + + unsigned char *rx_buf; + unsigned char *tx_buf; + unsigned int rx_fragsize; + unsigned int tx_fragsize; + unsigned int rx_frags; + unsigned int tx_frags; + unsigned int wdsize; + + /* for dummy dma transfer */ + void *dummy_buf; + unsigned int dummy_count; + + /* DMA descriptor ring head of current audio stream*/ + struct dmasg *dma_rx_desc; + struct dmasg *dma_tx_desc; + unsigned int rx_desc_bytes; + unsigned int tx_desc_bytes; + + unsigned int rx_run:1; /* rx is running */ + unsigned int tx_run:1; /* tx is running */ + + struct dmasg *dummy_rx_desc; + struct dmasg *dummy_tx_desc; + + struct dmasg *curr_rx_desc; + struct dmasg *curr_tx_desc; + + int rx_curr_frag; + int tx_curr_frag; + + unsigned int rcr1; + unsigned int rcr2; + int rx_tdm_count; + + unsigned int tcr1; + unsigned int tcr2; + int tx_tdm_count; + + void (*rx_callback)(void *data); + void *rx_data; + void (*tx_callback)(void *data); + void *tx_data; + void (*err_callback)(void *data); + void *err_data; + unsigned char *tx_dma_buf; + unsigned char *rx_dma_buf; +#ifdef CONFIG_SND_MMAP_SUPPORT + dma_addr_t tx_dma_phy; + dma_addr_t rx_dma_phy; + int tx_pos;/*pcm sample count*/ + int rx_pos; + unsigned int tx_buffer_size; + unsigned int rx_buffer_size; + int tx_delay_pos; + int once; +#endif + void *private_data; +}; + +extern struct sport_device *sport_handle; + +struct sport_param { + int dma_rx_chan; + int dma_tx_chan; + int err_irq; + struct sport_register *regs; +}; + +struct sport_device *sport_init(struct sport_param *param, unsigned wdsize, + unsigned dummy_count, void *private_data); + +void sport_done(struct sport_device *sport); + +/* first use these ...*/ + +/* note: multichannel is in units of 8 channels, tdm_count is number of channels + * NOT / 8 ! all channels are enabled by default */ +int sport_set_multichannel(struct sport_device *sport, int tdm_count, + u32 mask, int packed); + +int sport_config_rx(struct sport_device *sport, + unsigned int rcr1, unsigned int rcr2, + unsigned int clkdiv, unsigned int fsdiv); + +int sport_config_tx(struct sport_device *sport, + unsigned int tcr1, unsigned int tcr2, + unsigned int clkdiv, unsigned int fsdiv); + +/* ... then these: */ + +/* buffer size (in bytes) == fragcount * fragsize_bytes */ + +/* this is not a very general api, it sets the dma to 2d autobuffer mode */ + +int sport_config_rx_dma(struct sport_device *sport, void *buf, + int fragcount, size_t fragsize_bytes); + +int sport_config_tx_dma(struct sport_device *sport, void *buf, + int fragcount, size_t fragsize_bytes); + +int sport_tx_start(struct sport_device *sport); +int sport_tx_stop(struct sport_device *sport); +int sport_rx_start(struct sport_device *sport); +int sport_rx_stop(struct sport_device *sport); + +/* for use in interrupt handler */ +unsigned long sport_curr_offset_rx(struct sport_device *sport); +unsigned long sport_curr_offset_tx(struct sport_device *sport); + +void sport_incfrag(struct sport_device *sport, int *frag, int tx); +void sport_decfrag(struct sport_device *sport, int *frag, int tx); + +int sport_set_rx_callback(struct sport_device *sport, + void (*rx_callback)(void *), void *rx_data); +int sport_set_tx_callback(struct sport_device *sport, + void (*tx_callback)(void *), void *tx_data); +int sport_set_err_callback(struct sport_device *sport, + void (*err_callback)(void *), void *err_data); + +int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \ + u8 *in_data, int len); +#endif /* BF53X_SPORT_H */ diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c new file mode 100644 index 000000000000..e15f67fd7769 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ssm2602.c @@ -0,0 +1,186 @@ +/* + * File: sound/soc/blackfin/bf5xx-ssm2602.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: board driver for SSM2602 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> + +#include <asm/dma.h> +#include <asm/portmux.h> +#include <linux/gpio.h> +#include "../codecs/ssm2602.h" +#include "bf5xx-sport.h" +#include "bf5xx-i2s-pcm.h" +#include "bf5xx-i2s.h" + +static struct snd_soc_machine bf5xx_ssm2602; + +static int bf5xx_ssm2602_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + pr_debug("%s enter\n", __func__); + cpu_dai->private_data = sport_handle; + return 0; +} + +static int bf5xx_ssm2602_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + pr_debug("%s rate %d format %x\n", __func__, params_rate(params), + params_format(params)); + /* + * If you are using a crystal source which frequency is not 12MHz + * then modify the below case statement with frequency of the crystal. + * + * If you are using the SPORT to generate clocking then this is + * where to do it. + */ + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + case 11025: + case 22050: + case 44100: + clk = 12000000; + break; + } + + /* + * CODEC is master for BCLK and LRC in this configuration. + */ + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = codec_dai->dai_ops.set_sysclk(codec_dai, SSM2602_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops bf5xx_ssm2602_ops = { + .startup = bf5xx_ssm2602_startup, + .hw_params = bf5xx_ssm2602_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_ssm2602_dai = { + .name = "ssm2602", + .stream_name = "SSM2602", + .cpu_dai = &bf5xx_i2s_dai, + .codec_dai = &ssm2602_dai, + .ops = &bf5xx_ssm2602_ops, +}; + +/* + * SSM2602 2 wire address is determined by CSB + * state during powerup. + * low = 0x1a + * high = 0x1b + */ + +static struct ssm2602_setup_data bf5xx_ssm2602_setup = { + .i2c_bus = 0, + .i2c_address = 0x1b, +}; + +static struct snd_soc_machine bf5xx_ssm2602 = { + .name = "bf5xx_ssm2602", + .dai_link = &bf5xx_ssm2602_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_ssm2602_snd_devdata = { + .machine = &bf5xx_ssm2602, + .platform = &bf5xx_i2s_soc_platform, + .codec_dev = &soc_codec_dev_ssm2602, + .codec_data = &bf5xx_ssm2602_setup, +}; + +static struct platform_device *bf52x_ssm2602_snd_device; + +static int __init bf5xx_ssm2602_init(void) +{ + int ret; + + pr_debug("%s enter\n", __func__); + bf52x_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf52x_ssm2602_snd_device) + return -ENOMEM; + + platform_set_drvdata(bf52x_ssm2602_snd_device, + &bf5xx_ssm2602_snd_devdata); + bf5xx_ssm2602_snd_devdata.dev = &bf52x_ssm2602_snd_device->dev; + ret = platform_device_add(bf52x_ssm2602_snd_device); + + if (ret) + platform_device_put(bf52x_ssm2602_snd_device); + + return ret; +} + +static void __exit bf5xx_ssm2602_exit(void) +{ + pr_debug("%s enter\n", __func__); + platform_device_unregister(bf52x_ssm2602_snd_device); +} + +module_init(bf5xx_ssm2602_init); +module_exit(bf5xx_ssm2602_exit); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ALSA SoC SSM2602 BF527-EZKIT"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3903ab7dfa4a..4975d8573e4f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1,31 +1,50 @@ -config SND_SOC_AC97_CODEC - tristate - depends on SND_SOC +config SND_SOC_ALL_CODECS + tristate "Build all ASoC CODEC drivers" + depends on I2C + select SPI + select SPI_MASTER + select SND_SOC_AD73311 + select SND_SOC_AK4535 + select SND_SOC_CS4270 + select SND_SOC_SSM2602 + select SND_SOC_TLV320AIC23 + select SND_SOC_TLV320AIC26 + select SND_SOC_TLV320AIC3X + select SND_SOC_UDA1380 + select SND_SOC_WM8510 + select SND_SOC_WM8580 + select SND_SOC_WM8731 + select SND_SOC_WM8750 + select SND_SOC_WM8753 + select SND_SOC_WM8900 + select SND_SOC_WM8903 + select SND_SOC_WM8971 + select SND_SOC_WM8990 + help + Normally ASoC codec drivers are only built if a machine driver which + uses them is also built since they are only usable with a machine + driver. Selecting this option will allow these drivers to be built + without an explicit machine driver for test and development purposes. -config SND_SOC_WM8731 - tristate - depends on SND_SOC + If unsure select "N". -config SND_SOC_WM8750 + +config SND_SOC_AC97_CODEC tristate - depends on SND_SOC + select SND_AC97_CODEC -config SND_SOC_WM8753 +config SND_SOC_AD1980 tristate - depends on SND_SOC -config SND_SOC_WM9712 +config SND_SOC_AD73311 tristate - depends on SND_SOC -config SND_SOC_WM9713 +config SND_SOC_AK4535 tristate - depends on SND_SOC # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate - depends on SND_SOC # Cirrus Logic CS4270 Codec Hardware Mute Support # Select if you have external muting circuitry attached to your CS4270. @@ -41,6 +60,53 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_SSM2602 + tristate + +config SND_SOC_TLV320AIC23 + tristate + depends on I2C + +config SND_SOC_TLV320AIC26 + tristate "TI TLV320AIC26 Codec support" + depends on SPI + config SND_SOC_TLV320AIC3X tristate - depends on SND_SOC && I2C + depends on I2C + +config SND_SOC_UDA1380 + tristate + +config SND_SOC_WM8510 + tristate + +config SND_SOC_WM8580 + tristate + +config SND_SOC_WM8731 + tristate + +config SND_SOC_WM8750 + tristate + +config SND_SOC_WM8753 + tristate + +config SND_SOC_WM8900 + tristate + +config SND_SOC_WM8903 + tristate + +config SND_SOC_WM8971 + tristate + +config SND_SOC_WM8990 + tristate + +config SND_SOC_WM9712 + tristate + +config SND_SOC_WM9713 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4e1314c9d3ec..90f0a585fc70 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,17 +1,43 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ad1980-objs := ad1980.o +snd-soc-ad73311-objs := ad73311.o +snd-soc-ak4535-objs := ak4535.o +snd-soc-cs4270-objs := cs4270.o +snd-soc-ssm2602-objs := ssm2602.o +snd-soc-tlv320aic23-objs := tlv320aic23.o +snd-soc-tlv320aic26-objs := tlv320aic26.o +snd-soc-tlv320aic3x-objs := tlv320aic3x.o +snd-soc-uda1380-objs := uda1380.o +snd-soc-wm8510-objs := wm8510.o +snd-soc-wm8580-objs := wm8580.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o +snd-soc-wm8900-objs := wm8900.o +snd-soc-wm8903-objs := wm8903.o +snd-soc-wm8971-objs := wm8971.o +snd-soc-wm8990-objs := wm8990.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o -snd-soc-cs4270-objs := cs4270.o -snd-soc-tlv320aic3x-objs := tlv320aic3x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o +obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o +obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o +obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o +obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o +obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o +obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o +obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o +obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.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_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index 2a1ffe396908..bd1ebdc6c86c 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -2,17 +2,13 @@ * ac97.c -- ALSA Soc AC97 codec support * * Copyright 2005 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 17th Oct 2005 Initial version. - * * Generic AC97 support. */ @@ -24,6 +20,7 @@ #include <sound/ac97_codec.h> #include <sound/initval.h> #include <sound/soc.h> +#include "ac97.h" #define AC97_VERSION "0.6" @@ -43,7 +40,7 @@ static int ac97_prepare(struct snd_pcm_substream *substream) SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ SNDRV_PCM_RATE_48000) -struct snd_soc_codec_dai ac97_dai = { +struct snd_soc_dai ac97_dai = { .name = "AC97 HiFi", .type = SND_SOC_DAI_AC97, .playback = { @@ -146,9 +143,34 @@ static int ac97_soc_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int ac97_soc_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_ac97_suspend(socdev->codec->ac97); + + return 0; +} + +static int ac97_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_ac97_resume(socdev->codec->ac97); + + return 0; +} +#else +#define ac97_soc_suspend NULL +#define ac97_soc_resume NULL +#endif + struct snd_soc_codec_device soc_codec_dev_ac97 = { .probe = ac97_soc_probe, .remove = ac97_soc_remove, + .suspend = ac97_soc_suspend, + .resume = ac97_soc_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_ac97); diff --git a/sound/soc/codecs/ac97.h b/sound/soc/codecs/ac97.h index 2bf6d69fd069..281aa42e2bbb 100644 --- a/sound/soc/codecs/ac97.h +++ b/sound/soc/codecs/ac97.h @@ -14,6 +14,6 @@ #define __LINUX_SND_SOC_AC97_H extern struct snd_soc_codec_device soc_codec_dev_ac97; -extern struct snd_soc_codec_dai ac97_dai; +extern struct snd_soc_dai ac97_dai; #endif diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c new file mode 100644 index 000000000000..1397b8e06c0b --- /dev/null +++ b/sound/soc/codecs/ad1980.c @@ -0,0 +1,308 @@ +/* + * ad1980.c -- ALSA Soc AD1980 codec support + * + * Copyright: Analog Device Inc. + * Author: Roy Huang <roy.huang@analog.com> + * Cliff Cai <cliff.cai@analog.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "ad1980.h" + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * AD1980 register cache + */ +static const u16 ad1980_reg[] = { + 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */ + 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */ + 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */ + 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */ + 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */ + 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */ + 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */ + 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */ +}; + +static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line", + "Stereo Mix", "Mono Mix", "Phone"}; + +static const struct soc_enum ad1980_cap_src = + SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel); + +static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = { +SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1), + +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + +SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), +SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + +SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0), +SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1), + +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + +SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1), +SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1), + +SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1), +SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + +SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0), +SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0), + +SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1), + +SOC_ENUM("Capture Source", ad1980_cap_src), + +SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0), +}; + +/* add non dapm controls */ +static int ad1980_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, snd_soc_cnew( + &ad1980_snd_ac97_controls[i], codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + switch (reg) { + case AC97_RESET: + case AC97_INT_PAGING: + case AC97_POWERDOWN: + case AC97_EXTENDED_STATUS: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return soc_ac97_ops.read(codec->ac97, reg); + default: + reg = reg >> 1; + + if (reg >= (ARRAY_SIZE(ad1980_reg))) + return -EINVAL; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg < (ARRAY_SIZE(ad1980_reg))) + cache[reg] = val; + + return 0; +} + +struct snd_soc_dai ad1980_dai = { + .name = "AC97", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(ad1980_dai); + +static int ad1980_reset(struct snd_soc_codec *codec, int try_warm) +{ + u16 retry_cnt = 0; + +retry: + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (ac97_read(codec, AC97_RESET) == 0x0090) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + /* Set bit 16slot in register 74h, then every slot will has only 16 + * bits. This command is sent out in 20bit mode, in which case the + * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/ + ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900); + + if (ac97_read(codec, AC97_RESET) != 0x0090) + goto err; + return 0; + +err: + while (retry_cnt++ < 10) + goto retry; + + printk(KERN_ERR "AD1980 AC97 reset failed\n"); + return -EIO; +} + +static int ad1980_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + u16 vendor_id2; + + printk(KERN_INFO "AD1980 SoC Audio Codec\n"); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(ad1980_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + memcpy(codec->reg_cache, ad1980_reg, sizeof(u16) * \ + ARRAY_SIZE(ad1980_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(ad1980_reg); + codec->reg_cache_step = 2; + codec->name = "AD1980"; + codec->owner = THIS_MODULE; + codec->dai = &ad1980_dai; + codec->num_dai = 1; + codec->write = ac97_write; + codec->read = ac97_read; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register AC97 codec\n"); + goto codec_err; + } + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + + ret = ad1980_reset(codec, 0); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + /* Read out vendor ID to make sure it is ad1980 */ + if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144) + goto reset_err; + + vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2); + + if (vendor_id2 != 0x5370) { + if (vendor_id2 != 0x5374) + goto reset_err; + else + printk(KERN_WARNING "ad1980: " + "Found AD1981 - only 2/2 IN/OUT Channels " + "supported\n"); + } + + ac97_write(codec, AC97_MASTER, 0x0000); /* unmute line out volume */ + ac97_write(codec, AC97_PCM, 0x0000); /* unmute PCM out volume */ + ac97_write(codec, AC97_REC_GAIN, 0x0000);/* unmute record volume */ + + ad1980_add_controls(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register card\n"); + goto reset_err; + } + + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ad1980_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad1980 = { + .probe = ad1980_soc_probe, + .remove = ad1980_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad1980); + +MODULE_DESCRIPTION("ASoC ad1980 driver"); +MODULE_AUTHOR("Roy Huang, Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h new file mode 100644 index 000000000000..db6c8500d66b --- /dev/null +++ b/sound/soc/codecs/ad1980.h @@ -0,0 +1,23 @@ +/* + * ad1980.h -- ad1980 Soc Audio driver + */ + +#ifndef _AD1980_H +#define _AD1980_H +/* Bit definition of Power-Down Control/Status Register */ +#define ADC 0x0001 +#define DAC 0x0002 +#define ANL 0x0004 +#define REF 0x0008 +#define PR0 0x0100 +#define PR1 0x0200 +#define PR2 0x0400 +#define PR3 0x0800 +#define PR4 0x1000 +#define PR5 0x2000 +#define PR6 0x4000 + +extern struct snd_soc_dai ad1980_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad1980; + +#endif diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c new file mode 100644 index 000000000000..37af8607b00a --- /dev/null +++ b/sound/soc/codecs/ad73311.c @@ -0,0 +1,107 @@ +/* + * ad73311.c -- ALSA Soc AD73311 codec support + * + * Copyright: Analog Device Inc. + * Author: Cliff Cai <cliff.cai@analog.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 25th Sep 2008 Initial version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "ad73311.h" + +struct snd_soc_dai ad73311_dai = { + .name = "AD73311", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(ad73311_dai); + +static int ad73311_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + mutex_init(&codec->mutex); + codec->name = "AD73311"; + codec->owner = THIS_MODULE; + codec->dai = &ad73311_dai; + codec->num_dai = 1; + socdev->codec = codec; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ad73311: failed to create pcms\n"); + goto pcm_err; + } + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ad73311: failed to register card\n"); + goto register_err; + } + + return ret; + +register_err: + snd_soc_free_pcms(socdev); +pcm_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ad73311_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + snd_soc_free_pcms(socdev); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad73311 = { + .probe = ad73311_soc_probe, + .remove = ad73311_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad73311); + +MODULE_DESCRIPTION("ASoC ad73311 driver"); +MODULE_AUTHOR("Cliff Cai "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h new file mode 100644 index 000000000000..507ce0c30edf --- /dev/null +++ b/sound/soc/codecs/ad73311.h @@ -0,0 +1,90 @@ +/* + * File: sound/soc/codec/ad73311.h + * Based on: + * Author: Cliff Cai <cliff.cai@analog.com> + * + * Created: Thur Sep 25, 2008 + * Description: definitions for AD73311 registers + * + * + * Modified: + * Copyright 2006 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AD73311_H__ +#define __AD73311_H__ + +#define AD_CONTROL 0x8000 +#define AD_DATA 0x0000 +#define AD_READ 0x4000 +#define AD_WRITE 0x0000 + +/* Control register A */ +#define CTRL_REG_A (0 << 8) + +#define REGA_MODE_PRO 0x00 +#define REGA_MODE_DATA 0x01 +#define REGA_MODE_MIXED 0x03 +#define REGA_DLB 0x04 +#define REGA_SLB 0x08 +#define REGA_DEVC(x) ((x & 0x7) << 4) +#define REGA_RESET 0x80 + +/* Control register B */ +#define CTRL_REG_B (1 << 8) + +#define REGB_DIRATE(x) (x & 0x3) +#define REGB_SCDIV(x) ((x & 0x3) << 2) +#define REGB_MCDIV(x) ((x & 0x7) << 4) +#define REGB_CEE (1 << 7) + +/* Control register C */ +#define CTRL_REG_C (2 << 8) + +#define REGC_PUDEV (1 << 0) +#define REGC_PUADC (1 << 3) +#define REGC_PUDAC (1 << 4) +#define REGC_PUREF (1 << 5) +#define REGC_REFUSE (1 << 6) + +/* Control register D */ +#define CTRL_REG_D (3 << 8) + +#define REGD_IGS(x) (x & 0x7) +#define REGD_RMOD (1 << 3) +#define REGD_OGS(x) ((x & 0x7) << 4) +#define REGD_MUTE (x << 7) + +/* Control register E */ +#define CTRL_REG_E (4 << 8) + +#define REGE_DA(x) (x & 0x1f) +#define REGE_IBYP (1 << 5) + +/* Control register F */ +#define CTRL_REG_F (5 << 8) + +#define REGF_SEEN (1 << 5) +#define REGF_INV (1 << 6) +#define REGF_ALB (1 << 7) + +extern struct snd_soc_dai ad73311_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad73311; +#endif diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c new file mode 100644 index 000000000000..2a89b5888e11 --- /dev/null +++ b/sound/soc/codecs/ak4535.c @@ -0,0 +1,694 @@ +/* + * ak4535.c -- AK4535 ALSA Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/initval.h> + +#include "ak4535.h" + +#define AK4535_VERSION "0.3" + +struct snd_soc_codec_device soc_codec_dev_ak4535; + +/* codec private data */ +struct ak4535_priv { + unsigned int sysclk; +}; + +/* + * ak4535 register cache + */ +static const u16 ak4535_reg[AK4535_CACHEREGNUM] = { + 0x0000, 0x0080, 0x0000, 0x0003, + 0x0002, 0x0000, 0x0011, 0x0001, + 0x0000, 0x0040, 0x0036, 0x0010, + 0x0000, 0x0000, 0x0057, 0x0000, +}; + +/* + * read ak4535 register cache + */ +static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return -1; + return cache[reg]; +} + +static inline unsigned int ak4535_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 data; + data = reg; + + if (codec->hw_write(codec->control_data, &data, 1) != 1) + return -EIO; + + if (codec->hw_read(codec->control_data, &data, 1) != 1) + return -EIO; + + return data; +}; + +/* + * write ak4535 register cache + */ +static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the AK4535 register space + */ +static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 AK4535 register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + ak4535_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static int ak4535_sync(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i, r = 0; + + for (i = 0; i < AK4535_CACHEREGNUM; i++) + r |= ak4535_write(codec, i, cache[i]); + + return r; +}; + +static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"}; +static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"}; +static const char *ak4535_hp_out[] = {"Stereo", "Mono"}; +static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"}; +static const char *ak4535_mic_select[] = {"Internal", "External"}; + +static const struct soc_enum ak4535_enum[] = { + SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain), + SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out), + SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out), + SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp), + SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select), +}; + +static const struct snd_kcontrol_new ak4535_snd_controls[] = { + SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0), + SOC_ENUM("Mono 1 Output", ak4535_enum[1]), + SOC_ENUM("Mono 1 Gain", ak4535_enum[0]), + SOC_ENUM("Headphone Output", ak4535_enum[2]), + SOC_ENUM("Playback Deemphasis", ak4535_enum[3]), + SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0), + SOC_ENUM("Mic Select", ak4535_enum[4]), + SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0), + SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0), + SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0), + SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0), + SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0), + SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0), + SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1), + SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1), + SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0), + SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0), +}; + +/* add non dapm controls */ +static int ak4535_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ak4535_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Mono 1 Mixer */ +static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0), +}; + +/* Stereo Mixer */ +static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0), + SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0), +}; + +/* Input Mixer */ +static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0), + SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ak4535_input_mux_control = + SOC_DAPM_ENUM("Input Select", ak4535_enum[4]); + +/* HP L switch */ +static const struct snd_kcontrol_new ak4535_hpl_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1); + +/* HP R switch */ +static const struct snd_kcontrol_new ak4535_hpr_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1); + +/* mono 2 switch */ +static const struct snd_kcontrol_new ak4535_mono2_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0); + +/* Line out switch */ +static const struct snd_kcontrol_new ak4535_line_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0); + +/* ak4535 dapm widgets */ +static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_stereo_mixer_controls[0], + ARRAY_SIZE(ak4535_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_mono1_mixer_controls[0], + ARRAY_SIZE(ak4535_mono1_mixer_controls)), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_input_mixer_controls[0], + ARRAY_SIZE(ak4535_input_mixer_controls)), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ak4535_input_mux_control), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0), + SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, + &ak4535_mono2_control), + /* speaker powersave bit */ + SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ak4535_line_control), + SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpl_control), + SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpr_control), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPP"), + SND_SOC_DAPM_OUTPUT("SPN"), + SND_SOC_DAPM_OUTPUT("MOUT1"), + SND_SOC_DAPM_OUTPUT("MOUT2"), + SND_SOC_DAPM_OUTPUT("MICOUT"), + SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0), + SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0), + SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0), + SND_SOC_DAPM_INPUT("MICIN"), + SND_SOC_DAPM_INPUT("MICEXT"), + SND_SOC_DAPM_INPUT("AUX"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /*stereo mixer */ + {"Stereo Mixer", "Playback Switch", "DAC"}, + {"Stereo Mixer", "Mic Sidetone Switch", "Mic"}, + {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, + + /* mono1 mixer */ + {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"}, + {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, + + /* Mic */ + {"Mic", NULL, "AIN"}, + {"Input Mux", "Internal", "Mic Int Bias"}, + {"Input Mux", "External", "Mic Ext Bias"}, + {"Mic Int Bias", NULL, "MICIN"}, + {"Mic Ext Bias", NULL, "MICEXT"}, + {"MICOUT", NULL, "Input Mux"}, + + /* line out */ + {"LOUT", NULL, "Line Out Enable"}, + {"ROUT", NULL, "Line Out Enable"}, + {"Line Out Enable", "Switch", "Line Out"}, + {"Line Out", NULL, "Stereo Mixer"}, + + /* mono1 out */ + {"MOUT1", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono1 Mixer"}, + + /* left HP */ + {"HPL", NULL, "Left HP Enable"}, + {"Left HP Enable", "Switch", "HP L Amp"}, + {"HP L Amp", NULL, "Stereo Mixer"}, + + /* right HP */ + {"HPR", NULL, "Right HP Enable"}, + {"Right HP Enable", "Switch", "HP R Amp"}, + {"HP R Amp", NULL, "Stereo Mixer"}, + + /* speaker */ + {"SPP", NULL, "Speaker Enable"}, + {"SPN", NULL, "Speaker Enable"}, + {"Speaker Enable", "Switch", "Spk Amp"}, + {"Spk Amp", NULL, "MIN"}, + + /* mono 2 */ + {"MOUT2", NULL, "Mono 2 Enable"}, + {"Mono 2 Enable", "Switch", "Stereo Mixer"}, + + /* Aux In */ + {"Aux In", NULL, "AUX"}, + + /* ADC */ + {"ADC", NULL, "Input Mixer"}, + {"Input Mixer", "Mic Capture Switch", "Mic"}, + {"Input Mixer", "Aux Capture Switch", "Aux In"}, +}; + +static int ak4535_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak4535_dapm_widgets, + ARRAY_SIZE(ak4535_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int ak4535_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ak4535_priv *ak4535 = codec->private_data; + + ak4535->sysclk = freq; + return 0; +} + +static int ak4535_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 ak4535_priv *ak4535 = codec->private_data; + u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); + int rate = params_rate(params), fs = 256; + + if (rate) + fs = ak4535->sysclk / rate; + + /* set fs */ + switch (fs) { + case 1024: + mode2 |= (0x2 << 5); + break; + case 512: + mode2 |= (0x1 << 5); + break; + case 256: + break; + } + + /* set rate */ + ak4535_write(codec, AK4535_MODE2, mode2); + return 0; +} + +static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 mode1 = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode1 = 0x0002; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode1 = 0x0001; + break; + default: + return -EINVAL; + } + + /* use 32 fs for BCLK to save power */ + mode1 |= 0x4; + + ak4535_write(codec, AK4535_MODE1, mode1); + return 0; +} + +static int ak4535_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf; + if (!mute) + ak4535_write(codec, AK4535_DAC, mute_reg); + else + ak4535_write(codec, AK4535_DAC, mute_reg | 0x20); + return 0; +} + +static int ak4535_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 i; + + switch (level) { + case SND_SOC_BIAS_ON: + ak4535_mute(codec->dai, 0); + break; + case SND_SOC_BIAS_PREPARE: + ak4535_mute(codec->dai, 1); + break; + case SND_SOC_BIAS_STANDBY: + i = ak4535_read_reg_cache(codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i | 0x80); + i = ak4535_read_reg_cache(codec, AK4535_PM2); + ak4535_write(codec, AK4535_PM2, i & (~0x80)); + break; + case SND_SOC_BIAS_OFF: + i = ak4535_read_reg_cache(codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i & (~0x80)); + break; + } + codec->bias_level = level; + return 0; +} + +#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_dai ak4535_dai = { + .name = "AK4535", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = ak4535_hw_params, + }, + .dai_ops = { + .set_fmt = ak4535_set_dai_fmt, + .digital_mute = ak4535_mute, + .set_sysclk = ak4535_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(ak4535_dai); + +static int ak4535_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ak4535_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + ak4535_sync(codec); + ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ak4535_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the AK4535 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ak4535_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "AK4535"; + codec->owner = THIS_MODULE; + codec->read = ak4535_read_reg_cache; + codec->write = ak4535_write; + codec->set_bias_level = ak4535_set_bias_level; + codec->dai = &ak4535_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(ak4535_reg); + codec->reg_cache = kmemdup(ak4535_reg, sizeof(ak4535_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ak4535_add_controls(codec); + ak4535_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + + return ret; +} + +static struct snd_soc_device *ak4535_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static int ak4535_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = ak4535_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = ak4535_init(socdev); + if (ret < 0) + printk(KERN_ERR "failed to initialise AK4535\n"); + + return ret; +} + +static int ak4535_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id ak4535_i2c_id[] = { + { "ak4535", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id); + +static struct i2c_driver ak4535_i2c_driver = { + .driver = { + .name = "AK4535 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = ak4535_i2c_probe, + .remove = ak4535_i2c_remove, + .id_table = ak4535_i2c_id, +}; + +static int ak4535_add_i2c_device(struct platform_device *pdev, + const struct ak4535_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&ak4535_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "ak4535", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&ak4535_i2c_driver); + return -ENODEV; +} +#endif + +static int ak4535_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ak4535_setup_data *setup; + struct snd_soc_codec *codec; + struct ak4535_priv *ak4535; + int ret; + + printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL); + if (ak4535 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ak4535; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ak4535_socdev = socdev; + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + codec->hw_read = (hw_read_t)i2c_master_recv; + ret = ak4535_add_i2c_device(pdev, setup); + } +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +/* power down chip */ +static int ak4535_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&ak4535_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4535 = { + .probe = ak4535_probe, + .remove = ak4535_remove, + .suspend = ak4535_suspend, + .resume = ak4535_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535); + +MODULE_DESCRIPTION("Soc AK4535 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h new file mode 100644 index 000000000000..c7a58703ea39 --- /dev/null +++ b/sound/soc/codecs/ak4535.h @@ -0,0 +1,47 @@ +/* + * ak4535.h -- AK4535 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AK4535_H +#define _AK4535_H + +/* AK4535 register space */ + +#define AK4535_PM1 0x0 +#define AK4535_PM2 0x1 +#define AK4535_SIG1 0x2 +#define AK4535_SIG2 0x3 +#define AK4535_MODE1 0x4 +#define AK4535_MODE2 0x5 +#define AK4535_DAC 0x6 +#define AK4535_MIC 0x7 +#define AK4535_TIMER 0x8 +#define AK4535_ALC1 0x9 +#define AK4535_ALC2 0xa +#define AK4535_PGA 0xb +#define AK4535_LATT 0xc +#define AK4535_RATT 0xd +#define AK4535_VOL 0xe +#define AK4535_STATUS 0xf + +#define AK4535_CACHEREGNUM 0x10 + +struct ak4535_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ak4535_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4535; + +#endif diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index e73fcfd9f5cd..0bbd94501d7e 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -201,7 +201,7 @@ static struct { * driver what the input settings can be. This would need to be implemented * for stand-alone mode to work. */ -static int cs4270_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; @@ -251,7 +251,7 @@ static int cs4270_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, * data for playback only, but ASoC currently does not support different * formats for playback vs. record. */ -static int cs4270_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int format) { struct snd_soc_codec *codec = codec_dai->codec; @@ -471,7 +471,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, * board does not have the MUTEA or MUTEB pins connected to such circuitry, * then this function will do nothing. */ -static int cs4270_mute(struct snd_soc_codec_dai *dai, int mute) +static int cs4270_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; int reg6; @@ -490,34 +490,7 @@ static int cs4270_mute(struct snd_soc_codec_dai *dai, int mute) #endif -static int cs4270_i2c_probe(struct i2c_adapter *adap, int addr, int kind); - -/* - * Notify the driver that a new I2C bus has been found. - * - * This function is called for each I2C bus in the system. The function - * then asks the I2C subsystem to probe that bus at the addresses on which - * our device (the CS4270) could exist. If a device is found at one of - * those addresses, then our probe function (cs4270_i2c_probe) is called. - */ -static int cs4270_i2c_attach(struct i2c_adapter *adapter) -{ - return i2c_probe(adapter, &addr_data, cs4270_i2c_probe); -} - -static int cs4270_i2c_detach(struct i2c_client *client) -{ - struct snd_soc_codec *codec = i2c_get_clientdata(client); - - i2c_detach_client(client); - codec->control_data = NULL; - - kfree(codec->reg_cache); - codec->reg_cache = NULL; - - kfree(client); - return 0; -} +static int cs4270_i2c_probe(struct i2c_client *, const struct i2c_device_id *); /* A list of non-DAPM controls that the CS4270 supports */ static const struct snd_kcontrol_new cs4270_snd_controls[] = { @@ -525,14 +498,19 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = { CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1) }; +static const struct i2c_device_id cs4270_id[] = { + {"cs4270", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs4270_id); + static struct i2c_driver cs4270_i2c_driver = { .driver = { .name = "CS4270 I2C", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_CS4270, - .attach_adapter = cs4270_i2c_attach, - .detach_client = cs4270_i2c_detach, + .id_table = cs4270_id, + .probe = cs4270_i2c_probe, }; /* @@ -561,11 +539,11 @@ static struct snd_soc_device *cs4270_socdev; * Note: snd_soc_new_pcms() must be called before this function can be called, * because of snd_ctl_add(). */ -static int cs4270_i2c_probe(struct i2c_adapter *adapter, int addr, int kind) +static int cs4270_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = cs4270_socdev; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c_client = NULL; int i; int ret = 0; @@ -578,12 +556,6 @@ static int cs4270_i2c_probe(struct i2c_adapter *adapter, int addr, int kind) /* Note: codec_dai->codec is NULL here */ - i2c_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); - if (!i2c_client) { - printk(KERN_ERR "cs4270: could not allocate I2C client\n"); - return -ENOMEM; - } - codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL); if (!codec->reg_cache) { printk(KERN_ERR "cs4270: could not allocate register cache\n"); @@ -591,13 +563,6 @@ static int cs4270_i2c_probe(struct i2c_adapter *adapter, int addr, int kind) goto error; } - i2c_set_clientdata(i2c_client, codec); - strcpy(i2c_client->name, "CS4270"); - - i2c_client->driver = &cs4270_i2c_driver; - i2c_client->adapter = adapter; - i2c_client->addr = addr; - /* Verify that we have a CS4270 */ ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID); @@ -612,18 +577,10 @@ static int cs4270_i2c_probe(struct i2c_adapter *adapter, int addr, int kind) goto error; } - printk(KERN_INFO "cs4270: found device at I2C address %X\n", addr); + printk(KERN_INFO "cs4270: found device at I2C address %X\n", + i2c_client->addr); printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF); - /* Tell the I2C layer a new client has arrived */ - - ret = i2c_attach_client(i2c_client); - if (ret) { - printk(KERN_ERR "cs4270: could not attach codec, " - "I2C address %x, error code %i\n", addr, ret); - goto error; - } - codec->control_data = i2c_client; codec->read = cs4270_read_reg_cache; codec->write = cs4270_i2c_write; @@ -648,26 +605,23 @@ static int cs4270_i2c_probe(struct i2c_adapter *adapter, int addr, int kind) goto error; } + i2c_set_clientdata(i2c_client, codec); + return 0; error: - if (codec->control_data) { - i2c_detach_client(i2c_client); - codec->control_data = NULL; - } + codec->control_data = NULL; kfree(codec->reg_cache); codec->reg_cache = NULL; codec->reg_cache_size = 0; - kfree(i2c_client); - return ret; } #endif /* USE_I2C*/ -struct snd_soc_codec_dai cs4270_dai = { +struct snd_soc_dai cs4270_dai = { .name = "CS4270", .playback = { .stream_name = "Playback", @@ -727,7 +681,7 @@ static int cs4270_probe(struct platform_device *pdev) ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { printk(KERN_ERR "cs4270: failed to create PCMs\n"); - return ret; + goto error_free_codec; } #ifdef USE_I2C @@ -736,8 +690,7 @@ static int cs4270_probe(struct platform_device *pdev) ret = i2c_add_driver(&cs4270_i2c_driver); if (ret) { printk(KERN_ERR "cs4270: failed to attach driver"); - snd_soc_free_pcms(socdev); - return ret; + goto error_free_pcms; } /* Did we find a CS4270 on the I2C bus? */ @@ -759,10 +712,23 @@ static int cs4270_probe(struct platform_device *pdev) ret = snd_soc_register_card(socdev); if (ret < 0) { printk(KERN_ERR "cs4270: failed to register card\n"); - snd_soc_free_pcms(socdev); - return ret; + goto error_del_driver; } + return 0; + +error_del_driver: +#ifdef USE_I2C + i2c_del_driver(&cs4270_i2c_driver); + +error_free_pcms: +#endif + snd_soc_free_pcms(socdev); + +error_free_codec: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; } @@ -773,8 +739,7 @@ static int cs4270_remove(struct platform_device *pdev) snd_soc_free_pcms(socdev); #ifdef USE_I2C - if (socdev->codec->control_data) - i2c_del_driver(&cs4270_i2c_driver); + i2c_del_driver(&cs4270_i2c_driver); #endif kfree(socdev->codec); diff --git a/sound/soc/codecs/cs4270.h b/sound/soc/codecs/cs4270.h index 0ced49b7804d..adc6cd9667d4 100644 --- a/sound/soc/codecs/cs4270.h +++ b/sound/soc/codecs/cs4270.h @@ -16,7 +16,7 @@ * The ASoC codec DAI structure for the CS4270. Assign this structure to * the .codec_dai field of your machine driver's snd_soc_dai_link structure. */ -extern struct snd_soc_codec_dai cs4270_dai; +extern struct snd_soc_dai cs4270_dai; /* * The ASoC codec device structure for the CS4270. Assign this structure diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c new file mode 100644 index 000000000000..44ef0dacd564 --- /dev/null +++ b/sound/soc/codecs/ssm2602.c @@ -0,0 +1,775 @@ +/* + * File: sound/soc/codecs/ssm2602.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: Driver for ssm2602 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/initval.h> + +#include "ssm2602.h" + +#define SSM2602_VERSION "0.1" + +struct snd_soc_codec_device soc_codec_dev_ssm2602; + +/* codec private data */ +struct ssm2602_priv { + unsigned int sysclk; + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* + * ssm2602 register cache + * We can't read the ssm2602 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = { + 0x0017, 0x0017, 0x0079, 0x0079, + 0x0000, 0x0000, 0x0000, 0x000a, + 0x0000, 0x0000 +}; + +/* + * read ssm2602 register cache + */ +static inline unsigned int ssm2602_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == SSM2602_RESET) + return 0; + if (reg >= SSM2602_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write ssm2602 register cache + */ +static inline void ssm2602_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= SSM2602_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the ssm2602 register space + */ +static int ssm2602_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 ssm2602 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + ssm2602_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0) + +/*Appending several "None"s just for OSS mixer use*/ +static const char *ssm2602_input_select[] = { + "Line", "Mic", "None", "None", "None", + "None", "None", "None", +}; + +static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum ssm2602_enum[] = { + SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select), + SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph), +}; + +static const struct snd_kcontrol_new ssm2602_snd_controls[] = { + +SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, + 7, 1, 0), + +SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), +SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), + +SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), + +SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0), + +SOC_ENUM("Capture Source", ssm2602_enum[0]), + +SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), +}; + +/* add non dapm controls */ +static int ssm2602_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Output Mixer */ +static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ssm2602_input_mux_controls = +SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]); + +static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1, + &ssm2602_output_mixer_controls[0], + ARRAY_SIZE(ssm2602_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1), +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls), +SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1), +SND_SOC_DAPM_INPUT("MICIN"), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const struct snd_soc_dapm_route audio_conn[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line", "Line Input"}, + {"Input Mux", "Mic", "Mic Bias"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Bias", NULL, "MICIN"}, +}; + +static int ssm2602_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ssm2602_dapm_widgets, + ARRAY_SIZE(ssm2602_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return i; +} + +static int ssm2602_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + u16 srate; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; + int i = get_coeff(ssm2602->sysclk, params_rate(params)); + + /*no match is found*/ + if (i == ARRAY_SIZE(coeff_div)) + return -EINVAL; + + srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + ssm2602_write(codec, SSM2602_IFACE, iface); + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); + return 0; +} + +static int ssm2602_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + struct snd_pcm_runtime *master_runtime; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (ssm2602->master_substream) { + master_runtime = ssm2602->master_substream->runtime; + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + ssm2602->slave_substream = substream; + } else + ssm2602->master_substream = substream; + + return 0; +} + +static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* set active */ + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); + + return 0; +} + +static void ssm2602_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* deactivate */ + if (!codec->active) + ssm2602_write(codec, SSM2602_ACTIVE, 0); +} + +static int ssm2602_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE; + if (mute) + ssm2602_write(codec, SSM2602_APDIGI, + mute_reg | APDIGI_ENABLE_DAC_MUTE); + else + ssm2602_write(codec, SSM2602_APDIGI, mute_reg); + return 0; +} + +static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + ssm2602->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + ssm2602_write(codec, SSM2602_IFACE, iface); + return 0; +} + +static int ssm2602_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + ssm2602_write(codec, SSM2602_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + ssm2602_write(codec, SSM2602_PWR, reg | PWR_CLK_OUT_PDN); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_PWR, 0xffff); + break; + + } + codec->bias_level = level; + return 0; +} + +#define SSM2602_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 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +struct snd_soc_dai ssm2602_dai = { + .name = "SSM2602", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .ops = { + .startup = ssm2602_startup, + .prepare = ssm2602_pcm_prepare, + .hw_params = ssm2602_hw_params, + .shutdown = ssm2602_shutdown, + }, + .dai_ops = { + .digital_mute = ssm2602_mute, + .set_sysclk = ssm2602_set_dai_sysclk, + .set_fmt = ssm2602_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(ssm2602_dai); + +static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ssm2602_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(ssm2602_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ssm2602_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the ssm2602 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ssm2602_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "SSM2602"; + codec->owner = THIS_MODULE; + codec->read = ssm2602_read_reg_cache; + codec->write = ssm2602_write; + codec->set_bias_level = ssm2602_set_bias_level; + codec->dai = &ssm2602_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(ssm2602_reg); + codec->reg_cache = kmemdup(ssm2602_reg, sizeof(ssm2602_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + ssm2602_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("ssm2602: failed to create pcms\n"); + goto pcm_err; + } + /*power on device*/ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + /* set the update bits */ + reg = ssm2602_read_reg_cache(codec, SSM2602_LINVOL); + ssm2602_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_RINVOL); + ssm2602_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_LOUT1V); + ssm2602_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V); + ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH); + /*select Line in as default input*/ + ssm2602_write(codec, SSM2602_APANA, + APANA_ENABLE_MIC_BOOST2 | APANA_SELECT_DAC | + APANA_ENABLE_MIC_BOOST); + ssm2602_write(codec, SSM2602_PWR, 0); + + ssm2602_add_controls(codec); + ssm2602_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + pr_err("ssm2602: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *ssm2602_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * ssm2602 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static int ssm2602_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = ssm2602_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = ssm2602_init(socdev); + if (ret < 0) + pr_err("failed to initialise SSM2602\n"); + + return ret; +} + +static int ssm2602_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id ssm2602_i2c_id[] = { + { "ssm2602", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id); +/* corgi i2c codec control layer */ +static struct i2c_driver ssm2602_i2c_driver = { + .driver = { + .name = "SSM2602 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = ssm2602_i2c_probe, + .remove = ssm2602_i2c_remove, + .id_table = ssm2602_i2c_id, +}; + +static int ssm2602_add_i2c_device(struct platform_device *pdev, + const struct ssm2602_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&ssm2602_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "ssm2602", I2C_NAME_SIZE); + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + return 0; +err_driver: + i2c_del_driver(&ssm2602_i2c_driver); + return -ENODEV; +} +#endif + +static int ssm2602_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ssm2602_setup_data *setup; + struct snd_soc_codec *codec; + struct ssm2602_priv *ssm2602; + int ret = 0; + + pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL); + if (ssm2602 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ssm2602; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ssm2602_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = ssm2602_add_i2c_device(pdev, setup); + } +#else + /* other interfaces */ +#endif + return ret; +} + +/* remove everything here */ +static int ssm2602_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&ssm2602_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ssm2602 = { + .probe = ssm2602_probe, + .remove = ssm2602_remove, + .suspend = ssm2602_suspend, + .resume = ssm2602_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2602); + +MODULE_DESCRIPTION("ASoC ssm2602 driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h new file mode 100644 index 000000000000..f344e6d76e31 --- /dev/null +++ b/sound/soc/codecs/ssm2602.h @@ -0,0 +1,130 @@ +/* + * File: sound/soc/codecs/ssm2602.h + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SSM2602_H +#define _SSM2602_H + +/* SSM2602 Codec Register definitions */ + +#define SSM2602_LINVOL 0x00 +#define SSM2602_RINVOL 0x01 +#define SSM2602_LOUT1V 0x02 +#define SSM2602_ROUT1V 0x03 +#define SSM2602_APANA 0x04 +#define SSM2602_APDIGI 0x05 +#define SSM2602_PWR 0x06 +#define SSM2602_IFACE 0x07 +#define SSM2602_SRATE 0x08 +#define SSM2602_ACTIVE 0x09 +#define SSM2602_RESET 0x0f + +/*SSM2602 Codec Register Field definitions + *(Mask value to extract the corresponding Register field) + */ + +/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/ +#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */ + +/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/ +#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */ +#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */ +#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */ + +/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/ +#define LOUT1V_LHP_VOL 0x07F /* Left Channel Headphone volume control */ +#define LOUT1V_ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */ +#define LOUT1V_LRHP_BOTH 0x100 /* Left Channel Headphone volume update */ + +/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/ +#define ROUT1V_RHP_VOL 0x07F /* Right Channel Headphone volume control */ +#define ROUT1V_ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */ +#define ROUT1V_RLHP_BOTH 0x100 /* Right Channel Headphone volume update */ + +/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/ +#define APANA_ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */ +#define APANA_ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */ +#define APANA_ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */ +#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */ +#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */ +#define APANA_ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */ +#define APANA_SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */ +#define APANA_ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */ + +/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/ +#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */ +#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */ +#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */ +#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */ + +/*Power Down Control (SSM2602_REG_POWER) + *(1=Enable PowerDown, 0=Disable PowerDown) + */ +#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */ +#define PWR_MIC_PDN 0x002 /* Microphone Input & Bias Power Down */ +#define PWR_ADC_PDN 0x004 /* ADC Power Down */ +#define PWR_DAC_PDN 0x008 /* DAC Power Down */ +#define PWR_OUT_PDN 0x010 /* Outputs Power Down */ +#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */ +#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */ +#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */ + +/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/ +#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */ +#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */ +#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */ +#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */ +#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */ +#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */ + +/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/ +#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */ +#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */ +#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */ +#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */ +#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */ + +/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/ +#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */ + +/*********************************************************************/ + +#define SSM2602_CACHEREGNUM 10 + +#define SSM2602_SYSCLK 0 +#define SSM2602_DAI 0 + +struct ssm2602_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ssm2602_dai; +extern struct snd_soc_codec_device soc_codec_dev_ssm2602; + +#endif diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c new file mode 100644 index 000000000000..bac7815e00fb --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.c @@ -0,0 +1,714 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd., + * + * Based on sound/soc/codecs/wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Notes: + * The AIC23 is a driver for a low power stereo audio + * codec tlv320aic23 + * + * The machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/tlv.h> +#include <sound/initval.h> + +#include "tlv320aic23.h" + +#define AIC23_VERSION "0.1" + +struct tlv320aic23_srate_reg_info { + u32 sample_rate; + u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ + u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ +}; + +/* + * AIC23 register cache + */ +static const u16 tlv320aic23_reg[] = { + 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */ + 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */ + 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */ +}; + +/* + * read tlv320aic23 register cache + */ +static inline unsigned int tlv320aic23_read_reg_cache(struct snd_soc_codec + *codec, unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= ARRAY_SIZE(tlv320aic23_reg)) + return -1; + return cache[reg]; +} + +/* + * write tlv320aic23 register cache + */ +static inline void tlv320aic23_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u16 value) +{ + u16 *cache = codec->reg_cache; + if (reg >= ARRAY_SIZE(tlv320aic23_reg)) + return; + cache[reg] = value; +} + +/* + * write to the tlv320aic23 register space + */ +static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + + u8 data; + + /* TLV320AIC23 has 7 bit address and 9 bits of data + * so we need to switch one data bit into reg and rest + * of data into val + */ + + if ((reg < 0 || reg > 9) && (reg != 15)) { + printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg); + return -1; + } + + data = (reg << 1) | (value >> 8 & 0x01); + + tlv320aic23_write_reg_cache(codec, reg, value); + + if (codec->hw_write(codec->control_data, data, + (value & 0xff)) == 0) + return 0; + + printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__, + value, reg); + + return -EIO; +} + +static const char *rec_src_text[] = { "Line", "Mic" }; +static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum rec_src_enum = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); + +static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls = +SOC_DAPM_ENUM("Input Select", rec_src_enum); + +static const struct soc_enum tlv320aic23_rec_src = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); +static const struct soc_enum tlv320aic23_deemph = + SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0); +static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0); + +static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val, reg; + + val = (ucontrol->value.integer.value[0] & 0x07); + + /* linear conversion to userspace + * 000 = -6db + * 001 = -9db + * 010 = -12db + * 011 = -18db (Min) + * 100 = 0db (Max) + */ + val = (val >= 4) ? 4 : (3 - val); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (~0x1C0); + tlv320aic23_write(codec, TLV320AIC23_ANLG, reg | (val << 6)); + + return 0; +} + +static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val; + + val = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (0x1C0); + val = val >> 6; + val = (val >= 4) ? 4 : (3 - val); + ucontrol->value.integer.value[0] = val; + return 0; + +} + +#define SOC_TLV320AIC23_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = snd_soc_tlv320aic23_get_volsw,\ + .put = snd_soc_tlv320aic23_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL, + TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv), + SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1), + SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv), + SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1), + SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0), + SOC_TLV320AIC23_SINGLE_TLV("Sidetone Volume", TLV320AIC23_ANLG, + 6, 4, 0, sidetone_vol_tlv), + SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph), +}; + +/* add non dapm controls */ +static int tlv320aic23_add_controls(struct snd_soc_codec *codec) +{ + + int err, i; + + for (i = 0; i < ARRAY_SIZE(tlv320aic23_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&tlv320aic23_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; + +} + +/* PGA Mixer controls for Line and Mic switch */ +static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1), + SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0, + &tlv320aic23_rec_src_mux_controls), + SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1, + &tlv320aic23_output_mixer_controls[0], + ARRAY_SIZE(tlv320aic23_output_mixer_controls)), + SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LHPOUT"), + SND_SOC_DAPM_OUTPUT("RHPOUT"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("LLINEIN"), + SND_SOC_DAPM_INPUT("RLINEIN"), + + SND_SOC_DAPM_INPUT("MICIN"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Output Mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Input"}, + + /* Outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + + /* Inputs */ + {"Line Input", "NULL", "LLINEIN"}, + {"Line Input", "NULL", "RLINEIN"}, + + {"Mic Input", "NULL", "MICIN"}, + + /* input mux */ + {"Capture Source", "Line", "Line Input"}, + {"Capture Source", "Mic", "Mic Input"}, + {"ADC", NULL, "Capture Source"}, + +}; + +/* tlv320aic23 related */ +static const struct tlv320aic23_srate_reg_info srate_reg_info[] = { + {4000, 0x06, 1}, /* 4000 */ + {8000, 0x06, 0}, /* 8000 */ + {16000, 0x0C, 1}, /* 16000 */ + {22050, 0x11, 1}, /* 22050 */ + {24000, 0x00, 1}, /* 24000 */ + {32000, 0x0C, 0}, /* 32000 */ + {44100, 0x11, 0}, /* 44100 */ + {48000, 0x00, 0}, /* 48000 */ + {88200, 0x1F, 0}, /* 88200 */ + {96000, 0x0E, 0}, /* 96000 */ +}; + +static int tlv320aic23_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int tlv320aic23_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; + u16 iface_reg, data; + u8 count = 0; + + iface_reg = + tlv320aic23_read_reg_cache(codec, + TLV320AIC23_DIGT_FMT) & ~(0x03 << 2); + + /* Search for the right sample rate */ + /* Verify what happens if the rate is not supported + * now it goes to 96Khz */ + while ((srate_reg_info[count].sample_rate != params_rate(params)) && + (count < ARRAY_SIZE(srate_reg_info))) { + count++; + } + + data = (srate_reg_info[count].divider << TLV320AIC23_CLKIN_SHIFT) | + (srate_reg_info[count]. control << TLV320AIC23_BOSR_SHIFT) | + TLV320AIC23_USB_CLK_ON; + + tlv320aic23_write(codec, TLV320AIC23_SRATE, data); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface_reg |= (0x01 << 2); + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface_reg |= (0x02 << 2); + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface_reg |= (0x03 << 2); + break; + } + tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* set active */ + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001); + + return 0; +} + +static void tlv320aic23_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* deactivate */ + if (!codec->active) { + udelay(50); + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + } +} + +static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg; + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT); + if (mute) + reg |= TLV320AIC23_DACM_MUTE; + + else + reg &= ~TLV320AIC23_DACM_MUTE; + + tlv320aic23_write(codec, TLV320AIC23_DIGT, reg); + + return 0; +} + +static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface_reg; + + iface_reg = + tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT_FMT) & (~0x03); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface_reg |= TLV320AIC23_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface_reg |= TLV320AIC23_FOR_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg |= TLV320AIC23_FOR_DSP; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg |= TLV320AIC23_FOR_LJUST; + break; + default: + return -EINVAL; + + } + + tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + switch (freq) { + case 12000000: + return 0; + } + return -EINVAL; +} + +static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + tlv320aic23_write(codec, TLV320AIC23_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + tlv320aic23_write(codec, TLV320AIC23_PWR, reg | 0x0040); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + tlv320aic23_write(codec, TLV320AIC23_PWR, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +#define AIC23_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai tlv320aic23_dai = { + .name = "tlv320aic23", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .ops = { + .prepare = tlv320aic23_pcm_prepare, + .hw_params = tlv320aic23_hw_params, + .shutdown = tlv320aic23_shutdown, + }, + .dai_ops = { + .digital_mute = tlv320aic23_mute, + .set_fmt = tlv320aic23_set_dai_fmt, + .set_sysclk = tlv320aic23_set_dai_sysclk, + } +}; +EXPORT_SYMBOL_GPL(tlv320aic23_dai); + +static int tlv320aic23_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int tlv320aic23_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u16 reg; + + /* Sync reg_cache with the hardware */ + for (reg = 0; reg < ARRAY_SIZE(tlv320aic23_reg); i++) { + u16 val = tlv320aic23_read_reg_cache(codec, reg); + tlv320aic23_write(codec, reg, val); + } + + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + tlv320aic23_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +/* + * initialise the AIC23 driver + * register the mixer and dsp interfaces with the kernel + */ +static int tlv320aic23_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + u16 reg; + + codec->name = "tlv320aic23"; + codec->owner = THIS_MODULE; + codec->read = tlv320aic23_read_reg_cache; + codec->write = tlv320aic23_write; + codec->set_bias_level = tlv320aic23_set_bias_level; + codec->dai = &tlv320aic23_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(tlv320aic23_reg); + codec->reg_cache = + kmemdup(tlv320aic23_reg, sizeof(tlv320aic23_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* Reset codec */ + tlv320aic23_write(codec, TLV320AIC23_RESET, 0); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + tlv320aic23_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K); + + /* Unmute input */ + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_LINVOL); + tlv320aic23_write(codec, TLV320AIC23_LINVOL, + (reg & (~TLV320AIC23_LIM_MUTED)) | + (TLV320AIC23_LRS_ENABLED)); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_RINVOL); + tlv320aic23_write(codec, TLV320AIC23_RINVOL, + (reg & (~TLV320AIC23_LIM_MUTED)) | + TLV320AIC23_LRS_ENABLED); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG); + tlv320aic23_write(codec, TLV320AIC23_ANLG, + (reg) & (~TLV320AIC23_BYPASS_ON) & + (~TLV320AIC23_MICM_MUTED)); + + /* Default output volume */ + tlv320aic23_write(codec, TLV320AIC23_LCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & + TLV320AIC23_OUT_VOL_MASK); + tlv320aic23_write(codec, TLV320AIC23_RCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & + TLV320AIC23_OUT_VOL_MASK); + + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1); + + tlv320aic23_add_controls(codec); + tlv320aic23_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} +static struct snd_soc_device *tlv320aic23_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * If the i2c layer weren't so broken, we could pass this kind of data + * around + */ +static int tlv320aic23_codec_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct snd_soc_device *socdev = tlv320aic23_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = tlv320aic23_init(socdev); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to initialise AIC23\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} +static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c) +{ + put_device(&i2c->dev); + return 0; +} + +static const struct i2c_device_id tlv320aic23_id[] = { + {"tlv320aic23", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tlv320aic23_id); + +static struct i2c_driver tlv320aic23_i2c_driver = { + .driver = { + .name = "tlv320aic23", + }, + .probe = tlv320aic23_codec_probe, + .remove = __exit_p(tlv320aic23_i2c_remove), + .id_table = tlv320aic23_id, +}; + +#endif + +static int tlv320aic23_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION); + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + tlv320aic23_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + codec->hw_write = (hw_write_t) i2c_smbus_write_byte_data; + codec->hw_read = NULL; + ret = i2c_add_driver(&tlv320aic23_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); +#endif + return ret; +} + +static int tlv320aic23_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&tlv320aic23_i2c_driver); +#endif + kfree(codec->reg_cache); + kfree(codec); + + return 0; +} +struct snd_soc_codec_device soc_codec_dev_tlv320aic23 = { + .probe = tlv320aic23_probe, + .remove = tlv320aic23_remove, + .suspend = tlv320aic23_suspend, + .resume = tlv320aic23_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320aic23); + +MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver"); +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic23.h b/sound/soc/codecs/tlv320aic23.h new file mode 100644 index 000000000000..79d1faf8e570 --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.h @@ -0,0 +1,122 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _TLV320AIC23_H +#define _TLV320AIC23_H + +/* Codec TLV320AIC23 */ +#define TLV320AIC23_LINVOL 0x00 +#define TLV320AIC23_RINVOL 0x01 +#define TLV320AIC23_LCHNVOL 0x02 +#define TLV320AIC23_RCHNVOL 0x03 +#define TLV320AIC23_ANLG 0x04 +#define TLV320AIC23_DIGT 0x05 +#define TLV320AIC23_PWR 0x06 +#define TLV320AIC23_DIGT_FMT 0x07 +#define TLV320AIC23_SRATE 0x08 +#define TLV320AIC23_ACTIVE 0x09 +#define TLV320AIC23_RESET 0x0F + +/* Left (right) line input volume control register */ +#define TLV320AIC23_LRS_ENABLED 0x0100 +#define TLV320AIC23_LIM_MUTED 0x0080 +#define TLV320AIC23_LIV_DEFAULT 0x0017 +#define TLV320AIC23_LIV_MAX 0x001f +#define TLV320AIC23_LIV_MIN 0x0000 + +/* Left (right) channel headphone volume control register */ +#define TLV320AIC23_LZC_ON 0x0080 +#define TLV320AIC23_LHV_DEFAULT 0x0079 +#define TLV320AIC23_LHV_MAX 0x007f +#define TLV320AIC23_LHV_MIN 0x0000 + +/* Analog audio path control register */ +#define TLV320AIC23_STA_REG(x) ((x)<<6) +#define TLV320AIC23_STE_ENABLED 0x0020 +#define TLV320AIC23_DAC_SELECTED 0x0010 +#define TLV320AIC23_BYPASS_ON 0x0008 +#define TLV320AIC23_INSEL_MIC 0x0004 +#define TLV320AIC23_MICM_MUTED 0x0002 +#define TLV320AIC23_MICB_20DB 0x0001 + +/* Digital audio path control register */ +#define TLV320AIC23_DACM_MUTE 0x0008 +#define TLV320AIC23_DEEMP_32K 0x0002 +#define TLV320AIC23_DEEMP_44K 0x0004 +#define TLV320AIC23_DEEMP_48K 0x0006 +#define TLV320AIC23_ADCHP_ON 0x0001 + +/* Power control down register */ +#define TLV320AIC23_DEVICE_PWR_OFF 0x0080 +#define TLV320AIC23_CLK_OFF 0x0040 +#define TLV320AIC23_OSC_OFF 0x0020 +#define TLV320AIC23_OUT_OFF 0x0010 +#define TLV320AIC23_DAC_OFF 0x0008 +#define TLV320AIC23_ADC_OFF 0x0004 +#define TLV320AIC23_MIC_OFF 0x0002 +#define TLV320AIC23_LINE_OFF 0x0001 + +/* Digital audio interface register */ +#define TLV320AIC23_MS_MASTER 0x0040 +#define TLV320AIC23_LRSWAP_ON 0x0020 +#define TLV320AIC23_LRP_ON 0x0010 +#define TLV320AIC23_IWL_16 0x0000 +#define TLV320AIC23_IWL_20 0x0004 +#define TLV320AIC23_IWL_24 0x0008 +#define TLV320AIC23_IWL_32 0x000C +#define TLV320AIC23_FOR_I2S 0x0002 +#define TLV320AIC23_FOR_DSP 0x0003 +#define TLV320AIC23_FOR_LJUST 0x0001 + +/* Sample rate control register */ +#define TLV320AIC23_CLKOUT_HALF 0x0080 +#define TLV320AIC23_CLKIN_HALF 0x0040 +#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */ +#define TLV320AIC23_USB_CLK_ON 0x0001 +#define TLV320AIC23_SR_MASK 0xf +#define TLV320AIC23_CLKOUT_SHIFT 7 +#define TLV320AIC23_CLKIN_SHIFT 6 +#define TLV320AIC23_SR_SHIFT 2 +#define TLV320AIC23_BOSR_SHIFT 1 + +/* Digital interface register */ +#define TLV320AIC23_ACT_ON 0x0001 + +/* + * AUDIO related MACROS + */ + +#define TLV320AIC23_DEFAULT_OUT_VOL 0x70 +#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10 + +#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN +#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX +#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \ + TLV320AIC23_OUT_VOL_MIN) +#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX + +#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN +#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX +#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \ + TLV320AIC23_IN_VOL_MIN) +#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX + +#define TLV320AIC23_SIDETONE_MASK 0x1c0 +#define TLV320AIC23_SIDETONE_0 0x100 +#define TLV320AIC23_SIDETONE_6 0x000 +#define TLV320AIC23_SIDETONE_9 0x040 +#define TLV320AIC23_SIDETONE_12 0x080 +#define TLV320AIC23_SIDETONE_18 0x0c0 + +extern struct snd_soc_dai tlv320aic23_dai; +extern struct snd_soc_codec_device soc_codec_dev_tlv320aic23; + +#endif /* _TLV320AIC23_H */ diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c new file mode 100644 index 000000000000..bed8a9e63ddc --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.c @@ -0,0 +1,520 @@ +/* + * 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-simple.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_NUM_REGS]; /* 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 */ + cache[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 >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + return cache[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 >= 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 */ + cache[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); + + /* 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_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_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_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 + */ +#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) + +struct snd_soc_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, +}; +EXPORT_SYMBOL_GPL(aic26_soc_codec_dev); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: sysfs files for debugging + */ + +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 event */ +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; +} + +static 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 = AIC26_NUM_REGS; + 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 register cache */ + for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++) + aic26_reg_read(&aic26->codec, i); + + /* Register the sysfs files for debugging */ + /* Create SysFS files */ + 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_SIMPLE) + /* 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 000000000000..786ba16c945f --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.h @@ -0,0 +1,96 @@ +/* + * 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) + +/* 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) + +/* 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, +}; + +extern struct snd_soc_dai aic26_dai; +extern struct snd_soc_codec_device aic26_soc_codec_dev; + +#endif /* _TLV320AIC16_H_ */ diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 09b1661b8a3a..05336ed7e493 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1,7 +1,7 @@ /* * ALSA SoC TLV320AIC3X codec driver * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * Based on sound/soc/codecs/wm8753.c by Liam Girdwood @@ -29,7 +29,7 @@ * --------------------------------------- * * Hence the machine layer should disable unsupported inputs/outputs by - * snd_soc_dapm_set_endpoint(codec, "MONO_LOUT", 0), etc. + * snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc. */ #include <linux/module.h> @@ -48,8 +48,7 @@ #include "tlv320aic3x.h" -#define AUDIO_NAME "aic3x" -#define AIC3X_VERSION "0.1" +#define AIC3X_VERSION "0.2" /* codec private data */ struct aic3x_priv { @@ -138,6 +137,20 @@ static int aic3x_write(struct snd_soc_codec *codec, unsigned int reg, return -EIO; } +/* + * read from the aic3x register space + */ +static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg, + u8 *value) +{ + *value = reg & 0xff; + if (codec->hw_read(codec->control_data, value, 1) != 1) + return -EIO; + + aic3x_write_reg_cache(codec, reg, *value); + return 0; +} + #define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, \ @@ -192,7 +205,7 @@ static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol, } if (found) - snd_soc_dapm_sync_endpoints(widget->codec); + snd_soc_dapm_sync(widget->codec); } ret = snd_soc_update_bits(widget->codec, reg, val_mask, val); @@ -209,6 +222,8 @@ static const char *aic3x_right_hpcom_mux[] = { "differential of HPROUT", "constant VCM", "single-ended", "differential of HPLCOM", "external feedback" }; static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" }; +static const char *aic3x_adc_hpf[] = + { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" }; #define LDAC_ENUM 0 #define RDAC_ENUM 1 @@ -218,6 +233,7 @@ static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" }; #define LINE1R_ENUM 5 #define LINE2L_ENUM 6 #define LINE2R_ENUM 7 +#define ADC_HPF_ENUM 8 static const struct soc_enum aic3x_enum[] = { SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux), @@ -228,6 +244,7 @@ static const struct soc_enum aic3x_enum[] = { SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf), }; static const struct snd_kcontrol_new aic3x_snd_controls[] = { @@ -278,6 +295,8 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { /* Input */ SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0), SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1), + + SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]), }; /* add non dapm controls */ @@ -441,11 +460,34 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0, &aic3x_right_line2_mux_controls), + /* + * Not a real mic bias widget but similar function. This is for dynamic + * control of GPIO1 digital mic modulator clock output function when + * using digital mic. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk", + AIC3X_GPIO1_REG, 4, 0xf, + AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK, + AIC3X_GPIO1_FUNC_DISABLED), + + /* + * Also similar function like mic bias. Selects digital mic with + * configurable oversampling rate instead of ADC converter. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128", + AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64", + AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32", + AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0), + /* Mic Bias */ - SND_SOC_DAPM_MICBIAS("Mic Bias 2V", MICBIAS_CTRL, 6, 0), - SND_SOC_DAPM_MICBIAS("Mic Bias 2.5V", MICBIAS_CTRL, 7, 0), - SND_SOC_DAPM_MICBIAS("Mic Bias AVDD", MICBIAS_CTRL, 6, 0), - SND_SOC_DAPM_MICBIAS("Mic Bias AVDD", MICBIAS_CTRL, 7, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V", + MICBIAS_CTRL, 6, 3, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V", + MICBIAS_CTRL, 6, 3, 2, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD", + MICBIAS_CTRL, 6, 3, 3, 0), /* Left PGA to Left Output bypass */ SND_SOC_DAPM_MIXER("Left PGA Bypass Mixer", SND_SOC_NOPM, 0, 0, @@ -483,7 +525,7 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINE2R"), }; -static const char *intercon[][3] = { +static const struct snd_soc_dapm_route intercon[] = { /* Left Output */ {"Left DAC Mux", "DAC_L1", "Left DAC"}, {"Left DAC Mux", "DAC_L2", "Left DAC"}, @@ -554,6 +596,7 @@ static const char *intercon[][3] = { {"Left PGA Mixer", "Mic3L Switch", "MIC3L"}, {"Left ADC", NULL, "Left PGA Mixer"}, + {"Left ADC", NULL, "GPIO1 dmic modclk"}, /* Right Input */ {"Right Line1R Mux", "single-ended", "LINE1R"}, @@ -567,6 +610,7 @@ static const char *intercon[][3] = { {"Right PGA Mixer", "Mic3R Switch", "MIC3R"}, {"Right ADC", NULL, "Right PGA Mixer"}, + {"Right ADC", NULL, "GPIO1 dmic modclk"}, /* Left PGA Bypass */ {"Left PGA Bypass Mixer", "Line Switch", "Left PGA Mixer"}, @@ -628,101 +672,27 @@ static const char *intercon[][3] = { {"Mono Out", NULL, "Right Line2 Bypass Mixer"}, {"Right HP Out", NULL, "Right Line2 Bypass Mixer"}, - /* terminator */ - {NULL, NULL, NULL}, + /* + * Logical path between digital mic enable and GPIO1 modulator clock + * output function + */ + {"GPIO1 dmic modclk", NULL, "DMic Rate 128"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 64"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 32"}, }; static int aic3x_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(aic3x_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &aic3x_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets, + ARRAY_SIZE(aic3x_dapm_widgets)); /* set up audio path interconnects */ - for (i = 0; intercon[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, intercon[i][0], - intercon[i][1], intercon[i][2]); + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); snd_soc_dapm_new_widgets(codec); return 0; } -struct aic3x_rate_divs { - u32 mclk; - u32 rate; - u32 fsref_reg; - u8 sr_reg:4; - u8 pllj_reg; - u16 plld_reg; -}; - -/* AIC3X codec mclk clock divider coefficients */ -static const struct aic3x_rate_divs aic3x_divs[] = { - /* 8k */ - {12000000, 8000, 48000, 0xa, 16, 3840}, - {19200000, 8000, 48000, 0xa, 10, 2400}, - {22579200, 8000, 48000, 0xa, 8, 7075}, - {33868800, 8000, 48000, 0xa, 5, 8049}, - /* 11.025k */ - {12000000, 11025, 44100, 0x6, 15, 528}, - {19200000, 11025, 44100, 0x6, 9, 4080}, - {22579200, 11025, 44100, 0x6, 8, 0}, - {33868800, 11025, 44100, 0x6, 5, 3333}, - /* 16k */ - {12000000, 16000, 48000, 0x4, 16, 3840}, - {19200000, 16000, 48000, 0x4, 10, 2400}, - {22579200, 16000, 48000, 0x4, 8, 7075}, - {33868800, 16000, 48000, 0x4, 5, 8049}, - /* 22.05k */ - {12000000, 22050, 44100, 0x2, 15, 528}, - {19200000, 22050, 44100, 0x2, 9, 4080}, - {22579200, 22050, 44100, 0x2, 8, 0}, - {33868800, 22050, 44100, 0x2, 5, 3333}, - /* 32k */ - {12000000, 32000, 48000, 0x1, 16, 3840}, - {19200000, 32000, 48000, 0x1, 10, 2400}, - {22579200, 32000, 48000, 0x1, 8, 7075}, - {33868800, 32000, 48000, 0x1, 5, 8049}, - /* 44.1k */ - {12000000, 44100, 44100, 0x0, 15, 528}, - {19200000, 44100, 44100, 0x0, 9, 4080}, - {22579200, 44100, 44100, 0x0, 8, 0}, - {33868800, 44100, 44100, 0x0, 5, 3333}, - /* 48k */ - {12000000, 48000, 48000, 0x0, 16, 3840}, - {19200000, 48000, 48000, 0x0, 10, 2400}, - {22579200, 48000, 48000, 0x0, 8, 7075}, - {33868800, 48000, 48000, 0x0, 5, 8049}, - /* 64k */ - {12000000, 64000, 96000, 0x1, 16, 3840}, - {19200000, 64000, 96000, 0x1, 10, 2400}, - {22579200, 64000, 96000, 0x1, 8, 7075}, - {33868800, 64000, 96000, 0x1, 5, 8049}, - /* 88.2k */ - {12000000, 88200, 88200, 0x0, 15, 528}, - {19200000, 88200, 88200, 0x0, 9, 4080}, - {22579200, 88200, 88200, 0x0, 8, 0}, - {33868800, 88200, 88200, 0x0, 5, 3333}, - /* 96k */ - {12000000, 96000, 96000, 0x0, 16, 3840}, - {19200000, 96000, 96000, 0x0, 10, 2400}, - {22579200, 96000, 96000, 0x0, 8, 7075}, - {33868800, 96000, 96000, 0x0, 5, 8049}, -}; - -static inline int aic3x_get_divs(int mclk, int rate) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(aic3x_divs); i++) { - if (aic3x_divs[i].rate == rate && aic3x_divs[i].mclk == mclk) - return i; - } - - return 0; -} - static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -730,49 +700,107 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; struct aic3x_priv *aic3x = codec->private_data; - int i; - u8 data, pll_p, pll_r, pll_j; - u16 pll_d; + int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; + u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; + u16 pll_d = 1; - i = aic3x_get_divs(aic3x->sysclk, params_rate(params)); - - /* Route Left DAC to left channel input and - * right DAC to right channel input */ - data = (LDAC2LCH | RDAC2RCH); - switch (aic3x_divs[i].fsref_reg) { - case 44100: - data |= FSREF_44100; + /* select data word length */ + data = + aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: break; - case 48000: - data |= FSREF_48000; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (0x01 << 4); break; - case 88200: - data |= FSREF_44100 | DUAL_RATE_MODE; + case SNDRV_PCM_FORMAT_S24_LE: + data |= (0x02 << 4); break; - case 96000: - data |= FSREF_48000 | DUAL_RATE_MODE; + case SNDRV_PCM_FORMAT_S32_LE: + data |= (0x03 << 4); break; } + aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data); + + /* Fsref can be 44100 or 48000 */ + fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000; + + /* Try to find a value for Q which allows us to bypass the PLL and + * generate CODEC_CLK directly. */ + for (pll_q = 2; pll_q < 18; pll_q++) + if (aic3x->sysclk / (128 * pll_q) == fsref) { + bypass_pll = 1; + break; + } + + if (bypass_pll) { + pll_q &= 0xf; + aic3x_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT); + aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV); + } else + aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV); + + /* Route Left DAC to left channel input and + * right DAC to right channel input */ + data = (LDAC2LCH | RDAC2RCH); + data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; + if (params_rate(params) >= 64000) + data |= DUAL_RATE_MODE; aic3x_write(codec, AIC3X_CODEC_DATAPATH_REG, data); /* codec sample rate select */ - data = aic3x_divs[i].sr_reg; + data = (fsref * 20) / params_rate(params); + if (params_rate(params) < 64000) + data /= 2; + data /= 5; + data -= 2; data |= (data << 4); aic3x_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data); - /* Use PLL for generation Fsref by equation: - * Fsref = (MCLK * K * R)/(2048 * P); - * Fix P = 2 and R = 1 and calculate K, if - * K = J.D, i.e. J - an interger portion of K and D is the fractional - * one with 4 digits of precision; - * Example: - * For MCLK = 22.5792 MHz and Fsref = 48kHz: - * Select P = 2, R= 1, K = 8.7074, which results in J = 8, D = 7074 + if (bypass_pll) + return 0; + + /* Use PLL + * find an apropriate setup for j, d, r and p by iterating over + * p and r - j and d are calculated for each fraction. + * Up to 128 values are probed, the closest one wins the game. + * The sysclk is divided by 1000 to prevent integer overflows. */ - pll_p = 2; - pll_r = 1; - pll_j = aic3x_divs[i].pllj_reg; - pll_d = aic3x_divs[i].plld_reg; + codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000); + + for (r = 1; r <= 16; r++) + for (p = 1; p <= 8; p++) { + int clk, tmp = (codec_clk * pll_r * 10) / pll_p; + u8 j = tmp / 10000; + u16 d = tmp % 10000; + + if (j > 63) + continue; + + if (d != 0 && aic3x->sysclk < 10000000) + continue; + + /* This is actually 1000 * ((j + (d/10000)) * r) / p + * The term had to be converted to get rid of the + * division by 10000 */ + clk = ((10000 * j * r) + (d * r)) / (10 * p); + + /* check whether this values get closer than the best + * ones we had before */ + if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) { + pll_j = j; pll_d = d; pll_r = r; pll_p = p; + last_clk = clk; + } + + /* Early exit for exact matches */ + if (clk == codec_clk) + break; + } + + if (last_clk == 0) { + printk(KERN_ERR "%s(): unable to setup PLL\n", __func__); + return -EINVAL; + } data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT)); @@ -782,28 +810,10 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, aic3x_write(codec, AIC3X_PLL_PROGD_REG, (pll_d & 0x3F) << PLLD_LSB_SHIFT); - /* select data word length */ - data = - aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - break; - case SNDRV_PCM_FORMAT_S20_3LE: - data |= (0x01 << 4); - break; - case SNDRV_PCM_FORMAT_S24_LE: - data |= (0x02 << 4); - break; - case SNDRV_PCM_FORMAT_S32_LE: - data |= (0x03 << 4); - break; - } - aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data); - return 0; } -static int aic3x_mute(struct snd_soc_codec_dai *dai, int mute) +static int aic3x_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u8 ldac_reg = aic3x_read_reg_cache(codec, LDAC_VOL) & ~MUTE_ON; @@ -820,31 +830,25 @@ static int aic3x_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int aic3x_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int aic3x_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; struct aic3x_priv *aic3x = codec->private_data; - switch (freq) { - case 12000000: - case 19200000: - case 22579200: - case 33868800: - aic3x->sysclk = freq; - return 0; - } - - return -EINVAL; + aic3x->sysclk = freq; + return 0; } -static int aic3x_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; struct aic3x_priv *aic3x = codec->private_data; - u8 iface_areg = 0; - u8 iface_breg = 0; + u8 iface_areg, iface_breg; + + iface_areg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f; + iface_breg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -883,13 +887,14 @@ static int aic3x_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return 0; } -static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) +static int aic3x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { struct aic3x_priv *aic3x = codec->private_data; u8 reg; - switch (event) { - case SNDRV_CTL_POWER_D0: + switch (level) { + case SND_SOC_BIAS_ON: /* all power is driven by DAPM system */ if (aic3x->master) { /* enable pll */ @@ -898,10 +903,9 @@ static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) reg | PLL_ENABLE); } break; - case SNDRV_CTL_POWER_D1: - case SNDRV_CTL_POWER_D2: + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: + case SND_SOC_BIAS_STANDBY: /* * all power is driven by DAPM system, * so output power is safe if bypass was set @@ -913,7 +917,7 @@ static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) reg & ~PLL_ENABLE); } break; - case SNDRV_CTL_POWER_D3cold: + case SND_SOC_BIAS_OFF: /* force all power off */ reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON); @@ -949,17 +953,44 @@ static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) } break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } +void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state) +{ + u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG; + u8 bit = gpio ? 3: 0; + u8 val = aic3x_read_reg_cache(codec, reg) & ~(1 << bit); + aic3x_write(codec, reg, val | (!!state << bit)); +} +EXPORT_SYMBOL_GPL(aic3x_set_gpio); + +int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio) +{ + u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG; + u8 val, bit = gpio ? 2: 1; + + aic3x_read(codec, reg, &val); + return (val >> bit) & 1; +} +EXPORT_SYMBOL_GPL(aic3x_get_gpio); + +int aic3x_headset_detected(struct snd_soc_codec *codec) +{ + u8 val; + aic3x_read(codec, AIC3X_RT_IRQ_FLAGS_REG, &val); + return (val >> 2) & 1; +} +EXPORT_SYMBOL_GPL(aic3x_headset_detected); + #define AIC3X_RATES SNDRV_PCM_RATE_8000_96000 #define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) -struct snd_soc_codec_dai aic3x_dai = { - .name = "aic3x", +struct snd_soc_dai aic3x_dai = { + .name = "tlv320aic3x", .playback = { .stream_name = "Playback", .channels_min = 1, @@ -988,7 +1019,7 @@ static int aic3x_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; - aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -1008,7 +1039,7 @@ static int aic3x_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - aic3x_dapm_event(codec, codec->suspend_dapm_state); + aic3x_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -1020,16 +1051,17 @@ static int aic3x_resume(struct platform_device *pdev) static int aic3x_init(struct snd_soc_device *socdev) { struct snd_soc_codec *codec = socdev->codec; + struct aic3x_setup_data *setup = socdev->codec_data; int reg, ret = 0; - codec->name = "aic3x"; + codec->name = "tlv320aic3x"; codec->owner = THIS_MODULE; codec->read = aic3x_read_reg_cache; codec->write = aic3x_write; - codec->dapm_event = aic3x_dapm_event; + codec->set_bias_level = aic3x_set_bias_level; codec->dai = &aic3x_dai; codec->num_dai = 1; - codec->reg_cache_size = sizeof(aic3x_reg); + codec->reg_cache_size = ARRAY_SIZE(aic3x_reg); codec->reg_cache = kmemdup(aic3x_reg, sizeof(aic3x_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; @@ -1108,7 +1140,11 @@ static int aic3x_init(struct snd_soc_device *socdev) aic3x_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL); /* off, with power on */ - aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* setup GPIO functions */ + aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4); + aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4); aic3x_add_controls(codec); aic3x_add_widgets(codec); @@ -1135,73 +1171,39 @@ static struct snd_soc_device *aic3x_socdev; * AIC3X 2 wire address can be up to 4 devices with device addresses * 0x18, 0x19, 0x1A, 0x1B */ -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; - -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static struct i2c_driver aic3x_i2c_driver; -static struct i2c_client client_template; /* * If the i2c layer weren't so broken, we could pass this kind of data * around */ -static int aic3x_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int aic3x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = aic3x_socdev; - struct aic3x_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c; int ret; - if (addr != setup->i2c_address) - return -ENODEV; - - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) { - kfree(codec); - return -ENOMEM; - } i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - printk(KERN_ERR "aic3x: failed to attach codec at addr %x\n", - addr); - goto err; - } - ret = aic3x_init(socdev); - if (ret < 0) { + if (ret < 0) printk(KERN_ERR "aic3x: failed to initialise AIC3X\n"); - goto err; - } - return ret; - -err: - kfree(codec); - kfree(i2c); return ret; } -static int aic3x_i2c_detach(struct i2c_client *client) +static int aic3x_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); - i2c_detach_client(client); kfree(codec->reg_cache); - kfree(client); return 0; } -static int aic3x_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, aic3x_codec_probe); -} +static const struct i2c_device_id aic3x_i2c_id[] = { + { "tlv320aic3x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id); /* machine i2c codec control layer */ static struct i2c_driver aic3x_i2c_driver = { @@ -1209,14 +1211,56 @@ static struct i2c_driver aic3x_i2c_driver = { .name = "aic3x I2C Codec", .owner = THIS_MODULE, }, - .attach_adapter = aic3x_i2c_attach, - .detach_client = aic3x_i2c_detach, + .probe = aic3x_i2c_probe, + .remove = aic3x_i2c_remove, + .id_table = aic3x_i2c_id, }; -static struct i2c_client client_template = { - .name = "AIC3X", - .driver = &aic3x_i2c_driver, -}; +static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len) +{ + value[0] = i2c_smbus_read_byte_data(client, value[0]); + return (len == 1); +} + +static int aic3x_add_i2c_device(struct platform_device *pdev, + const struct aic3x_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&aic3x_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "tlv320aic3x", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&aic3x_i2c_driver); + return -ENODEV; +} #endif static int aic3x_probe(struct platform_device *pdev) @@ -1249,15 +1293,18 @@ static int aic3x_probe(struct platform_device *pdev) aic3x_socdev = socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t) i2c_master_send; - ret = i2c_add_driver(&aic3x_i2c_driver); - if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + codec->hw_read = (hw_read_t) aic3x_i2c_read; + ret = aic3x_add_i2c_device(pdev, setup); } #else /* Add other interfaces here */ #endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } return ret; } @@ -1268,11 +1315,12 @@ static int aic3x_remove(struct platform_device *pdev) /* power down chip */ if (codec->control_data) - aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3); + aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); i2c_del_driver(&aic3x_i2c_driver); #endif kfree(codec->private_data); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index d0cdeeb629de..00a195aa02e4 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -1,7 +1,7 @@ /* * ALSA SoC TLV320AIC3X codec driver * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -37,6 +37,8 @@ #define AIC3X_ASD_INTF_CTRLB 9 /* Audio overflow status and PLL R value programming register */ #define AIC3X_OVRF_STATUS_AND_PLLR_REG 11 +/* Audio codec digital filter control register */ +#define AIC3X_CODEC_DFILT_CTRL 12 /* ADC PGA Gain control registers */ #define LADC_VOL 15 @@ -108,6 +110,13 @@ #define DACR1_2_RLOPM_VOL 92 #define LLOPM_CTRL 86 #define RLOPM_CTRL 93 +/* GPIO/IRQ registers */ +#define AIC3X_STICKY_IRQ_FLAGS_REG 96 +#define AIC3X_RT_IRQ_FLAGS_REG 97 +#define AIC3X_GPIO1_REG 98 +#define AIC3X_GPIO2_REG 99 +#define AIC3X_GPIOA_REG 100 +#define AIC3X_GPIOB_REG 101 /* Clock generation control register */ #define AIC3X_CLKGEN_CTRL_REG 102 @@ -128,12 +137,15 @@ /* PLL registers bitfields */ #define PLLP_SHIFT 0 +#define PLLQ_SHIFT 3 #define PLLR_SHIFT 0 #define PLLJ_SHIFT 2 #define PLLD_MSB_SHIFT 0 #define PLLD_LSB_SHIFT 2 /* Clock generation register bits */ +#define CODEC_CLKIN_PLLDIV 0 +#define CODEC_CLKIN_CLKDIV 1 #define PLL_CLKIN_SHIFT 4 #define MCLK_SOURCE 0x0 #define PLL_CLKDIV_SHIFT 0 @@ -171,11 +183,53 @@ /* Default input volume */ #define DEFAULT_GAIN 0x20 +/* GPIO API */ +enum { + AIC3X_GPIO1_FUNC_DISABLED = 0, + AIC3X_GPIO1_FUNC_AUDIO_WORDCLK_ADC = 1, + AIC3X_GPIO1_FUNC_CLOCK_MUX = 2, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV2 = 3, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV4 = 4, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV8 = 5, + AIC3X_GPIO1_FUNC_SHORT_CIRCUIT_IRQ = 6, + AIC3X_GPIO1_FUNC_AGC_NOISE_IRQ = 7, + AIC3X_GPIO1_FUNC_INPUT = 8, + AIC3X_GPIO1_FUNC_OUTPUT = 9, + AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK = 10, + AIC3X_GPIO1_FUNC_AUDIO_WORDCLK = 11, + AIC3X_GPIO1_FUNC_BUTTON_IRQ = 12, + AIC3X_GPIO1_FUNC_HEADSET_DETECT_IRQ = 13, + AIC3X_GPIO1_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 14, + AIC3X_GPIO1_FUNC_ALL_IRQ = 16 +}; + +enum { + AIC3X_GPIO2_FUNC_DISABLED = 0, + AIC3X_GPIO2_FUNC_HEADSET_DETECT_IRQ = 2, + AIC3X_GPIO2_FUNC_INPUT = 3, + AIC3X_GPIO2_FUNC_OUTPUT = 4, + AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT = 5, + AIC3X_GPIO2_FUNC_AUDIO_BITCLK = 8, + AIC3X_GPIO2_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 9, + AIC3X_GPIO2_FUNC_ALL_IRQ = 10, + AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_OR_AGC_IRQ = 11, + AIC3X_GPIO2_FUNC_HEADSET_OR_BUTTON_PRESS_OR_SHORT_CIRCUIT_IRQ = 12, + AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_IRQ = 13, + AIC3X_GPIO2_FUNC_AGC_NOISE_IRQ = 14, + AIC3X_GPIO2_FUNC_BUTTON_PRESS_IRQ = 15 +}; + +void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state); +int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio); +int aic3x_headset_detected(struct snd_soc_codec *codec); + struct aic3x_setup_data { + int i2c_bus; unsigned short i2c_address; + unsigned int gpio_func[2]; }; -extern struct snd_soc_codec_dai aic3x_dai; +extern struct snd_soc_dai aic3x_dai; extern struct snd_soc_codec_device soc_codec_dev_aic3x; #endif /* _AIC3X_H */ diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c new file mode 100644 index 000000000000..a69ee72a7af5 --- /dev/null +++ b/sound/soc/codecs/uda1380.c @@ -0,0 +1,849 @@ +/* + * uda1380.c - Philips UDA1380 ALSA SoC audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com> + * Improved support for DAPM and audio routing/mixing capabilities, + * added TLV support. + * + * Modified by Richard Purdie <richard@openedhand.com> to fit into SoC + * codec model. + * + * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org> + * Copyright 2005 Openedhand Ltd. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +#include "uda1380.h" + +#define UDA1380_VERSION "0.6" + +/* + * uda1380 register cache + */ +static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { + 0x0502, 0x0000, 0x0000, 0x3f3f, + 0x0202, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0xff00, 0x0000, 0x4800, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x8000, 0x0002, 0x0000, +}; + +/* + * read uda1380 register cache + */ +static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == UDA1380_RESET) + return 0; + if (reg >= UDA1380_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write uda1380 register cache + */ +static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= UDA1380_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the UDA1380 register space + */ +static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + /* data is + * data[0] is register offset + * data[1] is MS byte + * data[2] is LS byte + */ + data[0] = reg; + data[1] = (value & 0xff00) >> 8; + data[2] = value & 0x00ff; + + uda1380_write_reg_cache(codec, reg, value); + + /* the interpolator & decimator regs must only be written when the + * codec DAI is active. + */ + if (!codec->active && (reg >= UDA1380_MVOL)) + return 0; + pr_debug("uda1380: hw write %x val %x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) { + unsigned int val; + i2c_master_send(codec->control_data, data, 1); + i2c_master_recv(codec->control_data, data, 2); + val = (data[0]<<8) | data[1]; + if (val != value) { + pr_debug("uda1380: READ BACK VAL %x\n", + (data[0]<<8) | data[1]); + return -EIO; + } + return 0; + } else + return -EIO; +} + +#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0) + +/* declarations of ALSA reg_elem_REAL controls */ +static const char *uda1380_deemp[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", + "96kHz", +}; +static const char *uda1380_input_sel[] = { + "Line", + "Mic + Line R", + "Line L", + "Mic", +}; +static const char *uda1380_output_sel[] = { + "DAC", + "Analog Mixer", +}; +static const char *uda1380_spf_mode[] = { + "Flat", + "Minimum1", + "Minimum2", + "Maximum" +}; +static const char *uda1380_capture_sel[] = { + "ADC", + "Digital Mixer" +}; +static const char *uda1380_sel_ns[] = { + "3rd-order", + "5th-order" +}; +static const char *uda1380_mix_control[] = { + "off", + "PCM only", + "before sound processing", + "after sound processing" +}; +static const char *uda1380_sdet_setting[] = { + "3200", + "4800", + "9600", + "19200" +}; +static const char *uda1380_os_setting[] = { + "single-speed", + "double-speed (no mixing)", + "quad-speed (no mixing)" +}; + +static const struct soc_enum uda1380_deemp_enum[] = { + SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp), + SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp), +}; +static const struct soc_enum uda1380_input_sel_enum = + SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel); /* SEL_MIC, SEL_LNA */ +static const struct soc_enum uda1380_output_sel_enum = + SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel); /* R02_EN_AVC */ +static const struct soc_enum uda1380_spf_enum = + SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode); /* M */ +static const struct soc_enum uda1380_capture_sel_enum = + SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel); /* SEL_SOURCE */ +static const struct soc_enum uda1380_sel_ns_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns); /* SEL_NS */ +static const struct soc_enum uda1380_mix_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control); /* MIX, MIX_POS */ +static const struct soc_enum uda1380_sdet_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting); /* SD_VALUE */ +static const struct soc_enum uda1380_os_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting); /* OS */ + +/* + * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB) + */ +static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1); + +/* + * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored), + * from -66 dB in 0.5 dB steps (2 dB steps, really) and + * from -52 dB in 0.25 dB steps + */ +static const unsigned int mvol_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1), + 16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0), + 44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0), +}; + +/* + * from -72 dB in 1.5 dB steps (6 dB steps really), + * from -66 dB in 0.75 dB steps (3 dB steps really), + * from -60 dB in 0.5 dB steps (2 dB steps really) and + * from -46 dB in 0.25 dB steps + */ +static const unsigned int vc_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1), + 8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0), + 16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0), + 44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0), +}; + +/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */ +static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0); + +/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts + * off at 18 dB max) */ +static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0); + +/* from -63 to 24 dB in 0.5 dB steps (-128...48) */ +static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1); + +/* from 0 to 24 dB in 3 dB steps */ +static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); + +/* from 0 to 30 dB in 2 dB steps */ +static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0); + +static const struct snd_kcontrol_new uda1380_snd_controls[] = { + SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */ + SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */ + SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */ + SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */ + SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */ + SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */ + SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */ +/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */ + SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */ + SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */ + SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */ + SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */ + SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */ + SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */ + SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */ + SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */ + SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */ + SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */ + SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */ + SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */ +/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */ + SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */ + SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */ + SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */ + SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */ + SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */ + SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */ + SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */ + /* -5.5, -8, -11.5, -14 dBFS */ + SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), +}; + +/* add non dapm controls */ +static int uda1380_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&uda1380_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Input mux */ +static const struct snd_kcontrol_new uda1380_input_mux_control = + SOC_DAPM_ENUM("Route", uda1380_input_sel_enum); + +/* Output mux */ +static const struct snd_kcontrol_new uda1380_output_mux_control = + SOC_DAPM_ENUM("Route", uda1380_output_sel_enum); + +/* Capture mux */ +static const struct snd_kcontrol_new uda1380_capture_mux_control = + SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum); + + +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &uda1380_input_mux_control), + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0, + &uda1380_output_mux_control), + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, + &uda1380_capture_mux_control), + SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0), + SND_SOC_DAPM_INPUT("VINM"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("VOUTLHP"), + SND_SOC_DAPM_OUTPUT("VOUTRHP"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0), + SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + + /* output mux */ + {"HeadPhone Driver", NULL, "Output Mux"}, + {"VOUTR", NULL, "Output Mux"}, + {"VOUTL", NULL, "Output Mux"}, + + {"Analog Mixer", NULL, "VINR"}, + {"Analog Mixer", NULL, "VINL"}, + {"Analog Mixer", NULL, "DAC"}, + + {"Output Mux", "DAC", "DAC"}, + {"Output Mux", "Analog Mixer", "Analog Mixer"}, + + /* {"DAC", "Digital Mixer", "I2S" } */ + + /* headphone driver */ + {"VOUTLHP", NULL, "HeadPhone Driver"}, + {"VOUTRHP", NULL, "HeadPhone Driver"}, + + /* input mux */ + {"Left ADC", NULL, "Input Mux"}, + {"Input Mux", "Mic", "Mic LNA"}, + {"Input Mux", "Mic + Line R", "Mic LNA"}, + {"Input Mux", "Line L", "Left PGA"}, + {"Input Mux", "Line", "Left PGA"}, + + /* right input */ + {"Right ADC", "Mic + Line R", "Right PGA"}, + {"Right ADC", "Line", "Right PGA"}, + + /* inputs */ + {"Mic LNA", NULL, "VINM"}, + {"Left PGA", NULL, "VINL"}, + {"Right PGA", NULL, "VINR"}, +}; + +static int uda1380_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets, + ARRAY_SIZE(uda1380_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(codec, UDA1380_IFACE); + iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK); + + /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16 | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB | R01_SFORO_I2S; + } + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) + iface |= R01_SIM; + + uda1380_write(codec, UDA1380_IFACE, iface); + + return 0; +} + +/* + * Flush reg cache + * We can only write the interpolator and decimator registers + * when the DAI is being clocked by the CPU DAI. It's up to the + * machine and cpu DAI driver to do this before we are called. + */ +static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg, reg_start, reg_end, clk; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg_start = UDA1380_MVOL; + reg_end = UDA1380_MIXER; + } else { + reg_start = UDA1380_DEC; + reg_end = UDA1380_AGC; + } + + /* FIXME disable DAC_CLK */ + clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK); + + for (reg = reg_start; reg <= reg_end; reg++) { + pr_debug("uda1380: flush reg %x val %x:", reg, + uda1380_read_reg_cache(codec, reg)); + uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg)); + } + + /* FIXME enable DAC_CLK */ + uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK); + + return 0; +} + +static int uda1380_pcm_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; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* set WSPLL power and divider if running from this clock */ + if (clk & R00_DAC_CLK) { + int rate = params_rate(params); + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + clk &= ~0x3; /* clear SEL_LOOP_DIV */ + switch (rate) { + case 6250 ... 12500: + clk |= 0x0; + break; + case 12501 ... 25000: + clk |= 0x1; + break; + case 25001 ... 50000: + clk |= 0x2; + break; + case 50001 ... 100000: + clk |= 0x3; + break; + } + uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk |= R00_EN_DAC | R00_EN_INT; + else + clk |= R00_EN_ADC | R00_EN_DEC; + + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* shut down WSPLL power if running from this clock */ + if (clk & R00_DAC_CLK) { + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk &= ~(R00_EN_DAC | R00_EN_INT); + else + clk &= ~(R00_EN_ADC | R00_EN_DEC); + + uda1380_write(codec, UDA1380_CLK, clk); +} + +static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM; + + /* FIXME: mute(codec,0) is called when the magician clock is already + * set to WSPLL, but for some unknown reason writing to interpolator + * registers works only when clocked by SYSCLK */ + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); + if (mute) + uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM); + else + uda1380_write(codec, UDA1380_DEEMP, mute_reg); + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static int uda1380_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int pm = uda1380_read_reg_cache(codec, UDA1380_PM); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm); + break; + case SND_SOC_BIAS_STANDBY: + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS); + break; + case SND_SOC_BIAS_OFF: + uda1380_write(codec, UDA1380_PM, 0x0); + break; + } + codec->bias_level = level; + return 0; +} + +#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_dai uda1380_dai[] = { +{ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* playback only - dual interface */ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* capture only - dual interface*/ + .name = "UDA1380", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .set_fmt = uda1380_set_dai_fmt, + }, +}, +}; +EXPORT_SYMBOL_GPL(uda1380_dai); + +static int uda1380_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int uda1380_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(uda1380_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + uda1380_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the UDA1380 driver + * register mixer and dsp interfaces with the kernel + */ +static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "UDA1380"; + codec->owner = THIS_MODULE; + codec->read = uda1380_read_reg_cache; + codec->write = uda1380_write; + codec->set_bias_level = uda1380_set_bias_level; + codec->dai = uda1380_dai; + codec->num_dai = ARRAY_SIZE(uda1380_dai); + codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); + codec->reg_cache_step = 1; + uda1380_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("uda1380: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + /* set clock input */ + switch (dac_clk) { + case UDA1380_DAC_CLK_SYSCLK: + uda1380_write(codec, UDA1380_CLK, 0); + break; + case UDA1380_DAC_CLK_WSPLL: + uda1380_write(codec, UDA1380_CLK, R00_DAC_CLK); + break; + } + + /* uda1380 init */ + uda1380_add_controls(codec); + uda1380_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + pr_err("uda1380: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *uda1380_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static int uda1380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = uda1380_socdev; + struct uda1380_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = uda1380_init(socdev, setup->dac_clk); + if (ret < 0) + pr_err("uda1380: failed to initialise UDA1380\n"); + + return ret; +} + +static int uda1380_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id uda1380_i2c_id[] = { + { "uda1380", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); + +static struct i2c_driver uda1380_i2c_driver = { + .driver = { + .name = "UDA1380 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = uda1380_i2c_probe, + .remove = uda1380_i2c_remove, + .id_table = uda1380_i2c_id, +}; + +static int uda1380_add_i2c_device(struct platform_device *pdev, + const struct uda1380_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "uda1380", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&uda1380_i2c_driver); + return -ENODEV; +} +#endif + +static int uda1380_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct uda1380_setup_data *setup; + struct snd_soc_codec *codec; + int ret; + + pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + uda1380_socdev = socdev; + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = uda1380_add_i2c_device(pdev, setup); + } +#endif + + if (ret != 0) + kfree(codec); + return ret; +} + +/* power down chip */ +static int uda1380_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&uda1380_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_uda1380 = { + .probe = uda1380_probe, + .remove = uda1380_remove, + .suspend = uda1380_suspend, + .resume = uda1380_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); + +MODULE_AUTHOR("Giorgio Padrin"); +MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h new file mode 100644 index 000000000000..c55c17a52a12 --- /dev/null +++ b/sound/soc/codecs/uda1380.h @@ -0,0 +1,90 @@ +/* + * Audio support for Philips UDA1380 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org> + */ + +#ifndef _UDA1380_H +#define _UDA1380_H + +#define UDA1380_CLK 0x00 +#define UDA1380_IFACE 0x01 +#define UDA1380_PM 0x02 +#define UDA1380_AMIX 0x03 +#define UDA1380_HP 0x04 +#define UDA1380_MVOL 0x10 +#define UDA1380_MIXVOL 0x11 +#define UDA1380_MODE 0x12 +#define UDA1380_DEEMP 0x13 +#define UDA1380_MIXER 0x14 +#define UDA1380_INTSTAT 0x18 +#define UDA1380_DEC 0x20 +#define UDA1380_PGA 0x21 +#define UDA1380_ADC 0x22 +#define UDA1380_AGC 0x23 +#define UDA1380_DECSTAT 0x28 +#define UDA1380_RESET 0x7f + +#define UDA1380_CACHEREGNUM 0x24 + +/* Register flags */ +#define R00_EN_ADC 0x0800 +#define R00_EN_DEC 0x0400 +#define R00_EN_DAC 0x0200 +#define R00_EN_INT 0x0100 +#define R00_DAC_CLK 0x0010 +#define R01_SFORI_I2S 0x0000 +#define R01_SFORI_LSB16 0x0100 +#define R01_SFORI_LSB18 0x0200 +#define R01_SFORI_LSB20 0x0300 +#define R01_SFORI_MSB 0x0500 +#define R01_SFORI_MASK 0x0700 +#define R01_SFORO_I2S 0x0000 +#define R01_SFORO_LSB16 0x0001 +#define R01_SFORO_LSB18 0x0002 +#define R01_SFORO_LSB20 0x0003 +#define R01_SFORO_LSB24 0x0004 +#define R01_SFORO_MSB 0x0005 +#define R01_SFORO_MASK 0x0007 +#define R01_SEL_SOURCE 0x0040 +#define R01_SIM 0x0010 +#define R02_PON_PLL 0x8000 +#define R02_PON_HP 0x2000 +#define R02_PON_DAC 0x0400 +#define R02_PON_BIAS 0x0100 +#define R02_EN_AVC 0x0080 +#define R02_PON_AVC 0x0040 +#define R02_PON_LNA 0x0010 +#define R02_PON_PGAL 0x0008 +#define R02_PON_ADCL 0x0004 +#define R02_PON_PGAR 0x0002 +#define R02_PON_ADCR 0x0001 +#define R13_MTM 0x4000 +#define R14_SILENCE 0x0080 +#define R14_SDET_ON 0x0040 +#define R21_MT_ADC 0x8000 +#define R22_SEL_LNA 0x0008 +#define R22_SEL_MIC 0x0004 +#define R22_SKIP_DCFIL 0x0002 +#define R23_AGC_EN 0x0001 + +struct uda1380_setup_data { + int i2c_bus; + unsigned short i2c_address; + int dac_clk; +#define UDA1380_DAC_CLK_SYSCLK 0 +#define UDA1380_DAC_CLK_WSPLL 1 +}; + +#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ +#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ +#define UDA1380_DAI_CAPTURE 2 /* capture DAI */ + +extern struct snd_soc_dai uda1380_dai[3]; +extern struct snd_soc_codec_device soc_codec_dev_uda1380; + +#endif /* _UDA1380_H */ diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c new file mode 100644 index 000000000000..d8ca2da8d634 --- /dev/null +++ b/sound/soc/codecs/wm8510.c @@ -0,0 +1,895 @@ +/* + * wm8510.c -- WM8510 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/initval.h> + +#include "wm8510.h" + +#define WM8510_VERSION "0.6" + +struct snd_soc_codec_device soc_codec_dev_wm8510; + +/* + * wm8510 register cache + * We can't read the WM8510 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x0000, 0x0000, 0x0100, 0x00ff, + 0x0000, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0003, 0x0010, 0x0000, 0x0000, + 0x0000, 0x0002, 0x0001, 0x0000, + 0x0000, 0x0000, 0x0039, 0x0000, + 0x0001, +}; + +#define WM8510_POWER1_BIASEN 0x08 +#define WM8510_POWER1_BUFIOEN 0x10 + +/* + * read wm8510 register cache + */ +static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8510_RESET) + return 0; + if (reg >= WM8510_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8510 register cache + */ +static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8510_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8510 register space + */ +static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8510 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8510_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0) + +static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" }; +static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8510_alc[] = { "ALC", "Limiter" }; + +static const struct soc_enum wm8510_enum[] = { + SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */ + SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */ + SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp), + SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc), +}; + +static const struct snd_kcontrol_new wm8510_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8510_enum[1]), +SOC_ENUM("ADC Companding", wm8510_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8510_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0), + +SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), + +SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8510_enum[3]), +SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1), +SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0), +SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1), +}; + +/* add non dapm controls */ +static int wm8510_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8510_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8510_boost_controls[] = { +SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 1), +SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0), +SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0), +}; + +static const struct snd_kcontrol_new wm8510_micpga_controls[] = { +SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0), +SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0), +SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0, + &wm8510_speaker_mixer_controls[0], + ARRAY_SIZE(wm8510_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0, + &wm8510_mono_mixer_controls[0], + ARRAY_SIZE(wm8510_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0, + &wm8510_micpga_controls[0], + ARRAY_SIZE(wm8510_micpga_controls)), +SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, + &wm8510_boost_controls[0], + ARRAY_SIZE(wm8510_boost_controls)), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Microphone PGA */ + {"Mic PGA", "MICN Switch", "MICN"}, + {"Mic PGA", "MICP Switch", "MICP"}, + { "Mic PGA", "AUX Switch", "Aux Input" }, + + /* Boost Mixer */ + {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, + {"Boost Mixer", "Mic Volume", "MICP"}, + {"Boost Mixer", "Aux Volume", "Aux Input"}, + + {"ADC", NULL, "Boost Mixer"}, +}; + +static int wm8510_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8510_dapm_widgets, + ARRAY_SIZE(wm8510_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct pll_ { + unsigned int pre_div:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8510 N value %d outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); + wm8510_write(codec, WM8510_CLOCK, reg & 0x0ff); + + /* Turn off PLL */ + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg & 0x1df); + return 0; + } + + pll_factors(freq_out*8, freq_in); + + wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); + wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); + wm8510_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff); + wm8510_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff); + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); + wm8510_write(codec, WM8510_CLOCK, reg | 0x100); + + return 0; +} + +/* + * Configure WM8510 clock dividers. + */ +static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8510_OPCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_GPIO) & 0x1cf; + wm8510_write(codec, WM8510_GPIO, reg | div); + break; + case WM8510_MCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1f; + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + case WM8510_ADCCLK: + reg = wm8510_read_reg_cache(codec, WM8510_ADC) & 0x1f7; + wm8510_write(codec, WM8510_ADC, reg | div); + break; + case WM8510_DACCLK: + reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0x1f7; + wm8510_write(codec, WM8510_DAC, reg | div); + break; + case WM8510_BCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1e3; + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_CLOCK, clk); + return 0; +} + +static int wm8510_pcm_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; + u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f; + u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + case SNDRV_PCM_RATE_44100: + case SNDRV_PCM_RATE_48000: + break; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_ADD, adn); + return 0; +} + +static int wm8510_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf; + + if (mute) + wm8510_write(codec, WM8510_DAC, mute_reg | 0x40); + else + wm8510_write(codec, WM8510_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8510_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 power1 = wm8510_read_reg_cache(codec, WM8510_POWER1) & ~0x3; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + power1 |= 0x1; /* VMID 50k */ + wm8510_write(codec, WM8510_POWER1, power1); + break; + + case SND_SOC_BIAS_STANDBY: + power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN; + + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Initial cap charge at VMID 5k */ + wm8510_write(codec, WM8510_POWER1, power1 | 0x3); + mdelay(100); + } + + power1 |= 0x2; /* VMID 500k */ + wm8510_write(codec, WM8510_POWER1, power1); + break; + + case SND_SOC_BIAS_OFF: + wm8510_write(codec, WM8510_POWER1, 0); + wm8510_write(codec, WM8510_POWER2, 0); + wm8510_write(codec, WM8510_POWER3, 0); + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai wm8510_dai = { + .name = "WM8510 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .ops = { + .hw_params = wm8510_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8510_mute, + .set_fmt = wm8510_set_dai_fmt, + .set_clkdiv = wm8510_set_dai_clkdiv, + .set_pll = wm8510_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8510_dai); + +static int wm8510_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8510_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8510_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the WM8510 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8510_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8510"; + codec->owner = THIS_MODULE; + codec->read = wm8510_read_reg_cache; + codec->write = wm8510_write; + codec->set_bias_level = wm8510_set_bias_level; + codec->dai = &wm8510_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8510_reg); + codec->reg_cache = kmemdup(wm8510_reg, sizeof(wm8510_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8510_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8510_add_controls(codec); + wm8510_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8510_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8510 2 wire address is 0x1a + */ + +static int wm8510_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8510_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8510\n"); + + return ret; +} + +static int wm8510_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8510_i2c_id[] = { + { "wm8510", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id); + +static struct i2c_driver wm8510_i2c_driver = { + .driver = { + .name = "WM8510 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8510_i2c_probe, + .remove = wm8510_i2c_remove, + .id_table = wm8510_i2c_id, +}; + +static int wm8510_add_i2c_device(struct platform_device *pdev, + const struct wm8510_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8510_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8510", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8510_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8510_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8510_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8510\n"); + + return ret; +} + +static int __devexit wm8510_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8510_spi_driver = { + .driver = { + .name = "wm8510", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8510_spi_probe, + .remove = __devexit_p(wm8510_spi_remove), +}; + +static int wm8510_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + +static int wm8510_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8510_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + pr_info("WM8510 Audio Codec %s", WM8510_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8510_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8510_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8510_spi_write; + ret = spi_register_driver(&wm8510_spi_driver); + if (ret != 0) + printk(KERN_ERR "can't add spi driver"); + } +#endif + + if (ret != 0) + kfree(codec); + return ret; +} + +/* power down chip */ +static int wm8510_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8510_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8510_spi_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8510 = { + .probe = wm8510_probe, + .remove = wm8510_remove, + .suspend = wm8510_suspend, + .resume = wm8510_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510); + +MODULE_DESCRIPTION("ASoC WM8510 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h new file mode 100644 index 000000000000..bdefcf5c69ff --- /dev/null +++ b/sound/soc/codecs/wm8510.h @@ -0,0 +1,105 @@ +/* + * wm8510.h -- WM8510 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8510_H +#define _WM8510_H + +/* WM8510 register space */ + +#define WM8510_RESET 0x0 +#define WM8510_POWER1 0x1 +#define WM8510_POWER2 0x2 +#define WM8510_POWER3 0x3 +#define WM8510_IFACE 0x4 +#define WM8510_COMP 0x5 +#define WM8510_CLOCK 0x6 +#define WM8510_ADD 0x7 +#define WM8510_GPIO 0x8 +#define WM8510_DAC 0xa +#define WM8510_DACVOL 0xb +#define WM8510_ADC 0xe +#define WM8510_ADCVOL 0xf +#define WM8510_EQ1 0x12 +#define WM8510_EQ2 0x13 +#define WM8510_EQ3 0x14 +#define WM8510_EQ4 0x15 +#define WM8510_EQ5 0x16 +#define WM8510_DACLIM1 0x18 +#define WM8510_DACLIM2 0x19 +#define WM8510_NOTCH1 0x1b +#define WM8510_NOTCH2 0x1c +#define WM8510_NOTCH3 0x1d +#define WM8510_NOTCH4 0x1e +#define WM8510_ALC1 0x20 +#define WM8510_ALC2 0x21 +#define WM8510_ALC3 0x22 +#define WM8510_NGATE 0x23 +#define WM8510_PLLN 0x24 +#define WM8510_PLLK1 0x25 +#define WM8510_PLLK2 0x26 +#define WM8510_PLLK3 0x27 +#define WM8510_ATTEN 0x28 +#define WM8510_INPUT 0x2c +#define WM8510_INPPGA 0x2d +#define WM8510_ADCBOOST 0x2f +#define WM8510_OUTPUT 0x31 +#define WM8510_SPKMIX 0x32 +#define WM8510_SPKVOL 0x36 +#define WM8510_MONOMIX 0x38 + +#define WM8510_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8510_OPCLKDIV 0 +#define WM8510_MCLKDIV 1 +#define WM8510_ADCCLK 2 +#define WM8510_DACCLK 3 +#define WM8510_BCLKDIV 4 + +/* DAC clock dividers */ +#define WM8510_DACCLK_F2 (1 << 3) +#define WM8510_DACCLK_F4 (0 << 3) + +/* ADC clock dividers */ +#define WM8510_ADCCLK_F2 (1 << 3) +#define WM8510_ADCCLK_F4 (0 << 3) + +/* PLL Out dividers */ +#define WM8510_OPCLKDIV_1 (0 << 4) +#define WM8510_OPCLKDIV_2 (1 << 4) +#define WM8510_OPCLKDIV_3 (2 << 4) +#define WM8510_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8510_BCLKDIV_1 (0 << 2) +#define WM8510_BCLKDIV_2 (1 << 2) +#define WM8510_BCLKDIV_4 (2 << 2) +#define WM8510_BCLKDIV_8 (3 << 2) +#define WM8510_BCLKDIV_16 (4 << 2) +#define WM8510_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8510_MCLKDIV_1 (0 << 5) +#define WM8510_MCLKDIV_1_5 (1 << 5) +#define WM8510_MCLKDIV_2 (2 << 5) +#define WM8510_MCLKDIV_3 (3 << 5) +#define WM8510_MCLKDIV_4 (4 << 5) +#define WM8510_MCLKDIV_6 (5 << 5) +#define WM8510_MCLKDIV_8 (6 << 5) +#define WM8510_MCLKDIV_12 (7 << 5) + +struct wm8510_setup_data { + int spi; + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8510_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8510; + +#endif diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c new file mode 100644 index 000000000000..627ebfb4209b --- /dev/null +++ b/sound/soc/codecs/wm8580.c @@ -0,0 +1,1053 @@ +/* + * wm8580.c -- WM8580 ALSA Soc Audio driver + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * The WM8580 is a multichannel codec with S/PDIF support, featuring six + * DAC channels and two ADC channels. + * + * Currently only the primary audio interface is supported - S/PDIF and + * the secondary audio interfaces are not. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/tlv.h> +#include <sound/initval.h> +#include <asm/div64.h> + +#include "wm8580.h" + +#define WM8580_VERSION "0.1" + +struct pll_state { + unsigned int in; + unsigned int out; +}; + +/* codec private data */ +struct wm8580_priv { + struct pll_state a; + struct pll_state b; +}; + +/* WM8580 register space */ +#define WM8580_PLLA1 0x00 +#define WM8580_PLLA2 0x01 +#define WM8580_PLLA3 0x02 +#define WM8580_PLLA4 0x03 +#define WM8580_PLLB1 0x04 +#define WM8580_PLLB2 0x05 +#define WM8580_PLLB3 0x06 +#define WM8580_PLLB4 0x07 +#define WM8580_CLKSEL 0x08 +#define WM8580_PAIF1 0x09 +#define WM8580_PAIF2 0x0A +#define WM8580_SAIF1 0x0B +#define WM8580_PAIF3 0x0C +#define WM8580_PAIF4 0x0D +#define WM8580_SAIF2 0x0E +#define WM8580_DAC_CONTROL1 0x0F +#define WM8580_DAC_CONTROL2 0x10 +#define WM8580_DAC_CONTROL3 0x11 +#define WM8580_DAC_CONTROL4 0x12 +#define WM8580_DAC_CONTROL5 0x13 +#define WM8580_DIGITAL_ATTENUATION_DACL1 0x14 +#define WM8580_DIGITAL_ATTENUATION_DACR1 0x15 +#define WM8580_DIGITAL_ATTENUATION_DACL2 0x16 +#define WM8580_DIGITAL_ATTENUATION_DACR2 0x17 +#define WM8580_DIGITAL_ATTENUATION_DACL3 0x18 +#define WM8580_DIGITAL_ATTENUATION_DACR3 0x19 +#define WM8580_MASTER_DIGITAL_ATTENUATION 0x1C +#define WM8580_ADC_CONTROL1 0x1D +#define WM8580_SPDTXCHAN0 0x1E +#define WM8580_SPDTXCHAN1 0x1F +#define WM8580_SPDTXCHAN2 0x20 +#define WM8580_SPDTXCHAN3 0x21 +#define WM8580_SPDTXCHAN4 0x22 +#define WM8580_SPDTXCHAN5 0x23 +#define WM8580_SPDMODE 0x24 +#define WM8580_INTMASK 0x25 +#define WM8580_GPO1 0x26 +#define WM8580_GPO2 0x27 +#define WM8580_GPO3 0x28 +#define WM8580_GPO4 0x29 +#define WM8580_GPO5 0x2A +#define WM8580_INTSTAT 0x2B +#define WM8580_SPDRXCHAN1 0x2C +#define WM8580_SPDRXCHAN2 0x2D +#define WM8580_SPDRXCHAN3 0x2E +#define WM8580_SPDRXCHAN4 0x2F +#define WM8580_SPDRXCHAN5 0x30 +#define WM8580_SPDSTAT 0x31 +#define WM8580_PWRDN1 0x32 +#define WM8580_PWRDN2 0x33 +#define WM8580_READBACK 0x34 +#define WM8580_RESET 0x35 + +/* PLLB4 (register 7h) */ +#define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60 +#define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20 +#define WM8580_PLLB4_MCLKOUTSRC_PLLB 0x40 +#define WM8580_PLLB4_MCLKOUTSRC_OSC 0x60 + +#define WM8580_PLLB4_CLKOUTSRC_MASK 0x180 +#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080 +#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100 +#define WM8580_PLLB4_CLKOUTSRC_OSCCLK 0x180 + +/* CLKSEL (register 8h) */ +#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02 + +/* AIF control 1 (registers 9h-bh) */ +#define WM8580_AIF_RATE_MASK 0x7 +#define WM8580_AIF_RATE_128 0x0 +#define WM8580_AIF_RATE_192 0x1 +#define WM8580_AIF_RATE_256 0x2 +#define WM8580_AIF_RATE_384 0x3 +#define WM8580_AIF_RATE_512 0x4 +#define WM8580_AIF_RATE_768 0x5 +#define WM8580_AIF_RATE_1152 0x6 + +#define WM8580_AIF_BCLKSEL_MASK 0x18 +#define WM8580_AIF_BCLKSEL_64 0x00 +#define WM8580_AIF_BCLKSEL_128 0x08 +#define WM8580_AIF_BCLKSEL_256 0x10 +#define WM8580_AIF_BCLKSEL_SYSCLK 0x18 + +#define WM8580_AIF_MS 0x20 + +#define WM8580_AIF_CLKSRC_MASK 0xc0 +#define WM8580_AIF_CLKSRC_PLLA 0x40 +#define WM8580_AIF_CLKSRC_PLLB 0x40 +#define WM8580_AIF_CLKSRC_MCLK 0xc0 + +/* AIF control 2 (registers ch-eh) */ +#define WM8580_AIF_FMT_MASK 0x03 +#define WM8580_AIF_FMT_RIGHTJ 0x00 +#define WM8580_AIF_FMT_LEFTJ 0x01 +#define WM8580_AIF_FMT_I2S 0x02 +#define WM8580_AIF_FMT_DSP 0x03 + +#define WM8580_AIF_LENGTH_MASK 0x0c +#define WM8580_AIF_LENGTH_16 0x00 +#define WM8580_AIF_LENGTH_20 0x04 +#define WM8580_AIF_LENGTH_24 0x08 +#define WM8580_AIF_LENGTH_32 0x0c + +#define WM8580_AIF_LRP 0x10 +#define WM8580_AIF_BCP 0x20 + +/* Powerdown Register 1 (register 32h) */ +#define WM8580_PWRDN1_PWDN 0x001 +#define WM8580_PWRDN1_ALLDACPD 0x040 + +/* Powerdown Register 2 (register 33h) */ +#define WM8580_PWRDN2_OSSCPD 0x001 +#define WM8580_PWRDN2_PLLAPD 0x002 +#define WM8580_PWRDN2_PLLBPD 0x004 +#define WM8580_PWRDN2_SPDIFPD 0x008 +#define WM8580_PWRDN2_SPDIFTXD 0x010 +#define WM8580_PWRDN2_SPDIFRXD 0x020 + +#define WM8580_DAC_CONTROL5_MUTEALL 0x10 + +/* + * wm8580 register cache + * We can't read the WM8580 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8580_reg[] = { + 0x0121, 0x017e, 0x007d, 0x0014, /*R3*/ + 0x0121, 0x017e, 0x007d, 0x0194, /*R7*/ + 0x001c, 0x0002, 0x0002, 0x00c2, /*R11*/ + 0x0182, 0x0082, 0x000a, 0x0024, /*R15*/ + 0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/ + 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/ + 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/ + 0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/ + 0x0000, 0x0000, 0x0031, 0x000b, /*R35*/ + 0x0039, 0x0000, 0x0010, 0x0032, /*R39*/ + 0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/ + 0x0000, 0x0000, 0x0000, 0x0000, /*R47*/ + 0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/ + 0x0000, 0x0000 /*R53*/ +}; + +/* + * read wm8580 register cache + */ +static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + return cache[reg]; +} + +/* + * write wm8580 register cache + */ +static inline void wm8580_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + cache[reg] = value; +} + +/* + * write to the WM8580 register space + */ +static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + + /* Registers are 9 bits wide */ + value &= 0x1ff; + + switch (reg) { + case WM8580_RESET: + /* Uncached */ + break; + default: + if (value == wm8580_read_reg_cache(codec, reg)) + return 0; + } + + /* data is + * D15..D9 WM8580 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8580_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static inline unsigned int wm8580_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + default: + return wm8580_read_reg_cache(codec, reg); + } +} + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); + +static int wm8580_out_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int reg2 = (kcontrol->private_value >> 24) & 0xff; + int ret; + u16 val; + + /* Clear the register cache so we write without VU set */ + wm8580_write_reg_cache(codec, reg, 0); + wm8580_write_reg_cache(codec, reg2, 0); + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* Now write again with the volume update bit set */ + val = wm8580_read_reg_cache(codec, reg); + wm8580_write(codec, reg, val | 0x0100); + + val = wm8580_read_reg_cache(codec, reg2); + wm8580_write(codec, reg2, val | 0x0100); + + return 0; +} + +#define SOC_WM8580_OUT_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = snd_soc_get_volsw_2r, .put = wm8580_out_vu, \ + .private_value = (reg_left) | ((shift) << 8) | \ + ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) } + +static const struct snd_kcontrol_new wm8580_snd_controls[] = { +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC1 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL1, + WM8580_DIGITAL_ATTENUATION_DACR1, + 0, 0xff, 0, dac_tlv), +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC2 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL2, + WM8580_DIGITAL_ATTENUATION_DACR2, + 0, 0xff, 0, dac_tlv), +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL3, + WM8580_DIGITAL_ATTENUATION_DACR3, + 0, 0xff, 0, dac_tlv), + +SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0), +SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0), +SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0), + +SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4, 0, 1, 1, 0), +SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4, 2, 3, 1, 0), +SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4, 4, 5, 1, 0), + +SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0), +SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 0), +SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 0), +SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 0), + +SOC_DOUBLE("ADC Mute Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 0), +SOC_SINGLE("ADC High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0), +}; + +/* Add non-DAPM controls */ +static int wm8580_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8580_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8580_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} +static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1), +SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1), +SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1), + +SND_SOC_DAPM_OUTPUT("VOUT1L"), +SND_SOC_DAPM_OUTPUT("VOUT1R"), +SND_SOC_DAPM_OUTPUT("VOUT2L"), +SND_SOC_DAPM_OUTPUT("VOUT2R"), +SND_SOC_DAPM_OUTPUT("VOUT3L"), +SND_SOC_DAPM_OUTPUT("VOUT3R"), + +SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1), + +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + { "VOUT1L", NULL, "DAC1" }, + { "VOUT1R", NULL, "DAC1" }, + + { "VOUT2L", NULL, "DAC2" }, + { "VOUT2R", NULL, "DAC2" }, + + { "VOUT3L", NULL, "DAC3" }, + { "VOUT3R", NULL, "DAC3" }, + + { "ADC", NULL, "AINL" }, + { "ADC", NULL, "AINR" }, +}; + +static int wm8580_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets, + ARRAY_SIZE(wm8580_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 prescale:1; + u32 postscale:1; + u32 freqmode:2; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide */ +#define FIXED_PLL_SIZE (1 << 22) + +/* PLL rate to output rate divisions */ +static struct { + unsigned int div; + unsigned int freqmode; + unsigned int postscale; +} post_table[] = { + { 2, 0, 0 }, + { 4, 0, 1 }, + { 4, 1, 0 }, + { 8, 1, 1 }, + { 8, 2, 0 }, + { 16, 2, 1 }, + { 12, 3, 0 }, + { 24, 3, 1 } +}; + +static int pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + int i; + + pr_debug("wm8580: PLL %dHz->%dHz\n", source, target); + + /* Scale the output frequency up; the PLL should run in the + * region of 90-100MHz. + */ + for (i = 0; i < ARRAY_SIZE(post_table); i++) { + if (target * post_table[i].div >= 90000000 && + target * post_table[i].div <= 100000000) { + pll_div->freqmode = post_table[i].freqmode; + pll_div->postscale = post_table[i].postscale; + target *= post_table[i].div; + break; + } + } + + if (i == ARRAY_SIZE(post_table)) { + printk(KERN_ERR "wm8580: Unable to scale output frequency " + "%u\n", target); + return -EINVAL; + } + + Ndiv = target / source; + + if (Ndiv < 5) { + source /= 2; + pll_div->prescale = 1; + Ndiv = target / source; + } else + pll_div->prescale = 0; + + if ((Ndiv < 5) || (Ndiv > 13)) { + printk(KERN_ERR + "WM8580 N=%d outside supported range\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + pll_div->k = K; + + pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n", + pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode, + pll_div->postscale); + + return 0; +} + +static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + int offset; + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8580_priv *wm8580 = codec->private_data; + struct pll_state *state; + struct _pll_div pll_div; + unsigned int reg; + unsigned int pwr_mask; + int ret; + + /* GCC isn't able to work out the ifs below for initialising/using + * pll_div so suppress warnings. + */ + memset(&pll_div, 0, sizeof(pll_div)); + + switch (pll_id) { + case WM8580_PLLA: + state = &wm8580->a; + offset = 0; + pwr_mask = WM8580_PWRDN2_PLLAPD; + break; + case WM8580_PLLB: + state = &wm8580->b; + offset = 4; + pwr_mask = WM8580_PWRDN2_PLLBPD; + break; + default: + return -ENODEV; + } + + if (freq_in && freq_out) { + ret = pll_factors(&pll_div, freq_out, freq_in); + if (ret != 0) + return ret; + } + + state->in = freq_in; + state->out = freq_out; + + /* Always disable the PLL - it is not safe to leave it running + * while reprogramming it. + */ + reg = wm8580_read(codec, WM8580_PWRDN2); + wm8580_write(codec, WM8580_PWRDN2, reg | pwr_mask); + + if (!freq_in || !freq_out) + return 0; + + wm8580_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff); + wm8580_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff); + wm8580_write(codec, WM8580_PLLA3 + offset, + (pll_div.k >> 18 & 0xf) | (pll_div.n << 4)); + + reg = wm8580_read(codec, WM8580_PLLA4 + offset); + reg &= ~0x3f; + reg |= pll_div.prescale | pll_div.postscale << 1 | + pll_div.freqmode << 4; + + wm8580_write(codec, WM8580_PLLA4 + offset, reg); + + /* All done, turn it on */ + reg = wm8580_read(codec, WM8580_PWRDN2); + wm8580_write(codec, WM8580_PWRDN2, reg & ~pwr_mask); + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai = rtd->dai; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->codec_dai->id); + + paifb &= ~WM8580_AIF_LENGTH_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + paifb |= WM8580_AIF_LENGTH_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + paifb |= WM8580_AIF_LENGTH_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + paifb |= WM8580_AIF_LENGTH_24; + break; + default: + return -EINVAL; + } + + wm8580_write(codec, WM8580_PAIF3 + dai->codec_dai->id, paifb); + return 0; +} + +static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int aifa; + unsigned int aifb; + int can_invert_lrclk; + + aifa = wm8580_read(codec, WM8580_PAIF1 + codec_dai->id); + aifb = wm8580_read(codec, WM8580_PAIF3 + codec_dai->id); + + aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aifa &= ~WM8580_AIF_MS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aifa |= WM8580_AIF_MS; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_RIGHTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_LEFTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + break; + case SND_SOC_DAIFMT_DSP_B: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + aifb |= WM8580_AIF_LRP; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_IB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_BCP; + aifb |= WM8580_AIF_LRP; + break; + + case SND_SOC_DAIFMT_IB_NF: + aifb |= WM8580_AIF_BCP; + break; + + case SND_SOC_DAIFMT_NB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_LRP; + break; + + default: + return -EINVAL; + } + + wm8580_write(codec, WM8580_PAIF1 + codec_dai->id, aifa); + wm8580_write(codec, WM8580_PAIF3 + codec_dai->id, aifb); + + return 0; +} + +static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + switch (div_id) { + case WM8580_MCLK: + reg = wm8580_read(codec, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_MCLK: + /* Input */ + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA; + break; + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_MCLKOUTSRC_OSC; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_PLLB4, reg); + break; + + case WM8580_DAC_CLKSEL: + reg = wm8580_read(codec, WM8580_CLKSEL); + reg &= ~WM8580_CLKSEL_DAC_CLKSEL_MASK; + + switch (div) { + case WM8580_CLKSRC_MCLK: + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLA; + break; + + case WM8580_CLKSRC_PLLB: + reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLB; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_CLKSEL, reg); + break; + + case WM8580_CLKOUTSRC: + reg = wm8580_read(codec, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_NONE: + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK; + break; + + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_PLLB4, reg); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + reg = wm8580_read(codec, WM8580_DAC_CONTROL5); + + if (mute) + reg |= WM8580_DAC_CONTROL5_MUTEALL; + else + reg &= ~WM8580_DAC_CONTROL5_MUTEALL; + + wm8580_write(codec, WM8580_DAC_CONTROL5, reg); + + return 0; +} + +static int wm8580_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + reg = wm8580_read(codec, WM8580_PWRDN1); + wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai wm8580_dai[] = { + { + .name = "WM8580 PAIFRX", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = { + .hw_params = wm8580_paif_hw_params, + }, + .dai_ops = { + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + .digital_mute = wm8580_digital_mute, + }, + }, + { + .name = "WM8580 PAIFTX", + .id = 1, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = { + .hw_params = wm8580_paif_hw_params, + }, + .dai_ops = { + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + }, + }, +}; +EXPORT_SYMBOL_GPL(wm8580_dai); + +/* + * initialise the WM8580 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8580_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8580"; + codec->owner = THIS_MODULE; + codec->read = wm8580_read_reg_cache; + codec->write = wm8580_write; + codec->set_bias_level = wm8580_set_bias_level; + codec->dai = wm8580_dai; + codec->num_dai = ARRAY_SIZE(wm8580_dai); + codec->reg_cache_size = ARRAY_SIZE(wm8580_reg); + codec->reg_cache = kmemdup(wm8580_reg, sizeof(wm8580_reg), + GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* Get the codec into a known state */ + wm8580_write(codec, WM8580_RESET, 0); + + /* Power up and get individual control of the DACs */ + wm8580_write(codec, WM8580_PWRDN1, wm8580_read(codec, WM8580_PWRDN1) & + ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD)); + + /* Make VMID high impedence */ + wm8580_write(codec, WM8580_ADC_CONTROL1, + wm8580_read(codec, WM8580_ADC_CONTROL1) & ~0x100); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8580: failed to create pcms\n"); + goto pcm_err; + } + + wm8580_add_controls(codec); + wm8580_add_widgets(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8580: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8580_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8580 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8580_i2c_driver; +static struct i2c_client client_template; + +static int wm8580_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8580_socdev; + struct wm8580_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + dev_err(&i2c->dev, "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8580_init(socdev); + if (ret < 0) { + dev_err(&i2c->dev, "failed to initialise WM8580\n"); + goto err; + } + + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8580_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8580_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8580_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8580_i2c_driver = { + .driver = { + .name = "WM8580 I2C Codec", + .owner = THIS_MODULE, + }, + .attach_adapter = wm8580_i2c_attach, + .detach_client = wm8580_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8580", + .driver = &wm8580_i2c_driver, +}; +#endif + +static int wm8580_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8580_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8580_priv *wm8580; + int ret = 0; + + pr_info("WM8580 Audio Codec %s\n", WM8580_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL); + if (wm8580 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8580; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8580_socdev = socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8580_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8580_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8580_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8580 = { + .probe = wm8580_probe, + .remove = wm8580_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580); + +MODULE_DESCRIPTION("ASoC WM8580 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h new file mode 100644 index 000000000000..589ddaba21d7 --- /dev/null +++ b/sound/soc/codecs/wm8580.h @@ -0,0 +1,42 @@ +/* + * wm8580.h -- audio driver for WM8580 + * + * Copyright 2008 Samsung Electronics. + * Author: Ryu Euiyoul + * ryu.real@gmail.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8580_H +#define _WM8580_H + +#define WM8580_PLLA 1 +#define WM8580_PLLB 2 + +#define WM8580_MCLK 1 +#define WM8580_DAC_CLKSEL 2 +#define WM8580_CLKOUTSRC 3 + +#define WM8580_CLKSRC_MCLK 1 +#define WM8580_CLKSRC_PLLA 2 +#define WM8580_CLKSRC_PLLB 3 +#define WM8580_CLKSRC_OSC 4 +#define WM8580_CLKSRC_NONE 5 + +struct wm8580_setup_data { + unsigned short i2c_address; +}; + +#define WM8580_DAI_PAIFRX 0 +#define WM8580_DAI_PAIFTX 1 + +extern struct snd_soc_dai wm8580_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_wm8580; + +#endif + diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 0cf9265fca8f..7f8a7e36b33e 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -19,6 +19,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> +#include <linux/spi/spi.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -28,28 +29,8 @@ #include "wm8731.h" -#define AUDIO_NAME "wm8731" #define WM8731_VERSION "0.13" -/* - * Debug - */ - -#define WM8731_DEBUG 0 - -#ifdef WM8731_DEBUG -#define dbg(format, arg...) \ - printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) -#else -#define dbg(format, arg...) do {} while (0) -#endif -#define err(format, arg...) \ - printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) -#define info(format, arg...) \ - printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) -#define warn(format, arg...) \ - printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) - struct snd_soc_codec_device soc_codec_dev_wm8731; /* codec private data */ @@ -193,7 +174,7 @@ SND_SOC_DAPM_INPUT("RLINEIN"), SND_SOC_DAPM_INPUT("LLINEIN"), }; -static const char *intercon[][3] = { +static const struct snd_soc_dapm_route intercon[] = { /* output mixer */ {"Output Mixer", "Line Bypass Switch", "Line Input"}, {"Output Mixer", "HiFi Playback Switch", "DAC"}, @@ -214,22 +195,14 @@ static const char *intercon[][3] = { {"Line Input", NULL, "LLINEIN"}, {"Line Input", NULL, "RLINEIN"}, {"Mic Bias", NULL, "MICIN"}, - - /* terminator */ - {NULL, NULL, NULL}, }; static int wm8731_add_widgets(struct snd_soc_codec *codec) { - int i; + snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets, + ARRAY_SIZE(wm8731_dapm_widgets)); - for (i = 0; i < ARRAY_SIZE(wm8731_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8731_dapm_widgets[i]); - - /* set up audio path interconnects */ - for (i = 0; intercon[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, intercon[i][0], - intercon[i][1], intercon[i][2]); + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); snd_soc_dapm_new_widgets(codec); return 0; @@ -345,7 +318,7 @@ static void wm8731_shutdown(struct snd_pcm_substream *substream) } } -static int wm8731_mute(struct snd_soc_codec_dai *dai, int mute) +static int wm8731_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 mute_reg = wm8731_read_reg_cache(codec, WM8731_APDIGI) & 0xfff7; @@ -357,7 +330,7 @@ static int wm8731_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int wm8731_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; @@ -376,7 +349,7 @@ static int wm8731_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, } -static int wm8731_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -435,29 +408,29 @@ static int wm8731_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return 0; } -static int wm8731_dapm_event(struct snd_soc_codec *codec, int event) +static int wm8731_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* vref/mid, osc on, dac unmute */ wm8731_write(codec, WM8731_PWR, reg); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* everything off except vref/vmid, */ wm8731_write(codec, WM8731_PWR, reg | 0x0040); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: /* everything off, dac mute, inactive */ wm8731_write(codec, WM8731_ACTIVE, 0x0); wm8731_write(codec, WM8731_PWR, 0xffff); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -470,7 +443,7 @@ static int wm8731_dapm_event(struct snd_soc_codec *codec, int event) #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) -struct snd_soc_codec_dai wm8731_dai = { +struct snd_soc_dai wm8731_dai = { .name = "WM8731", .playback = { .stream_name = "Playback", @@ -503,7 +476,7 @@ static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_codec *codec = socdev->codec; wm8731_write(codec, WM8731_ACTIVE, 0x0); - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -521,8 +494,8 @@ static int wm8731_resume(struct platform_device *pdev) data[1] = cache[i] & 0x00ff; codec->hw_write(codec->control_data, data, 2); } - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3hot); - wm8731_dapm_event(codec, codec->suspend_dapm_state); + wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8731_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -539,10 +512,10 @@ static int wm8731_init(struct snd_soc_device *socdev) codec->owner = THIS_MODULE; codec->read = wm8731_read_reg_cache; codec->write = wm8731_write; - codec->dapm_event = wm8731_dapm_event; + codec->set_bias_level = wm8731_set_bias_level; codec->dai = &wm8731_dai; codec->num_dai = 1; - codec->reg_cache_size = sizeof(wm8731_reg); + codec->reg_cache_size = ARRAY_SIZE(wm8731_reg); codec->reg_cache = kmemdup(wm8731_reg, sizeof(wm8731_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; @@ -557,7 +530,7 @@ static int wm8731_init(struct snd_soc_device *socdev) } /* power on device */ - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set the update bits */ reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V); @@ -597,90 +570,144 @@ static struct snd_soc_device *wm8731_socdev; * low = 0x1a * high = 0x1b */ -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; - -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; -static struct i2c_driver wm8731_i2c_driver; -static struct i2c_client client_template; - -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ - -static int wm8731_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8731_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8731_socdev; - struct wm8731_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c; int ret; - if (addr != setup->i2c_address) - return -ENODEV; - - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) { - kfree(codec); - return -ENOMEM; - } i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8731_init(socdev); - if (ret < 0) { - err("failed to initialise WM8731\n"); - goto err; - } - return ret; + if (ret < 0) + pr_err("failed to initialise WM8731\n"); -err: - kfree(codec); - kfree(i2c); return ret; } -static int wm8731_i2c_detach(struct i2c_client *client) +static int wm8731_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); - i2c_detach_client(client); kfree(codec->reg_cache); - kfree(client); return 0; } -static int wm8731_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8731_codec_probe); -} +static const struct i2c_device_id wm8731_i2c_id[] = { + { "wm8731", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8731_i2c_driver = { .driver = { .name = "WM8731 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_WM8731, - .attach_adapter = wm8731_i2c_attach, - .detach_client = wm8731_i2c_detach, - .command = NULL, + .probe = wm8731_i2c_probe, + .remove = wm8731_i2c_remove, + .id_table = wm8731_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8731", - .driver = &wm8731_i2c_driver, -}; +static int wm8731_add_i2c_device(struct platform_device *pdev, + const struct wm8731_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8731_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8731", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8731_i2c_driver); + return -ENODEV; +} #endif +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8731_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8731_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8731_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8731\n"); + + return ret; +} + +static int __devexit wm8731_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8731_spi_driver = { + .driver = { + .name = "wm8731", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8731_spi_probe, + .remove = __devexit_p(wm8731_spi_remove), +}; + +static int wm8731_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + static int wm8731_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -689,7 +716,7 @@ static int wm8731_probe(struct platform_device *pdev) struct wm8731_priv *wm8731; int ret = 0; - info("WM8731 Audio Codec %s", WM8731_VERSION); + pr_info("WM8731 Audio Codec %s", WM8731_VERSION); setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); @@ -709,17 +736,27 @@ static int wm8731_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_paths); wm8731_socdev = socdev; + ret = -ENODEV; + #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; - ret = i2c_add_driver(&wm8731_i2c_driver); + ret = wm8731_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8731_spi_write; + ret = spi_register_driver(&wm8731_spi_driver); if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + printk(KERN_ERR "can't add spi driver"); } -#else - /* Add other interfaces here */ #endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } return ret; } @@ -730,13 +767,17 @@ static int wm8731_remove(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->codec; if (codec->control_data) - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8731_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8731_spi_driver); +#endif kfree(codec->private_data); kfree(codec); diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h index 5bcab6a7afb4..95190e9c0c14 100644 --- a/sound/soc/codecs/wm8731.h +++ b/sound/soc/codecs/wm8731.h @@ -35,10 +35,12 @@ #define WM8731_DAI 0 struct wm8731_setup_data { + int spi; + int i2c_bus; unsigned short i2c_address; }; -extern struct snd_soc_codec_dai wm8731_dai; +extern struct snd_soc_dai wm8731_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8731; #endif diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 16cd5d4d5ad9..9b7296ee5b08 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -19,6 +19,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> +#include <linux/spi/spi.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -28,28 +29,8 @@ #include "wm8750.h" -#define AUDIO_NAME "WM8750" #define WM8750_VERSION "0.12" -/* - * Debug - */ - -#define WM8750_DEBUG 0 - -#ifdef WM8750_DEBUG -#define dbg(format, arg...) \ - printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) -#else -#define dbg(format, arg...) do {} while (0) -#endif -#define err(format, arg...) \ - printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) -#define info(format, arg...) \ - printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) -#define warn(format, arg...) \ - printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) - /* codec private data */ struct wm8750_priv { unsigned int sysclk; @@ -367,8 +348,9 @@ static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("ROUT1"), SND_SOC_DAPM_OUTPUT("LOUT2"), SND_SOC_DAPM_OUTPUT("ROUT2"), - SND_SOC_DAPM_OUTPUT("MONO"), + SND_SOC_DAPM_OUTPUT("MONO1"), SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("VREF"), SND_SOC_DAPM_INPUT("LINPUT1"), SND_SOC_DAPM_INPUT("LINPUT2"), @@ -378,7 +360,7 @@ static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { SND_SOC_DAPM_INPUT("RINPUT3"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* left mixer */ {"Left Mixer", "Playback Switch", "Left DAC"}, {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, @@ -470,22 +452,14 @@ static const char *audio_map[][3] = { /* ADC */ {"Left ADC", NULL, "Left ADC Mux"}, {"Right ADC", NULL, "Right ADC Mux"}, - - /* terminator */ - {NULL, NULL, NULL}, }; static int wm8750_add_widgets(struct snd_soc_codec *codec) { - int i; + snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); - for (i = 0; i < ARRAY_SIZE(wm8750_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8750_dapm_widgets[i]); - - /* set up audio path audio_mapnects */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -563,7 +537,7 @@ static inline int get_coeff(int mclk, int rate) return -EINVAL; } -static int wm8750_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int wm8750_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; @@ -581,7 +555,7 @@ static int wm8750_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, return -EINVAL; } -static int wm8750_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -674,7 +648,7 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -static int wm8750_mute(struct snd_soc_codec_dai *dai, int mute) +static int wm8750_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7; @@ -686,29 +660,29 @@ static int wm8750_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int wm8750_dapm_event(struct snd_soc_codec *codec, int event) +static int wm8750_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* set vmid to 50k and unmute dac */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: /* set vmid to 5k for quick power up */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* mute dac and set vmid to 500k, enable VREF */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: wm8750_write(codec, WM8750_PWR1, 0x0001); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -719,7 +693,7 @@ static int wm8750_dapm_event(struct snd_soc_codec *codec, int event) #define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) -struct snd_soc_codec_dai wm8750_dai = { +struct snd_soc_dai wm8750_dai = { .name = "WM8750", .playback = { .stream_name = "Playback", @@ -748,7 +722,7 @@ static void wm8750_work(struct work_struct *work) { struct snd_soc_codec *codec = container_of(work, struct snd_soc_codec, delayed_work.work); - wm8750_dapm_event(codec, codec->dapm_state); + wm8750_set_bias_level(codec, codec->bias_level); } static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) @@ -756,7 +730,7 @@ static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -777,12 +751,12 @@ static int wm8750_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* charge wm8750 caps */ - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D0; + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_ON; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); } @@ -803,10 +777,10 @@ static int wm8750_init(struct snd_soc_device *socdev) codec->owner = THIS_MODULE; codec->read = wm8750_read_reg_cache; codec->write = wm8750_write; - codec->dapm_event = wm8750_dapm_event; + codec->set_bias_level = wm8750_set_bias_level; codec->dai = &wm8750_dai; codec->num_dai = 1; - codec->reg_cache_size = sizeof(wm8750_reg); + codec->reg_cache_size = ARRAY_SIZE(wm8750_reg); codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; @@ -821,8 +795,8 @@ static int wm8750_init(struct snd_soc_device *socdev) } /* charge output caps */ - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D3hot; + wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_STANDBY; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); /* set the update bits */ @@ -867,90 +841,147 @@ static struct snd_soc_device *wm8750_socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) /* - * WM8731 2 wire address is determined by GPIO5 + * WM8750 2 wire address is determined by GPIO5 * state during powerup. * low = 0x1a * high = 0x1b */ -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; - -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; -static struct i2c_driver wm8750_i2c_driver; -static struct i2c_client client_template; - -static int wm8750_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8750_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8750_socdev; - struct wm8750_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c; int ret; - if (addr != setup->i2c_address) - return -ENODEV; - - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) { - kfree(codec); - return -ENOMEM; - } i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8750_init(socdev); - if (ret < 0) { - err("failed to initialise WM8750\n"); - goto err; - } - return ret; + if (ret < 0) + pr_err("failed to initialise WM8750\n"); -err: - kfree(codec); - kfree(i2c); return ret; } -static int wm8750_i2c_detach(struct i2c_client *client) +static int wm8750_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); - i2c_detach_client(client); kfree(codec->reg_cache); - kfree(client); return 0; } -static int wm8750_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8750_codec_probe); -} +static const struct i2c_device_id wm8750_i2c_id[] = { + { "wm8750", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8750_i2c_driver = { .driver = { .name = "WM8750 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_WM8750, - .attach_adapter = wm8750_i2c_attach, - .detach_client = wm8750_i2c_detach, - .command = NULL, + .probe = wm8750_i2c_probe, + .remove = wm8750_i2c_remove, + .id_table = wm8750_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8750", - .driver = &wm8750_i2c_driver, +static int wm8750_add_i2c_device(struct platform_device *pdev, + const struct wm8750_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8750_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8750", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8750_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8750_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8750_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8750_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8750\n"); + + return ret; +} + +static int __devexit wm8750_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8750_spi_driver = { + .driver = { + .name = "wm8750", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8750_spi_probe, + .remove = __devexit_p(wm8750_spi_remove), }; + +static int wm8750_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} #endif static int wm8750_probe(struct platform_device *pdev) @@ -959,9 +990,9 @@ static int wm8750_probe(struct platform_device *pdev) struct wm8750_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec; struct wm8750_priv *wm8750; - int ret = 0; + int ret; - info("WM8750 Audio Codec %s", WM8750_VERSION); + pr_info("WM8750 Audio Codec %s", WM8750_VERSION); codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; @@ -980,18 +1011,27 @@ static int wm8750_probe(struct platform_device *pdev) wm8750_socdev = socdev; INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work); + ret = -ENODEV; + #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; - ret = i2c_add_driver(&wm8750_i2c_driver); + ret = wm8750_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8750_spi_write; + ret = spi_register_driver(&wm8750_spi_driver); if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + printk(KERN_ERR "can't add spi driver"); } -#else - /* Add other interfaces here */ #endif + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } return ret; } @@ -1021,13 +1061,17 @@ static int wm8750_remove(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->codec; if (codec->control_data) - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); run_delayed_work(&codec->delayed_work); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8750_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8750_spi_driver); +#endif kfree(codec->private_data); kfree(codec); diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h index a97a54a6348e..1dc100e19cfe 100644 --- a/sound/soc/codecs/wm8750.h +++ b/sound/soc/codecs/wm8750.h @@ -58,10 +58,12 @@ #define WM8750_SYSCLK 0 struct wm8750_setup_data { + int spi; + int i2c_bus; unsigned short i2c_address; }; -extern struct snd_soc_codec_dai wm8750_dai; +extern struct snd_soc_dai wm8750_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8750; #endif diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index fb41826c4c4c..d426eaa22185 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -2,8 +2,7 @@ * wm8753.c -- WM8753 ALSA Soc Audio driver * * Copyright 2003 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -34,13 +33,13 @@ #include <linux/module.h> #include <linux/moduleparam.h> -#include <linux/version.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> +#include <linux/spi/spi.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -52,28 +51,8 @@ #include "wm8753.h" -#define AUDIO_NAME "wm8753" #define WM8753_VERSION "0.16" -/* - * Debug - */ - -#define WM8753_DEBUG 0 - -#ifdef WM8753_DEBUG -#define dbg(format, arg...) \ - printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) -#else -#define dbg(format, arg...) do {} while (0) -#endif -#define err(format, arg...) \ - printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) -#define info(format, arg...) \ - printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) -#define warn(format, arg...) \ - printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) - static int caps_charge = 2000; module_param(caps_charge, int, 0); MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); @@ -260,28 +239,50 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol, return 1; } -static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); +static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const unsigned int out_tlv[] = { + TLV_DB_RANGE_HEAD(2), + /* 0000000 - 0101111 = "Analogue mute" */ + 0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0), + 48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0), +}; +static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0); static const struct snd_kcontrol_new wm8753_snd_controls[] = { -SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0), - -SOC_DOUBLE_R("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0), - -SOC_DOUBLE_R("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, 0, 127, 0), -SOC_DOUBLE_R("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, 127, 0), - -SOC_SINGLE("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0), - -SOC_DOUBLE_R("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, 1), -SOC_DOUBLE_R("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, 7, 1), -SOC_DOUBLE_R("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, 1), - -SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, 1, 0), -SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, 1, 0), - -SOC_SINGLE("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1), -SOC_SINGLE("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1), -SOC_SINGLE("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1), +SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0, + adc_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, + 127, 0, out_tlv), + +SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv), + +SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, + 1, mix_tlv), +SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, + 7, 1, mix_tlv), +SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, + 1, voice_mix_tlv), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, + 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, + 1, 0), + +SOC_SINGLE_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv), +SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1, + mix_tlv), +SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1, + voice_mix_tlv), SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0), SOC_ENUM("Bass Boost", wm8753_enum[0]), @@ -291,10 +292,13 @@ SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), SOC_ENUM("Treble Cut-off", wm8753_enum[2]), -SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, rec_mix_tlv), -SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, rec_mix_tlv), +SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, + rec_mix_tlv), +SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, + rec_mix_tlv), -SOC_DOUBLE_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0), +SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0, + pga_tlv), SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0), SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1), @@ -326,8 +330,8 @@ SOC_ENUM("De-emphasis", wm8753_enum[8]), SOC_ENUM("Playback Mono Mix", wm8753_enum[9]), SOC_ENUM("Playback Phase", wm8753_enum[10]), -SOC_SINGLE("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0), -SOC_SINGLE("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0), +SOC_SINGLE_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv), +SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv), SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai), @@ -523,7 +527,7 @@ SND_SOC_DAPM_INPUT("MIC2"), SND_SOC_DAPM_VMID("VREF"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* left mixer */ {"Left Mixer", "Left Playback Switch", "Left DAC"}, {"Left Mixer", "Voice Playback Switch", "Voice DAC"}, @@ -578,7 +582,7 @@ static const char *audio_map[][3] = { /* out 4 */ {"Out4 Mux", "VREF", "VREF"}, - {"Out4 Mux", "Capture ST", "Capture ST Mixer"}, + {"Out4 Mux", "Capture ST", "Playback Mixer"}, {"Out4 Mux", "LOUT2", "LOUT2"}, {"Out 4", NULL, "Out4 Mux"}, {"OUT4", NULL, "Out 4"}, @@ -602,7 +606,7 @@ static const char *audio_map[][3] = { /* Capture Right Mux */ {"Capture Right Mux", "PGA", "Right Capture Volume"}, {"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"}, - {"Capture Right Mux", "Sidetone", "Capture ST Mixer"}, + {"Capture Right Mux", "Sidetone", "Playback Mixer"}, /* Mono Capture mixer-mux */ {"Capture Right Mixer", "Stereo", "Capture Right Mux"}, @@ -674,23 +678,14 @@ static const char *audio_map[][3] = { /* ACOP */ {"ACOP", NULL, "ALC Mixer"}, - - /* terminator */ - {NULL, NULL, NULL}, }; static int wm8753_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(wm8753_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8753_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, + ARRAY_SIZE(wm8753_dapm_widgets)); - /* set up the WM8753 audio map */ - for (i = 0; audio_map[i][0] != NULL; i++) { - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); - } + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -743,7 +738,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, pll_div->k = K; } -static int wm8753_set_dai_pll(struct snd_soc_codec_dai *codec_dai, +static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, unsigned int freq_in, unsigned int freq_out) { u16 reg, enable; @@ -866,7 +861,7 @@ static int get_coeff(int mclk, int rate) /* * Clock after PLL and dividers */ -static int wm8753_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; @@ -893,7 +888,7 @@ static int wm8753_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, /* * Set's ADC and Voice DAC format. */ -static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -963,7 +958,7 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, /* * Set's PCM dai fmt and BCLK. */ -static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1029,7 +1024,7 @@ static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return 0; } -static int wm8753_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, +static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1057,7 +1052,7 @@ static int wm8753_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, /* * Set's HiFi DAC format. */ -static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1090,7 +1085,7 @@ static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, /* * Set's I2S DAI format. */ -static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1198,7 +1193,7 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, return 0; } -static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1213,7 +1208,7 @@ static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_pcm_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) @@ -1221,7 +1216,7 @@ static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_i2s_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1236,7 +1231,7 @@ static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_i2s_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1253,7 +1248,7 @@ static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_i2s_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mute(struct snd_soc_codec_dai *dai, int mute) +static int wm8753_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 mute_reg = wm8753_read_reg_cache(codec, WM8753_DAC) & 0xfff7; @@ -1274,29 +1269,29 @@ static int wm8753_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int wm8753_dapm_event(struct snd_soc_codec *codec, int event) +static int wm8753_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 pwr_reg = wm8753_read_reg_cache(codec, WM8753_PWR1) & 0xfe3e; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* set vmid to 50k and unmute dac */ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: /* set vmid to 5k for quick power up */ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x01c1); break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* mute dac and set vmid to 500k, enable VREF */ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x0141); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: wm8753_write(codec, WM8753_PWR1, 0x0001); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -1319,7 +1314,7 @@ static int wm8753_dapm_event(struct snd_soc_codec *codec, int event) * 3. Voice disabled - HIFI over HIFI * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture */ -static const struct snd_soc_codec_dai wm8753_all_dai[] = { +static const struct snd_soc_dai wm8753_all_dai[] = { /* DAI HiFi mode 1 */ { .name = "WM8753 HiFi", .id = 1, @@ -1459,7 +1454,7 @@ static const struct snd_soc_codec_dai wm8753_all_dai[] = { }, }; -struct snd_soc_codec_dai wm8753_dai[2]; +struct snd_soc_dai wm8753_dai[2]; EXPORT_SYMBOL_GPL(wm8753_dai); static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode) @@ -1500,7 +1495,7 @@ static void wm8753_work(struct work_struct *work) { struct snd_soc_codec *codec = container_of(work, struct snd_soc_codec, delayed_work.work); - wm8753_dapm_event(codec, codec->dapm_state); + wm8753_set_bias_level(codec, codec->bias_level); } static int wm8753_suspend(struct platform_device *pdev, pm_message_t state) @@ -1512,7 +1507,7 @@ static int wm8753_suspend(struct platform_device *pdev, pm_message_t state) if (!codec->card) return 0; - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -1537,12 +1532,12 @@ static int wm8753_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* charge wm8753 caps */ - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D0; + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_ON; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(caps_charge)); } @@ -1563,10 +1558,10 @@ static int wm8753_init(struct snd_soc_device *socdev) codec->owner = THIS_MODULE; codec->read = wm8753_read_reg_cache; codec->write = wm8753_write; - codec->dapm_event = wm8753_dapm_event; + codec->set_bias_level = wm8753_set_bias_level; codec->dai = wm8753_dai; codec->num_dai = 2; - codec->reg_cache_size = sizeof(wm8753_reg); + codec->reg_cache_size = ARRAY_SIZE(wm8753_reg); codec->reg_cache = kmemdup(wm8753_reg, sizeof(wm8753_reg), GFP_KERNEL); if (codec->reg_cache == NULL) @@ -1584,8 +1579,8 @@ static int wm8753_init(struct snd_soc_device *socdev) } /* charge output caps */ - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D3hot; + wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_STANDBY; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(caps_charge)); @@ -1641,88 +1636,145 @@ static struct snd_soc_device *wm8753_socdev; * low = 0x1a * high = 0x1b */ -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static struct i2c_driver wm8753_i2c_driver; -static struct i2c_client client_template; - -static int wm8753_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8753_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8753_socdev; - struct wm8753_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c; int ret; - if (addr != setup->i2c_address) - return -ENODEV; - - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (!i2c) { - kfree(codec); - return -ENOMEM; - } i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8753_init(socdev); - if (ret < 0) { - err("failed to initialise WM8753\n"); - goto err; - } + if (ret < 0) + pr_err("failed to initialise WM8753\n"); return ret; - -err: - kfree(codec); - kfree(i2c); - return ret; } -static int wm8753_i2c_detach(struct i2c_client *client) +static int wm8753_i2c_remove(struct i2c_client *client) { struct snd_soc_codec *codec = i2c_get_clientdata(client); - i2c_detach_client(client); kfree(codec->reg_cache); - kfree(client); return 0; } -static int wm8753_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8753_codec_probe); -} +static const struct i2c_device_id wm8753_i2c_id[] = { + { "wm8753", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8753_i2c_driver = { .driver = { .name = "WM8753 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_WM8753, - .attach_adapter = wm8753_i2c_attach, - .detach_client = wm8753_i2c_detach, - .command = NULL, + .probe = wm8753_i2c_probe, + .remove = wm8753_i2c_remove, + .id_table = wm8753_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8753", - .driver = &wm8753_i2c_driver, +static int wm8753_add_i2c_device(struct platform_device *pdev, + const struct wm8753_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8753_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8753", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8753_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8753_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8753_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8753_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8753\n"); + + return ret; +} + +static int __devexit wm8753_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8753_spi_driver = { + .driver = { + .name = "wm8753", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8753_spi_probe, + .remove = __devexit_p(wm8753_spi_remove), }; + +static int wm8753_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} #endif + static int wm8753_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -1731,7 +1783,7 @@ static int wm8753_probe(struct platform_device *pdev) struct wm8753_priv *wm8753; int ret = 0; - info("WM8753 Audio Codec %s", WM8753_VERSION); + pr_info("WM8753 Audio Codec %s", WM8753_VERSION); setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); @@ -1754,15 +1806,23 @@ static int wm8753_probe(struct platform_device *pdev) #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; - ret = i2c_add_driver(&wm8753_i2c_driver); + ret = wm8753_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8753_spi_write; + ret = spi_register_driver(&wm8753_spi_driver); if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + printk(KERN_ERR "can't add spi driver"); } -#else - /* Add other interfaces here */ #endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } return ret; } @@ -1792,13 +1852,17 @@ static int wm8753_remove(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->codec; if (codec->control_data) - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); run_delayed_work(&codec->delayed_work); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); i2c_del_driver(&wm8753_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8753_spi_driver); +#endif kfree(codec->private_data); kfree(codec); diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h index 95e2a1f53169..f55704ce931b 100644 --- a/sound/soc/codecs/wm8753.h +++ b/sound/soc/codecs/wm8753.h @@ -2,8 +2,7 @@ * wm8753.h -- audio driver for WM8753 * * Copyright 2003 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -79,6 +78,8 @@ #define WM8753_ADCTL2 0x3f struct wm8753_setup_data { + int spi; + int i2c_bus; unsigned short i2c_address; }; @@ -120,7 +121,7 @@ struct wm8753_setup_data { #define WM8753_DAI_HIFI 0 #define WM8753_DAI_VOICE 1 -extern struct snd_soc_codec_dai wm8753_dai[2]; +extern struct snd_soc_dai wm8753_dai[2]; extern struct snd_soc_codec_device soc_codec_dev_wm8753; #endif diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c new file mode 100644 index 000000000000..3b326c9b5586 --- /dev/null +++ b/sound/soc/codecs/wm8900.c @@ -0,0 +1,1541 @@ +/* + * wm8900.c -- WM8900 ALSA Soc Audio driver + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - Tristating. + * - TDM. + * - Jack detect. + * - FLL source configuration, currently only MCLK is supported. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/initval.h> +#include <sound/tlv.h> + +#include "wm8900.h" + +/* WM8900 register space */ +#define WM8900_REG_RESET 0x0 +#define WM8900_REG_ID 0x0 +#define WM8900_REG_POWER1 0x1 +#define WM8900_REG_POWER2 0x2 +#define WM8900_REG_POWER3 0x3 +#define WM8900_REG_AUDIO1 0x4 +#define WM8900_REG_AUDIO2 0x5 +#define WM8900_REG_CLOCKING1 0x6 +#define WM8900_REG_CLOCKING2 0x7 +#define WM8900_REG_AUDIO3 0x8 +#define WM8900_REG_AUDIO4 0x9 +#define WM8900_REG_DACCTRL 0xa +#define WM8900_REG_LDAC_DV 0xb +#define WM8900_REG_RDAC_DV 0xc +#define WM8900_REG_SIDETONE 0xd +#define WM8900_REG_ADCCTRL 0xe +#define WM8900_REG_LADC_DV 0xf +#define WM8900_REG_RADC_DV 0x10 +#define WM8900_REG_GPIO 0x12 +#define WM8900_REG_INCTL 0x15 +#define WM8900_REG_LINVOL 0x16 +#define WM8900_REG_RINVOL 0x17 +#define WM8900_REG_INBOOSTMIX1 0x18 +#define WM8900_REG_INBOOSTMIX2 0x19 +#define WM8900_REG_ADCPATH 0x1a +#define WM8900_REG_AUXBOOST 0x1b +#define WM8900_REG_ADDCTL 0x1e +#define WM8900_REG_FLLCTL1 0x24 +#define WM8900_REG_FLLCTL2 0x25 +#define WM8900_REG_FLLCTL3 0x26 +#define WM8900_REG_FLLCTL4 0x27 +#define WM8900_REG_FLLCTL5 0x28 +#define WM8900_REG_FLLCTL6 0x29 +#define WM8900_REG_LOUTMIXCTL1 0x2c +#define WM8900_REG_ROUTMIXCTL1 0x2d +#define WM8900_REG_BYPASS1 0x2e +#define WM8900_REG_BYPASS2 0x2f +#define WM8900_REG_AUXOUT_CTL 0x30 +#define WM8900_REG_LOUT1CTL 0x33 +#define WM8900_REG_ROUT1CTL 0x34 +#define WM8900_REG_LOUT2CTL 0x35 +#define WM8900_REG_ROUT2CTL 0x36 +#define WM8900_REG_HPCTL1 0x3a +#define WM8900_REG_OUTBIASCTL 0x73 + +#define WM8900_MAXREG 0x80 + +#define WM8900_REG_ADDCTL_OUT1_DIS 0x80 +#define WM8900_REG_ADDCTL_OUT2_DIS 0x40 +#define WM8900_REG_ADDCTL_VMID_DIS 0x20 +#define WM8900_REG_ADDCTL_BIAS_SRC 0x10 +#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04 +#define WM8900_REG_ADDCTL_TEMP_SD 0x02 + +#define WM8900_REG_GPIO_TEMP_ENA 0x2 + +#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100 +#define WM8900_REG_POWER1_BIAS_ENA 0x0008 +#define WM8900_REG_POWER1_VMID_BUF_ENA 0x0004 +#define WM8900_REG_POWER1_FLL_ENA 0x0040 + +#define WM8900_REG_POWER2_SYSCLK_ENA 0x8000 +#define WM8900_REG_POWER2_ADCL_ENA 0x0002 +#define WM8900_REG_POWER2_ADCR_ENA 0x0001 + +#define WM8900_REG_POWER3_DACL_ENA 0x0002 +#define WM8900_REG_POWER3_DACR_ENA 0x0001 + +#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018 +#define WM8900_REG_AUDIO1_LRCLK_INV 0x0080 +#define WM8900_REG_AUDIO1_BCLK_INV 0x0100 + +#define WM8900_REG_CLOCKING1_BCLK_DIR 0x1 +#define WM8900_REG_CLOCKING1_MCLK_SRC 0x100 +#define WM8900_REG_CLOCKING1_BCLK_MASK (~0x01e) +#define WM8900_REG_CLOCKING1_OPCLK_MASK (~0x7000) + +#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0 +#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c + +#define WM8900_REG_DACCTRL_MUTE 0x004 +#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400 + +#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800 + +#define WM8900_REG_AUDIO4_DACLRC_DIR 0x0800 + +#define WM8900_REG_FLLCTL1_OSC_ENA 0x100 + +#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100 + +#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80 +#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40 +#define WM8900_REG_HPCTL1_HP_CLAMP_IP 0x20 +#define WM8900_REG_HPCTL1_HP_CLAMP_OP 0x10 +#define WM8900_REG_HPCTL1_HP_SHORT 0x08 +#define WM8900_REG_HPCTL1_HP_SHORT2 0x04 + +#define WM8900_LRC_MASK 0xfc00 + +struct snd_soc_codec_device soc_codec_dev_wm8900; + +struct wm8900_priv { + u32 fll_in; /* FLL input frequency */ + u32 fll_out; /* FLL output frequency */ +}; + +/* + * wm8900 register cache. We can't read the entire register space and we + * have slow control buses so we cache the registers. + */ +static const u16 wm8900_reg_defaults[WM8900_MAXREG] = { + 0x8900, 0x0000, + 0xc000, 0x0000, + 0x4050, 0x4000, + 0x0008, 0x0000, + 0x0040, 0x0040, + 0x1004, 0x00c0, + 0x00c0, 0x0000, + 0x0100, 0x00c0, + 0x00c0, 0x0000, + 0xb001, 0x0000, + 0x0000, 0x0044, + 0x004c, 0x004c, + 0x0044, 0x0044, + 0x0000, 0x0044, + 0x0000, 0x0000, + 0x0002, 0x0000, + 0x0000, 0x0000, + 0x0000, 0x0000, + 0x0008, 0x0000, + 0x0000, 0x0008, + 0x0097, 0x0100, + 0x0000, 0x0000, + 0x0050, 0x0050, + 0x0055, 0x0055, + 0x0055, 0x0000, + 0x0000, 0x0079, + 0x0079, 0x0079, + 0x0079, 0x0000, + /* Remaining registers all zero */ +}; + +/* + * read wm8900 register cache + */ +static inline unsigned int wm8900_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= WM8900_MAXREG); + + if (reg == WM8900_REG_ID) + return 0; + + return cache[reg]; +} + +/* + * write wm8900 register cache + */ +static inline void wm8900_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= WM8900_MAXREG); + + cache[reg] = value; +} + +/* + * write to the WM8900 register space + */ +static int wm8900_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + if (value == wm8900_read_reg_cache(codec, reg)) + return 0; + + /* data is + * D15..D9 WM8900 register offset + * D8...D0 register data + */ + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + wm8900_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +/* + * Read from the wm8900. + */ +static unsigned int wm8900_chip_read(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + BUG_ON(reg != WM8900_REG_ID && reg != WM8900_REG_POWER1); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + printk(KERN_CRIT "i2c_transfer returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +/* + * Read from the WM8900 register space. Most registers can't be read + * and are therefore supplied from cache. + */ +static unsigned int wm8900_read(struct snd_soc_codec *codec, unsigned int reg) +{ + switch (reg) { + case WM8900_REG_ID: + return wm8900_chip_read(codec, reg); + default: + return wm8900_read_reg_cache(codec, reg); + } +} + +static void wm8900_reset(struct snd_soc_codec *codec) +{ + wm8900_write(codec, WM8900_REG_RESET, 0); + + memcpy(codec->reg_cache, wm8900_reg_defaults, + sizeof(codec->reg_cache)); +} + +static int wm8900_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 hpctl1 = wm8900_read(codec, WM8900_REG_HPCTL1); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Clamp headphone outputs */ + hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMU: + /* Enable the input stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP; + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT | + WM8900_REG_HPCTL1_HP_SHORT2 | + WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + msleep(400); + + /* Enable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Remove the shorts */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Short the output */ + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Disable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Clamp the outputs and power down input */ + hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Disable everything */ + wm8900_write(codec, WM8900_REG_HPCTL1, 0); + break; + + default: + BUG(); + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0); + +static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0); + +static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0); + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); + +static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" }; + +static const struct soc_enum mic_bias_level = +SOC_ENUM_SINGLE(WM8900_REG_INCTL, 8, 2, mic_bias_level_txt); + +static const char *dac_mute_rate_txt[] = { "Fast", "Slow" }; + +static const struct soc_enum dac_mute_rate = +SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 7, 2, dac_mute_rate_txt); + +static const char *dac_deemphasis_txt[] = { + "Disabled", "32kHz", "44.1kHz", "48kHz" +}; + +static const struct soc_enum dac_deemphasis = +SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 4, 4, dac_deemphasis_txt); + +static const char *adc_hpf_cut_txt[] = { + "Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3" +}; + +static const struct soc_enum adc_hpf_cut = +SOC_ENUM_SINGLE(WM8900_REG_ADCCTRL, 5, 4, adc_hpf_cut_txt); + +static const char *lr_txt[] = { + "Left", "Right" +}; + +static const struct soc_enum aifl_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 15, 2, lr_txt); + +static const struct soc_enum aifr_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 14, 2, lr_txt); + +static const struct soc_enum dacl_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 15, 2, lr_txt); + +static const struct soc_enum dacr_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 14, 2, lr_txt); + +static const char *sidetone_txt[] = { + "Disabled", "Left ADC", "Right ADC" +}; + +static const struct soc_enum dacl_sidetone = +SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 2, 3, sidetone_txt); + +static const struct soc_enum dacr_sidetone = +SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 0, 3, sidetone_txt); + +static const struct snd_kcontrol_new wm8900_snd_controls[] = { +SOC_ENUM("Mic Bias Level", mic_bias_level), + +SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1), +SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0), + +SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1), +SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0), + +SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1), +SOC_ENUM("DAC Mute Rate", dac_mute_rate), +SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0), +SOC_ENUM("DAC Deemphasis", dac_deemphasis), +SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0), +SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL, + 12, 1, 0), + +SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0), +SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut), +SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0), +SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0, + adc_svol_tlv), +SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0, + adc_svol_tlv), +SOC_ENUM("Left Digital Audio Source", aifl_src), +SOC_ENUM("Right Digital Audio Source", aifr_src), + +SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0, + dac_boost_tlv), +SOC_ENUM("Left DAC Source", dacl_src), +SOC_ENUM("Right DAC Source", dacr_src), +SOC_ENUM("Left DAC Sidetone", dacl_sidetone), +SOC_ENUM("Right DAC Sidetone", dacr_sidetone), +SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Digital Playback Volume", + WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV, + 1, 96, 0, dac_tlv), +SOC_DOUBLE_R_TLV("Digital Capture Volume", + WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv), + +SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0, + in_boost_tlv), + +SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 6, 1, 1), +SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("LINEOUT2 Volume", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT2 Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1), +SOC_DOUBLE_R("LINEOUT2 ZC Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0), +SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1, + 0, 1, 1), + +}; + +/* add non dapm controls */ +static int wm8900_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8900_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8900_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static const struct snd_kcontrol_new wm8900_dapm_loutput2_control = +SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0); + +static const struct snd_kcontrol_new wm8900_dapm_routput2_control = +SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0); + +static const struct snd_kcontrol_new wm8900_loutmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0), +SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_routmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linpga_controls[] = { +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinpga_controls[] = { +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0), +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0), +}; + +static const char *wm9700_lp_mux[] = { "Disabled", "Enabled" }; + +static const struct soc_enum wm8900_lineout2_lp_mux = +SOC_ENUM_SINGLE(WM8900_REG_LOUTMIXCTL1, 1, 2, wm9700_lp_mux); + +static const struct snd_kcontrol_new wm8900_lineout2_lp = +SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux); + +static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = { + +/* Externally visible pins */ +SND_SOC_DAPM_OUTPUT("LINEOUT1L"), +SND_SOC_DAPM_OUTPUT("LINEOUT1R"), +SND_SOC_DAPM_OUTPUT("LINEOUT2L"), +SND_SOC_DAPM_OUTPUT("LINEOUT2R"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), + +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT3"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("AUX"), + +SND_SOC_DAPM_VMID("VMID"), + +/* Input */ +SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0, + wm8900_linpga_controls, + ARRAY_SIZE(wm8900_linpga_controls)), +SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0, + wm8900_rinpga_controls, + ARRAY_SIZE(wm8900_rinpga_controls)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0, + wm8900_linmix_controls, + ARRAY_SIZE(wm8900_linmix_controls)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0, + wm8900_rinmix_controls, + ARRAY_SIZE(wm8900_rinmix_controls)), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8900_REG_POWER1, 4, 0), + +SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0), + +/* Output */ +SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0), +SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0), + +SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0, + wm8900_hp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0), + +SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp), +SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0, + wm8900_loutmix_controls, + ARRAY_SIZE(wm8900_loutmix_controls)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0, + wm8900_routmix_controls, + ARRAY_SIZE(wm8900_routmix_controls)), +}; + +/* Target, Path, Source */ +static const struct snd_soc_dapm_route audio_map[] = { +/* Inputs */ +{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"}, +{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"}, + +{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"}, +{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"}, + +{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"}, +{"Left Input Mixer", "AUX Switch", "AUX"}, +{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"}, + +{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"}, +{"Right Input Mixer", "AUX Switch", "AUX"}, +{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"}, + +{"ADCL", NULL, "Left Input Mixer"}, +{"ADCR", NULL, "Right Input Mixer"}, + +/* Outputs */ +{"LINEOUT1L", NULL, "LINEOUT1L PGA"}, +{"LINEOUT1L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT1R", NULL, "LINEOUT1R PGA"}, +{"LINEOUT1R PGA", NULL, "Right Output Mixer"}, + +{"LINEOUT2L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"}, +{"LINEOUT2 LP", "Enabled", "Left Output Mixer"}, +{"LINEOUT2L", NULL, "LINEOUT2 LP"}, + +{"LINEOUT2R PGA", NULL, "Right Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"}, +{"LINEOUT2 LP", "Enabled", "Right Output Mixer"}, +{"LINEOUT2R", NULL, "LINEOUT2 LP"}, + +{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"}, +{"Left Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Left Output Mixer", "DACL Switch", "DACL"}, + +{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"}, +{"Right Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Right Output Mixer", "DACR Switch", "DACR"}, + +/* Note that the headphone output stage needs to be connected + * externally to LINEOUT2 via DC blocking capacitors. Other + * configurations are not supported. + * + * Note also that left and right headphone paths are treated as a + * mono path. + */ +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"HP_L", NULL, "Headphone Amplifier"}, +{"HP_R", NULL, "Headphone Amplifier"}, +}; + +static int wm8900_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets, + ARRAY_SIZE(wm8900_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int wm8900_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; + u16 reg; + + reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 0x20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 0x40; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 0x60; + break; + default: + return -EINVAL; + } + + wm8900_write(codec, WM8900_REG_AUDIO1, reg); + + return 0; +} + +/* FLL divisors */ +struct _fll_div { + u16 fll_ratio; + u16 fllclk_div; + u16 fll_slow_lock_ref; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + + BUG_ON(!Fout); + + /* The FLL must run at 90-100MHz which is then scaled down to + * the output value by FLLCLK_DIV. */ + target = Fout; + div = 1; + while (target < 90000000) { + div *= 2; + target *= 2; + } + + if (target > 100000000) + printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d" + " Fout=%d\n", target, Fref, Fout); + if (div > 32) { + printk(KERN_ERR "wm8900: Invalid FLL division rate %u, " + "Fref=%d, Fout=%d, target=%d\n", + div, Fref, Fout, target); + return -EINVAL; + } + + fll_div->fllclk_div = div >> 2; + + if (Fref < 48000) + fll_div->fll_slow_lock_ref = 1; + else + fll_div->fll_slow_lock_ref = 0; + + Ndiv = target / Fref; + + if (Fref < 1000000) + fll_div->fll_ratio = 8; + else + fll_div->fll_ratio = 1; + + fll_div->n = Ndiv / fll_div->fll_ratio; + Nmod = (target / fll_div->fll_ratio) % Fref; + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + BUG_ON(target != Fout * (fll_div->fllclk_div << 2)); + BUG_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n); + + return 0; +} + +static int wm8900_set_fll(struct snd_soc_codec *codec, + int fll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm8900_priv *wm8900 = codec->private_data; + struct _fll_div fll_div; + unsigned int reg; + + if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out) + return 0; + + /* The digital side should be disabled during any change. */ + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg & (~WM8900_REG_POWER1_FLL_ENA)); + + /* Disable the FLL? */ + if (!freq_in || !freq_out) { + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + reg & (~WM8900_REG_CLOCKING1_MCLK_SRC)); + + reg = wm8900_read(codec, WM8900_REG_FLLCTL1); + wm8900_write(codec, WM8900_REG_FLLCTL1, + reg & (~WM8900_REG_FLLCTL1_OSC_ENA)); + + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + return 0; + } + + if (fll_factors(&fll_div, freq_in, freq_out) != 0) + goto reenable; + + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + /* The osclilator *MUST* be enabled before we enable the + * digital circuit. */ + wm8900_write(codec, WM8900_REG_FLLCTL1, + fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA); + + wm8900_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5); + wm8900_write(codec, WM8900_REG_FLLCTL5, + (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f)); + + if (fll_div.k) { + wm8900_write(codec, WM8900_REG_FLLCTL2, + (fll_div.k >> 8) | 0x100); + wm8900_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff); + } else + wm8900_write(codec, WM8900_REG_FLLCTL2, 0); + + if (fll_div.fll_slow_lock_ref) + wm8900_write(codec, WM8900_REG_FLLCTL6, + WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF); + else + wm8900_write(codec, WM8900_REG_FLLCTL6, 0); + + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg | WM8900_REG_POWER1_FLL_ENA); + +reenable: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + reg | WM8900_REG_CLOCKING1_MCLK_SRC); + + return 0; +} + +static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out); +} + +static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + switch (div_id) { + case WM8900_BCLK_DIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK)); + break; + case WM8900_OPCLK_DIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK)); + break; + case WM8900_DAC_LRCLK: + reg = wm8900_read(codec, WM8900_REG_AUDIO4); + wm8900_write(codec, WM8900_REG_AUDIO4, + div | (reg & WM8900_LRC_MASK)); + break; + case WM8900_ADC_LRCLK: + reg = wm8900_read(codec, WM8900_REG_AUDIO3); + wm8900_write(codec, WM8900_REG_AUDIO3, + div | (reg & WM8900_LRC_MASK)); + break; + case WM8900_DAC_CLKDIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING2); + wm8900_write(codec, WM8900_REG_CLOCKING2, + div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV)); + break; + case WM8900_ADC_CLKDIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING2); + wm8900_write(codec, WM8900_REG_CLOCKING2, + div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV)); + break; + case WM8900_LRCLK_MODE: + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + wm8900_write(codec, WM8900_REG_DACCTRL, + div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE)); + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int clocking1, aif1, aif3, aif4; + + clocking1 = wm8900_read(codec, WM8900_REG_CLOCKING1); + aif1 = wm8900_read(codec, WM8900_REG_AUDIO1); + aif3 = wm8900_read(codec, WM8900_REG_AUDIO3); + aif4 = wm8900_read(codec, WM8900_REG_AUDIO4); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8900_write(codec, WM8900_REG_CLOCKING1, clocking1); + wm8900_write(codec, WM8900_REG_AUDIO1, aif1); + wm8900_write(codec, WM8900_REG_AUDIO3, aif3); + wm8900_write(codec, WM8900_REG_AUDIO4, aif4); + + return 0; +} + +static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + + if (mute) + reg |= WM8900_REG_DACCTRL_MUTE; + else + reg &= ~WM8900_REG_DACCTRL_MUTE; + + wm8900_write(codec, WM8900_REG_DACCTRL, reg); + + return 0; +} + +#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8900_PCM_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_dai wm8900_dai = { + .name = "WM8900 HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .ops = { + .hw_params = wm8900_hw_params, + }, + .dai_ops = { + .set_clkdiv = wm8900_set_dai_clkdiv, + .set_pll = wm8900_set_dai_pll, + .set_fmt = wm8900_set_dai_fmt, + .digital_mute = wm8900_digital_mute, + }, +}; +EXPORT_SYMBOL_GPL(wm8900_dai); + +static int wm8900_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + /* Enable thermal shutdown */ + reg = wm8900_read(codec, WM8900_REG_GPIO); + wm8900_write(codec, WM8900_REG_GPIO, + reg | WM8900_REG_GPIO_TEMP_ENA); + reg = wm8900_read(codec, WM8900_REG_ADDCTL); + wm8900_write(codec, WM8900_REG_ADDCTL, + reg | WM8900_REG_ADDCTL_TEMP_SD); + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + /* Charge capacitors if initial power up */ + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* STARTUP_BIAS_ENA on */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + + /* Startup bias mode */ + wm8900_write(codec, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* VMID 2x50k */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1); + + /* Allow capacitors to charge */ + schedule_timeout_interruptible(msecs_to_jiffies(400)); + + /* Enable bias */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + + wm8900_write(codec, WM8900_REG_ADDCTL, 0); + + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_BIAS_ENA | 0x1); + } + + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + (reg & WM8900_REG_POWER1_FLL_ENA) | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + wm8900_write(codec, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + wm8900_write(codec, WM8900_REG_POWER3, 0); + break; + + case SND_SOC_BIAS_OFF: + /* Startup bias enable */ + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA); + wm8900_write(codec, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* Discharge caps */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + schedule_timeout_interruptible(msecs_to_jiffies(500)); + + /* Remove clamp */ + wm8900_write(codec, WM8900_REG_HPCTL1, 0); + + /* Power down */ + wm8900_write(codec, WM8900_REG_ADDCTL, 0); + wm8900_write(codec, WM8900_REG_POWER1, 0); + wm8900_write(codec, WM8900_REG_POWER2, 0); + wm8900_write(codec, WM8900_REG_POWER3, 0); + + /* Need to let things settle before stopping the clock + * to ensure that restart works, see "Stopping the + * master clock" in the datasheet. */ + schedule_timeout_interruptible(msecs_to_jiffies(1)); + wm8900_write(codec, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + break; + } + codec->bias_level = level; + return 0; +} + +static int wm8900_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm8900_priv *wm8900 = codec->private_data; + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + int ret; + + /* Stop the FLL in an orderly fashion */ + ret = wm8900_set_fll(codec, 0, 0, 0); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to stop FLL\n"); + return ret; + } + + wm8900->fll_out = fll_out; + wm8900->fll_in = fll_in; + + wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8900_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm8900_priv *wm8900 = codec->private_data; + u16 *cache; + int i, ret; + + cache = kmemdup(codec->reg_cache, sizeof(wm8900_reg_defaults), + GFP_KERNEL); + + wm8900_reset(codec); + wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Restart the FLL? */ + if (wm8900->fll_out) { + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + + wm8900->fll_in = 0; + wm8900->fll_out = 0; + + ret = wm8900_set_fll(codec, 0, fll_in, fll_out); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to restart FLL\n"); + return ret; + } + } + + if (cache) { + for (i = 0; i < WM8900_MAXREG; i++) + wm8900_write(codec, i, cache[i]); + kfree(cache); + } else + dev_err(&pdev->dev, "Unable to allocate register cache\n"); + + return 0; +} + +/* + * initialise the WM8900 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8900_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + unsigned int reg; + struct i2c_client *i2c_client = socdev->codec->control_data; + + codec->name = "WM8900"; + codec->owner = THIS_MODULE; + codec->read = wm8900_read; + codec->write = wm8900_write; + codec->dai = &wm8900_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8900_MAXREG; + codec->reg_cache = kmemdup(wm8900_reg_defaults, + sizeof(wm8900_reg_defaults), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + reg = wm8900_read(codec, WM8900_REG_ID); + if (reg != 0x8900) { + dev_err(&i2c_client->dev, "Device is not a WM8900 - ID %x\n", + reg); + return -ENODEV; + } + + codec->private_data = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL); + if (codec->private_data == NULL) { + ret = -ENOMEM; + goto priv_err; + } + + /* Read back from the chip */ + reg = wm8900_chip_read(codec, WM8900_REG_POWER1); + reg = (reg >> 12) & 0xf; + dev_info(&i2c_client->dev, "WM8900 revision %d\n", reg); + + wm8900_reset(codec); + + /* Latch the volume update bits */ + wm8900_write(codec, WM8900_REG_LINVOL, + wm8900_read(codec, WM8900_REG_LINVOL) | 0x100); + wm8900_write(codec, WM8900_REG_RINVOL, + wm8900_read(codec, WM8900_REG_RINVOL) | 0x100); + wm8900_write(codec, WM8900_REG_LOUT1CTL, + wm8900_read(codec, WM8900_REG_LOUT1CTL) | 0x100); + wm8900_write(codec, WM8900_REG_ROUT1CTL, + wm8900_read(codec, WM8900_REG_ROUT1CTL) | 0x100); + wm8900_write(codec, WM8900_REG_LOUT2CTL, + wm8900_read(codec, WM8900_REG_LOUT2CTL) | 0x100); + wm8900_write(codec, WM8900_REG_ROUT2CTL, + wm8900_read(codec, WM8900_REG_ROUT2CTL) | 0x100); + wm8900_write(codec, WM8900_REG_LDAC_DV, + wm8900_read(codec, WM8900_REG_LDAC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_RDAC_DV, + wm8900_read(codec, WM8900_REG_RDAC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_LADC_DV, + wm8900_read(codec, WM8900_REG_LADC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_RADC_DV, + wm8900_read(codec, WM8900_REG_RADC_DV) | 0x100); + + /* Set the DAC and mixer output bias */ + wm8900_write(codec, WM8900_REG_OUTBIASCTL, 0x81); + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to register new PCMs\n"); + goto pcm_err; + } + + /* Turn the chip on */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8900_add_controls(codec); + wm8900_add_widgets(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); +priv_err: + kfree(codec->private_data); + return ret; +} + +static struct snd_soc_device *wm8900_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8900_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static int wm8900_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8900_socdev; + struct wm8900_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + dev_err(&adap->dev, "Probe on %x\n", addr); + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + dev_err(&adap->dev, + "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8900_init(socdev); + if (ret < 0) { + dev_err(&adap->dev, "failed to initialise WM8900\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8900_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8900_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8900_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8900_i2c_driver = { + .driver = { + .name = "WM8900 I2C codec", + .owner = THIS_MODULE, + }, + .attach_adapter = wm8900_i2c_attach, + .detach_client = wm8900_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8900", + .driver = &wm8900_i2c_driver, +}; +#endif + +static int wm8900_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8900_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + dev_info(&pdev->dev, "WM8900 Audio Codec\n"); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + socdev->codec = codec; + + codec->set_bias_level = wm8900_set_bias_level; + + wm8900_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8900_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else +#error Non-I2C interfaces not yet supported +#endif + return ret; +} + +/* power down chip */ +static int wm8900_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8900_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8900 = { + .probe = wm8900_probe, + .remove = wm8900_remove, + .suspend = wm8900_suspend, + .resume = wm8900_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8900); + +MODULE_DESCRIPTION("ASoC WM8900 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8900.h b/sound/soc/codecs/wm8900.h new file mode 100644 index 000000000000..ba450d99e902 --- /dev/null +++ b/sound/soc/codecs/wm8900.h @@ -0,0 +1,64 @@ +/* + * wm8900.h -- WM890 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8900_H +#define _WM8900_H + +#define WM8900_FLL 1 + +#define WM8900_BCLK_DIV 1 +#define WM8900_ADC_CLKDIV 2 +#define WM8900_DAC_CLKDIV 3 +#define WM8900_ADC_LRCLK 4 +#define WM8900_DAC_LRCLK 5 +#define WM8900_OPCLK_DIV 6 +#define WM8900_LRCLK_MODE 7 + +#define WM8900_BCLK_DIV_1 0x00 +#define WM8900_BCLK_DIV_1_5 0x02 +#define WM8900_BCLK_DIV_2 0x04 +#define WM8900_BCLK_DIV_3 0x06 +#define WM8900_BCLK_DIV_4 0x08 +#define WM8900_BCLK_DIV_5_5 0x0a +#define WM8900_BCLK_DIV_6 0x0c +#define WM8900_BCLK_DIV_8 0x0e +#define WM8900_BCLK_DIV_11 0x10 +#define WM8900_BCLK_DIV_12 0x12 +#define WM8900_BCLK_DIV_16 0x14 +#define WM8900_BCLK_DIV_22 0x16 +#define WM8900_BCLK_DIV_24 0x18 +#define WM8900_BCLK_DIV_32 0x1a +#define WM8900_BCLK_DIV_44 0x1c +#define WM8900_BCLK_DIV_48 0x1e + +#define WM8900_ADC_CLKDIV_1 0x00 +#define WM8900_ADC_CLKDIV_1_5 0x20 +#define WM8900_ADC_CLKDIV_2 0x40 +#define WM8900_ADC_CLKDIV_3 0x60 +#define WM8900_ADC_CLKDIV_4 0x80 +#define WM8900_ADC_CLKDIV_5_5 0xa0 +#define WM8900_ADC_CLKDIV_6 0xc0 + +#define WM8900_DAC_CLKDIV_1 0x00 +#define WM8900_DAC_CLKDIV_1_5 0x04 +#define WM8900_DAC_CLKDIV_2 0x08 +#define WM8900_DAC_CLKDIV_3 0x0c +#define WM8900_DAC_CLKDIV_4 0x10 +#define WM8900_DAC_CLKDIV_5_5 0x14 +#define WM8900_DAC_CLKDIV_6 0x18 + +#define WM8900_ + +struct wm8900_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8900_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8900; + +#endif diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c new file mode 100644 index 000000000000..ce40d7877605 --- /dev/null +++ b/sound/soc/codecs/wm8903.c @@ -0,0 +1,1813 @@ +/* + * wm8903.c -- WM8903 ALSA SoC Audio driver + * + * Copyright 2008 Wolfson Microelectronics + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - TDM mode configuration. + * - Mic detect. + * - Digital microphone support. + * - Interrupt support (mic detect and sequencer). + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8903.h" + +struct wm8903_priv { + int sysclk; + + /* Reference counts */ + int charge_pump_users; + int class_w_users; + int playback_active; + int capture_active; + + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* Register defaults at reset */ +static u16 wm8903_reg_defaults[] = { + 0x8903, /* R0 - SW Reset and ID */ + 0x0000, /* R1 - Revision Number */ + 0x0000, /* R2 */ + 0x0000, /* R3 */ + 0x0018, /* R4 - Bias Control 0 */ + 0x0000, /* R5 - VMID Control 0 */ + 0x0000, /* R6 - Mic Bias Control 0 */ + 0x0000, /* R7 */ + 0x0001, /* R8 - Analogue DAC 0 */ + 0x0000, /* R9 */ + 0x0001, /* R10 - Analogue ADC 0 */ + 0x0000, /* R11 */ + 0x0000, /* R12 - Power Management 0 */ + 0x0000, /* R13 - Power Management 1 */ + 0x0000, /* R14 - Power Management 2 */ + 0x0000, /* R15 - Power Management 3 */ + 0x0000, /* R16 - Power Management 4 */ + 0x0000, /* R17 - Power Management 5 */ + 0x0000, /* R18 - Power Management 6 */ + 0x0000, /* R19 */ + 0x0400, /* R20 - Clock Rates 0 */ + 0x0D07, /* R21 - Clock Rates 1 */ + 0x0000, /* R22 - Clock Rates 2 */ + 0x0000, /* R23 */ + 0x0050, /* R24 - Audio Interface 0 */ + 0x0242, /* R25 - Audio Interface 1 */ + 0x0008, /* R26 - Audio Interface 2 */ + 0x0022, /* R27 - Audio Interface 3 */ + 0x0000, /* R28 */ + 0x0000, /* R29 */ + 0x00C0, /* R30 - DAC Digital Volume Left */ + 0x00C0, /* R31 - DAC Digital Volume Right */ + 0x0000, /* R32 - DAC Digital 0 */ + 0x0000, /* R33 - DAC Digital 1 */ + 0x0000, /* R34 */ + 0x0000, /* R35 */ + 0x00C0, /* R36 - ADC Digital Volume Left */ + 0x00C0, /* R37 - ADC Digital Volume Right */ + 0x0000, /* R38 - ADC Digital 0 */ + 0x0073, /* R39 - Digital Microphone 0 */ + 0x09BF, /* R40 - DRC 0 */ + 0x3241, /* R41 - DRC 1 */ + 0x0020, /* R42 - DRC 2 */ + 0x0000, /* R43 - DRC 3 */ + 0x0085, /* R44 - Analogue Left Input 0 */ + 0x0085, /* R45 - Analogue Right Input 0 */ + 0x0044, /* R46 - Analogue Left Input 1 */ + 0x0044, /* R47 - Analogue Right Input 1 */ + 0x0000, /* R48 */ + 0x0000, /* R49 */ + 0x0008, /* R50 - Analogue Left Mix 0 */ + 0x0004, /* R51 - Analogue Right Mix 0 */ + 0x0000, /* R52 - Analogue Spk Mix Left 0 */ + 0x0000, /* R53 - Analogue Spk Mix Left 1 */ + 0x0000, /* R54 - Analogue Spk Mix Right 0 */ + 0x0000, /* R55 - Analogue Spk Mix Right 1 */ + 0x0000, /* R56 */ + 0x002D, /* R57 - Analogue OUT1 Left */ + 0x002D, /* R58 - Analogue OUT1 Right */ + 0x0039, /* R59 - Analogue OUT2 Left */ + 0x0039, /* R60 - Analogue OUT2 Right */ + 0x0100, /* R61 */ + 0x0139, /* R62 - Analogue OUT3 Left */ + 0x0139, /* R63 - Analogue OUT3 Right */ + 0x0000, /* R64 */ + 0x0000, /* R65 - Analogue SPK Output Control 0 */ + 0x0000, /* R66 */ + 0x0010, /* R67 - DC Servo 0 */ + 0x0100, /* R68 */ + 0x00A4, /* R69 - DC Servo 2 */ + 0x0807, /* R70 */ + 0x0000, /* R71 */ + 0x0000, /* R72 */ + 0x0000, /* R73 */ + 0x0000, /* R74 */ + 0x0000, /* R75 */ + 0x0000, /* R76 */ + 0x0000, /* R77 */ + 0x0000, /* R78 */ + 0x000E, /* R79 */ + 0x0000, /* R80 */ + 0x0000, /* R81 */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0006, /* R87 */ + 0x0000, /* R88 */ + 0x0000, /* R89 */ + 0x0000, /* R90 - Analogue HP 0 */ + 0x0060, /* R91 */ + 0x0000, /* R92 */ + 0x0000, /* R93 */ + 0x0000, /* R94 - Analogue Lineout 0 */ + 0x0060, /* R95 */ + 0x0000, /* R96 */ + 0x0000, /* R97 */ + 0x0000, /* R98 - Charge Pump 0 */ + 0x1F25, /* R99 */ + 0x2B19, /* R100 */ + 0x01C0, /* R101 */ + 0x01EF, /* R102 */ + 0x2B00, /* R103 */ + 0x0000, /* R104 - Class W 0 */ + 0x01C0, /* R105 */ + 0x1C10, /* R106 */ + 0x0000, /* R107 */ + 0x0000, /* R108 - Write Sequencer 0 */ + 0x0000, /* R109 - Write Sequencer 1 */ + 0x0000, /* R110 - Write Sequencer 2 */ + 0x0000, /* R111 - Write Sequencer 3 */ + 0x0000, /* R112 - Write Sequencer 4 */ + 0x0000, /* R113 */ + 0x0000, /* R114 - Control Interface */ + 0x0000, /* R115 */ + 0x00A8, /* R116 - GPIO Control 1 */ + 0x00A8, /* R117 - GPIO Control 2 */ + 0x00A8, /* R118 - GPIO Control 3 */ + 0x0220, /* R119 - GPIO Control 4 */ + 0x01A0, /* R120 - GPIO Control 5 */ + 0x0000, /* R121 - Interrupt Status 1 */ + 0xFFFF, /* R122 - Interrupt Status 1 Mask */ + 0x0000, /* R123 - Interrupt Polarity 1 */ + 0x0000, /* R124 */ + 0x0003, /* R125 */ + 0x0000, /* R126 - Interrupt Control */ + 0x0000, /* R127 */ + 0x0005, /* R128 */ + 0x0000, /* R129 - Control Interface Test 1 */ + 0x0000, /* R130 */ + 0x0000, /* R131 */ + 0x0000, /* R132 */ + 0x0000, /* R133 */ + 0x0000, /* R134 */ + 0x03FF, /* R135 */ + 0x0007, /* R136 */ + 0x0040, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0000, /* R140 */ + 0x0000, /* R141 */ + 0x0000, /* R142 */ + 0x0000, /* R143 */ + 0x0000, /* R144 */ + 0x0000, /* R145 */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x4000, /* R148 */ + 0x6810, /* R149 - Charge Pump Test 1 */ + 0x0004, /* R150 */ + 0x0000, /* R151 */ + 0x0000, /* R152 */ + 0x0000, /* R153 */ + 0x0000, /* R154 */ + 0x0000, /* R155 */ + 0x0000, /* R156 */ + 0x0000, /* R157 */ + 0x0000, /* R158 */ + 0x0000, /* R159 */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 */ + 0x0028, /* R164 - Clock Rate Test 4 */ + 0x0004, /* R165 */ + 0x0000, /* R166 */ + 0x0060, /* R167 */ + 0x0000, /* R168 */ + 0x0000, /* R169 */ + 0x0000, /* R170 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Analogue Output Bias 0 */ +}; + +static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); + + return cache[reg]; +} + +static unsigned int wm8903_hw_read(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + pr_err("i2c_transfer returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static unsigned int wm8903_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + case WM8903_INTERRUPT_STATUS_1: + case WM8903_WRITE_SEQUENCER_4: + return wm8903_hw_read(codec, reg); + + default: + return wm8903_read_reg_cache(codec, reg); + } +} + +static void wm8903_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); + + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + break; + + default: + cache[reg] = value; + break; + } +} + +static int wm8903_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + wm8903_write_reg_cache(codec, reg, value); + + /* Data format is 1 byte of address followed by 2 bytes of data */ + data[0] = reg; + data[1] = (value >> 8) & 0xff; + data[2] = value & 0xff; + + if (codec->hw_write(codec->control_data, data, 3) == 2) + return 0; + else + return -EIO; +} + +static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) +{ + u16 reg[5]; + struct i2c_client *i2c = codec->control_data; + + BUG_ON(start > 48); + + /* Enable the sequencer */ + reg[0] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_0); + reg[0] |= WM8903_WSEQ_ENA; + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); + + dev_dbg(&i2c->dev, "Starting sequence at %d\n", start); + + wm8903_write(codec, WM8903_WRITE_SEQUENCER_3, + start | WM8903_WSEQ_START); + + /* Wait for it to complete. If we have the interrupt wired up then + * we could block waiting for an interrupt, though polling may still + * be desirable for diagnostic purposes. + */ + do { + msleep(10); + + reg[4] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_4); + } while (reg[4] & WM8903_WSEQ_BUSY); + + dev_dbg(&i2c->dev, "Sequence complete\n"); + + /* Disable the sequencer again */ + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, + reg[0] & ~WM8903_WSEQ_ENA); + + return 0; +} + +static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) +{ + int i; + + /* There really ought to be something better we can do here :/ */ + for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++) + cache[i] = wm8903_hw_read(codec, i); +} + +static void wm8903_reset(struct snd_soc_codec *codec) +{ + wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0); +} + +#define WM8903_OUTPUT_SHORT 0x8 +#define WM8903_OUTPUT_OUT 0x4 +#define WM8903_OUTPUT_INT 0x2 +#define WM8903_OUTPUT_IN 0x1 + +/* + * Event for headphone and line out amplifier power changes. Special + * power up/down sequences are required in order to maximise pop/click + * performance. + */ +static int wm8903_output_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + u16 val; + u16 reg; + int shift; + u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0); + + switch (w->reg) { + case WM8903_POWER_MANAGEMENT_2: + reg = WM8903_ANALOGUE_HP_0; + break; + case WM8903_POWER_MANAGEMENT_3: + reg = WM8903_ANALOGUE_LINEOUT_0; + break; + default: + BUG(); + } + + switch (w->shift) { + case 0: + shift = 0; + break; + case 1: + shift = 4; + break; + default: + BUG(); + } + + if (event & SND_SOC_DAPM_PRE_PMU) { + val = wm8903_read(codec, reg); + + /* Short the output */ + val &= ~(WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + + wm8903->charge_pump_users++; + + dev_dbg(&i2c->dev, "Charge pump use count now %d\n", + wm8903->charge_pump_users); + + if (wm8903->charge_pump_users == 1) { + dev_dbg(&i2c->dev, "Enabling charge pump\n"); + wm8903_write(codec, WM8903_CHARGE_PUMP_0, + cp_reg | WM8903_CP_ENA); + mdelay(4); + } + } + + if (event & SND_SOC_DAPM_POST_PMU) { + val = wm8903_read(codec, reg); + + val |= (WM8903_OUTPUT_IN << shift); + wm8903_write(codec, reg, val); + + val |= (WM8903_OUTPUT_INT << shift); + wm8903_write(codec, reg, val); + + /* Turn on the output ENA_OUTP */ + val |= (WM8903_OUTPUT_OUT << shift); + wm8903_write(codec, reg, val); + + /* Remove the short */ + val |= (WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + val = wm8903_read(codec, reg); + + /* Short the output */ + val &= ~(WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + + /* Then disable the intermediate and output stages */ + val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | + WM8903_OUTPUT_IN) << shift); + wm8903_write(codec, reg, val); + } + + if (event & SND_SOC_DAPM_POST_PMD) { + wm8903->charge_pump_users--; + + dev_dbg(&i2c->dev, "Charge pump use count now %d\n", + wm8903->charge_pump_users); + + if (wm8903->charge_pump_users == 0) { + dev_dbg(&i2c->dev, "Disabling charge pump\n"); + wm8903_write(codec, WM8903_CHARGE_PUMP_0, + cp_reg & ~WM8903_CP_ENA); + } + } + + return 0; +} + +/* + * When used with DAC outputs only the WM8903 charge pump supports + * operation in class W mode, providing very low power consumption + * when used with digital sources. Enable and disable this mode + * automatically depending on the mixer configuration. + * + * All the relevant controls are simple switches. + */ +static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = widget->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + u16 reg; + int ret; + + reg = wm8903_read(codec, WM8903_CLASS_W_0); + + /* Turn it off if we're about to enable bypass */ + if (ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 0) { + dev_dbg(&i2c->dev, "Disabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg & + ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V)); + } + wm8903->class_w_users++; + } + + /* Implement the change */ + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + /* If we've just disabled the last bypass path turn Class W on */ + if (!ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 1) { + dev_dbg(&i2c->dev, "Enabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg | + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); + } + wm8903->class_w_users--; + } + + dev_dbg(&i2c->dev, "Bypass use count now %d\n", + wm8903->class_w_users); + + return ret; +} + +#define SOC_DAPM_SINGLE_W(xname, reg, shift, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = wm8903_class_w_put, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +/* ALSA can only do steps of .01dB */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0); + +static const char *drc_slope_text[] = { + "1", "1/2", "1/4", "1/8", "1/16", "0" +}; + +static const struct soc_enum drc_slope_r0 = + SOC_ENUM_SINGLE(WM8903_DRC_2, 3, 6, drc_slope_text); + +static const struct soc_enum drc_slope_r1 = + SOC_ENUM_SINGLE(WM8903_DRC_2, 0, 6, drc_slope_text); + +static const char *drc_attack_text[] = { + "instantaneous", + "363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms", + "46.4ms", "92.8ms", "185.6ms" +}; + +static const struct soc_enum drc_attack = + SOC_ENUM_SINGLE(WM8903_DRC_1, 12, 11, drc_attack_text); + +static const char *drc_decay_text[] = { + "186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s", + "23.87s", "47.56s" +}; + +static const struct soc_enum drc_decay = + SOC_ENUM_SINGLE(WM8903_DRC_1, 8, 9, drc_decay_text); + +static const char *drc_ff_delay_text[] = { + "5 samples", "9 samples" +}; + +static const struct soc_enum drc_ff_delay = + SOC_ENUM_SINGLE(WM8903_DRC_0, 5, 2, drc_ff_delay_text); + +static const char *drc_qr_decay_text[] = { + "0.725ms", "1.45ms", "5.8ms" +}; + +static const struct soc_enum drc_qr_decay = + SOC_ENUM_SINGLE(WM8903_DRC_1, 4, 3, drc_qr_decay_text); + +static const char *drc_smoothing_text[] = { + "Low", "Medium", "High" +}; + +static const struct soc_enum drc_smoothing = + SOC_ENUM_SINGLE(WM8903_DRC_0, 11, 3, drc_smoothing_text); + +static const char *soft_mute_text[] = { + "Fast (fs/2)", "Slow (fs/32)" +}; + +static const struct soc_enum soft_mute = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 10, 2, soft_mute_text); + +static const char *mute_mode_text[] = { + "Hard", "Soft" +}; + +static const struct soc_enum mute_mode = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 9, 2, mute_mode_text); + +static const char *dac_deemphasis_text[] = { + "Disabled", "32kHz", "44.1kHz", "48kHz" +}; + +static const struct soc_enum dac_deemphasis = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 1, 4, dac_deemphasis_text); + +static const char *companding_text[] = { + "ulaw", "alaw" +}; + +static const struct soc_enum dac_companding = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 0, 2, companding_text); + +static const struct soc_enum adc_companding = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 2, 2, companding_text); + +static const char *input_mode_text[] = { + "Single-Ended", "Differential Line", "Differential Mic" +}; + +static const struct soc_enum linput_mode_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text); + +static const struct soc_enum rinput_mode_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text); + +static const char *linput_mux_text[] = { + "IN1L", "IN2L", "IN3L" +}; + +static const struct soc_enum linput_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 2, 3, linput_mux_text); + +static const struct soc_enum linput_inv_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 4, 3, linput_mux_text); + +static const char *rinput_mux_text[] = { + "IN1R", "IN2R", "IN3R" +}; + +static const struct soc_enum rinput_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 2, 3, rinput_mux_text); + +static const struct soc_enum rinput_inv_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text); + + +static const struct snd_kcontrol_new wm8903_snd_controls[] = { + +/* Input PGAs - No TLV since the scale depends on PGA mode */ +SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1, + 6, 1, 0), + +SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1, + 6, 1, 0), + +/* ADCs */ +SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0), +SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0), +SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1), +SOC_SINGLE_TLV("DRC Compressor Threashold Volume", WM8903_DRC_3, 5, 124, 1, + drc_tlv_thresh), +SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp), +SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min), +SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max), +SOC_ENUM("DRC Attack Rate", drc_attack), +SOC_ENUM("DRC Decay Rate", drc_decay), +SOC_ENUM("DRC FF Delay", drc_ff_delay), +SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), +SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +SOC_SINGLE_TLV("DRC QR Threashold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), +SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), +SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), +SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +SOC_ENUM("DRC Smoothing Threashold", drc_smoothing), +SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup), + +SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT, + WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv), +SOC_ENUM("ADC Companding Mode", adc_companding), +SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0), + +/* DAC */ +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT, + WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv), +SOC_ENUM("DAC Soft Mute Rate", soft_mute), +SOC_ENUM("DAC Mute Mode", mute_mode), +SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0), +SOC_ENUM("DAC De-emphasis", dac_deemphasis), +SOC_SINGLE("DAC Sloping Stopband Filter Switch", + WM8903_DAC_DIGITAL_1, 11, 1, 0), +SOC_ENUM("DAC Companding Mode", dac_companding), +SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0), + +/* Headphones */ +SOC_DOUBLE_R("Headphone Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Headphone Volume", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 0, 63, 0, out_tlv), + +/* Line out */ +SOC_DOUBLE_R("Line Out Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Line Out ZC Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Line Out Volume", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 0, 63, 0, out_tlv), + +/* Speaker */ +SOC_DOUBLE_R("Speaker Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1), +SOC_DOUBLE_R("Speaker ZC Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0), +SOC_DOUBLE_R_TLV("Speaker Volume", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, + 0, 63, 0, out_tlv), +}; + +static int wm8903_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8903_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8903_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static const struct snd_kcontrol_new linput_mode_mux = + SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum); + +static const struct snd_kcontrol_new rinput_mode_mux = + SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum); + +static const struct snd_kcontrol_new linput_mux = + SOC_DAPM_ENUM("Left Input Mux", linput_enum); + +static const struct snd_kcontrol_new linput_inv_mux = + SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum); + +static const struct snd_kcontrol_new rinput_mux = + SOC_DAPM_ENUM("Right Input Mux", rinput_enum); + +static const struct snd_kcontrol_new rinput_inv_mux = + SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum); + +static const struct snd_kcontrol_new left_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, + 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LINEOUTL"), +SND_SOC_DAPM_OUTPUT("LINEOUTR"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8903_MIC_BIAS_CONTROL_0, 0, 0), + +SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux), +SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &linput_inv_mux), +SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux), + +SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux), +SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &rinput_inv_mux), +SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux), + +SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + +SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), + +SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, + 1, 0, NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, + 0, 0, NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, + NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, + NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, + NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, + NULL, 0), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + + { "Left Input Mux", "IN1L", "IN1L" }, + { "Left Input Mux", "IN2L", "IN2L" }, + { "Left Input Mux", "IN3L", "IN3L" }, + + { "Left Input Inverting Mux", "IN1L", "IN1L" }, + { "Left Input Inverting Mux", "IN2L", "IN2L" }, + { "Left Input Inverting Mux", "IN3L", "IN3L" }, + + { "Right Input Mux", "IN1R", "IN1R" }, + { "Right Input Mux", "IN2R", "IN2R" }, + { "Right Input Mux", "IN3R", "IN3R" }, + + { "Right Input Inverting Mux", "IN1R", "IN1R" }, + { "Right Input Inverting Mux", "IN2R", "IN2R" }, + { "Right Input Inverting Mux", "IN3R", "IN3R" }, + + { "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Inverting Mux" }, + + { "Right Input Mode Mux", "Single-Ended", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Inverting Mux" }, + + { "Left Input PGA", NULL, "Left Input Mode Mux" }, + { "Right Input PGA", NULL, "Right Input Mode Mux" }, + + { "ADCL", NULL, "Left Input PGA" }, + { "ADCR", NULL, "Right Input PGA" }, + + { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Output Mixer", "DACL Switch", "DACL" }, + { "Left Output Mixer", "DACR Switch", "DACR" }, + + { "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Output Mixer", "DACL Switch", "DACL" }, + { "Right Output Mixer", "DACR Switch", "DACR" }, + + { "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Speaker Mixer", "DACL Switch", "DACL" }, + { "Left Speaker Mixer", "DACR Switch", "DACR" }, + + { "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Speaker Mixer", "DACL Switch", "DACL" }, + { "Right Speaker Mixer", "DACR Switch", "DACR" }, + + { "Left Line Output PGA", NULL, "Left Output Mixer" }, + { "Right Line Output PGA", NULL, "Right Output Mixer" }, + + { "Left Headphone Output PGA", NULL, "Left Output Mixer" }, + { "Right Headphone Output PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker PGA", NULL, "Left Speaker Mixer" }, + { "Right Speaker PGA", NULL, "Right Speaker Mixer" }, + + { "HPOUTL", NULL, "Left Headphone Output PGA" }, + { "HPOUTR", NULL, "Right Headphone Output PGA" }, + + { "LINEOUTL", NULL, "Left Line Output PGA" }, + { "LINEOUTR", NULL, "Right Line Output PGA" }, + + { "LOP", NULL, "Left Speaker PGA" }, + { "LON", NULL, "Left Speaker PGA" }, + + { "ROP", NULL, "Right Speaker PGA" }, + { "RON", NULL, "Right Speaker PGA" }, +}; + +static int wm8903_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets, + ARRAY_SIZE(wm8903_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int wm8903_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct i2c_client *i2c = codec->control_data; + u16 reg, reg2; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg &= ~(WM8903_VMID_RES_MASK); + reg |= WM8903_VMID_RES_50K; + wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + wm8903_run_sequence(codec, 0); + wm8903_sync_reg_cache(codec, codec->reg_cache); + + /* Enable low impedence charge pump output */ + reg = wm8903_read(codec, + WM8903_CONTROL_INTERFACE_TEST_1); + wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + reg | WM8903_TEST_KEY); + reg2 = wm8903_read(codec, WM8903_CHARGE_PUMP_TEST_1); + wm8903_write(codec, WM8903_CHARGE_PUMP_TEST_1, + reg2 | WM8903_CP_SW_KELVIN_MODE_MASK); + wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + reg); + + /* By default no bypass paths are enabled so + * enable Class W support. + */ + dev_dbg(&i2c->dev, "Enabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg | + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); + } + + reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg &= ~(WM8903_VMID_RES_MASK); + reg |= WM8903_VMID_RES_250K; + wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + break; + + case SND_SOC_BIAS_OFF: + wm8903_run_sequence(codec, 32); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm8903_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8903_priv *wm8903 = codec->private_data; + + wm8903->sysclk = freq; + + return 0; +} + +static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); + + aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK | + WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif1 |= WM8903_LRCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif1 |= WM8903_BCLK_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x3; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= 0x3 | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 |= 0x1; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8903_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8903_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8903_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + + return 0; +} + +static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + reg = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + + if (mute) + reg |= WM8903_DAC_MUTE; + else + reg &= ~WM8903_DAC_MUTE; + + wm8903_write(codec, WM8903_DAC_DIGITAL_1, reg); + + return 0; +} + +/* Lookup table for CLK_SYS/fs ratio. 256fs or more is recommended + * for optimal performance so we list the lower rates first and match + * on the last match we find. */ +static struct { + int div; + int rate; + int mode; + int mclk_div; +} clk_sys_ratios[] = { + { 64, 0x0, 0x0, 1 }, + { 68, 0x0, 0x1, 1 }, + { 125, 0x0, 0x2, 1 }, + { 128, 0x1, 0x0, 1 }, + { 136, 0x1, 0x1, 1 }, + { 192, 0x2, 0x0, 1 }, + { 204, 0x2, 0x1, 1 }, + + { 64, 0x0, 0x0, 2 }, + { 68, 0x0, 0x1, 2 }, + { 125, 0x0, 0x2, 2 }, + { 128, 0x1, 0x0, 2 }, + { 136, 0x1, 0x1, 2 }, + { 192, 0x2, 0x0, 2 }, + { 204, 0x2, 0x1, 2 }, + + { 250, 0x2, 0x2, 1 }, + { 256, 0x3, 0x0, 1 }, + { 272, 0x3, 0x1, 1 }, + { 384, 0x4, 0x0, 1 }, + { 408, 0x4, 0x1, 1 }, + { 375, 0x4, 0x2, 1 }, + { 512, 0x5, 0x0, 1 }, + { 544, 0x5, 0x1, 1 }, + { 500, 0x5, 0x2, 1 }, + { 768, 0x6, 0x0, 1 }, + { 816, 0x6, 0x1, 1 }, + { 750, 0x6, 0x2, 1 }, + { 1024, 0x7, 0x0, 1 }, + { 1088, 0x7, 0x1, 1 }, + { 1000, 0x7, 0x2, 1 }, + { 1408, 0x8, 0x0, 1 }, + { 1496, 0x8, 0x1, 1 }, + { 1536, 0x9, 0x0, 1 }, + { 1632, 0x9, 0x1, 1 }, + { 1500, 0x9, 0x2, 1 }, + + { 250, 0x2, 0x2, 2 }, + { 256, 0x3, 0x0, 2 }, + { 272, 0x3, 0x1, 2 }, + { 384, 0x4, 0x0, 2 }, + { 408, 0x4, 0x1, 2 }, + { 375, 0x4, 0x2, 2 }, + { 512, 0x5, 0x0, 2 }, + { 544, 0x5, 0x1, 2 }, + { 500, 0x5, 0x2, 2 }, + { 768, 0x6, 0x0, 2 }, + { 816, 0x6, 0x1, 2 }, + { 750, 0x6, 0x2, 2 }, + { 1024, 0x7, 0x0, 2 }, + { 1088, 0x7, 0x1, 2 }, + { 1000, 0x7, 0x2, 2 }, + { 1408, 0x8, 0x0, 2 }, + { 1496, 0x8, 0x1, 2 }, + { 1536, 0x9, 0x0, 2 }, + { 1632, 0x9, 0x1, 2 }, + { 1500, 0x9, 0x2, 2 }, +}; + +/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */ +static struct { + int ratio; + int div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 55, 6 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 110, 10 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 15 }, + { 250, 16 }, + { 300, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + +/* Sample rates for DSP */ +static struct { + int rate; + int value; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 88200, 9 }, + { 96000, 10 }, + { 0, 0 }, +}; + +static int wm8903_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + struct snd_pcm_runtime *master_runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + wm8903->playback_active++; + else + wm8903->capture_active++; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (wm8903->master_substream) { + master_runtime = wm8903->master_substream->runtime; + + dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n", + master_runtime->sample_bits, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + wm8903->slave_substream = substream; + } else + wm8903->master_substream = substream; + + return 0; +} + +static void wm8903_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + wm8903->playback_active--; + else + wm8903->capture_active--; + + if (wm8903->master_substream == substream) + wm8903->master_substream = wm8903->slave_substream; + + wm8903->slave_substream = NULL; +} + +static int wm8903_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 wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + int fs = params_rate(params); + int bclk; + int bclk_div; + int i; + int dsp_config; + int clk_config; + int best_val; + int cur_val; + int clk_sys; + + u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); + u16 aif2 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_2); + u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3); + u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0); + u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1); + + if (substream == wm8903->slave_substream) { + dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); + return 0; + } + + /* Configure sample rate logic for DSP - choose nearest rate */ + dsp_config = 0; + best_val = abs(sample_rates[dsp_config].rate - fs); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + cur_val = abs(sample_rates[i].rate - fs); + if (cur_val <= best_val) { + dsp_config = i; + best_val = cur_val; + } + } + + /* Constraints should stop us hitting this but let's make sure */ + if (wm8903->capture_active) + switch (sample_rates[dsp_config].rate) { + case 88200: + case 96000: + dev_err(&i2c->dev, "%dHz unsupported by ADC\n", + fs); + return -EINVAL; + + default: + break; + } + + dev_dbg(&i2c->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate); + clock1 &= ~WM8903_SAMPLE_RATE_MASK; + clock1 |= sample_rates[dsp_config].value; + + aif1 &= ~WM8903_AIF_WL_MASK; + bclk = 2 * fs; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + bclk *= 20; + aif1 |= 0x4; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bclk *= 24; + aif1 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bclk *= 32; + aif1 |= 0xc; + break; + default: + return -EINVAL; + } + + dev_dbg(&i2c->dev, "MCLK = %dHz, target sample rate = %dHz\n", + wm8903->sysclk, fs); + + /* We may not have an MCLK which allows us to generate exactly + * the clock we want, particularly with USB derived inputs, so + * approximate. + */ + clk_config = 0; + best_val = abs((wm8903->sysclk / + (clk_sys_ratios[0].mclk_div * + clk_sys_ratios[0].div)) - fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) { + cur_val = abs((wm8903->sysclk / + (clk_sys_ratios[i].mclk_div * + clk_sys_ratios[i].div)) - fs); + + if (cur_val <= best_val) { + clk_config = i; + best_val = cur_val; + } + } + + if (clk_sys_ratios[clk_config].mclk_div == 2) { + clock0 |= WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk / 2; + } else { + clock0 &= ~WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk; + } + + clock1 &= ~(WM8903_CLK_SYS_RATE_MASK | + WM8903_CLK_SYS_MODE_MASK); + clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT; + clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT; + + dev_dbg(&i2c->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n", + clk_sys_ratios[clk_config].rate, + clk_sys_ratios[clk_config].mode, + clk_sys_ratios[clk_config].div); + + dev_dbg(&i2c->dev, "Actual CLK_SYS = %dHz\n", clk_sys); + + /* We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + bclk_div = 0; + best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk; + i = 1; + while (i < ARRAY_SIZE(bclk_divs)) { + cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk; + if (cur_val < 0) /* BCLK table is sorted */ + break; + bclk_div = i; + best_val = cur_val; + i++; + } + + aif2 &= ~WM8903_BCLK_DIV_MASK; + aif3 &= ~WM8903_LRCLK_RATE_MASK; + + dev_dbg(&i2c->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n", + bclk_divs[bclk_div].ratio / 10, bclk, + (clk_sys * 10) / bclk_divs[bclk_div].ratio); + + aif2 |= bclk_divs[bclk_div].div; + aif3 |= bclk / fs; + + wm8903_write(codec, WM8903_CLOCK_RATES_0, clock0); + wm8903_write(codec, WM8903_CLOCK_RATES_1, clock1); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3); + + return 0; +} + +#define WM8903_PLAYBACK_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 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define WM8903_CAPTURE_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 WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8903_dai = { + .name = "WM8903", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_PLAYBACK_RATES, + .formats = WM8903_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_CAPTURE_RATES, + .formats = WM8903_FORMATS, + }, + .ops = { + .startup = wm8903_startup, + .shutdown = wm8903_shutdown, + .hw_params = wm8903_hw_params, + }, + .dai_ops = { + .digital_mute = wm8903_digital_mute, + .set_fmt = wm8903_set_dai_fmt, + .set_sysclk = wm8903_set_dai_sysclk + } +}; +EXPORT_SYMBOL_GPL(wm8903_dai); + +static int wm8903_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8903_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c = codec->control_data; + int i; + u16 *reg_cache = codec->reg_cache; + u16 *tmp_cache = kmemdup(codec->reg_cache, sizeof(wm8903_reg_defaults), + GFP_KERNEL); + + /* Bring the codec back up to standby first to minimise pop/clicks */ + wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8903_set_bias_level(codec, codec->suspend_bias_level); + + /* Sync back everything else */ + if (tmp_cache) { + for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++) + if (tmp_cache[i] != reg_cache[i]) + wm8903_write(codec, i, tmp_cache[i]); + } else { + dev_err(&i2c->dev, "Failed to allocate temporary cache\n"); + } + + return 0; +} + +/* + * initialise the WM8903 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8903_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c = codec->control_data; + int ret = 0; + u16 val; + + val = wm8903_hw_read(codec, WM8903_SW_RESET_AND_ID); + if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) { + dev_err(&i2c->dev, + "Device with ID register %x is not a WM8903\n", val); + return -ENODEV; + } + + codec->name = "WM8903"; + codec->owner = THIS_MODULE; + codec->read = wm8903_read; + codec->write = wm8903_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8903_set_bias_level; + codec->dai = &wm8903_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8903_reg_defaults); + codec->reg_cache = kmemdup(wm8903_reg_defaults, + sizeof(wm8903_reg_defaults), + GFP_KERNEL); + if (codec->reg_cache == NULL) { + dev_err(&i2c->dev, "Failed to allocate register cache\n"); + return -ENOMEM; + } + + val = wm8903_read(codec, WM8903_REVISION_NUMBER); + dev_info(&i2c->dev, "WM8903 revision %d\n", + val & WM8903_CHIP_REV_MASK); + + wm8903_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&i2c->dev, "failed to create pcms\n"); + goto pcm_err; + } + + /* SYSCLK is required for pretty much anything */ + wm8903_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA); + + /* power on device */ + wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Latch volume update bits */ + val = wm8903_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT); + val |= WM8903_ADCVU; + wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val); + wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val); + + val = wm8903_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT); + val |= WM8903_DACVU; + wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val); + wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT1_LEFT); + val |= WM8903_HPOUTVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT2_LEFT); + val |= WM8903_LINEOUTVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT3_LEFT); + val |= WM8903_SPKVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); + + /* Enable DAC soft mute by default */ + val = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + val |= WM8903_DAC_MUTEMODE; + wm8903_write(codec, WM8903_DAC_DIGITAL_1, val); + + wm8903_add_controls(codec); + wm8903_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&i2c->dev, "wm8903: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8903_socdev; + +static int wm8903_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8903_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8903_init(socdev); + if (ret < 0) + dev_err(&i2c->dev, "Device initialisation failed\n"); + + return ret; +} + +static int wm8903_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +/* i2c codec control layer */ +static const struct i2c_device_id wm8903_i2c_id[] = { + { "wm8903", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id); + +static struct i2c_driver wm8903_i2c_driver = { + .driver = { + .name = "WM8903", + .owner = THIS_MODULE, + }, + .probe = wm8903_i2c_probe, + .remove = wm8903_i2c_remove, + .id_table = wm8903_i2c_id, +}; + +static int wm8903_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8903_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8903_priv *wm8903; + struct i2c_board_info board_info; + struct i2c_adapter *adapter; + struct i2c_client *i2c_client; + int ret = 0; + + setup = socdev->codec_data; + + if (!setup->i2c_address) { + dev_err(&pdev->dev, "No codec address provided\n"); + return -ENODEV; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); + if (wm8903 == NULL) { + ret = -ENOMEM; + goto err_codec; + } + + codec->private_data = wm8903; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8903_socdev = socdev; + + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8903_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + goto err_priv; + } else { + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "wm8903", I2C_NAME_SIZE); + board_info.addr = setup->i2c_address; + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "Can't get I2C bus %d\n", + setup->i2c_bus); + ret = -ENODEV; + goto err_adapter; + } + + i2c_client = i2c_new_device(adapter, &board_info); + i2c_put_adapter(adapter); + if (i2c_client == NULL) { + dev_err(&pdev->dev, + "I2C driver registration failed\n"); + ret = -ENODEV; + goto err_adapter; + } + } + + return ret; + +err_adapter: + i2c_del_driver(&wm8903_i2c_driver); +err_priv: + kfree(codec->private_data); +err_codec: + kfree(codec); + return ret; +} + +/* power down chip */ +static int wm8903_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + i2c_unregister_device(socdev->codec->control_data); + i2c_del_driver(&wm8903_i2c_driver); + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8903 = { + .probe = wm8903_probe, + .remove = wm8903_remove, + .suspend = wm8903_suspend, + .resume = wm8903_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8903); + +MODULE_DESCRIPTION("ASoC WM8903 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.cm>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h new file mode 100644 index 000000000000..cec622f2f660 --- /dev/null +++ b/sound/soc/codecs/wm8903.h @@ -0,0 +1,1463 @@ +/* + * wm8903.h - WM8903 audio codec interface + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8903_H +#define _WM8903_H + +#include <linux/i2c.h> + +extern struct snd_soc_dai wm8903_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8903; + +struct wm8903_setup_data { + int i2c_bus; + int i2c_address; +}; + +#define WM8903_MCLK_DIV_2 1 +#define WM8903_CLK_SYS 2 +#define WM8903_BCLK 3 +#define WM8903_LRCLK 4 + +/* + * Register values. + */ +#define WM8903_SW_RESET_AND_ID 0x00 +#define WM8903_REVISION_NUMBER 0x01 +#define WM8903_BIAS_CONTROL_0 0x04 +#define WM8903_VMID_CONTROL_0 0x05 +#define WM8903_MIC_BIAS_CONTROL_0 0x06 +#define WM8903_ANALOGUE_DAC_0 0x08 +#define WM8903_ANALOGUE_ADC_0 0x0A +#define WM8903_POWER_MANAGEMENT_0 0x0C +#define WM8903_POWER_MANAGEMENT_1 0x0D +#define WM8903_POWER_MANAGEMENT_2 0x0E +#define WM8903_POWER_MANAGEMENT_3 0x0F +#define WM8903_POWER_MANAGEMENT_4 0x10 +#define WM8903_POWER_MANAGEMENT_5 0x11 +#define WM8903_POWER_MANAGEMENT_6 0x12 +#define WM8903_CLOCK_RATES_0 0x14 +#define WM8903_CLOCK_RATES_1 0x15 +#define WM8903_CLOCK_RATES_2 0x16 +#define WM8903_AUDIO_INTERFACE_0 0x18 +#define WM8903_AUDIO_INTERFACE_1 0x19 +#define WM8903_AUDIO_INTERFACE_2 0x1A +#define WM8903_AUDIO_INTERFACE_3 0x1B +#define WM8903_DAC_DIGITAL_VOLUME_LEFT 0x1E +#define WM8903_DAC_DIGITAL_VOLUME_RIGHT 0x1F +#define WM8903_DAC_DIGITAL_0 0x20 +#define WM8903_DAC_DIGITAL_1 0x21 +#define WM8903_ADC_DIGITAL_VOLUME_LEFT 0x24 +#define WM8903_ADC_DIGITAL_VOLUME_RIGHT 0x25 +#define WM8903_ADC_DIGITAL_0 0x26 +#define WM8903_DIGITAL_MICROPHONE_0 0x27 +#define WM8903_DRC_0 0x28 +#define WM8903_DRC_1 0x29 +#define WM8903_DRC_2 0x2A +#define WM8903_DRC_3 0x2B +#define WM8903_ANALOGUE_LEFT_INPUT_0 0x2C +#define WM8903_ANALOGUE_RIGHT_INPUT_0 0x2D +#define WM8903_ANALOGUE_LEFT_INPUT_1 0x2E +#define WM8903_ANALOGUE_RIGHT_INPUT_1 0x2F +#define WM8903_ANALOGUE_LEFT_MIX_0 0x32 +#define WM8903_ANALOGUE_RIGHT_MIX_0 0x33 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_0 0x34 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_1 0x35 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0 0x36 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1 0x37 +#define WM8903_ANALOGUE_OUT1_LEFT 0x39 +#define WM8903_ANALOGUE_OUT1_RIGHT 0x3A +#define WM8903_ANALOGUE_OUT2_LEFT 0x3B +#define WM8903_ANALOGUE_OUT2_RIGHT 0x3C +#define WM8903_ANALOGUE_OUT3_LEFT 0x3E +#define WM8903_ANALOGUE_OUT3_RIGHT 0x3F +#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41 +#define WM8903_DC_SERVO_0 0x43 +#define WM8903_DC_SERVO_2 0x45 +#define WM8903_ANALOGUE_HP_0 0x5A +#define WM8903_ANALOGUE_LINEOUT_0 0x5E +#define WM8903_CHARGE_PUMP_0 0x62 +#define WM8903_CLASS_W_0 0x68 +#define WM8903_WRITE_SEQUENCER_0 0x6C +#define WM8903_WRITE_SEQUENCER_1 0x6D +#define WM8903_WRITE_SEQUENCER_2 0x6E +#define WM8903_WRITE_SEQUENCER_3 0x6F +#define WM8903_WRITE_SEQUENCER_4 0x70 +#define WM8903_CONTROL_INTERFACE 0x72 +#define WM8903_GPIO_CONTROL_1 0x74 +#define WM8903_GPIO_CONTROL_2 0x75 +#define WM8903_GPIO_CONTROL_3 0x76 +#define WM8903_GPIO_CONTROL_4 0x77 +#define WM8903_GPIO_CONTROL_5 0x78 +#define WM8903_INTERRUPT_STATUS_1 0x79 +#define WM8903_INTERRUPT_STATUS_1_MASK 0x7A +#define WM8903_INTERRUPT_POLARITY_1 0x7B +#define WM8903_INTERRUPT_CONTROL 0x7E +#define WM8903_CONTROL_INTERFACE_TEST_1 0x81 +#define WM8903_CHARGE_PUMP_TEST_1 0x95 +#define WM8903_CLOCK_RATE_TEST_4 0xA4 +#define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC + +#define WM8903_REGISTER_COUNT 75 +#define WM8903_MAX_REGISTER 0xAC + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - SW Reset and ID + */ +#define WM8903_SW_RESET_DEV_ID1_MASK 0xFFFF /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_SHIFT 0 /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_WIDTH 16 /* SW_RESET_DEV_ID1 - [15:0] */ + +/* + * R1 (0x01) - Revision Number + */ +#define WM8903_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */ + +/* + * R4 (0x04) - Bias Control 0 + */ +#define WM8903_POBCTRL 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_MASK 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_SHIFT 4 /* POBCTRL */ +#define WM8903_POBCTRL_WIDTH 1 /* POBCTRL */ +#define WM8903_ISEL_MASK 0x000C /* ISEL - [3:2] */ +#define WM8903_ISEL_SHIFT 2 /* ISEL - [3:2] */ +#define WM8903_ISEL_WIDTH 2 /* ISEL - [3:2] */ +#define WM8903_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8903_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R5 (0x05) - VMID Control 0 + */ +#define WM8903_VMID_TIE_ENA 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_MASK 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_SHIFT 7 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_WIDTH 1 /* VMID_TIE_ENA */ +#define WM8903_BUFIO_ENA 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_MASK 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_SHIFT 6 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_WIDTH 1 /* BUFIO_ENA */ +#define WM8903_VMID_IO_ENA 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_MASK 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_SHIFT 5 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_WIDTH 1 /* VMID_IO_ENA */ +#define WM8903_VMID_SOFT_MASK 0x0018 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_SHIFT 3 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_WIDTH 2 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM8903_VMID_BUF_ENA 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_MASK 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_SHIFT 0 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ + +#define WM8903_VMID_RES_50K 2 +#define WM8903_VMID_RES_250K 3 +#define WM8903_VMID_RES_5K 4 + +/* + * R6 (0x06) - Mic Bias Control 0 + */ +#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ +#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + +/* + * R8 (0x08) - Analogue DAC 0 + */ +#define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_SHIFT 3 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_WIDTH 2 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACVMID_BIAS_SEL_MASK 0x0006 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_SHIFT 1 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_WIDTH 2 /* DACVMID_BIAS_SEL - [2:1] */ + +/* + * R10 (0x0A) - Analogue ADC 0 + */ +#define WM8903_ADC_OSR128 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ + +/* + * R12 (0x0C) - Power Management 0 + */ +#define WM8903_INL_ENA 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_MASK 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_SHIFT 1 /* INL_ENA */ +#define WM8903_INL_ENA_WIDTH 1 /* INL_ENA */ +#define WM8903_INR_ENA 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_MASK 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_SHIFT 0 /* INR_ENA */ +#define WM8903_INR_ENA_WIDTH 1 /* INR_ENA */ + +/* + * R13 (0x0D) - Power Management 1 + */ +#define WM8903_MIXOUTL_ENA 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_MASK 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_SHIFT 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTR_ENA 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_MASK 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_SHIFT 0 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ + +/* + * R14 (0x0E) - Power Management 2 + */ +#define WM8903_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */ +#define WM8903_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */ + +/* + * R15 (0x0F) - Power Management 3 + */ +#define WM8903_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */ + +/* + * R16 (0x10) - Power Management 4 + */ +#define WM8903_MIXSPKL_ENA 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_MASK 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_SHIFT 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_WIDTH 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKR_ENA 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_MASK 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_SHIFT 0 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_WIDTH 1 /* MIXSPKR_ENA */ + +/* + * R17 (0x11) - Power Management 5 + */ +#define WM8903_SPKL_ENA 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_MASK 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_SHIFT 1 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ +#define WM8903_SPKR_ENA 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_MASK 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_SHIFT 0 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ + +/* + * R18 (0x12) - Power Management 6 + */ +#define WM8903_DACL_ENA 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_MASK 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_SHIFT 3 /* DACL_ENA */ +#define WM8903_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8903_DACR_ENA 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_MASK 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_SHIFT 2 /* DACR_ENA */ +#define WM8903_DACR_ENA_WIDTH 1 /* DACR_ENA */ +#define WM8903_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8903_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R20 (0x14) - Clock Rates 0 + */ +#define WM8903_MCLKDIV2 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_MASK 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_SHIFT 0 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ + +/* + * R21 (0x15) - Clock Rates 1 + */ +#define WM8903_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_MODE_MASK 0x0300 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_SHIFT 8 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_WIDTH 2 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */ + +/* + * R22 (0x16) - Clock Rates 2 + */ +#define WM8903_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8903_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8903_TO_ENA 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_MASK 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_SHIFT 0 /* TO_ENA */ +#define WM8903_TO_ENA_WIDTH 1 /* TO_ENA */ + +/* + * R24 (0x18) - Audio Interface 0 + */ +#define WM8903_DACL_DATINV 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_SHIFT 12 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_WIDTH 1 /* DACL_DATINV */ +#define WM8903_DACR_DATINV 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_SHIFT 11 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_WIDTH 1 /* DACR_DATINV */ +#define WM8903_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */ +#define WM8903_LOOPBACK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_MASK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_SHIFT 8 /* LOOPBACK */ +#define WM8903_LOOPBACK_WIDTH 1 /* LOOPBACK */ +#define WM8903_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */ +#define WM8903_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */ +#define WM8903_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */ +#define WM8903_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */ +#define WM8903_ADC_COMP 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_MASK 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_SHIFT 3 /* ADC_COMP */ +#define WM8903_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8903_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8903_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM8903_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8903_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R25 (0x19) - Audio Interface 1 + */ +#define WM8903_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFADC_TDM 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */ +#define WM8903_LRCLK_DIR 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_MASK 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_SHIFT 9 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM8903_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM8903_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM8903_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM8903_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R26 (0x1A) - Audio Interface 2 + */ +#define WM8903_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R27 (0x1B) - Audio Interface 3 + */ +#define WM8903_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R30 (0x1E) - DAC Digital Volume Left + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital Volume Right + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R32 (0x20) - DAC Digital 0 + */ +#define WM8903_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */ + +/* + * R33 (0x21) - DAC Digital 1 + */ +#define WM8903_DAC_MONO 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_MASK 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_SHIFT 12 /* DAC_MONO */ +#define WM8903_DAC_MONO_WIDTH 1 /* DAC_MONO */ +#define WM8903_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */ +#define WM8903_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8903_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R36 (0x24) - ADC Digital Volume Left + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R37 (0x25) - ADC Digital Volume Right + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R38 (0x26) - ADC Digital 0 + */ +#define WM8903_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_ENA 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_MASK 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_SHIFT 4 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_WIDTH 1 /* ADC_HPF_ENA */ +#define WM8903_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */ +#define WM8903_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */ + +/* + * R39 (0x27) - Digital Microphone 0 + */ +#define WM8903_DIGMIC_MODE_SEL 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_MASK 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_SHIFT 8 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_WIDTH 1 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_CLK_SEL_L_MASK 0x00C0 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_SHIFT 6 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_WIDTH 2 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_R_MASK 0x0030 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_SHIFT 4 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_WIDTH 2 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_RT_MASK 0x000C /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_MASK 0x0003 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_SHIFT 0 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_WIDTH 2 /* DIGMIC_CLK_SEL - [1:0] */ + +/* + * R40 (0x28) - DRC 0 + */ +#define WM8903_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM8903_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM8903_DRC_THRESH_HYST_MASK 0x1800 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_SHIFT 11 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */ +#define WM8903_DRC_SMOOTH_ENA 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_MASK 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_SHIFT 3 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_QR_ENA 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_MASK 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_SHIFT 2 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */ +#define WM8903_DRC_ANTICLIP_ENA 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_MASK 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_SHIFT 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_HYST_ENA 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_MASK 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_SHIFT 0 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */ + +/* + * R41 (0x29) - DRC 1 + */ +#define WM8903_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_THRESH_QR_MASK 0x00C0 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_SHIFT 6 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_RATE_QR_MASK 0x0030 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_SHIFT 4 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R42 (0x2A) - DRC 2 + */ +#define WM8903_DRC_R0_SLOPE_COMP_MASK 0x0038 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_SHIFT 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R1_SLOPE_COMP_MASK 0x0007 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_SHIFT 0 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [2:0] */ + +/* + * R43 (0x2B) - DRC 3 + */ +#define WM8903_DRC_THRESH_COMP_MASK 0x07E0 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_SHIFT 5 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_AMP_COMP_MASK 0x001F /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_SHIFT 0 /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [4:0] */ + +/* + * R44 (0x2C) - Analogue Left Input 0 + */ +#define WM8903_LINMUTE 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8903_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8903_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */ + +/* + * R45 (0x2D) - Analogue Right Input 0 + */ +#define WM8903_RINMUTE 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8903_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8903_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */ + +/* + * R46 (0x2E) - Analogue Left Input 1 + */ +#define WM8903_INL_CM_ENA 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */ +#define WM8903_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_SHIFT 0 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_WIDTH 2 /* L_MODE - [1:0] */ + +/* + * R47 (0x2F) - Analogue Right Input 1 + */ +#define WM8903_INR_CM_ENA 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */ +#define WM8903_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_SHIFT 0 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_WIDTH 2 /* R_MODE - [1:0] */ + +/* + * R50 (0x32) - Analogue Left Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTL 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_MASK 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_SHIFT 3 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_MASK 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_SHIFT 2 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_WIDTH 1 /* DACR_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_MASK 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_MASK 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT 0 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH 1 /* BYPASSR_TO_MIXOUTL */ + +/* + * R51 (0x33) - Analogue Right Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTR 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_MASK 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_SHIFT 3 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_WIDTH 1 /* DACL_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_MASK 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_SHIFT 2 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_MASK 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_MASK 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT 0 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH 1 /* BYPASSR_TO_MIXOUTR */ + +/* + * R52 (0x34) - Analogue Spk Mix Left 0 + */ +#define WM8903_DACL_TO_MIXSPKL 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_MASK 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_SHIFT 3 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_WIDTH 1 /* DACL_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_MASK 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_SHIFT 2 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_WIDTH 1 /* DACR_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_MASK 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_MASK 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT 0 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH 1 /* BYPASSR_TO_MIXSPKL */ + +/* + * R53 (0x35) - Analogue Spk Mix Left 1 + */ +#define WM8903_DACL_MIXSPKL_VOL 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_MASK 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_SHIFT 3 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_WIDTH 1 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_MASK 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_SHIFT 2 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_WIDTH 1 /* DACR_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_MASK 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_MASK 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT 0 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH 1 /* BYPASSR_MIXSPKL_VOL */ + +/* + * R54 (0x36) - Analogue Spk Mix Right 0 + */ +#define WM8903_DACL_TO_MIXSPKR 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_MASK 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_SHIFT 3 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_WIDTH 1 /* DACL_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_MASK 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_SHIFT 2 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_WIDTH 1 /* DACR_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_MASK 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_MASK 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT 0 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH 1 /* BYPASSR_TO_MIXSPKR */ + +/* + * R55 (0x37) - Analogue Spk Mix Right 1 + */ +#define WM8903_DACL_MIXSPKR_VOL 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_MASK 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_SHIFT 3 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_WIDTH 1 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_MASK 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_SHIFT 2 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_WIDTH 1 /* DACR_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_MASK 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_MASK 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT 0 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH 1 /* BYPASSR_MIXSPKR_VOL */ + +/* + * R57 (0x39) - Analogue OUT1 Left + */ +#define WM8903_HPL_MUTE 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_MASK 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_SHIFT 8 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_WIDTH 1 /* HPL_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTLZC 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_SHIFT 6 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_WIDTH 1 /* HPOUTLZC */ +#define WM8903_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */ + +/* + * R58 (0x3A) - Analogue OUT1 Right + */ +#define WM8903_HPR_MUTE 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_MASK 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_SHIFT 8 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_WIDTH 1 /* HPR_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTRZC 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_SHIFT 6 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_WIDTH 1 /* HPOUTRZC */ +#define WM8903_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */ + +/* + * R59 (0x3B) - Analogue OUT2 Left + */ +#define WM8903_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTLZC 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */ +#define WM8903_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */ + +/* + * R60 (0x3C) - Analogue OUT2 Right + */ +#define WM8903_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTRZC 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */ +#define WM8903_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */ + +/* + * R62 (0x3E) - Analogue OUT3 Left + */ +#define WM8903_SPKL_MUTE 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_MASK 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_SHIFT 8 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKLZC 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_MASK 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_SHIFT 6 /* SPKLZC */ +#define WM8903_SPKLZC_WIDTH 1 /* SPKLZC */ +#define WM8903_SPKL_VOL_MASK 0x003F /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_WIDTH 6 /* SPKL_VOL - [5:0] */ + +/* + * R63 (0x3F) - Analogue OUT3 Right + */ +#define WM8903_SPKR_MUTE 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_MASK 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_SHIFT 8 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKRZC 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_MASK 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_SHIFT 6 /* SPKRZC */ +#define WM8903_SPKRZC_WIDTH 1 /* SPKRZC */ +#define WM8903_SPKR_VOL_MASK 0x003F /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_SHIFT 0 /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_WIDTH 6 /* SPKR_VOL - [5:0] */ + +/* + * R65 (0x41) - Analogue SPK Output Control 0 + */ +#define WM8903_SPK_DISCHARGE 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_MASK 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_SHIFT 1 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_WIDTH 1 /* SPK_DISCHARGE */ +#define WM8903_VROI 0x0001 /* VROI */ +#define WM8903_VROI_MASK 0x0001 /* VROI */ +#define WM8903_VROI_SHIFT 0 /* VROI */ +#define WM8903_VROI_WIDTH 1 /* VROI */ + +/* + * R67 (0x43) - DC Servo 0 + */ +#define WM8903_DCS_MASTER_ENA 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_MASK 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_SHIFT 4 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_WIDTH 1 /* DCS_MASTER_ENA */ +#define WM8903_DCS_ENA_MASK 0x000F /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_SHIFT 0 /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_WIDTH 4 /* DCS_ENA - [3:0] */ + +/* + * R69 (0x45) - DC Servo 2 + */ +#define WM8903_DCS_MODE_MASK 0x0003 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_SHIFT 0 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_WIDTH 2 /* DCS_MODE - [1:0] */ + +/* + * R90 (0x5A) - Analogue HP 0 + */ +#define WM8903_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8903_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8903_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8903_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8903_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8903_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R94 (0x5E) - Analogue Lineout 0 + */ +#define WM8903_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */ + +/* + * R98 (0x62) - Charge Pump 0 + */ +#define WM8903_CP_ENA 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8903_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R104 (0x68) - Class W 0 + */ +#define WM8903_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_V 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_SHIFT 0 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_WIDTH 1 /* CP_DYN_V */ + +/* + * R108 (0x6C) - Write Sequencer 0 + */ +#define WM8903_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8903_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R109 (0x6D) - Write Sequencer 1 + */ +#define WM8903_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R110 (0x6E) - Write Sequencer 2 + */ +#define WM8903_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8903_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R111 (0x6F) - Write Sequencer 3 + */ +#define WM8903_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8903_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8903_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8903_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R112 (0x70) - Write Sequencer 4 + */ +#define WM8903_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R114 (0x72) - Control Interface + */ +#define WM8903_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ + +/* + * R116 (0x74) - GPIO Control 1 + */ +#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */ +#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */ +#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */ +#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */ +#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM8903_GP1_PD 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */ +#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM8903_GP1_PU 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */ +#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */ +#define WM8903_GP1_DB 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */ +#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */ + +/* + * R117 (0x75) - GPIO Control 2 + */ +#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */ +#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */ +#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */ +#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */ +#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8903_GP2_PD 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */ +#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM8903_GP2_PU 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */ +#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */ +#define WM8903_GP2_DB 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */ +#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */ + +/* + * R118 (0x76) - GPIO Control 3 + */ +#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */ +#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */ +#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */ +#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */ +#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8903_GP3_PD 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */ +#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM8903_GP3_PU 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */ +#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */ +#define WM8903_GP3_DB 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */ +#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */ + +/* + * R119 (0x77) - GPIO Control 4 + */ +#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */ +#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */ +#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */ +#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */ +#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM8903_GP4_PD 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */ +#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM8903_GP4_PU 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */ +#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */ +#define WM8903_GP4_DB 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */ +#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */ + +/* + * R120 (0x78) - GPIO Control 5 + */ +#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */ +#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */ +#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */ +#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */ +#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8903_GP5_PD 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */ +#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8903_GP5_PU 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */ +#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */ +#define WM8903_GP5_DB 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */ +#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ + +/* + * R121 (0x79) - Interrupt Status 1 + */ +#define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_MASK 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_SHIFT 15 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_WIDTH 1 /* MICSHRT_EINT */ +#define WM8903_MICDET_EINT 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_MASK 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_SHIFT 14 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_WIDTH 1 /* MICDET_EINT */ +#define WM8903_WSEQ_BUSY_EINT 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_MASK 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_SHIFT 13 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM8903_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM8903_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM8903_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM8903_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM8903_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM8903_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM8903_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM8903_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM8903_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM8903_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R122 (0x7A) - Interrupt Status 1 Mask + */ +#define WM8903_IM_MICSHRT_EINT 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_MASK 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_SHIFT 15 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_WIDTH 1 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICDET_EINT 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_MASK 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_SHIFT 14 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_WIDTH 1 /* IM_MICDET_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_MASK 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT 13 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM8903_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM8903_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM8903_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R123 (0x7B) - Interrupt Polarity 1 + */ +#define WM8903_MICSHRT_INV 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_MASK 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_SHIFT 15 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_WIDTH 1 /* MICSHRT_INV */ +#define WM8903_MICDET_INV 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_MASK 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_SHIFT 14 /* MICDET_INV */ +#define WM8903_MICDET_INV_WIDTH 1 /* MICDET_INV */ + +/* + * R126 (0x7E) - Interrupt Control + */ +#define WM8903_IRQ_POL 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_MASK 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_SHIFT 0 /* IRQ_POL */ +#define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */ + +/* + * R129 (0x81) - Control Interface Test 1 + */ +#define WM8903_USER_KEY 0x0002 /* USER_KEY */ +#define WM8903_USER_KEY_MASK 0x0002 /* USER_KEY */ +#define WM8903_USER_KEY_SHIFT 1 /* USER_KEY */ +#define WM8903_USER_KEY_WIDTH 1 /* USER_KEY */ +#define WM8903_TEST_KEY 0x0001 /* TEST_KEY */ +#define WM8903_TEST_KEY_MASK 0x0001 /* TEST_KEY */ +#define WM8903_TEST_KEY_SHIFT 0 /* TEST_KEY */ +#define WM8903_TEST_KEY_WIDTH 1 /* TEST_KEY */ + +/* + * R149 (0x95) - Charge Pump Test 1 + */ +#define WM8903_CP_SW_KELVIN_MODE_MASK 0x0006 /* CP_SW_KELVIN_MODE - [2:1] */ +#define WM8903_CP_SW_KELVIN_MODE_SHIFT 1 /* CP_SW_KELVIN_MODE - [2:1] */ +#define WM8903_CP_SW_KELVIN_MODE_WIDTH 2 /* CP_SW_KELVIN_MODE - [2:1] */ + +/* + * R164 (0xA4) - Clock Rate Test 4 + */ +#define WM8903_ADC_DIG_MIC 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_MASK 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_SHIFT 9 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_WIDTH 1 /* ADC_DIG_MIC */ + +/* + * R172 (0xAC) - Analogue Output Bias 0 + */ +#define WM8903_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */ + +#endif diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c new file mode 100644 index 000000000000..f41a578ddd4f --- /dev/null +++ b/sound/soc/codecs/wm8971.c @@ -0,0 +1,941 @@ +/* + * wm8971.c -- WM8971 ALSA SoC Audio driver + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly <kiraly@lab126.com> + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/initval.h> + +#include "wm8971.h" + +#define WM8971_VERSION "0.9" + +#define WM8971_REG_COUNT 43 + +static struct workqueue_struct *wm8971_workq = NULL; + +/* codec private data */ +struct wm8971_priv { + unsigned int sysclk; +}; + +/* + * wm8971 register cache + * We can't read the WM8971 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8971_reg[] = { + 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + return cache[reg]; + + return -1; +} + +static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + cache[reg] = value; +} + +static int wm8971_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8971_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0) + +/* WM8971 Controls */ +static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" }; +static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz", + "200Hz @ 48kHz" }; +static const char *wm8971_treble[] = { "8kHz", "4kHz" }; +static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" }; +static const char *wm8971_ng_type[] = { "Constant PGA Gain", + "Mute ADC Output" }; +static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" }; +static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA", + "Differential"}; +static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA", + "Differential"}; +static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"}; +static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"}; +static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; + +static const struct soc_enum wm8971_enum[] = { + SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */ + SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter), + SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble), + SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func), + SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */ + SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase), + SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */ + SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux), + SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel), + SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel), + SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */ + SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux), +}; + +static const struct snd_kcontrol_new wm8971_snd_controls[] = { + SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0), + SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL, + 6, 1, 0), + SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1), + + SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V, + WM8971_ROUT1V, 7, 1, 0), + SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V, + WM8971_ROUT2V, 7, 1, 0), + SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0), + + SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0), + + SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1, + WM8971_LOUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1, + WM8971_ROUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1, + WM8971_MOUTM2, 4, 7, 1), + + SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V, + WM8971_ROUT1V, 0, 127, 0), + SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V, + WM8971_ROUT2V, 0, 127, 0), + + SOC_ENUM("Bass Boost", wm8971_enum[0]), + SOC_ENUM("Bass Filter", wm8971_enum[1]), + SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1), + + SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0), + SOC_ENUM("Treble Cut-off", wm8971_enum[2]), + + SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1), + + SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0), + + SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0), + SOC_ENUM("ALC Capture Function", wm8971_enum[3]), + SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0), + SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0), + SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0), + SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0), + SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0), + SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]), + SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0), + + SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0), + SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0), + + SOC_ENUM("Playback De-emphasis", wm8971_enum[5]), + SOC_ENUM("Playback Function", wm8971_enum[6]), + SOC_ENUM("Playback Phase", wm8971_enum[7]), + + SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0), +}; + +/* add non-DAPM controls */ +static int wm8971_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8971_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8971_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8971_left_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8971_right_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8971_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8971_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[11]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8971_monomux_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[13]); + +static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_left_mixer_controls[0], + ARRAY_SIZE(wm8971_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_right_mixer_controls[0], + ARRAY_SIZE(wm8971_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0, + &wm8971_mono_mixer_controls[0], + ARRAY_SIZE(wm8971_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0, + &wm8971_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0, + &wm8971_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_right_line_controls), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("MIC"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line", "LINPUT1"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line", "RINPUT1"}, + {"Right Line Mux", "Mic", "MIC"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line", "LINPUT1"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line", "RINPUT1"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line", "LINPUT1"}, + {"Differential Mux", "Line", "RINPUT1"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, +}; + +static int wm8971_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8971_dapm_widgets, + ARRAY_SIZE(wm8971_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int wm8971_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8971_priv *wm8971 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8971->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + wm8971_write(codec, WM8971_IFACE, iface); + return 0; +} + +static int wm8971_pcm_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 wm8971_priv *wm8971 = codec->private_data; + u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3; + u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0; + int coeff = get_coeff(wm8971->sysclk, params_rate(params)); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + wm8971_write(codec, WM8971_IFACE, iface); + if (coeff >= 0) + wm8971_write(codec, WM8971_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8971_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7; + + if (mute) + wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8); + else + wm8971_write(codec, WM8971_ADCDAC, mute_reg); + return 0; +} + +static int wm8971_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* mute dac and set vmid to 500k, enable VREF */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140); + break; + case SND_SOC_BIAS_OFF: + wm8971_write(codec, WM8971_PWR1, 0x0001); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8971_dai = { + .name = "WM8971", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .ops = { + .hw_params = wm8971_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8971_mute, + .set_fmt = wm8971_set_dai_fmt, + .set_sysclk = wm8971_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8971_dai); + +static void wm8971_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + wm8971_set_bias_level(codec, codec->bias_level); +} + +static int wm8971_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8971_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + u16 reg; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) { + if (i + 1 == WM8971_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* charge wm8971 caps */ + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + codec->bias_level = SND_SOC_BIAS_ON; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + } + + return 0; +} + +static int wm8971_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8971"; + codec->owner = THIS_MODULE; + codec->read = wm8971_read_reg_cache; + codec->write = wm8971_write; + codec->set_bias_level = wm8971_set_bias_level; + codec->dai = &wm8971_dai; + codec->reg_cache_size = ARRAY_SIZE(wm8971_reg); + codec->num_dai = 1; + codec->reg_cache = kmemdup(wm8971_reg, sizeof(wm8971_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8971_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps - set vmid to 5k for quick power up */ + reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + codec->bias_level = SND_SOC_BIAS_STANDBY; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + + /* set the update bits */ + reg = wm8971_read_reg_cache(codec, WM8971_LDAC); + wm8971_write(codec, WM8971_LDAC, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RDAC); + wm8971_write(codec, WM8971_RDAC, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V); + wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V); + wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V); + wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V); + wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LINVOL); + wm8971_write(codec, WM8971_LINVOL, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RINVOL); + wm8971_write(codec, WM8971_RINVOL, reg | 0x0100); + + wm8971_add_controls(codec); + wm8971_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8971_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +static int wm8971_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8971_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = wm8971_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8971\n"); + + return ret; +} + +static int wm8971_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8971_i2c_id[] = { + { "wm8971", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id); + +static struct i2c_driver wm8971_i2c_driver = { + .driver = { + .name = "WM8971 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8971_i2c_probe, + .remove = wm8971_i2c_remove, + .id_table = wm8971_i2c_id, +}; + +static int wm8971_add_i2c_device(struct platform_device *pdev, + const struct wm8971_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8971_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8971", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8971_i2c_driver); + return -ENODEV; +} + +#endif + +static int wm8971_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8971_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8971_priv *wm8971; + int ret = 0; + + pr_info("WM8971 Audio Codec %s", WM8971_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL); + if (wm8971 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8971; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8971_socdev = socdev; + + INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work); + wm8971_workq = create_workqueue("wm8971"); + if (wm8971_workq == NULL) { + kfree(codec->private_data); + kfree(codec); + return -ENOMEM; + } + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8971_add_i2c_device(pdev, setup); + } +#endif + /* Add other interfaces here */ + + if (ret != 0) { + destroy_workqueue(wm8971_workq); + kfree(codec->private_data); + kfree(codec); + } + + return ret; +} + +/* power down chip */ +static int wm8971_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); + if (wm8971_workq) + destroy_workqueue(wm8971_workq); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8971_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8971 = { + .probe = wm8971_probe, + .remove = wm8971_remove, + .suspend = wm8971_suspend, + .resume = wm8971_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8971); + +MODULE_DESCRIPTION("ASoC WM8971 driver"); +MODULE_AUTHOR("Lab126"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8971.h b/sound/soc/codecs/wm8971.h new file mode 100644 index 000000000000..ef4f08f9f344 --- /dev/null +++ b/sound/soc/codecs/wm8971.h @@ -0,0 +1,64 @@ +/* + * wm8971.h -- audio driver for WM8971 + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly <kiraly@lab126.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8971_H +#define _WM8971_H + +#define WM8971_LINVOL 0x00 +#define WM8971_RINVOL 0x01 +#define WM8971_LOUT1V 0x02 +#define WM8971_ROUT1V 0x03 +#define WM8971_ADCDAC 0x05 +#define WM8971_IFACE 0x07 +#define WM8971_SRATE 0x08 +#define WM8971_LDAC 0x0a +#define WM8971_RDAC 0x0b +#define WM8971_BASS 0x0c +#define WM8971_TREBLE 0x0d +#define WM8971_RESET 0x0f +#define WM8971_ALC1 0x11 +#define WM8971_ALC2 0x12 +#define WM8971_ALC3 0x13 +#define WM8971_NGATE 0x14 +#define WM8971_LADC 0x15 +#define WM8971_RADC 0x16 +#define WM8971_ADCTL1 0x17 +#define WM8971_ADCTL2 0x18 +#define WM8971_PWR1 0x19 +#define WM8971_PWR2 0x1a +#define WM8971_ADCTL3 0x1b +#define WM8971_ADCIN 0x1f +#define WM8971_LADCIN 0x20 +#define WM8971_RADCIN 0x21 +#define WM8971_LOUTM1 0x22 +#define WM8971_LOUTM2 0x23 +#define WM8971_ROUTM1 0x24 +#define WM8971_ROUTM2 0x25 +#define WM8971_MOUTM1 0x26 +#define WM8971_MOUTM2 0x27 +#define WM8971_LOUT2V 0x28 +#define WM8971_ROUT2V 0x29 +#define WM8971_MOUTV 0x2A + +#define WM8971_SYSCLK 0 + +struct wm8971_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8971_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8971; + +#endif diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c new file mode 100644 index 000000000000..572d22b0880b --- /dev/null +++ b/sound/soc/codecs/wm8990.c @@ -0,0 +1,1635 @@ +/* + * wm8990.c -- WM8990 ALSA Soc Audio driver + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * lg@opensource.wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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/initval.h> +#include <sound/tlv.h> +#include <asm/div64.h> + +#include "wm8990.h" + +#define WM8990_VERSION "0.2" + +/* codec private data */ +struct wm8990_priv { + unsigned int sysclk; + unsigned int pcmclk; +}; + +/* + * wm8990 register cache. Note that register 0 is not included in the + * cache. + */ +static const u16 wm8990_reg[] = { + 0x8990, /* R0 - Reset */ + 0x0000, /* R1 - Power Management (1) */ + 0x6000, /* R2 - Power Management (2) */ + 0x0000, /* R3 - Power Management (3) */ + 0x4050, /* R4 - Audio Interface (1) */ + 0x4000, /* R5 - Audio Interface (2) */ + 0x01C8, /* R6 - Clocking (1) */ + 0x0000, /* R7 - Clocking (2) */ + 0x0040, /* R8 - Audio Interface (3) */ + 0x0040, /* R9 - Audio Interface (4) */ + 0x0004, /* R10 - DAC CTRL */ + 0x00C0, /* R11 - Left DAC Digital Volume */ + 0x00C0, /* R12 - Right DAC Digital Volume */ + 0x0000, /* R13 - Digital Side Tone */ + 0x0100, /* R14 - ADC CTRL */ + 0x00C0, /* R15 - Left ADC Digital Volume */ + 0x00C0, /* R16 - Right ADC Digital Volume */ + 0x0000, /* R17 */ + 0x0000, /* R18 - GPIO CTRL 1 */ + 0x1000, /* R19 - GPIO1 & GPIO2 */ + 0x1010, /* R20 - GPIO3 & GPIO4 */ + 0x1010, /* R21 - GPIO5 & GPIO6 */ + 0x8000, /* R22 - GPIOCTRL 2 */ + 0x0800, /* R23 - GPIO_POL */ + 0x008B, /* R24 - Left Line Input 1&2 Volume */ + 0x008B, /* R25 - Left Line Input 3&4 Volume */ + 0x008B, /* R26 - Right Line Input 1&2 Volume */ + 0x008B, /* R27 - Right Line Input 3&4 Volume */ + 0x0000, /* R28 - Left Output Volume */ + 0x0000, /* R29 - Right Output Volume */ + 0x0066, /* R30 - Line Outputs Volume */ + 0x0022, /* R31 - Out3/4 Volume */ + 0x0079, /* R32 - Left OPGA Volume */ + 0x0079, /* R33 - Right OPGA Volume */ + 0x0003, /* R34 - Speaker Volume */ + 0x0003, /* R35 - ClassD1 */ + 0x0000, /* R36 */ + 0x0100, /* R37 - ClassD3 */ + 0x0079, /* R38 - ClassD4 */ + 0x0000, /* R39 - Input Mixer1 */ + 0x0000, /* R40 - Input Mixer2 */ + 0x0000, /* R41 - Input Mixer3 */ + 0x0000, /* R42 - Input Mixer4 */ + 0x0000, /* R43 - Input Mixer5 */ + 0x0000, /* R44 - Input Mixer6 */ + 0x0000, /* R45 - Output Mixer1 */ + 0x0000, /* R46 - Output Mixer2 */ + 0x0000, /* R47 - Output Mixer3 */ + 0x0000, /* R48 - Output Mixer4 */ + 0x0000, /* R49 - Output Mixer5 */ + 0x0000, /* R50 - Output Mixer6 */ + 0x0180, /* R51 - Out3/4 Mixer */ + 0x0000, /* R52 - Line Mixer1 */ + 0x0000, /* R53 - Line Mixer2 */ + 0x0000, /* R54 - Speaker Mixer */ + 0x0000, /* R55 - Additional Control */ + 0x0000, /* R56 - AntiPOP1 */ + 0x0000, /* R57 - AntiPOP2 */ + 0x0000, /* R58 - MICBIAS */ + 0x0000, /* R59 */ + 0x0008, /* R60 - PLL1 */ + 0x0031, /* R61 - PLL2 */ + 0x0026, /* R62 - PLL3 */ +}; + +/* + * read wm8990 register cache + */ +static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1); + return cache[reg]; +} + +/* + * write wm8990 register cache + */ +static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1); + + /* Reset register is uncached */ + if (reg == 0) + return; + + cache[reg] = value; +} + +/* + * write to the wm8990 register space + */ +static int wm8990_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + data[0] = reg & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[2] = value & 0xFF; + + wm8990_write_reg_cache(codec, reg, value); + + if (codec->hw_write(codec->control_data, data, 3) == 2) + return 0; + else + return -EIO; +} + +#define wm8990_reset(c) wm8990_write(c, WM8990_RESET, 0) + +static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); + +static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000); + +static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, 0, -2100); + +static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600); + +static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0); + +static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0); + +static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763); + +static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0); + +static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = wm8990_read_reg_cache(codec, reg); + return wm8990_write(codec, reg, val | 0x0100); +} + +#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ + tlv_array) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +static const char *wm8990_digital_sidetone[] = + {"None", "Left ADC", "Right ADC", "Reserved"}; + +static const struct soc_enum wm8990_left_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACL_SHIFT, + WM8990_ADC_TO_DACL_MASK, + wm8990_digital_sidetone); + +static const struct soc_enum wm8990_right_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACR_SHIFT, + WM8990_ADC_TO_DACR_MASK, + wm8990_digital_sidetone); + +static const char *wm8990_adcmode[] = + {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static const struct soc_enum wm8990_right_adcmode_enum = +SOC_ENUM_SINGLE(WM8990_ADC_CTRL, + WM8990_ADC_HPF_CUT_SHIFT, + WM8990_ADC_HPF_CUT_MASK, + wm8990_adcmode); + +static const struct snd_kcontrol_new wm8990_snd_controls[] = { +/* INMIXL */ +SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), +SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), +/* INMIXR */ +SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), + +/* LOMIX */ +SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), + +/* ROMIX */ +SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv), + +/* LOUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME, + WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0), + +/* ROUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME, + WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0), + +/* LOPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAZC_BIT, 1, 0), + +/* ROPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAZC_BIT, 1, 0), + +SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LONMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOPMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOATTN_BIT, 1, 0), +SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_RONMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROPMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROATTN_BIT, 1, 0), + +SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3MUTE_BIT, 1, 0), +SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3ATTN_BIT, 1, 0), + +SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4MUTE_BIT, 1, 0), +SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4ATTN_BIT, 1, 0), + +SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1, + WM8990_CDMODE_BIT, 1, 0), + +SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME, + WM8990_SPKATTN_SHIFT, WM8990_SPKATTN_MASK, 0), +SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3, + WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0), +SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3, + WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0), +SOC_SINGLE_TLV("Speaker Volume", WM8990_CLASSD4, + WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("Speaker ZC Switch", WM8990_CLASSD4, + WM8990_SPKZC_SHIFT, WM8990_SPKZC_MASK, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8990_LEFT_DAC_DIGITAL_VOLUME, + WM8990_DACL_VOL_SHIFT, + WM8990_DACL_VOL_MASK, + 0, + out_dac_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8990_RIGHT_DAC_DIGITAL_VOLUME, + WM8990_DACR_VOL_SHIFT, + WM8990_DACR_VOL_MASK, + 0, + out_dac_tlv), + +SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum), +SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum), + +SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0, + out_sidetone_tlv), +SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + +SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL, + WM8990_ADC_HPF_ENA_BIT, 1, 0), + +SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8990_LEFT_ADC_DIGITAL_VOLUME, + WM8990_ADCL_VOL_SHIFT, + WM8990_ADCL_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8990_RIGHT_ADC_DIGITAL_VOLUME, + WM8990_ADCR_VOL_SHIFT, + WM8990_ADCR_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LIN12VOL_SHIFT, + WM8990_LIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12ZC_BIT, 1, 0), + +SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LIN34VOL_SHIFT, + WM8990_LIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34ZC_BIT, 1, 0), + +SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RIN12VOL_SHIFT, + WM8990_RIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12ZC_BIT, 1, 0), + +SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RIN34VOL_SHIFT, + WM8990_RIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34ZC_BIT, 1, 0), + +SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34MUTE_BIT, 1, 0), + +}; + +/* add non dapm controls */ +static int wm8990_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8990_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8990_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * _DAPM_ Controls + */ + +static int inmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 reg, fakepower; + + reg = wm8990_read_reg_cache(w->codec, WM8990_POWER_MANAGEMENT_2); + fakepower = wm8990_read_reg_cache(w->codec, WM8990_INTDRIVBITS); + + if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) | + (1 << WM8990_AINLMUX_PWR_BIT))) { + reg |= WM8990_AINL_ENA; + } else { + reg &= ~WM8990_AINL_ENA; + } + + if (fakepower & ((1 << WM8990_INMIXR_PWR_BIT) | + (1 << WM8990_AINRMUX_PWR_BIT))) { + reg |= WM8990_AINR_ENA; + } else { + reg &= ~WM8990_AINL_ENA; + } + wm8990_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg); + + return 0; +} + +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u32 reg_shift = kcontrol->private_value & 0xfff; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) : + reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER1); + if (reg & WM8990_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER2); + if (reg & WM8990_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + if (reg & WM8990_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + if (reg & WM8990_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const unsigned int in_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600), +}; + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = { +SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = { +SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = { +SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = { +SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3, + WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4, + WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8990_ainlmux[] = + {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static const struct soc_enum wm8990_ainlmux_enum = +SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT, + ARRAY_SIZE(wm8990_ainlmux), wm8990_ainlmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8990_ainrmux[] = + {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static const struct soc_enum wm8990_ainrmux_enum = +SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT, + ARRAY_SIZE(wm8990_ainrmux), wm8990_ainrmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = { +SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT, + WM8990_LR4BVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT, + WM8990_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = { +SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LR12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LL12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1, + WM8990_LDLO_BIT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = { +SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RL12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RR12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2, + WM8990_RDRO_BIT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = { +SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LROPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1, + WM8990_LOPLON_BIT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = { +SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LR12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LL12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALOP_BIT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = { +SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RLOPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2, + WM8990_ROPRON_BIT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = { +SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RR12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGAROP_BIT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = { +SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_LI4O3_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_LPGAO3_BIT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = { +SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_RPGAO4_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_RI4O4_BIT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = { +SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LI2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LB2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_LOPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_LDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_RDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_ROPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RI2SPK_BIT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = { +/* Input Side */ +/* Input Lines */ +SND_SOC_DAPM_INPUT("LIN1"), +SND_SOC_DAPM_INPUT("LIN2"), +SND_SOC_DAPM_INPUT("LIN3"), +SND_SOC_DAPM_INPUT("LIN4/RXN"), +SND_SOC_DAPM_INPUT("RIN3"), +SND_SOC_DAPM_INPUT("RIN4/RXP"), +SND_SOC_DAPM_INPUT("RIN1"), +SND_SOC_DAPM_INPUT("RIN2"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +/* DACs */ +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCL_ENA_BIT, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCR_ENA_BIT, 0), + +/* Input PGAs */ +SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT, + 0, &wm8990_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)), +SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT, + 0, &wm8990_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)), +SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT, + 0, &wm8990_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)), +SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT, + 0, &wm8990_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)), + +/* INMIXL */ +SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0, + &wm8990_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixl_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINLMUX */ +SND_SOC_DAPM_MUX_E("AILNMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0, + &wm8990_dapm_ainlmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* INMIXR */ +SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0, + &wm8990_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixr_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINRMUX */ +SND_SOC_DAPM_MUX_E("AIRNMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0, + &wm8990_dapm_ainrmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* Output Side */ +/* DACs */ +SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACL_ENA_BIT, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACR_ENA_BIT, 0), + +/* LOMIX */ +SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT, + 0, &wm8990_dapm_lomix_controls[0], + ARRAY_SIZE(wm8990_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LONMIX */ +SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0, + &wm8990_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lonmix_controls)), + +/* LOPMIX */ +SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0, + &wm8990_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lopmix_controls)), + +/* OUT3MIX */ +SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0, + &wm8990_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out3mix_controls)), + +/* SPKMIX */ +SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0, + &wm8990_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + +/* OUT4MIX */ +SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0, + &wm8990_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out4mix_controls)), + +/* ROPMIX */ +SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0, + &wm8990_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ropmix_controls)), + +/* RONMIX */ +SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0, + &wm8990_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ronmix_controls)), + +/* ROMIX */ +SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT, + 0, &wm8990_dapm_romix_controls[0], + ARRAY_SIZE(wm8990_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LOUT PGA */ +SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0, + NULL, 0), + +/* ROUT PGA */ +SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0, + NULL, 0), + +/* LOPGA */ +SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0, + NULL, 0), + +/* ROPGA */ +SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0, + NULL, 0), + +/* MICBIAS */ +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8990_POWER_MANAGEMENT_1, + WM8990_MICBIAS_ENA_BIT, 0), + +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AILNMUX */ + {"AILNMUX", "INMIXL Mix", "INMIXL"}, + {"AILNMUX", "DIFFINL Mix", "LIN12PGA"}, + {"AILNMUX", "DIFFINL Mix", "LIN34PGA"}, + {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Left ADC", NULL, "AILNMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AIRNMUX */ + {"AIRNMUX", "INMIXR Mix", "INMIXR"}, + {"AIRNMUX", "DIFFINR Mix", "RIN12PGA"}, + {"AIRNMUX", "DIFFINR Mix", "RIN34PGA"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXN"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Right ADC", NULL, "AIRNMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXP"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +static int wm8990_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8990_dapm_widgets, + ARRAY_SIZE(wm8990_dapm_widgets)); + + /* set up the WM8990 audio map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 div2; + u32 n; + u32 k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 16) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8990 N value outwith recommended range! N = %d\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + struct snd_soc_codec *codec = codec_dai->codec; + struct _pll_div pll_div; + + if (freq_in && freq_out) { + pll_factors(&pll_div, freq_out * 4, freq_in); + + /* Turn on PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg |= WM8990_PLL_ENA; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + + /* sysclk comes from PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2); + wm8990_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC); + + /* set up N , fractional mode and pre-divisor if neccessary */ + wm8990_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM | + (pll_div.div2?WM8990_PRESCALE:0)); + wm8990_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8)); + wm8990_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF)); + } else { + /* Turn on PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg &= ~WM8990_PLL_ENA; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + } + return 0; +} + +/* + * Clock after PLL and dividers + */ +static int wm8990_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8990_priv *wm8990 = codec->private_data; + + wm8990->sysclk = freq; + return 0; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 audio1, audio3; + + audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); + audio3 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8990_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8990_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8990_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8990_AIF_TMF_I2S; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8990_AIF_TMF_RIGHTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8990_AIF_TMF_LEFTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8990_AIF_TMF_DSP; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + wm8990_write(codec, WM8990_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8990_MCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_MCLK_DIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_DACCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_DAC_CLKDIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_ADCCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_ADC_CLKDIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_BCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_1) & + ~WM8990_BCLK_DIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8990_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; + u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); + + audio1 &= ~WM8990_AIF_WL_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + audio1 |= WM8990_AIF_WL_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + audio1 |= WM8990_AIF_WL_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio1 |= WM8990_AIF_WL_32BITS; + break; + } + + wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8990_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 val; + + val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE; + + if (mute) + wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + else + wm8990_write(codec, WM8990_DAC_CTRL, val); + + return 0; +} + +static int wm8990_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Enable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_VMIDTOG); + + /* Delay to allow output caps to discharge */ + msleep(msecs_to_jiffies(300)); + + /* Disable VMIDTOG */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL); + + /* disable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, 0); + + /* Enable outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00); + + msleep(msecs_to_jiffies(50)); + + /* Enable VMID at 2x50k */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02); + + msleep(msecs_to_jiffies(100)); + + /* Enable VREF */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + msleep(msecs_to_jiffies(600)); + + /* Enable BUFIOEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* Disable outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN); + } else { + /* ON -> standby */ + + } + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_POBCTRL | WM8990_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* mute DAC */ + val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL); + wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + + /* Enable any disabled outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + /* Disable VMID */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01); + + msleep(msecs_to_jiffies(300)); + + /* Enable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Disable VREF */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, 0x0); + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* + * The WM8990 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +struct snd_soc_dai wm8990_dai = { +/* ADC/DAC on primary */ + .name = "WM8990 ADC/DAC Primary", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .ops = { + .hw_params = wm8990_hw_params,}, + .dai_ops = { + .digital_mute = wm8990_mute, + .set_fmt = wm8990_set_dai_fmt, + .set_clkdiv = wm8990_set_dai_clkdiv, + .set_pll = wm8990_set_dai_pll, + .set_sysclk = wm8990_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8990_dai); + +static int wm8990_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + /* we only need to suspend if we are a valid card */ + if (!codec->card) + return 0; + + wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8990_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* we only need to resume if we are a valid card */ + if (!codec->card) + return 0; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8990_reg); i++) { + if (i + 1 == WM8990_RESET) + continue; + data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +/* + * initialise the WM8990 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8990_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + u16 reg; + int ret = 0; + + codec->name = "WM8990"; + codec->owner = THIS_MODULE; + codec->read = wm8990_read_reg_cache; + codec->write = wm8990_write; + codec->set_bias_level = wm8990_set_bias_level; + codec->dai = &wm8990_dai; + codec->num_dai = 2; + codec->reg_cache_size = ARRAY_SIZE(wm8990_reg); + codec->reg_cache = kmemdup(wm8990_reg, sizeof(wm8990_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8990_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8990: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + reg = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_4); + wm8990_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1); + + reg = wm8990_read_reg_cache(codec, WM8990_GPIO1_GPIO2) & + ~WM8990_GPIO1_SEL_MASK; + wm8990_write(codec, WM8990_GPIO1_GPIO2, reg | 1); + + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA); + + wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + wm8990_add_controls(codec); + wm8990_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8990: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8990_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM891 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x34 + * high = 0x36 + */ + +static int wm8990_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8990_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8990_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8990\n"); + + return ret; +} + +static int wm8990_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8990_i2c_id[] = { + { "wm8990", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id); + +static struct i2c_driver wm8990_i2c_driver = { + .driver = { + .name = "WM8990 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8990_i2c_probe, + .remove = wm8990_i2c_remove, + .id_table = wm8990_i2c_id, +}; + +static int wm8990_add_i2c_device(struct platform_device *pdev, + const struct wm8990_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8990_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8990", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8990_i2c_driver); + return -ENODEV; +} +#endif + +static int wm8990_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8990_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8990_priv *wm8990; + int ret; + + pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8990 = kzalloc(sizeof(struct wm8990_priv), GFP_KERNEL); + if (wm8990 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8990; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8990_socdev = socdev; + + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8990_add_i2c_device(pdev, setup); + } +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +/* power down chip */ +static int wm8990_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8990_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8990 = { + .probe = wm8990_probe, + .remove = wm8990_remove, + .suspend = wm8990_suspend, + .resume = wm8990_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8990); + +MODULE_DESCRIPTION("ASoC WM8990 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h new file mode 100644 index 000000000000..0e192f3b0788 --- /dev/null +++ b/sound/soc/codecs/wm8990.h @@ -0,0 +1,843 @@ +/* + * wm8990.h -- audio driver for WM8990 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __WM8990REGISTERDEFS_H__ +#define __WM8990REGISTERDEFS_H__ + +/* + * Register values. + */ +#define WM8990_RESET 0x00 +#define WM8990_POWER_MANAGEMENT_1 0x01 +#define WM8990_POWER_MANAGEMENT_2 0x02 +#define WM8990_POWER_MANAGEMENT_3 0x03 +#define WM8990_AUDIO_INTERFACE_1 0x04 +#define WM8990_AUDIO_INTERFACE_2 0x05 +#define WM8990_CLOCKING_1 0x06 +#define WM8990_CLOCKING_2 0x07 +#define WM8990_AUDIO_INTERFACE_3 0x08 +#define WM8990_AUDIO_INTERFACE_4 0x09 +#define WM8990_DAC_CTRL 0x0A +#define WM8990_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8990_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8990_DIGITAL_SIDE_TONE 0x0D +#define WM8990_ADC_CTRL 0x0E +#define WM8990_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8990_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8990_GPIO_CTRL_1 0x12 +#define WM8990_GPIO1_GPIO2 0x13 +#define WM8990_GPIO3_GPIO4 0x14 +#define WM8990_GPIO5_GPIO6 0x15 +#define WM8990_GPIOCTRL_2 0x16 +#define WM8990_GPIO_POL 0x17 +#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8990_LEFT_OUTPUT_VOLUME 0x1C +#define WM8990_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8990_LINE_OUTPUTS_VOLUME 0x1E +#define WM8990_OUT3_4_VOLUME 0x1F +#define WM8990_LEFT_OPGA_VOLUME 0x20 +#define WM8990_RIGHT_OPGA_VOLUME 0x21 +#define WM8990_SPEAKER_VOLUME 0x22 +#define WM8990_CLASSD1 0x23 +#define WM8990_CLASSD3 0x25 +#define WM8990_CLASSD4 0x26 +#define WM8990_INPUT_MIXER1 0x27 +#define WM8990_INPUT_MIXER2 0x28 +#define WM8990_INPUT_MIXER3 0x29 +#define WM8990_INPUT_MIXER4 0x2A +#define WM8990_INPUT_MIXER5 0x2B +#define WM8990_INPUT_MIXER6 0x2C +#define WM8990_OUTPUT_MIXER1 0x2D +#define WM8990_OUTPUT_MIXER2 0x2E +#define WM8990_OUTPUT_MIXER3 0x2F +#define WM8990_OUTPUT_MIXER4 0x30 +#define WM8990_OUTPUT_MIXER5 0x31 +#define WM8990_OUTPUT_MIXER6 0x32 +#define WM8990_OUT3_4_MIXER 0x33 +#define WM8990_LINE_MIXER1 0x34 +#define WM8990_LINE_MIXER2 0x35 +#define WM8990_SPEAKER_MIXER 0x36 +#define WM8990_ADDITIONAL_CONTROL 0x37 +#define WM8990_ANTIPOP1 0x38 +#define WM8990_ANTIPOP2 0x39 +#define WM8990_MICBIAS 0x3A +#define WM8990_PLL1 0x3C +#define WM8990_PLL2 0x3D +#define WM8990_PLL3 0x3E +#define WM8990_INTDRIVBITS 0x3F + +#define WM8990_REGISTER_COUNT 60 +#define WM8990_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Reset + */ +#define WM8990_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8990_SPK_ENA 0x1000 /* SPK_ENA */ +#define WM8990_SPK_ENA_BIT 12 +#define WM8990_OUT3_ENA 0x0800 /* OUT3_ENA */ +#define WM8990_OUT3_ENA_BIT 11 +#define WM8990_OUT4_ENA 0x0400 /* OUT4_ENA */ +#define WM8990_OUT4_ENA_BIT 10 +#define WM8990_LOUT_ENA 0x0200 /* LOUT_ENA */ +#define WM8990_LOUT_ENA_BIT 9 +#define WM8990_ROUT_ENA 0x0100 /* ROUT_ENA */ +#define WM8990_ROUT_ENA_BIT 8 +#define WM8990_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */ +#define WM8990_MICBIAS_ENA_BIT 4 +#define WM8990_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */ +#define WM8990_VREF_ENA 0x0001 /* VREF_ENA */ +#define WM8990_VREF_ENA_BIT 0 + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8990_PLL_ENA 0x8000 /* PLL_ENA */ +#define WM8990_PLL_ENA_BIT 15 +#define WM8990_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8990_TSHUT_ENA_BIT 14 +#define WM8990_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8990_TSHUT_OPDIS_BIT 13 +#define WM8990_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8990_OPCLK_ENA_BIT 11 +#define WM8990_AINL_ENA 0x0200 /* AINL_ENA */ +#define WM8990_AINL_ENA_BIT 9 +#define WM8990_AINR_ENA 0x0100 /* AINR_ENA */ +#define WM8990_AINR_ENA_BIT 8 +#define WM8990_LIN34_ENA 0x0080 /* LIN34_ENA */ +#define WM8990_LIN34_ENA_BIT 7 +#define WM8990_LIN12_ENA 0x0040 /* LIN12_ENA */ +#define WM8990_LIN12_ENA_BIT 6 +#define WM8990_RIN34_ENA 0x0020 /* RIN34_ENA */ +#define WM8990_RIN34_ENA_BIT 5 +#define WM8990_RIN12_ENA 0x0010 /* RIN12_ENA */ +#define WM8990_RIN12_ENA_BIT 4 +#define WM8990_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8990_ADCL_ENA_BIT 1 +#define WM8990_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8990_ADCR_ENA_BIT 0 + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8990_LON_ENA 0x2000 /* LON_ENA */ +#define WM8990_LON_ENA_BIT 13 +#define WM8990_LOP_ENA 0x1000 /* LOP_ENA */ +#define WM8990_LOP_ENA_BIT 12 +#define WM8990_RON_ENA 0x0800 /* RON_ENA */ +#define WM8990_RON_ENA_BIT 11 +#define WM8990_ROP_ENA 0x0400 /* ROP_ENA */ +#define WM8990_ROP_ENA_BIT 10 +#define WM8990_LOPGA_ENA 0x0080 /* LOPGA_ENA */ +#define WM8990_LOPGA_ENA_BIT 7 +#define WM8990_ROPGA_ENA 0x0040 /* ROPGA_ENA */ +#define WM8990_ROPGA_ENA_BIT 6 +#define WM8990_LOMIX_ENA 0x0020 /* LOMIX_ENA */ +#define WM8990_LOMIX_ENA_BIT 5 +#define WM8990_ROMIX_ENA 0x0010 /* ROMIX_ENA */ +#define WM8990_ROMIX_ENA_BIT 4 +#define WM8990_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8990_DACL_ENA_BIT 1 +#define WM8990_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8990_DACR_ENA_BIT 0 + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8990_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8990_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8990_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8990_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8990_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8990_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8990_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8990_AIF_WL_16BITS (0 << 5) +#define WM8990_AIF_WL_20BITS (1 << 5) +#define WM8990_AIF_WL_24BITS (2 << 5) +#define WM8990_AIF_WL_32BITS (3 << 5) +#define WM8990_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8990_AIF_TMF_RIGHTJ (0 << 3) +#define WM8990_AIF_TMF_LEFTJ (1 << 3) +#define WM8990_AIF_TMF_I2S (2 << 3) +#define WM8990_AIF_TMF_DSP (3 << 3) + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8990_DACL_SRC 0x8000 /* DACL_SRC */ +#define WM8990_DACR_SRC 0x4000 /* DACR_SRC */ +#define WM8990_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8990_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8990_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST */ +#define WM8990_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8990_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8990_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8990_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8990_LOOPBACK 0x0001 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking (1) + */ +#define WM8990_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8990_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8990_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */ +#define WM8990_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8990_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8990_BCLK_DIV_1 (0x0 << 1) +#define WM8990_BCLK_DIV_1_5 (0x1 << 1) +#define WM8990_BCLK_DIV_2 (0x2 << 1) +#define WM8990_BCLK_DIV_3 (0x3 << 1) +#define WM8990_BCLK_DIV_4 (0x4 << 1) +#define WM8990_BCLK_DIV_5_5 (0x5 << 1) +#define WM8990_BCLK_DIV_6 (0x6 << 1) +#define WM8990_BCLK_DIV_8 (0x7 << 1) +#define WM8990_BCLK_DIV_11 (0x8 << 1) +#define WM8990_BCLK_DIV_12 (0x9 << 1) +#define WM8990_BCLK_DIV_16 (0xA << 1) +#define WM8990_BCLK_DIV_22 (0xB << 1) +#define WM8990_BCLK_DIV_24 (0xC << 1) +#define WM8990_BCLK_DIV_32 (0xD << 1) +#define WM8990_BCLK_DIV_44 (0xE << 1) +#define WM8990_BCLK_DIV_48 (0xF << 1) + +/* + * R7 (0x07) - Clocking (2) + */ +#define WM8990_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8990_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8990_CLK_FORCE 0x2000 /* CLK_FORCE */ +#define WM8990_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */ +#define WM8990_MCLK_DIV_1 (0 << 11) +#define WM8990_MCLK_DIV_2 (2 << 11) +#define WM8990_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8990_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV */ +#define WM8990_ADC_CLKDIV_1 (0 << 5) +#define WM8990_ADC_CLKDIV_1_5 (1 << 5) +#define WM8990_ADC_CLKDIV_2 (2 << 5) +#define WM8990_ADC_CLKDIV_3 (3 << 5) +#define WM8990_ADC_CLKDIV_4 (4 << 5) +#define WM8990_ADC_CLKDIV_5_5 (5 << 5) +#define WM8990_ADC_CLKDIV_6 (6 << 5) +#define WM8990_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */ +#define WM8990_DAC_CLKDIV_1 (0 << 2) +#define WM8990_DAC_CLKDIV_1_5 (1 << 2) +#define WM8990_DAC_CLKDIV_2 (2 << 2) +#define WM8990_DAC_CLKDIV_3 (3 << 2) +#define WM8990_DAC_CLKDIV_4 (4 << 2) +#define WM8990_DAC_CLKDIV_5_5 (5 << 2) +#define WM8990_DAC_CLKDIV_6 (6 << 2) + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8990_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8990_AIF_MSTR2 0x4000 /* AIF_MSTR2 */ +#define WM8990_AIF_SEL 0x2000 /* AIF_SEL */ +#define WM8990_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */ +#define WM8990_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8990_ALRCGPIO1 0x8000 /* ALRCGPIO1 */ +#define WM8990_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */ +#define WM8990_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8990_DACLRC_DIR 0x0800 /* DACLRC_DIR */ +#define WM8990_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8990_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */ +#define WM8990_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8990_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8990_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8990_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */ +#define WM8990_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */ +#define WM8990_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8990_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8990_DACR_DATINV 0x0001 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8990_DACL_VOL_SHIFT 0 +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8990_DACR_VOL_SHIFT 0 +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8990_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL */ +#define WM8990_ADCL_DAC_SVOL_SHIFT 9 +#define WM8990_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL */ +#define WM8990_ADCR_DAC_SVOL_SHIFT 5 +#define WM8990_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */ +#define WM8990_ADC_TO_DACL_SHIFT 2 +#define WM8990_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */ +#define WM8990_ADC_TO_DACR_SHIFT 0 + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8990_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */ +#define WM8990_ADC_HPF_ENA_BIT 8 +#define WM8990_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */ +#define WM8990_ADC_HPF_CUT_SHIFT 5 +#define WM8990_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8990_ADCL_DATINV_BIT 1 +#define WM8990_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8990_ADCR_DATINV_BIT 0 + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8990_ADCL_VOL_SHIFT 0 + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8990_ADCR_VOL_SHIFT 0 + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8990_IRQ 0x1000 /* IRQ */ +#define WM8990_TEMPOK 0x0800 /* TEMPOK */ +#define WM8990_MICSHRT 0x0400 /* MICSHRT */ +#define WM8990_MICDET 0x0200 /* MICDET */ +#define WM8990_PLL_LCK 0x0100 /* PLL_LCK */ +#define WM8990_GPI8_STATUS 0x0080 /* GPI8_STATUS */ +#define WM8990_GPI7_STATUS 0x0040 /* GPI7_STATUS */ +#define WM8990_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */ +#define WM8990_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */ +#define WM8990_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */ +#define WM8990_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */ +#define WM8990_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */ +#define WM8990_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */ + +/* + * R19 (0x13) - GPIO1 & GPIO2 + */ +#define WM8990_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */ +#define WM8990_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */ +#define WM8990_GPIO2_PU 0x2000 /* GPIO2_PU */ +#define WM8990_GPIO2_PD 0x1000 /* GPIO2_PD */ +#define WM8990_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */ +#define WM8990_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */ +#define WM8990_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */ +#define WM8990_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8990_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8990_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - GPIO3 & GPIO4 + */ +#define WM8990_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */ +#define WM8990_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */ +#define WM8990_GPIO4_PU 0x2000 /* GPIO4_PU */ +#define WM8990_GPIO4_PD 0x1000 /* GPIO4_PD */ +#define WM8990_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */ +#define WM8990_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */ +#define WM8990_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */ +#define WM8990_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8990_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8990_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ + +/* + * R21 (0x15) - GPIO5 & GPIO6 + */ +#define WM8990_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */ +#define WM8990_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */ +#define WM8990_GPIO6_PU 0x2000 /* GPIO6_PU */ +#define WM8990_GPIO6_PD 0x1000 /* GPIO6_PD */ +#define WM8990_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */ +#define WM8990_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */ +#define WM8990_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */ +#define WM8990_GPIO5_PU 0x0020 /* GPIO5_PU */ +#define WM8990_GPIO5_PD 0x0010 /* GPIO5_PD */ +#define WM8990_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8990_RD_3W_ENA 0x8000 /* RD_3W_ENA */ +#define WM8990_MODE_3W4W 0x4000 /* MODE_3W4W */ +#define WM8990_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */ +#define WM8990_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */ +#define WM8990_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */ +#define WM8990_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */ +#define WM8990_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */ +#define WM8990_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */ +#define WM8990_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8990_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */ +#define WM8990_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */ +#define WM8990_GPI7_ENA 0x0001 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8990_IRQ_INV 0x1000 /* IRQ_INV */ +#define WM8990_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8990_MICSHRT_POL 0x0400 /* MICSHRT_POL */ +#define WM8990_MICDET_POL 0x0200 /* MICDET_POL */ +#define WM8990_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */ +#define WM8990_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8990_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8990_GPIO6_POL 0x0020 /* GPIO6_POL */ +#define WM8990_GPIO5_POL 0x0010 /* GPIO5_POL */ +#define WM8990_GPIO4_POL 0x0008 /* GPIO4_POL */ +#define WM8990_GPIO3_POL 0x0004 /* GPIO3_POL */ +#define WM8990_GPIO2_POL 0x0002 /* GPIO2_POL */ +#define WM8990_GPIO1_POL 0x0001 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI12MUTE 0x0080 /* LI12MUTE */ +#define WM8990_LI12MUTE_BIT 7 +#define WM8990_LI12ZC 0x0040 /* LI12ZC */ +#define WM8990_LI12ZC_BIT 6 +#define WM8990_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */ +#define WM8990_LIN12VOL_SHIFT 0 +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI34MUTE 0x0080 /* LI34MUTE */ +#define WM8990_LI34MUTE_BIT 7 +#define WM8990_LI34ZC 0x0040 /* LI34ZC */ +#define WM8990_LI34ZC_BIT 6 +#define WM8990_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */ +#define WM8990_LIN34VOL_SHIFT 0 + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI12MUTE 0x0080 /* RI12MUTE */ +#define WM8990_RI12MUTE_BIT 7 +#define WM8990_RI12ZC 0x0040 /* RI12ZC */ +#define WM8990_RI12ZC_BIT 6 +#define WM8990_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */ +#define WM8990_RIN12VOL_SHIFT 0 + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI34MUTE 0x0080 /* RI34MUTE */ +#define WM8990_RI34MUTE_BIT 7 +#define WM8990_RI34ZC 0x0040 /* RI34ZC */ +#define WM8990_RI34ZC_BIT 6 +#define WM8990_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */ +#define WM8990_RIN34VOL_SHIFT 0 + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOZC 0x0080 /* LOZC */ +#define WM8990_LOZC_BIT 7 +#define WM8990_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8990_LOUTVOL_SHIFT 0 +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROZC 0x0080 /* ROZC */ +#define WM8990_ROZC_BIT 7 +#define WM8990_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8990_ROUTVOL_SHIFT 0 +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8990_LONMUTE 0x0040 /* LONMUTE */ +#define WM8990_LONMUTE_BIT 6 +#define WM8990_LOPMUTE 0x0020 /* LOPMUTE */ +#define WM8990_LOPMUTE_BIT 5 +#define WM8990_LOATTN 0x0010 /* LOATTN */ +#define WM8990_LOATTN_BIT 4 +#define WM8990_RONMUTE 0x0004 /* RONMUTE */ +#define WM8990_RONMUTE_BIT 2 +#define WM8990_ROPMUTE 0x0002 /* ROPMUTE */ +#define WM8990_ROPMUTE_BIT 1 +#define WM8990_ROATTN 0x0001 /* ROATTN */ +#define WM8990_ROATTN_BIT 0 + +/* + * R31 (0x1F) - Out3/4 Volume + */ +#define WM8990_OUT3MUTE 0x0020 /* OUT3MUTE */ +#define WM8990_OUT3MUTE_BIT 5 +#define WM8990_OUT3ATTN 0x0010 /* OUT3ATTN */ +#define WM8990_OUT3ATTN_BIT 4 +#define WM8990_OUT4MUTE 0x0002 /* OUT4MUTE */ +#define WM8990_OUT4MUTE_BIT 1 +#define WM8990_OUT4ATTN 0x0001 /* OUT4ATTN */ +#define WM8990_OUT4ATTN_BIT 0 + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOPGAZC 0x0080 /* LOPGAZC */ +#define WM8990_LOPGAZC_BIT 7 +#define WM8990_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */ +#define WM8990_LOPGAVOL_SHIFT 0 + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROPGAZC 0x0080 /* ROPGAZC */ +#define WM8990_ROPGAZC_BIT 7 +#define WM8990_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */ +#define WM8990_ROPGAVOL_SHIFT 0 +/* + * R34 (0x22) - Speaker Volume + */ +#define WM8990_SPKATTN_MASK 0x0003 /* SPKATTN - [1:0] */ +#define WM8990_SPKATTN_SHIFT 0 + +/* + * R35 (0x23) - ClassD1 + */ +#define WM8990_CDMODE 0x0100 /* CDMODE */ +#define WM8990_CDMODE_BIT 8 + +/* + * R37 (0x25) - ClassD3 + */ +#define WM8990_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */ +#define WM8990_DCGAIN_SHIFT 3 +#define WM8990_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */ +#define WM8990_ACGAIN_SHIFT 0 + +/* + * R38 (0x26) - ClassD4 + */ +#define WM8990_SPKZC_MASK 0x0001 /* SPKZC */ +#define WM8990_SPKZC_SHIFT 7 /* SPKZC */ +#define WM8990_SPKVOL_MASK 0x007F /* SPKVOL - [6:0] */ +#define WM8990_SPKVOL_SHIFT 0 /* SPKVOL - [6:0] */ + +/* + * R39 (0x27) - Input Mixer1 + */ +#define WM8990_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */ +#define WM8990_AINLMODE_SHIFT 2 +#define WM8990_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */ +#define WM8990_AINRMODE_SHIFT 0 + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8990_LMP4 0x0080 /* LMP4 */ +#define WM8990_LMP4_BIT 7 /* LMP4 */ +#define WM8990_LMN3 0x0040 /* LMN3 */ +#define WM8990_LMN3_BIT 6 /* LMN3 */ +#define WM8990_LMP2 0x0020 /* LMP2 */ +#define WM8990_LMP2_BIT 5 /* LMP2 */ +#define WM8990_LMN1 0x0010 /* LMN1 */ +#define WM8990_LMN1_BIT 4 /* LMN1 */ +#define WM8990_RMP4 0x0008 /* RMP4 */ +#define WM8990_RMP4_BIT 3 /* RMP4 */ +#define WM8990_RMN3 0x0004 /* RMN3 */ +#define WM8990_RMN3_BIT 2 /* RMN3 */ +#define WM8990_RMP2 0x0002 /* RMP2 */ +#define WM8990_RMP2_BIT 1 /* RMP2 */ +#define WM8990_RMN1 0x0001 /* RMN1 */ +#define WM8990_RMN1_BIT 0 /* RMN1 */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8990_L34MNB 0x0100 /* L34MNB */ +#define WM8990_L34MNB_BIT 8 +#define WM8990_L34MNBST 0x0080 /* L34MNBST */ +#define WM8990_L34MNBST_BIT 7 +#define WM8990_L12MNB 0x0020 /* L12MNB */ +#define WM8990_L12MNB_BIT 5 +#define WM8990_L12MNBST 0x0010 /* L12MNBST */ +#define WM8990_L12MNBST_BIT 4 +#define WM8990_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */ +#define WM8990_LDBVOL_SHIFT 0 + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8990_R34MNB 0x0100 /* R34MNB */ +#define WM8990_R34MNB_BIT 8 +#define WM8990_R34MNBST 0x0080 /* R34MNBST */ +#define WM8990_R34MNBST_BIT 7 +#define WM8990_R12MNB 0x0020 /* R12MNB */ +#define WM8990_R12MNB_BIT 5 +#define WM8990_R12MNBST 0x0010 /* R12MNBST */ +#define WM8990_R12MNBST_BIT 4 +#define WM8990_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */ +#define WM8990_RDBVOL_SHIFT 0 + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8990_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */ +#define WM8990_LI2BVOL_SHIFT 6 +#define WM8990_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */ +#define WM8990_LR4BVOL_SHIFT 3 +#define WM8990_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */ +#define WM8990_LL4BVOL_SHIFT 0 + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8990_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */ +#define WM8990_RI2BVOL_SHIFT 6 +#define WM8990_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */ +#define WM8990_RL4BVOL_SHIFT 3 +#define WM8990_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */ +#define WM8990_RR4BVOL_SHIFT 0 + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8990_LRBLO 0x0080 /* LRBLO */ +#define WM8990_LRBLO_BIT 7 +#define WM8990_LLBLO 0x0040 /* LLBLO */ +#define WM8990_LLBLO_BIT 6 +#define WM8990_LRI3LO 0x0020 /* LRI3LO */ +#define WM8990_LRI3LO_BIT 5 +#define WM8990_LLI3LO 0x0010 /* LLI3LO */ +#define WM8990_LLI3LO_BIT 4 +#define WM8990_LR12LO 0x0008 /* LR12LO */ +#define WM8990_LR12LO_BIT 3 +#define WM8990_LL12LO 0x0004 /* LL12LO */ +#define WM8990_LL12LO_BIT 2 +#define WM8990_LDLO 0x0001 /* LDLO */ +#define WM8990_LDLO_BIT 0 + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8990_RLBRO 0x0080 /* RLBRO */ +#define WM8990_RLBRO_BIT 7 +#define WM8990_RRBRO 0x0040 /* RRBRO */ +#define WM8990_RRBRO_BIT 6 +#define WM8990_RLI3RO 0x0020 /* RLI3RO */ +#define WM8990_RLI3RO_BIT 5 +#define WM8990_RRI3RO 0x0010 /* RRI3RO */ +#define WM8990_RRI3RO_BIT 4 +#define WM8990_RL12RO 0x0008 /* RL12RO */ +#define WM8990_RL12RO_BIT 3 +#define WM8990_RR12RO 0x0004 /* RR12RO */ +#define WM8990_RR12RO_BIT 2 +#define WM8990_RDRO 0x0001 /* RDRO */ +#define WM8990_RDRO_BIT 0 + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8990_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */ +#define WM8990_LLI3LOVOL_SHIFT 6 +#define WM8990_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */ +#define WM8990_LR12LOVOL_SHIFT 3 +#define WM8990_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */ +#define WM8990_LL12LOVOL_SHIFT 0 + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8990_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */ +#define WM8990_RRI3ROVOL_SHIFT 6 +#define WM8990_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */ +#define WM8990_RL12ROVOL_SHIFT 3 +#define WM8990_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */ +#define WM8990_RR12ROVOL_SHIFT 0 + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8990_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */ +#define WM8990_LRI3LOVOL_SHIFT 6 +#define WM8990_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */ +#define WM8990_LRBLOVOL_SHIFT 3 +#define WM8990_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */ +#define WM8990_LLBLOVOL_SHIFT 0 + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8990_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */ +#define WM8990_RLI3ROVOL_SHIFT 6 +#define WM8990_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */ +#define WM8990_RLBROVOL_SHIFT 3 +#define WM8990_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */ +#define WM8990_RRBROVOL_SHIFT 0 + +/* + * R51 (0x33) - Out3/4 Mixer + */ +#define WM8990_VSEL_MASK 0x0180 /* VSEL - [8:7] */ +#define WM8990_LI4O3 0x0020 /* LI4O3 */ +#define WM8990_LI4O3_BIT 5 +#define WM8990_LPGAO3 0x0010 /* LPGAO3 */ +#define WM8990_LPGAO3_BIT 4 +#define WM8990_RI4O4 0x0002 /* RI4O4 */ +#define WM8990_RI4O4_BIT 1 +#define WM8990_RPGAO4 0x0001 /* RPGAO4 */ +#define WM8990_RPGAO4_BIT 0 +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8990_LLOPGALON 0x0040 /* LLOPGALON */ +#define WM8990_LLOPGALON_BIT 6 +#define WM8990_LROPGALON 0x0020 /* LROPGALON */ +#define WM8990_LROPGALON_BIT 5 +#define WM8990_LOPLON 0x0010 /* LOPLON */ +#define WM8990_LOPLON_BIT 4 +#define WM8990_LR12LOP 0x0004 /* LR12LOP */ +#define WM8990_LR12LOP_BIT 2 +#define WM8990_LL12LOP 0x0002 /* LL12LOP */ +#define WM8990_LL12LOP_BIT 1 +#define WM8990_LLOPGALOP 0x0001 /* LLOPGALOP */ +#define WM8990_LLOPGALOP_BIT 0 +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8990_RROPGARON 0x0040 /* RROPGARON */ +#define WM8990_RROPGARON_BIT 6 +#define WM8990_RLOPGARON 0x0020 /* RLOPGARON */ +#define WM8990_RLOPGARON_BIT 5 +#define WM8990_ROPRON 0x0010 /* ROPRON */ +#define WM8990_ROPRON_BIT 4 +#define WM8990_RL12ROP 0x0004 /* RL12ROP */ +#define WM8990_RL12ROP_BIT 2 +#define WM8990_RR12ROP 0x0002 /* RR12ROP */ +#define WM8990_RR12ROP_BIT 1 +#define WM8990_RROPGAROP 0x0001 /* RROPGAROP */ +#define WM8990_RROPGAROP_BIT 0 + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8990_LB2SPK 0x0080 /* LB2SPK */ +#define WM8990_LB2SPK_BIT 7 +#define WM8990_RB2SPK 0x0040 /* RB2SPK */ +#define WM8990_RB2SPK_BIT 6 +#define WM8990_LI2SPK 0x0020 /* LI2SPK */ +#define WM8990_LI2SPK_BIT 5 +#define WM8990_RI2SPK 0x0010 /* RI2SPK */ +#define WM8990_RI2SPK_BIT 4 +#define WM8990_LOPGASPK 0x0008 /* LOPGASPK */ +#define WM8990_LOPGASPK_BIT 3 +#define WM8990_ROPGASPK 0x0004 /* ROPGASPK */ +#define WM8990_ROPGASPK_BIT 2 +#define WM8990_LDSPK 0x0002 /* LDSPK */ +#define WM8990_LDSPK_BIT 1 +#define WM8990_RDSPK 0x0001 /* RDSPK */ +#define WM8990_RDSPK_BIT 0 + +/* + * R55 (0x37) - Additional Control + */ +#define WM8990_VROI 0x0001 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8990_DIS_LLINE 0x0020 /* DIS_LLINE */ +#define WM8990_DIS_RLINE 0x0010 /* DIS_RLINE */ +#define WM8990_DIS_OUT3 0x0008 /* DIS_OUT3 */ +#define WM8990_DIS_OUT4 0x0004 /* DIS_OUT4 */ +#define WM8990_DIS_LOUT 0x0002 /* DIS_LOUT */ +#define WM8990_DIS_ROUT 0x0001 /* DIS_ROUT */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8990_SOFTST 0x0040 /* SOFTST */ +#define WM8990_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8990_BUFDCOPEN 0x0004 /* BUFDCOPEN */ +#define WM8990_POBCTRL 0x0002 /* POBCTRL */ +#define WM8990_VMIDTOG 0x0001 /* VMIDTOG */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8990_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */ +#define WM8990_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */ +#define WM8990_MCD 0x0004 /* MCD */ +#define WM8990_MBSEL 0x0001 /* MBSEL */ + +/* + * R60 (0x3C) - PLL1 + */ +#define WM8990_SDM 0x0080 /* SDM */ +#define WM8990_PRESCALE 0x0040 /* PRESCALE */ +#define WM8990_PLLN_MASK 0x000F /* PLLN - [3:0] */ + +/* + * R61 (0x3D) - PLL2 + */ +#define WM8990_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */ + +/* + * R62 (0x3E) - PLL3 + */ +#define WM8990_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */ + +/* + * R63 (0x3F) - Internal Driver Bits + */ +#define WM8990_INMIXL_PWR_BIT 0 +#define WM8990_AINLMUX_PWR_BIT 1 +#define WM8990_INMIXR_PWR_BIT 2 +#define WM8990_AINRMUX_PWR_BIT 3 + +struct wm8990_setup_data { + unsigned i2c_bus; + unsigned short i2c_address; +}; + +#define WM8990_MCLK_DIV 0 +#define WM8990_DACCLK_DIV 1 +#define WM8990_ADCCLK_DIV 2 +#define WM8990_BCLK_DIV 3 + +extern struct snd_soc_dai wm8990_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8990; + +#endif /* __WM8990REGISTERDEFS_H__ */ +/*------------------------------ END OF FILE ---------------------------------*/ diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 76c1e2d33e7d..ffb471e420e2 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -2,21 +2,16 @@ * wm9712.c -- ALSA Soc WM9712 codec support * * Copyright 2006 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. - * - * Revision history - * 4th Feb 2006 Initial version. */ #include <linux/init.h> #include <linux/module.h> -#include <linux/version.h> #include <linux/kernel.h> #include <linux/device.h> #include <sound/core.h> @@ -25,6 +20,7 @@ #include <sound/initval.h> #include <sound/soc.h> #include <sound/soc-dapm.h> +#include "wm9712.h" #define WM9712_VERSION "0.4" @@ -351,7 +347,7 @@ SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* virtual mixer - mixes left & right channels for spk and mono */ {"AC97 Mixer", NULL, "Left DAC"}, {"AC97 Mixer", NULL, "Right DAC"}, @@ -429,38 +425,31 @@ static const char *audio_map[][3] = { {"HPOUTR", NULL, "Headphone PGA"}, {"Headphone PGA", NULL, "Right HP Mixer"}, - /* mono hp mixer */ - {"Mono HP Mixer", NULL, "Left HP Mixer"}, - {"Mono HP Mixer", NULL, "Right HP Mixer"}, + /* mono mixer */ + {"Mono Mixer", NULL, "Left HP Mixer"}, + {"Mono Mixer", NULL, "Right HP Mixer"}, /* Out3 Mux */ {"Out3 Mux", "Left", "Left HP Mixer"}, {"Out3 Mux", "Mono", "Phone Mixer"}, - {"Out3 Mux", "Left + Right", "Mono HP Mixer"}, + {"Out3 Mux", "Left + Right", "Mono Mixer"}, {"Out 3 PGA", NULL, "Out3 Mux"}, {"OUT3", NULL, "Out 3 PGA"}, /* speaker Mux */ {"Speaker Mux", "Speaker Mix", "Speaker Mixer"}, - {"Speaker Mux", "Headphone Mix", "Mono HP Mixer"}, + {"Speaker Mux", "Headphone Mix", "Mono Mixer"}, {"Speaker PGA", NULL, "Speaker Mux"}, {"LOUT2", NULL, "Speaker PGA"}, {"ROUT2", NULL, "Speaker PGA"}, - - {NULL, NULL, NULL}, }; static int wm9712_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(wm9712_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm9712_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm9712_dapm_widgets, + ARRAY_SIZE(wm9712_dapm_widgets)); - /* set up audio path connects */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -541,7 +530,7 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream) SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ SNDRV_PCM_RATE_48000) -struct snd_soc_codec_dai wm9712_dai[] = { +struct snd_soc_dai wm9712_dai[] = { { .name = "AC97 HiFi", .type = SND_SOC_DAI_AC97_BUS, @@ -574,23 +563,23 @@ struct snd_soc_codec_dai wm9712_dai[] = { }; EXPORT_SYMBOL_GPL(wm9712_dai); -static int wm9712_dapm_event(struct snd_soc_codec *codec, int event) +static int wm9712_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: ac97_write(codec, AC97_POWERDOWN, 0x0000); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: /* disable everything including AC link */ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); ac97_write(codec, AC97_POWERDOWN, 0xffff); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -598,12 +587,12 @@ static int wm9712_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); - if (!(ac97_read(codec, 0) & 0x8000)) + if (ac97_read(codec, 0) == wm9712_reg[0]) return 1; } soc_ac97_ops.reset(codec->ac97); - if (ac97_read(codec, 0) & 0x8000) + if (ac97_read(codec, 0) != wm9712_reg[0]) goto err; return 0; @@ -618,7 +607,7 @@ static int wm9712_soc_suspend(struct platform_device *pdev, struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -635,7 +624,7 @@ static int wm9712_soc_resume(struct platform_device *pdev) return ret; } - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret == 0) { /* Sync reg_cache with the hardware after cold reset */ @@ -647,8 +636,8 @@ static int wm9712_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D0); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + wm9712_set_bias_level(codec, SND_SOC_BIAS_ON); return ret; } @@ -682,7 +671,7 @@ static int wm9712_soc_probe(struct platform_device *pdev) codec->num_dai = ARRAY_SIZE(wm9712_dai); codec->write = ac97_write; codec->read = ac97_read; - codec->dapm_event = wm9712_dapm_event; + codec->set_bias_level = wm9712_set_bias_level; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -706,7 +695,7 @@ static int wm9712_soc_probe(struct platform_device *pdev) /* set alc mux to none */ ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000); - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); wm9712_add_controls(codec); wm9712_add_widgets(codec); ret = snd_soc_register_card(socdev); diff --git a/sound/soc/codecs/wm9712.h b/sound/soc/codecs/wm9712.h index 719105d61e65..d29e8a18ca6d 100644 --- a/sound/soc/codecs/wm9712.h +++ b/sound/soc/codecs/wm9712.h @@ -8,7 +8,7 @@ #define WM9712_DAI_AC97_HIFI 0 #define WM9712_DAI_AC97_AUX 1 -extern struct snd_soc_codec_dai wm9712_dai[2]; +extern struct snd_soc_dai wm9712_dai[2]; extern struct snd_soc_codec_device soc_codec_dev_wm9712; #endif diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 1f241161445c..aba402b3c999 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -2,17 +2,13 @@ * wm9713.c -- ALSA Soc WM9713 codec support * * Copyright 2006 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 4th Feb 2006 Initial version. - * * Features:- * * o Support for AC97 Codec, Voice DAC and Aux DAC @@ -422,8 +418,12 @@ SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), -SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_EXTENDED_MID, 5, 1), -SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_EXTENDED_MID, 4, 1), +SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0), +SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0), SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0), SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0), @@ -456,7 +456,7 @@ SND_SOC_DAPM_INPUT("MIC2B"), SND_SOC_DAPM_VMID("VMID"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* left HP mixer */ {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"}, @@ -586,9 +586,13 @@ static const char *audio_map[][3] = { /* left ADC */ {"Left ADC", NULL, "Left Capture Source"}, + {"Left Voice ADC", NULL, "Left ADC"}, + {"Left HiFi ADC", NULL, "Left ADC"}, /* right ADC */ {"Right ADC", NULL, "Right Capture Source"}, + {"Right Voice ADC", NULL, "Right ADC"}, + {"Right HiFi ADC", NULL, "Right ADC"}, /* mic */ {"Mic A Pre Amp", NULL, "Mic A Source"}, @@ -607,21 +611,14 @@ static const char *audio_map[][3] = { {"Capture Mono Mux", "Stereo", "Capture Mixer"}, {"Capture Mono Mux", "Left", "Left Capture Source"}, {"Capture Mono Mux", "Right", "Right Capture Source"}, - - {NULL, NULL, NULL}, }; static int wm9713_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(wm9713_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm9713_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm9713_dapm_widgets, + ARRAY_SIZE(wm9713_dapm_widgets)); - /* set up audio path audio_mapnects */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -799,7 +796,7 @@ static int wm9713_set_pll(struct snd_soc_codec *codec, return 0; } -static int wm9713_set_dai_pll(struct snd_soc_codec_dai *codec_dai, +static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; @@ -810,7 +807,7 @@ static int wm9713_set_dai_pll(struct snd_soc_codec_dai *codec_dai, * Tristate the PCM DAI lines, tristate can be disabled by calling * wm9713_set_dai_fmt() */ -static int wm9713_set_dai_tristate(struct snd_soc_codec_dai *codec_dai, +static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai, int tristate) { struct snd_soc_codec *codec = codec_dai->codec; @@ -826,7 +823,7 @@ static int wm9713_set_dai_tristate(struct snd_soc_codec_dai *codec_dai, * Configure WM9713 clock dividers. * Voice DAC needs 256 FS */ -static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, +static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { struct snd_soc_codec *codec = codec_dai->codec; @@ -868,7 +865,7 @@ static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, return 0; } -static int wm9713_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -886,7 +883,7 @@ static int wm9713_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, gpio |= 0x0018; break; case SND_SOC_DAIFMT_CBS_CFS: - reg |= 0x0200; + reg |= 0x2000; gpio |= 0x001a; break; case SND_SOC_DAIFMT_CBS_CFM: @@ -959,17 +956,17 @@ static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, static void wm9713_voiceshutdown(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; - u16 status; - - /* Gracefully shut down the voice interface. */ - status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; - ac97_write(codec, AC97_HANDSET_RATE, 0x0280); - schedule_timeout_interruptible(msecs_to_jiffies(1)); - ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); - ac97_write(codec, AC97_EXTENDED_MID, status); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 status; + + /* Gracefully shut down the voice interface. */ + status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; + ac97_write(codec, AC97_HANDSET_RATE, 0x0280); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); + ac97_write(codec, AC97_EXTENDED_MID, status); } static int ac97_hifi_prepare(struct snd_pcm_substream *substream) @@ -1011,15 +1008,24 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream) return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate); } -#define WM9713_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ - SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ - SNDRV_PCM_RATE_48000) +#define WM9713_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) #define WM9713_PCM_FORMATS \ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ SNDRV_PCM_FORMAT_S24_LE) -struct snd_soc_codec_dai wm9713_dai[] = { +struct snd_soc_dai wm9713_dai[] = { { .name = "AC97 HiFi", .type = SND_SOC_DAI_AC97_BUS, @@ -1061,13 +1067,13 @@ struct snd_soc_codec_dai wm9713_dai[] = { .stream_name = "Voice Playback", .channels_min = 1, .channels_max = 1, - .rates = WM9713_RATES, + .rates = WM9713_PCM_RATES, .formats = WM9713_PCM_FORMATS,}, .capture = { .stream_name = "Voice Capture", .channels_min = 1, .channels_max = 2, - .rates = WM9713_RATES, + .rates = WM9713_PCM_RATES, .formats = WM9713_PCM_FORMATS,}, .ops = { .hw_params = wm9713_pcm_hw_params, @@ -1086,44 +1092,44 @@ int wm9713_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); - if (!(ac97_read(codec, 0) & 0x8000)) + if (ac97_read(codec, 0) == wm9713_reg[0]) return 1; } soc_ac97_ops.reset(codec->ac97); - if (ac97_read(codec, 0) & 0x8000) + if (ac97_read(codec, 0) != wm9713_reg[0]) return -EIO; return 0; } EXPORT_SYMBOL_GPL(wm9713_reset); -static int wm9713_dapm_event(struct snd_soc_codec *codec, int event) +static int wm9713_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 reg; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* enable thermal shutdown */ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff; ac97_write(codec, AC97_EXTENDED_MID, reg); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* enable master bias and vmid */ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff; ac97_write(codec, AC97_EXTENDED_MID, reg); ac97_write(codec, AC97_POWERDOWN, 0x0000); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: /* disable everything including AC link */ ac97_write(codec, AC97_EXTENDED_MID, 0xffff); ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); ac97_write(codec, AC97_POWERDOWN, 0xffff); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -1160,7 +1166,7 @@ static int wm9713_soc_resume(struct platform_device *pdev) return ret; } - wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* do we need to re-start the PLL ? */ if (wm9713->pll_out) @@ -1176,8 +1182,8 @@ static int wm9713_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) - wm9713_dapm_event(codec, SNDRV_CTL_POWER_D0); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + wm9713_set_bias_level(codec, SND_SOC_BIAS_ON); return ret; } @@ -1216,7 +1222,7 @@ static int wm9713_soc_probe(struct platform_device *pdev) codec->num_dai = ARRAY_SIZE(wm9713_dai); codec->write = ac97_write; codec->read = ac97_read; - codec->dapm_event = wm9713_dapm_event; + codec->set_bias_level = wm9713_set_bias_level; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -1238,7 +1244,7 @@ static int wm9713_soc_probe(struct platform_device *pdev) goto reset_err; } - wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* unmute the adc - move to kcontrol */ reg = ac97_read(codec, AC97_CD) & 0x7fff; diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h index d357b6c8134b..63b8d81756e3 100644 --- a/sound/soc/codecs/wm9713.h +++ b/sound/soc/codecs/wm9713.h @@ -46,7 +46,7 @@ #define WM9713_DAI_PCM_VOICE 2 extern struct snd_soc_codec_device soc_codec_dev_wm9713; -extern struct snd_soc_codec_dai wm9713_dai[3]; +extern struct snd_soc_dai wm9713_dai[3]; int wm9713_reset(struct snd_soc_codec *codec, int try_warm); diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 20680c551aab..8f7e33834902 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -1,6 +1,6 @@ config SND_DAVINCI_SOC tristate "SoC Audio for the TI DAVINCI chip" - depends on ARCH_DAVINCI && SND_SOC + depends on ARCH_DAVINCI help Say Y or M if you want to add support for codecs attached to the DAVINCI AC97 or I2S interface. You will also need diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index fcd165240333..9e6062cd6b59 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -1,7 +1,7 @@ /* * ASoC driver for TI DAVINCI EVM platform * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -19,9 +19,8 @@ #include <sound/soc.h> #include <sound/soc-dapm.h> -#include <asm/mach-types.h> #include <asm/dma.h> -#include <asm/arch/hardware.h> +#include <mach/hardware.h> #include "../codecs/tlv320aic3x.h" #include "davinci-pcm.h" @@ -33,24 +32,24 @@ static int evm_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_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; int ret = 0; /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM); if (ret < 0) return ret; /* set cpu DAI configuration */ - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM | + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF); if (ret < 0) return ret; /* set the codec system clock */ - ret = codec_dai->dai_ops.set_sysclk(codec_dai, 0, EVM_CODEC_CLOCK, + ret = snd_soc_dai_set_sysclk(codec_dai, 0, EVM_CODEC_CLOCK, SND_SOC_CLOCK_OUT); if (ret < 0) return ret; @@ -71,7 +70,7 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { }; /* davinci-evm machine audio_mapnections to the codec pins */ -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* Headphone connected to HPLOUT, HPROUT */ {"Headphone Jack", NULL, "HPLOUT"}, {"Headphone Jack", NULL, "HPROUT"}, @@ -90,36 +89,30 @@ static const char *audio_map[][3] = { {"LINE2L", NULL, "Line In"}, {"LINE1R", NULL, "Line In"}, {"LINE2R", NULL, "Line In"}, - - {NULL, NULL, NULL}, }; /* Logic for a aic3x as connected on a davinci-evm */ static int evm_aic3x_init(struct snd_soc_codec *codec) { - int i; - /* Add davinci-evm specific widgets */ - for (i = 0; i < ARRAY_SIZE(aic3x_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &aic3x_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets, + ARRAY_SIZE(aic3x_dapm_widgets)); /* Set up davinci-evm specific audio path audio_map */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); /* not connected */ - snd_soc_dapm_set_endpoint(codec, "MONO_LOUT", 0); - snd_soc_dapm_set_endpoint(codec, "HPLCOM", 0); - snd_soc_dapm_set_endpoint(codec, "HPRCOM", 0); + snd_soc_dapm_disable_pin(codec, "MONO_LOUT"); + snd_soc_dapm_disable_pin(codec, "HPLCOM"); + snd_soc_dapm_disable_pin(codec, "HPRCOM"); /* always connected */ - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); - snd_soc_dapm_set_endpoint(codec, "Line Out", 1); - snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1); - snd_soc_dapm_set_endpoint(codec, "Line In", 1); + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Line Out"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + snd_soc_dapm_enable_pin(codec, "Line In"); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } @@ -143,6 +136,7 @@ static struct snd_soc_machine snd_soc_machine_evm = { /* evm audio private data */ static struct aic3x_setup_data evm_aic3x_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index c421774b33ee..abb5fedb0b1e 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -1,7 +1,7 @@ /* * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -147,7 +147,7 @@ static void davinci_mcbsp_stop(struct snd_pcm_substream *substream) static int davinci_i2s_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; cpu_dai->dma_data = dev->dma_params[substream->stream]; @@ -155,7 +155,7 @@ static int davinci_i2s_startup(struct snd_pcm_substream *substream) return 0; } -static int davinci_i2s_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, +static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { struct davinci_mcbsp_dev *dev = cpu_dai->private_data; @@ -256,7 +256,7 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, mcbsp_word_length = DAVINCI_MCBSP_WORD_32; break; default: - printk(KERN_WARNING "davinci-i2s: unsupported PCM format"); + printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n"); return -EINVAL; } @@ -295,11 +295,12 @@ static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd) return ret; } -static int davinci_i2s_probe(struct platform_device *pdev) +static int davinci_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_machine *machine = socdev->machine; - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[pdev->id].cpu_dai; + struct snd_soc_dai *cpu_dai = machine->dai_link[pdev->id].cpu_dai; struct davinci_mcbsp_dev *dev; struct resource *mem, *ioarea; struct evm_snd_platform_data *pdata; @@ -356,11 +357,12 @@ err_release_region: return ret; } -static void davinci_i2s_remove(struct platform_device *pdev) +static void davinci_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_machine *machine = socdev->machine; - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[pdev->id].cpu_dai; + struct snd_soc_dai *cpu_dai = machine->dai_link[pdev->id].cpu_dai; struct davinci_mcbsp_dev *dev = cpu_dai->private_data; struct resource *mem; @@ -376,7 +378,7 @@ static void davinci_i2s_remove(struct platform_device *pdev) #define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000 -struct snd_soc_cpu_dai davinci_i2s_dai = { +struct snd_soc_dai davinci_i2s_dai = { .name = "davinci-i2s", .id = 0, .type = SND_SOC_DAI_I2S, diff --git a/sound/soc/davinci/davinci-i2s.h b/sound/soc/davinci/davinci-i2s.h index 9592d17db320..241648ce8873 100644 --- a/sound/soc/davinci/davinci-i2s.h +++ b/sound/soc/davinci/davinci-i2s.h @@ -1,7 +1,7 @@ /* * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -12,6 +12,6 @@ #ifndef _DAVINCI_I2S_H #define _DAVINCI_I2S_H -extern struct snd_soc_cpu_dai davinci_i2s_dai; +extern struct snd_soc_dai davinci_i2s_dai; #endif diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index 6a76927c9971..76feaa657375 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -1,7 +1,7 @@ /* * ALSA PCM interface for the TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -350,7 +350,7 @@ static void davinci_pcm_free(struct snd_pcm *pcm) static u64 davinci_pcm_dmamask = 0xffffffff; static int davinci_pcm_new(struct snd_card *card, - struct snd_soc_codec_dai *dai, struct snd_pcm *pcm) + struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret; diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h index 8d6a45e75a6e..62cb4eb07e34 100644 --- a/sound/soc/davinci/davinci-pcm.h +++ b/sound/soc/davinci/davinci-pcm.h @@ -1,7 +1,7 @@ /* * ALSA PCM interface for the TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 257101f44e9e..bba9546ba5f5 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,8 +1,9 @@ -menu "ALSA SoC audio for Freescale SOCs" +config SND_SOC_OF_SIMPLE + tristate config SND_SOC_MPC8610 bool "ALSA SoC support for the MPC8610 SOC" - depends on SND_SOC && MPC8610_HPCD + depends on MPC8610_HPCD default y if MPC8610 help Say Y if you want to add support for codecs attached to the SSI @@ -17,4 +18,9 @@ config SND_SOC_MPC8610_HPCD help Say Y if you want to enable audio on the Freescale MPC8610 HPCD. -endmenu +config SND_SOC_MPC5200_I2S + tristate "Freescale MPC5200 PSC in I2S mode driver" + select SND_SOC_OF_SIMPLE + depends on SND_SOC && PPC_MPC52xx + help + Say Y here to support the MPC5200 PSCs in I2S mode. diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 62f680a4a776..035da4afec34 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -1,6 +1,11 @@ +# Simple machine driver that extracts configuration from the OF device tree +obj-$(CONFIG_SND_SOC_OF_SIMPLE) += soc-of-simple.o + # MPC8610 HPCD Machine Support 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/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index 78de7168d2ba..d2d3da9729f2 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -132,12 +132,17 @@ struct fsl_dma_private { * Since each link descriptor has a 32-bit byte count field, we set * period_bytes_max to the largest 32-bit number. We also have no maximum * number of periods. + * + * Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a + * limitation in the SSI driver requires the sample rates for playback and + * capture to be the same. */ static const struct snd_pcm_hardware fsl_dma_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID, + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_JOINT_DUPLEX, .formats = FSLDMA_PCM_FORMATS, .rates = FSLDMA_PCM_RATES, .rate_min = 5512, @@ -282,7 +287,7 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id) * once for each .dai_link in the machine driver's snd_soc_machine * structure. */ -static int fsl_dma_new(struct snd_card *card, struct snd_soc_codec_dai *dai, +static int fsl_dma_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { static u64 fsl_dma_dmamask = DMA_BIT_MASK(32); @@ -322,14 +327,75 @@ static int fsl_dma_new(struct snd_card *card, struct snd_soc_codec_dai *dai, * fsl_dma_open: open a new substream. * * Each substream has its own DMA buffer. + * + * ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link + * descriptors that ping-pong from one period to the next. For example, if + * there are six periods and two link descriptors, this is how they look + * before playback starts: + * + * The last link descriptor + * ____________ points back to the first + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * | | + * V V + * _________________________________________ + * | | | | | | | The DMA buffer is + * | | | | | | | divided into 6 parts + * |______|______|______|______|______|______| + * + * and here's how they look after the first period is finished playing: + * + * ____________ + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * |______________ + * | | + * V V + * _________________________________________ + * | | | | | | | + * | | | | | | | + * |______|______|______|______|______|______| + * + * The first link descriptor now points to the third period. The DMA + * controller is currently playing the second period. When it finishes, it + * will jump back to the first descriptor and play the third period. + * + * There are four reasons we do this: + * + * 1. The only way to get the DMA controller to automatically restart the + * transfer when it gets to the end of the buffer is to use chaining + * mode. Basic direct mode doesn't offer that feature. + * 2. We need to receive an interrupt at the end of every period. The DMA + * controller can generate an interrupt at the end of every link transfer + * (aka segment). Making each period into a DMA segment will give us the + * interrupts we need. + * 3. By creating only two link descriptors, regardless of the number of + * periods, we do not need to reallocate the link descriptors if the + * number of periods changes. + * 4. All of the audio data is still stored in a single, contiguous DMA + * buffer, which is what ALSA expects. We're just dividing it into + * contiguous parts, and creating a link descriptor for each one. */ static int fsl_dma_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_dma_private *dma_private; + struct ccsr_dma_channel __iomem *dma_channel; dma_addr_t ld_buf_phys; + u64 temp_link; /* Pointer to next link descriptor */ + u32 mr; unsigned int channel; int ret = 0; + unsigned int i; /* * Reject any DMA buffer whose size is not a multiple of the period @@ -390,68 +456,74 @@ static int fsl_dma_open(struct snd_pcm_substream *substream) snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware); runtime->private_data = dma_private; + /* Program the fixed DMA controller parameters */ + + dma_channel = dma_private->dma_channel; + + temp_link = dma_private->ld_buf_phys + + sizeof(struct fsl_dma_link_descriptor); + + for (i = 0; i < NUM_DMA_LINKS; i++) { + struct fsl_dma_link_descriptor *link = &dma_private->link[i]; + + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP); + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP); + link->next = cpu_to_be64(temp_link); + + temp_link += sizeof(struct fsl_dma_link_descriptor); + } + /* The last link descriptor points to the first */ + dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys); + + /* Tell the DMA controller where the first link descriptor is */ + out_be32(&dma_channel->clndar, + CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys)); + out_be32(&dma_channel->eclndar, + CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys)); + + /* The manual says the BCR must be clear before enabling EMP */ + out_be32(&dma_channel->bcr, 0); + + /* + * Program the mode register for interrupts, external master control, + * and source/destination hold. Also clear the Channel Abort bit. + */ + mr = in_be32(&dma_channel->mr) & + ~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE); + + /* + * We want External Master Start and External Master Pause enabled, + * because the SSI is controlling the DMA controller. We want the DMA + * controller to be set up in advance, and then we signal only the SSI + * to start transferring. + * + * We want End-Of-Segment Interrupts enabled, because this will generate + * an interrupt at the end of each segment (each link descriptor + * represents one segment). Each DMA segment is the same thing as an + * ALSA period, so this is how we get an interrupt at the end of every + * period. + * + * We want Error Interrupt enabled, so that we can get an error if + * the DMA controller is mis-programmed somehow. + */ + mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN | + CCSR_DMA_MR_EMS_EN; + + /* For playback, we want the destination address to be held. For + capture, set the source address to be held. */ + mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE; + + out_be32(&dma_channel->mr, mr); + return 0; } /** - * fsl_dma_hw_params: allocate the DMA buffer and the DMA link descriptors. + * fsl_dma_hw_params: continue initializing the DMA links * - * ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link - * descriptors that ping-pong from one period to the next. For example, if - * there are six periods and two link descriptors, this is how they look - * before playback starts: - * - * The last link descriptor - * ____________ points back to the first - * | | - * V | - * ___ ___ | - * | |->| |->| - * |___| |___| - * | | - * | | - * V V - * _________________________________________ - * | | | | | | | The DMA buffer is - * | | | | | | | divided into 6 parts - * |______|______|______|______|______|______| - * - * and here's how they look after the first period is finished playing: - * - * ____________ - * | | - * V | - * ___ ___ | - * | |->| |->| - * |___| |___| - * | | - * |______________ - * | | - * V V - * _________________________________________ - * | | | | | | | - * | | | | | | | - * |______|______|______|______|______|______| - * - * The first link descriptor now points to the third period. The DMA - * controller is currently playing the second period. When it finishes, it - * will jump back to the first descriptor and play the third period. - * - * There are four reasons we do this: - * - * 1. The only way to get the DMA controller to automatically restart the - * transfer when it gets to the end of the buffer is to use chaining - * mode. Basic direct mode doesn't offer that feature. - * 2. We need to receive an interrupt at the end of every period. The DMA - * controller can generate an interrupt at the end of every link transfer - * (aka segment). Making each period into a DMA segment will give us the - * interrupts we need. - * 3. By creating only two link descriptors, regardless of the number of - * periods, we do not need to reallocate the link descriptors if the - * number of periods changes. - * 4. All of the audio data is still stored in a single, contiguous DMA - * buffer, which is what ALSA expects. We're just dividing it into - * contiguous parts, and creating a link descriptor for each one. + * This function obtains hardware parameters about the opened stream and + * programs the DMA controller accordingly. * * Note that due to a quirk of the SSI's STX register, the target address * for the DMA operations depends on the sample size. So we don't program @@ -463,11 +535,8 @@ static int fsl_dma_hw_params(struct snd_pcm_substream *substream, { struct snd_pcm_runtime *runtime = substream->runtime; struct fsl_dma_private *dma_private = runtime->private_data; - struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; dma_addr_t temp_addr; /* Pointer to next period */ - u64 temp_link; /* Pointer to next link descriptor */ - u32 mr; /* Temporary variable for MR register */ unsigned int i; @@ -485,8 +554,6 @@ static int fsl_dma_hw_params(struct snd_pcm_substream *substream, dma_private->dma_buf_next = dma_private->dma_buf_phys; /* - * Initialize each link descriptor. - * * The actual address in STX0 (destination for playback, source for * capture) is based on the sample size, but we don't know the sample * size in this function, so we'll have to adjust that later. See @@ -502,16 +569,11 @@ static int fsl_dma_hw_params(struct snd_pcm_substream *substream, * buffer itself. */ temp_addr = substream->dma_buffer.addr; - temp_link = dma_private->ld_buf_phys + - sizeof(struct fsl_dma_link_descriptor); for (i = 0; i < NUM_DMA_LINKS; i++) { struct fsl_dma_link_descriptor *link = &dma_private->link[i]; link->count = cpu_to_be32(period_size); - link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP); - link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP); - link->next = cpu_to_be64(temp_link); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) link->source_addr = cpu_to_be32(temp_addr); @@ -519,51 +581,7 @@ static int fsl_dma_hw_params(struct snd_pcm_substream *substream, link->dest_addr = cpu_to_be32(temp_addr); temp_addr += period_size; - temp_link += sizeof(struct fsl_dma_link_descriptor); } - /* The last link descriptor points to the first */ - dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys); - - /* Tell the DMA controller where the first link descriptor is */ - out_be32(&dma_channel->clndar, - CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys)); - out_be32(&dma_channel->eclndar, - CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys)); - - /* The manual says the BCR must be clear before enabling EMP */ - out_be32(&dma_channel->bcr, 0); - - /* - * Program the mode register for interrupts, external master control, - * and source/destination hold. Also clear the Channel Abort bit. - */ - mr = in_be32(&dma_channel->mr) & - ~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE); - - /* - * We want External Master Start and External Master Pause enabled, - * because the SSI is controlling the DMA controller. We want the DMA - * controller to be set up in advance, and then we signal only the SSI - * to start transfering. - * - * We want End-Of-Segment Interrupts enabled, because this will generate - * an interrupt at the end of each segment (each link descriptor - * represents one segment). Each DMA segment is the same thing as an - * ALSA period, so this is how we get an interrupt at the end of every - * period. - * - * We want Error Interrupt enabled, so that we can get an error if - * the DMA controller is mis-programmed somehow. - */ - mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN | - CCSR_DMA_MR_EMS_EN; - - /* For playback, we want the destination address to be held. For - capture, set the source address to be held. */ - mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? - CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE; - - out_be32(&dma_channel->mr, mr); return 0; } diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h index 430a6ce8b0d0..385d4a42603c 100644 --- a/sound/soc/fsl/fsl_dma.h +++ b/sound/soc/fsl/fsl_dma.h @@ -126,7 +126,7 @@ struct fsl_dma_link_descriptor { u8 res[4]; /* Reserved */ } __attribute__ ((aligned(32), packed)); -/* DMA information needed to create a snd_soc_cpu_dai object +/* DMA information needed to create a snd_soc_dai object * * ssi_stx_phys: bus address of SSI STX register to use * ssi_srx_phys: bus address of SSI SRX register to use diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index f588545698f3..157a7895ffa1 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -67,6 +67,8 @@ * @ssi: pointer to the SSI's registers * @ssi_phys: physical address of the SSI registers * @irq: IRQ of this SSI + * @first_stream: pointer to the stream that was opened first + * @second_stream: pointer to second stream * @dev: struct device pointer * @playback: the number of playback streams opened * @capture: the number of capture streams opened @@ -79,10 +81,12 @@ struct fsl_ssi_private { struct ccsr_ssi __iomem *ssi; dma_addr_t ssi_phys; unsigned int irq; + struct snd_pcm_substream *first_stream; + struct snd_pcm_substream *second_stream; struct device *dev; unsigned int playback; unsigned int capture; - struct snd_soc_cpu_dai cpu_dai; + struct snd_soc_dai cpu_dai; struct device_attribute dev_attr; struct { @@ -342,6 +346,49 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream) */ } + if (!ssi_private->first_stream) + ssi_private->first_stream = substream; + else { + /* This is the second stream open, so we need to impose sample + * rate and maybe sample size constraints. Note that this can + * cause a race condition if the second stream is opened before + * the first stream is fully initialized. + * + * We provide some protection by checking to make sure the first + * stream is initialized, but it's not perfect. ALSA sometimes + * re-initializes the driver with a different sample rate or + * size. If the second stream is opened before the first stream + * has received its final parameters, then the second stream may + * be constrained to the wrong sample rate or size. + * + * FIXME: This code does not handle opening and closing streams + * repeatedly. If you open two streams and then close the first + * one, you may not be able to open another stream until you + * close the second one as well. + */ + struct snd_pcm_runtime *first_runtime = + ssi_private->first_stream->runtime; + + if (!first_runtime->rate || !first_runtime->sample_bits) { + dev_err(substream->pcm->card->dev, + "set sample rate and size in %s stream first\n", + substream->stream == SNDRV_PCM_STREAM_PLAYBACK + ? "capture" : "playback"); + return -EAGAIN; + } + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + first_runtime->rate, first_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + first_runtime->sample_bits, + first_runtime->sample_bits); + + ssi_private->second_stream = substream; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ssi_private->playback++; @@ -371,18 +418,16 @@ static int fsl_ssi_prepare(struct snd_pcm_substream *substream) struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; struct ccsr_ssi __iomem *ssi = ssi_private->ssi; - u32 wl; - wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format)); + if (substream == ssi_private->first_stream) { + u32 wl; - clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + /* The SSI should always be disabled at this points (SSIEN=0) */ + wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format)); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + /* In synchronous mode, the SSI uses STCCR for capture */ clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); - else - clrsetbits_be32(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); - - setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + } return 0; } @@ -407,9 +452,13 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - setbits32(&ssi->scr, CCSR_SSI_SCR_TE); + clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + setbits32(&ssi->scr, + CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE); } else { - setbits32(&ssi->scr, CCSR_SSI_SCR_RE); + clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + setbits32(&ssi->scr, + CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE); /* * I think we need this delay to allow time for the SSI @@ -452,6 +501,11 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream) if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ssi_private->capture--; + if (ssi_private->first_stream == substream) + ssi_private->first_stream = ssi_private->second_stream; + + ssi_private->second_stream = NULL; + /* * If this is the last active substream, disable the SSI and release * the IRQ. @@ -479,7 +533,7 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream) * @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 fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, +static int fsl_ssi_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { @@ -497,7 +551,7 @@ static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, * * @format: one of SND_SOC_DAIFMT_xxx */ -static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format) +static int fsl_ssi_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) { return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; } @@ -505,7 +559,7 @@ static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format) /** * fsl_ssi_dai_template: template CPU DAI for the SSI */ -static struct snd_soc_cpu_dai fsl_ssi_dai_template = { +static struct snd_soc_dai fsl_ssi_dai_template = { .playback = { /* The SSI does not support monaural audio. */ .channels_min = 2, @@ -569,15 +623,15 @@ static ssize_t fsl_sysfs_ssi_show(struct device *dev, } /** - * fsl_ssi_create_dai: create a snd_soc_cpu_dai structure + * fsl_ssi_create_dai: create a snd_soc_dai structure * - * This function is called by the machine driver to create a snd_soc_cpu_dai + * This function is called by the machine driver to create a snd_soc_dai * structure. The function creates an ssi_private object, which contains - * the snd_soc_cpu_dai. It also creates the sysfs statistics device. + * the snd_soc_dai. It also creates the sysfs statistics device. */ -struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) +struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) { - struct snd_soc_cpu_dai *fsl_ssi_dai; + struct snd_soc_dai *fsl_ssi_dai; struct fsl_ssi_private *ssi_private; int ret = 0; struct device_attribute *dev_attr; @@ -588,7 +642,7 @@ struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) return NULL; } memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template, - sizeof(struct snd_soc_cpu_dai)); + sizeof(struct snd_soc_dai)); fsl_ssi_dai = &ssi_private->cpu_dai; dev_attr = &ssi_private->dev_attr; @@ -623,11 +677,11 @@ struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) EXPORT_SYMBOL_GPL(fsl_ssi_create_dai); /** - * fsl_ssi_destroy_dai: destroy the snd_soc_cpu_dai object + * fsl_ssi_destroy_dai: destroy the snd_soc_dai object * * This function undoes the operations of fsl_ssi_create_dai() */ -void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai) +void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai) { struct fsl_ssi_private *ssi_private = container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai); diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h index c5ce88e15651..83b44d700e33 100644 --- a/sound/soc/fsl/fsl_ssi.h +++ b/sound/soc/fsl/fsl_ssi.h @@ -217,8 +217,8 @@ struct fsl_ssi_info { struct device *dev; }; -struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info); -void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai); +struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info); +void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai); #endif diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c new file mode 100644 index 000000000000..86923299bc10 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -0,0 +1,884 @@ +/* + * 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-simple.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 + * @dai: the CPU DAI for this device + * @sicr: Base value used in serial interface control register; mode is ORed + * with this value. + * @playback: Playback stream context data + * @capture: 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_dai dai; + spinlock_t lock; + u32 sicr; + + /* per-stream data */ + struct psc_i2s_stream playback; + struct psc_i2s_stream capture; + + /* 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 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + + /* Playback underrun error */ + if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_i2s->stats.underrun_count++; + + /* Capture overrun error */ + if (psc_i2s->capture.active && (isr & 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; + + /* 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); + } + + /* 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) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + int rc; + + dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); + + if (!psc_i2s->playback.active && + !psc_i2s->capture.active) { + /* Setup the IRQs */ + rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, + "psc-i2s-status", psc_i2s); + rc |= request_irq(psc_i2s->capture.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-capture", &psc_i2s->capture); + rc |= request_irq(psc_i2s->playback.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-playback", &psc_i2s->playback); + if (rc) { + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, + &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, + &psc_i2s->playback); + 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 mode; + + dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params)); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mode = MPC52xx_PSC_SICR_SIM_CODEC_8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_16; + break; + case SNDRV_PCM_FORMAT_S24_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_24; + break; + case SNDRV_PCM_FORMAT_S32_BE: + mode = 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, psc_i2s->sicr | mode); + + 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; + else + s = &psc_i2s->playback; + + 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); + + /* 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.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.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.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_i2s->capture.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.active && + !psc_i2s->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + + /* Release irqs */ + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, &psc_i2s->playback); + } +} + +/** + * 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_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_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_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, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 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; + + 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; + else + s = &psc_i2s->playback; + + snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); + + 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; + else + s = &psc_i2s->playback; + + 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; + else + s = &psc_i2s->playback; + + 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_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) + goto playback_alloc_err; + } + + 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) + goto capture_alloc_err; + } + + return 0; + + capture_alloc_err: + if (pcm->streams[0].substream) + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + playback_alloc_err: + dev_err(card->dev, "Cannot allocate buffer(s)\n"); + return -ENOMEM; +} + +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.psc_i2s = psc_i2s; + psc_i2s->capture.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.bcom_task = + bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512); + psc_i2s->playback.bcom_task = + bcom_psc_gen_bd_tx_init(psc_id, 10, fifo); + if (!psc_i2s->capture.bcom_task || + !psc_i2s->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + iounmap(regs); + kfree(psc_i2s); + return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */ + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */ + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + + /* Configure the serial interface mode; defaulting to CODEC8 mode */ + psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + MPC52xx_PSC_SICR_CLKPOL; + if (of_get_property(op->node, "fsl,cellslave", NULL)) + psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | + MPC52xx_PSC_SICR_GENCLK; + out_be32(&psc_i2s->psc_regs->sicr, + psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); + + /* Check for the codec handle. If it is not present then we + * are done */ + if (!of_get_property(op->node, "codec-handle", NULL)) + return 0; + + /* Set up mode register; + * First write: RxRdy (FIFO Alarm) generates rx FIFO irq + * Second write: register Normal mode for non loopback + */ + out_8(&psc_i2s->psc_regs->mode, 0); + out_8(&psc_i2s->psc_regs->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100); + out_8(&psc_i2s->fifo_regs->rfcntl, 0x4); + out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100); + out_8(&psc_i2s->fifo_regs->tfcntl, 0x7); + + /* Lookup the IRQ numbers */ + psc_i2s->playback.irq = + bcom_get_task_irq(psc_i2s->playback.bcom_task); + psc_i2s->capture.irq = + bcom_get_task_irq(psc_i2s->capture.bcom_task); + + /* 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.bcom_task); + bcom_gen_bd_tx_release(psc_i2s->playback.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); + + diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index a00aac7a71f1..94f89debde1f 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -58,9 +58,9 @@ static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device) sound_device->dev.platform_data; /* Program the signal routing between the SSI and the DMA */ - guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + guts_set_dmacr(machine_data->guts, machine_data->dma_id, machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI); - guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + guts_set_dmacr(machine_data->guts, machine_data->dma_id, machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI); guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id, @@ -68,10 +68,6 @@ static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device) guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id, machine_data->dma_channel_id[1], 0); - guts_set_pmuxcr_dma(machine_data->guts, 1, 0, 0); - guts_set_pmuxcr_dma(machine_data->guts, 1, 3, 0); - guts_set_pmuxcr_dma(machine_data->guts, 0, 3, 0); - switch (machine_data->ssi_id) { case 0: clrsetbits_be32(&machine_data->guts->pmuxcr, @@ -96,62 +92,52 @@ static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device) static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct mpc8610_hpcd_data *machine_data = rtd->socdev->dev->platform_data; int ret = 0; /* Tell the CPU driver what the serial protocol is. */ - if (cpu_dai->dai_ops.set_fmt) { - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, - machine_data->dai_format); - if (ret < 0) { - dev_err(substream->pcm->card->dev, - "could not set CPU driver audio format\n"); - return ret; - } + ret = snd_soc_dai_set_fmt(cpu_dai, machine_data->dai_format); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set CPU driver audio format\n"); + return ret; } /* Tell the codec driver what the serial protocol is. */ - if (codec_dai->dai_ops.set_fmt) { - ret = codec_dai->dai_ops.set_fmt(codec_dai, - machine_data->dai_format); - if (ret < 0) { - dev_err(substream->pcm->card->dev, - "could not set codec driver audio format\n"); - return ret; - } + ret = snd_soc_dai_set_fmt(codec_dai, machine_data->dai_format); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set codec driver audio format\n"); + return ret; } /* * Tell the CPU driver what the clock frequency is, and whether it's a * slave or master. */ - if (cpu_dai->dai_ops.set_sysclk) { - ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, 0, - machine_data->clk_frequency, - machine_data->cpu_clk_direction); - if (ret < 0) { - dev_err(substream->pcm->card->dev, - "could not set CPU driver clock parameters\n"); - return ret; - } + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, + machine_data->clk_frequency, + machine_data->cpu_clk_direction); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set CPU driver clock parameters\n"); + return ret; } /* * Tell the codec driver what the MCLK frequency is, and whether it's * a slave or master. */ - if (codec_dai->dai_ops.set_sysclk) { - ret = codec_dai->dai_ops.set_sysclk(codec_dai, 0, - machine_data->clk_frequency, - machine_data->codec_clk_direction); - if (ret < 0) { - dev_err(substream->pcm->card->dev, - "could not set codec driver clock params\n"); - return ret; - } + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + machine_data->clk_frequency, + machine_data->codec_clk_direction); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set codec driver clock params\n"); + return ret; } return 0; @@ -170,9 +156,9 @@ int mpc8610_hpcd_machine_remove(struct platform_device *sound_device) /* Restore the signal routing */ - guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + guts_set_dmacr(machine_data->guts, machine_data->dma_id, machine_data->dma_channel_id[0], 0); - guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + guts_set_dmacr(machine_data->guts, machine_data->dma_id, machine_data->dma_channel_id[1], 0); switch (machine_data->ssi_id) { @@ -182,7 +168,7 @@ int mpc8610_hpcd_machine_remove(struct platform_device *sound_device) break; case 1: clrsetbits_be32(&machine_data->guts->pmuxcr, - CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI1_LA); + CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA); break; } @@ -240,6 +226,8 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, struct fsl_ssi_info ssi_info; struct fsl_dma_info dma_info; int ret = -ENODEV; + unsigned int playback_dma_channel; + unsigned int capture_dma_channel; machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); if (!machine_data) @@ -391,8 +379,9 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, goto error; } - /* Find the DMA channels to use. For now, we always use the first DMA - controller. */ + /* Find the DMA channels to use. Both SSIs need to use the same DMA + * controller, so let's use DMA#1. + */ for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") { iprop = of_get_property(dma_np, "cell-index", NULL); if (iprop && (*iprop == 0)) { @@ -407,14 +396,19 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, } machine_data->dma_id = *iprop; + /* SSI1 needs to use DMA Channels 0 and 1, and SSI2 needs to use DMA + * channels 2 and 3. This is just how the MPC8610 is wired + * internally. + */ + playback_dma_channel = (machine_data->ssi_id == 0) ? 0 : 2; + capture_dma_channel = (machine_data->ssi_id == 0) ? 1 : 3; + /* - * Find the DMA channels to use. For now, we always use DMA channel 0 - * for playback, and DMA channel 1 for capture. + * Find the DMA channels to use. */ while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) { iprop = of_get_property(dma_channel_np, "cell-index", NULL); - /* Is it DMA channel 0? */ - if (iprop && (*iprop == 0)) { + if (iprop && (*iprop == playback_dma_channel)) { /* dma_channel[0] and dma_irq[0] are for playback */ dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0); dma_info.dma_irq[0] = @@ -422,7 +416,7 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, machine_data->dma_channel_id[0] = *iprop; continue; } - if (iprop && (*iprop == 1)) { + if (iprop && (*iprop == capture_dma_channel)) { /* dma_channel[1] and dma_irq[1] are for capture */ dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0); dma_info.dma_irq[1] = diff --git a/sound/soc/fsl/soc-of-simple.c b/sound/soc/fsl/soc-of-simple.c new file mode 100644 index 000000000000..0382fdac51cd --- /dev/null +++ b/sound/soc/fsl/soc-of-simple.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-simple.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_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 = (char *)node->name; + of_soc->dai_link.stream_name = (char *)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_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); diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 0230d83e8e5e..8b7766b998d7 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -1,5 +1,3 @@ -menu "SoC Audio for the Texas Instruments OMAP" - config SND_OMAP_SOC tristate "SoC Audio for the Texas Instruments OMAP chips" depends on ARCH_OMAP && SND_SOC @@ -16,4 +14,10 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810. -endmenu +config SND_OMAP_SOC_OSK5912 + tristate "SoC Audio support for omap osk5912" + depends on SND_OMAP_SOC && MACH_OMAP_OSK + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC23 + help + Say Y if you want to add support for SoC audio on osk5912. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index d8d8d58075e3..e09d1f297f64 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -7,5 +7,7 @@ obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o # OMAP Machine Support snd-soc-n810-objs := n810.o +snd-soc-osk5912-objs := osk5912.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index 6533563a6011..fae3ad36e0bf 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -29,16 +29,16 @@ #include <sound/soc-dapm.h> #include <asm/mach-types.h> -#include <asm/arch/hardware.h> -#include <asm/arch/gpio.h> -#include <asm/arch/mcbsp.h> +#include <mach/hardware.h> +#include <linux/gpio.h> +#include <mach/mcbsp.h> #include "omap-mcbsp.h" #include "omap-pcm.h" #include "../codecs/tlv320aic3x.h" -#define RX44_HEADSET_AMP_GPIO 10 -#define RX44_SPEAKER_AMP_GPIO 101 +#define N810_HEADSET_AMP_GPIO 10 +#define N810_SPEAKER_AMP_GPIO 101 static struct clk *sys_clkout2; static struct clk *sys_clkout2_src; @@ -46,13 +46,26 @@ static struct clk *func96m_clk; static int n810_spk_func; static int n810_jack_func; +static int n810_dmic_func; static void n810_ext_control(struct snd_soc_codec *codec) { - snd_soc_dapm_set_endpoint(codec, "Ext Spk", n810_spk_func); - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", n810_jack_func); + if (n810_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + + if (n810_jack_func) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + if (n810_dmic_func) + snd_soc_dapm_enable_pin(codec, "DMic"); + else + snd_soc_dapm_disable_pin(codec, "DMic"); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); } static int n810_startup(struct snd_pcm_substream *substream) @@ -73,12 +86,12 @@ static int n810_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_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; int err; /* Set codec DAI configuration */ - err = codec_dai->dai_ops.set_fmt(codec_dai, + err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); @@ -86,7 +99,7 @@ static int n810_hw_params(struct snd_pcm_substream *substream, return err; /* Set cpu DAI configuration */ - err = cpu_dai->dai_ops.set_fmt(cpu_dai, + err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); @@ -94,7 +107,7 @@ static int n810_hw_params(struct snd_pcm_substream *substream, return err; /* Set the codec system clock for DAC and ADC */ - err = codec_dai->dai_ops.set_sysclk(codec_dai, 0, 12000000, + err = snd_soc_dai_set_sysclk(codec_dai, 0, 12000000, SND_SOC_CLOCK_IN); return err; @@ -150,13 +163,35 @@ static int n810_set_jack(struct snd_kcontrol *kcontrol, return 1; } +static int n810_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = n810_dmic_func; + + return 0; +} + +static int n810_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (n810_dmic_func == ucontrol->value.integer.value[0]) + return 0; + + n810_dmic_func = ucontrol->value.integer.value[0]; + n810_ext_control(codec); + + return 1; +} + static int n810_spk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { if (SND_SOC_DAPM_EVENT_ON(event)) - omap_set_gpio_dataout(RX44_SPEAKER_AMP_GPIO, 1); + gpio_set_value(N810_SPEAKER_AMP_GPIO, 1); else - omap_set_gpio_dataout(RX44_SPEAKER_AMP_GPIO, 0); + gpio_set_value(N810_SPEAKER_AMP_GPIO, 0); return 0; } @@ -165,9 +200,9 @@ static int n810_jack_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { if (SND_SOC_DAPM_EVENT_ON(event)) - omap_set_gpio_dataout(RX44_HEADSET_AMP_GPIO, 1); + gpio_set_value(N810_HEADSET_AMP_GPIO, 1); else - omap_set_gpio_dataout(RX44_HEADSET_AMP_GPIO, 0); + gpio_set_value(N810_HEADSET_AMP_GPIO, 0); return 0; } @@ -175,21 +210,27 @@ static int n810_jack_event(struct snd_soc_dapm_widget *w, static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = { SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event), SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event), + SND_SOC_DAPM_MIC("DMic", NULL), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { {"Headphone Jack", NULL, "HPLOUT"}, {"Headphone Jack", NULL, "HPROUT"}, {"Ext Spk", NULL, "LLOUT"}, {"Ext Spk", NULL, "RLOUT"}, + + {"DMic Rate 64", NULL, "Mic Bias 2V"}, + {"Mic Bias 2V", NULL, "DMic"}, }; static const char *spk_function[] = {"Off", "On"}; static const char *jack_function[] = {"Off", "Headphone"}; +static const char *input_function[] = {"ADC", "Digital Mic"}; static const struct soc_enum n810_enum[] = { SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), }; static const struct snd_kcontrol_new aic33_n810_controls[] = { @@ -197,6 +238,8 @@ static const struct snd_kcontrol_new aic33_n810_controls[] = { n810_get_spk, n810_set_spk), SOC_ENUM_EXT("Jack Function", n810_enum[1], n810_get_jack, n810_set_jack), + SOC_ENUM_EXT("Input Select", n810_enum[2], + n810_get_input, n810_set_input), }; static int n810_aic33_init(struct snd_soc_codec *codec) @@ -204,9 +247,9 @@ static int n810_aic33_init(struct snd_soc_codec *codec) int i, err; /* Not connected */ - snd_soc_dapm_set_endpoint(codec, "MONO_LOUT", 0); - snd_soc_dapm_set_endpoint(codec, "HPLCOM", 0); - snd_soc_dapm_set_endpoint(codec, "HPRCOM", 0); + snd_soc_dapm_nc_pin(codec, "MONO_LOUT"); + snd_soc_dapm_nc_pin(codec, "HPLCOM"); + snd_soc_dapm_nc_pin(codec, "HPRCOM"); /* Add N810 specific controls */ for (i = 0; i < ARRAY_SIZE(aic33_n810_controls); i++) { @@ -217,15 +260,13 @@ static int n810_aic33_init(struct snd_soc_codec *codec) } /* Add N810 specific widgets */ - for (i = 0; i < ARRAY_SIZE(aic33_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &aic33_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, aic33_dapm_widgets, + ARRAY_SIZE(aic33_dapm_widgets)); /* Set up N810 specific audio path audio_map */ - for (i = 0; i < ARRAY_SIZE(audio_map); i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } @@ -249,7 +290,10 @@ static struct snd_soc_machine snd_soc_machine_n810 = { /* Audio private data */ static struct aic3x_setup_data n810_aic33_setup = { + .i2c_bus = 2, .i2c_address = 0x18, + .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, + .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, }; /* Audio subsystem */ @@ -267,7 +311,7 @@ static int __init n810_soc_init(void) int err; struct device *dev; - if (!machine_is_nokia_n810()) + if (!(machine_is_nokia_n810() || machine_is_nokia_n810_wimax())) return -ENODEV; n810_snd_device = platform_device_alloc("soc-audio", -1); @@ -286,12 +330,14 @@ static int __init n810_soc_init(void) sys_clkout2_src = clk_get(dev, "sys_clkout2_src"); if (IS_ERR(sys_clkout2_src)) { dev_err(dev, "Could not get sys_clkout2_src clock\n"); - return -ENODEV; + err = PTR_ERR(sys_clkout2_src); + goto err2; } sys_clkout2 = clk_get(dev, "sys_clkout2"); if (IS_ERR(sys_clkout2)) { dev_err(dev, "Could not get sys_clkout2\n"); - goto err1; + err = PTR_ERR(sys_clkout2); + goto err3; } /* * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use @@ -300,31 +346,40 @@ static int __init n810_soc_init(void) func96m_clk = clk_get(dev, "func_96m_ck"); if (IS_ERR(func96m_clk)) { dev_err(dev, "Could not get func 96M clock\n"); - goto err2; + err = PTR_ERR(func96m_clk); + goto err4; } clk_set_parent(sys_clkout2_src, func96m_clk); clk_set_rate(sys_clkout2, 12000000); - if (omap_request_gpio(RX44_HEADSET_AMP_GPIO) < 0) + if (gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) BUG(); - if (omap_request_gpio(RX44_SPEAKER_AMP_GPIO) < 0) + if (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0) BUG(); - omap_set_gpio_direction(RX44_HEADSET_AMP_GPIO, 0); - omap_set_gpio_direction(RX44_SPEAKER_AMP_GPIO, 0); + gpio_direction_output(N810_HEADSET_AMP_GPIO, 0); + gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0); return 0; -err2: +err4: clk_put(sys_clkout2); +err3: + clk_put(sys_clkout2_src); +err2: platform_device_del(n810_snd_device); err1: platform_device_put(n810_snd_device); return err; - } static void __exit n810_soc_exit(void) { + gpio_free(N810_SPEAKER_AMP_GPIO); + gpio_free(N810_HEADSET_AMP_GPIO); + clk_put(sys_clkout2_src); + clk_put(sys_clkout2); + clk_put(func96m_clk); + platform_device_unregister(n810_snd_device); } diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 40d87e6d0de8..0a063a98a661 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -30,9 +30,9 @@ #include <sound/initval.h> #include <sound/soc.h> -#include <asm/arch/control.h> -#include <asm/arch/dma.h> -#include <asm/arch/mcbsp.h> +#include <mach/control.h> +#include <mach/dma.h> +#include <mach/mcbsp.h> #include "omap-mcbsp.h" #include "omap-pcm.h" @@ -59,12 +59,7 @@ static struct omap_mcbsp_data mcbsp_data[NUM_LINKS]; * Stream DMA parameters. DMA request line and port address are set runtime * since they are different between OMAP1 and later OMAPs */ -static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2] = { -{ - { .name = "I2S PCM Stereo out", }, - { .name = "I2S PCM Stereo in", }, -}, -}; +static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2]; #if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX) static const int omap1_dma_reqs[][2] = { @@ -84,11 +79,22 @@ static const unsigned long omap1_mcbsp_port[][2] = { static const int omap1_dma_reqs[][2] = {}; static const unsigned long omap1_mcbsp_port[][2] = {}; #endif -#if defined(CONFIG_ARCH_OMAP2420) -static const int omap2420_dma_reqs[][2] = { + +#if defined(CONFIG_ARCH_OMAP24XX) || defined(CONFIG_ARCH_OMAP34XX) +static const int omap24xx_dma_reqs[][2] = { { OMAP24XX_DMA_MCBSP1_TX, OMAP24XX_DMA_MCBSP1_RX }, { OMAP24XX_DMA_MCBSP2_TX, OMAP24XX_DMA_MCBSP2_RX }, +#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX) + { OMAP24XX_DMA_MCBSP3_TX, OMAP24XX_DMA_MCBSP3_RX }, + { OMAP24XX_DMA_MCBSP4_TX, OMAP24XX_DMA_MCBSP4_RX }, + { OMAP24XX_DMA_MCBSP5_TX, OMAP24XX_DMA_MCBSP5_RX }, +#endif }; +#else +static const int omap24xx_dma_reqs[][2] = {}; +#endif + +#if defined(CONFIG_ARCH_OMAP2420) static const unsigned long omap2420_mcbsp_port[][2] = { { OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1, OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 }, @@ -96,14 +102,47 @@ static const unsigned long omap2420_mcbsp_port[][2] = { OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 }, }; #else -static const int omap2420_dma_reqs[][2] = {}; static const unsigned long omap2420_mcbsp_port[][2] = {}; #endif +#if defined(CONFIG_ARCH_OMAP2430) +static const unsigned long omap2430_mcbsp_port[][2] = { + { OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR, + OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR, + OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DXR, + OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DXR, + OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DXR, + OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DRR }, +}; +#else +static const unsigned long omap2430_mcbsp_port[][2] = {}; +#endif + +#if defined(CONFIG_ARCH_OMAP34XX) +static const unsigned long omap34xx_mcbsp_port[][2] = { + { OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DRR }, +}; +#else +static const unsigned long omap34xx_mcbsp_port[][2] = {}; +#endif + static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); int err = 0; @@ -116,7 +155,7 @@ static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream) static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); if (!cpu_dai->active) { @@ -128,7 +167,7 @@ static void omap_mcbsp_dai_shutdown(struct snd_pcm_substream *substream) static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); int err = 0; @@ -157,7 +196,7 @@ static int omap_mcbsp_dai_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs; int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id; @@ -167,14 +206,19 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, dma = omap1_dma_reqs[bus_id][substream->stream]; port = omap1_mcbsp_port[bus_id][substream->stream]; } else if (cpu_is_omap2420()) { - dma = omap2420_dma_reqs[bus_id][substream->stream]; + dma = omap24xx_dma_reqs[bus_id][substream->stream]; port = omap2420_mcbsp_port[bus_id][substream->stream]; + } else if (cpu_is_omap2430()) { + dma = omap24xx_dma_reqs[bus_id][substream->stream]; + port = omap2430_mcbsp_port[bus_id][substream->stream]; + } else if (cpu_is_omap343x()) { + dma = omap24xx_dma_reqs[bus_id][substream->stream]; + port = omap34xx_mcbsp_port[bus_id][substream->stream]; } else { - /* - * TODO: Add support for 2430 and 3430 - */ return -ENODEV; } + omap_mcbsp_dai_dma_params[id][substream->stream].name = + substream->stream ? "Audio Capture" : "Audio Playback"; omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma; omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port; cpu_dai->dma_data = &omap_mcbsp_dai_dma_params[id][substream->stream]; @@ -223,7 +267,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, * This must be called before _set_clkdiv and _set_sysclk since McBSP register * cache is initialized here */ -static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, +static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); @@ -245,6 +289,11 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); break; + case SND_SOC_DAIFMT_DSP_A: + /* 0-bit data delay */ + regs->rcr2 |= RDATDLY(0); + regs->xcr2 |= XDATDLY(0); + break; default: /* Unsupported data format */ return -EINVAL; @@ -292,7 +341,7 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, return 0; } -static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, +static int omap_mcbsp_dai_set_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int div) { struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); @@ -310,7 +359,7 @@ static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data, int clk_id) { int sel_bit; - u16 reg; + u16 reg, reg_devconf1 = OMAP243X_CONTROL_DEVCONF1; if (cpu_class_is_omap1()) { /* OMAP1's can use only external source clock */ @@ -320,6 +369,12 @@ static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data, return 0; } + if (cpu_is_omap2420() && mcbsp_data->bus_id > 1) + return -EINVAL; + + if (cpu_is_omap343x()) + reg_devconf1 = OMAP343X_CONTROL_DEVCONF1; + switch (mcbsp_data->bus_id) { case 0: reg = OMAP2_CONTROL_DEVCONF0; @@ -329,25 +384,31 @@ static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data, reg = OMAP2_CONTROL_DEVCONF0; sel_bit = 6; break; - /* TODO: Support for ports 3 - 5 in OMAP2430 and OMAP34xx */ + case 2: + reg = reg_devconf1; + sel_bit = 0; + break; + case 3: + reg = reg_devconf1; + sel_bit = 2; + break; + case 4: + reg = reg_devconf1; + sel_bit = 4; + break; default: return -EINVAL; } - if (cpu_class_is_omap2()) { - if (clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK) { - omap_ctrl_writel(omap_ctrl_readl(reg) & - ~(1 << sel_bit), reg); - } else { - omap_ctrl_writel(omap_ctrl_readl(reg) | - (1 << sel_bit), reg); - } - } + if (clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK) + omap_ctrl_writel(omap_ctrl_readl(reg) & ~(1 << sel_bit), reg); + else + omap_ctrl_writel(omap_ctrl_readl(reg) | (1 << sel_bit), reg); return 0; } -static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, +static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { @@ -376,37 +437,49 @@ static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, return err; } -struct snd_soc_cpu_dai omap_mcbsp_dai[NUM_LINKS] = { -{ - .name = "omap-mcbsp-dai", - .id = 0, - .type = SND_SOC_DAI_I2S, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = OMAP_MCBSP_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = OMAP_MCBSP_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .ops = { - .startup = omap_mcbsp_dai_startup, - .shutdown = omap_mcbsp_dai_shutdown, - .trigger = omap_mcbsp_dai_trigger, - .hw_params = omap_mcbsp_dai_hw_params, - }, - .dai_ops = { - .set_fmt = omap_mcbsp_dai_set_dai_fmt, - .set_clkdiv = omap_mcbsp_dai_set_clkdiv, - .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, - }, - .private_data = &mcbsp_data[0].bus_id, -}, +#define OMAP_MCBSP_DAI_BUILDER(link_id) \ +{ \ + .name = "omap-mcbsp-dai-(link_id)", \ + .id = (link_id), \ + .type = SND_SOC_DAI_I2S, \ + .playback = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = OMAP_MCBSP_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .capture = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = OMAP_MCBSP_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = { \ + .startup = omap_mcbsp_dai_startup, \ + .shutdown = omap_mcbsp_dai_shutdown, \ + .trigger = omap_mcbsp_dai_trigger, \ + .hw_params = omap_mcbsp_dai_hw_params, \ + }, \ + .dai_ops = { \ + .set_fmt = omap_mcbsp_dai_set_dai_fmt, \ + .set_clkdiv = omap_mcbsp_dai_set_clkdiv, \ + .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, \ + }, \ + .private_data = &mcbsp_data[(link_id)].bus_id, \ +} + +struct snd_soc_dai omap_mcbsp_dai[] = { + OMAP_MCBSP_DAI_BUILDER(0), + OMAP_MCBSP_DAI_BUILDER(1), +#if NUM_LINKS >= 3 + OMAP_MCBSP_DAI_BUILDER(2), +#endif +#if NUM_LINKS == 5 + OMAP_MCBSP_DAI_BUILDER(3), + OMAP_MCBSP_DAI_BUILDER(4), +#endif }; + EXPORT_SYMBOL_GPL(omap_mcbsp_dai); MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>"); diff --git a/sound/soc/omap/omap-mcbsp.h b/sound/soc/omap/omap-mcbsp.h index 9965fd4b0427..df7ad13ba73d 100644 --- a/sound/soc/omap/omap-mcbsp.h +++ b/sound/soc/omap/omap-mcbsp.h @@ -38,12 +38,18 @@ enum omap_mcbsp_div { OMAP_MCBSP_CLKGDV, /* Sample rate generator divider */ }; -/* - * REVISIT: Preparation for the ASoC v2. Let the number of available links to - * be same than number of McBSP ports found in OMAP(s) we are compiling for. - */ -#define NUM_LINKS 1 +#if defined(CONFIG_ARCH_OMAP2420) +#define NUM_LINKS 2 +#endif +#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX) +#undef NUM_LINKS +#define NUM_LINKS 3 +#endif +#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX) +#undef NUM_LINKS +#define NUM_LINKS 5 +#endif -extern struct snd_soc_cpu_dai omap_mcbsp_dai[NUM_LINKS]; +extern struct snd_soc_dai omap_mcbsp_dai[NUM_LINKS]; #endif diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 62370202c649..e9084fdd2082 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -27,7 +27,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> -#include <asm/arch/dma.h> +#include <mach/dma.h> #include "omap-pcm.h" static const struct snd_pcm_hardware omap_pcm_hardware = { @@ -97,7 +97,7 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, prtd->dma_data = dma_data; err = omap_request_dma(dma_data->dma_req, dma_data->name, omap_pcm_dma_irq, substream, &prtd->dma_ch); - if (!cpu_is_omap1510()) { + if (!err & !cpu_is_omap1510()) { /* * Link channel with itself so DMA doesn't need any * reprogramming while looping the buffer @@ -147,12 +147,14 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) dma_params.src_or_dst_synch = OMAP_DMA_DST_SYNC; dma_params.src_start = runtime->dma_addr; dma_params.dst_start = dma_data->port_addr; + dma_params.dst_port = OMAP_DMA_PORT_MPUI; } else { dma_params.src_amode = OMAP_DMA_AMODE_CONSTANT; dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; dma_params.src_or_dst_synch = OMAP_DMA_SRC_SYNC; dma_params.src_start = dma_data->port_addr; dma_params.dst_start = runtime->dma_addr; + dma_params.src_port = OMAP_DMA_PORT_MPUI; } /* * Set DMA transfer frame size equal to ALSA period size and frame @@ -316,7 +318,7 @@ static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm) } } -int omap_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, +int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c new file mode 100644 index 000000000000..0fe733796898 --- /dev/null +++ b/sound/soc/omap/osk5912.c @@ -0,0 +1,232 @@ +/* + * osk5912.c -- SoC audio for OSK 5912 + * + * Copyright (C) 2008 Mistral Solutions + * + * Contact: Arun KS <arunks@mistralsolutions.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <linux/gpio.h> +#include <mach/mcbsp.h> + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/tlv320aic23.h" + +#define CODEC_CLOCK 12000000 + +static struct clk *tlv320aic23_mclk; + +static int osk_startup(struct snd_pcm_substream *substream) +{ + return clk_enable(tlv320aic23_mclk); +} + +static void osk_shutdown(struct snd_pcm_substream *substream) +{ + clk_disable(tlv320aic23_mclk); +} + +static int osk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return err; + } + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return err; + } + + /* Set the codec system clock for DAC and ADC */ + err = + snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN); + + if (err < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return err; + } + + return err; +} + +static struct snd_soc_ops osk_ops = { + .startup = osk_startup, + .hw_params = osk_hw_params, + .shutdown = osk_shutdown, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static int osk_tlv320aic23_init(struct snd_soc_codec *codec) +{ + + /* Add osk5912 specific widgets */ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + /* Set up osk5912 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Line In"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link osk_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &tlv320aic23_dai, + .init = osk_tlv320aic23_init, + .ops = &osk_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_machine snd_soc_machine_osk = { + .name = "OSK5912", + .dai_link = &osk_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device osk_snd_devdata = { + .machine = &snd_soc_machine_osk, + .platform = &omap_soc_platform, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static struct platform_device *osk_snd_device; + +static int __init osk_soc_init(void) +{ + int err; + u32 curRate; + struct device *dev; + + if (!(machine_is_omap_osk())) + return -ENODEV; + + osk_snd_device = platform_device_alloc("soc-audio", -1); + if (!osk_snd_device) + return -ENOMEM; + + platform_set_drvdata(osk_snd_device, &osk_snd_devdata); + osk_snd_devdata.dev = &osk_snd_device->dev; + *(unsigned int *)osk_dai.cpu_dai->private_data = 0; /* McBSP1 */ + err = platform_device_add(osk_snd_device); + if (err) + goto err1; + + dev = &osk_snd_device->dev; + + tlv320aic23_mclk = clk_get(dev, "mclk"); + if (IS_ERR(tlv320aic23_mclk)) { + printk(KERN_ERR "Could not get mclk clock\n"); + return -ENODEV; + } + + if (clk_get_usecount(tlv320aic23_mclk) > 0) { + /* MCLK is already in use */ + printk(KERN_WARNING + "MCLK in use at %d Hz. We change it to %d Hz\n", + (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK); + } + + /* + * Configure 12 MHz output on MCLK. + */ + curRate = (uint) clk_get_rate(tlv320aic23_mclk); + if (curRate != CODEC_CLOCK) { + if (clk_set_rate(tlv320aic23_mclk, CODEC_CLOCK)) { + printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n"); + err = -ECANCELED; + goto err1; + } + } + + printk(KERN_INFO "MCLK = %d [%d], usecount = %d\n", + (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK, + clk_get_usecount(tlv320aic23_mclk)); + + return 0; +err1: + clk_put(tlv320aic23_mclk); + platform_device_del(osk_snd_device); + platform_device_put(osk_snd_device); + + return err; + +} + +static void __exit osk_soc_exit(void) +{ + platform_device_unregister(osk_snd_device); +} + +module_init(osk_soc_init); +module_exit(osk_soc_exit); + +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_DESCRIPTION("ALSA SoC OSK 5912"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 484f883459e0..f8c1cdd940ac 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -1,6 +1,7 @@ config SND_PXA2XX_SOC tristate "SoC Audio for the Intel PXA2xx chip" - depends on ARCH_PXA && SND_SOC + depends on ARCH_PXA + select SND_PXA2XX_LIB help Say Y or M if you want to add support for codecs attached to the PXA2xx AC97, I2S or SSP interface. You will also need @@ -13,6 +14,8 @@ config SND_PXA2XX_AC97 config SND_PXA2XX_SOC_AC97 tristate select AC97_BUS + select SND_ARM + select SND_PXA2XX_LIB_AC97 select SND_SOC_AC97_BUS config SND_PXA2XX_SOC_I2S @@ -48,6 +51,7 @@ config SND_PXA2XX_SOC_POODLE config SND_PXA2XX_SOC_TOSA tristate "SoC AC97 Audio support for Tosa" depends on SND_PXA2XX_SOC && MACH_TOSA + depends on MFD_TC6393XB select SND_PXA2XX_SOC_AC97 select SND_SOC_WM9712 help @@ -62,3 +66,12 @@ config SND_PXA2XX_SOC_E800 help Say Y if you want to add support for SoC audio on the Toshiba e800 PDA + +config SND_PXA2XX_SOC_EM_X270 + tristate "SoC Audio support for CompuLab EM-x270" + depends on SND_PXA2XX_SOC && MACH_EM_X270 + select SND_PXA2XX_SOC_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for SoC audio on + CompuLab EM-x270. diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 04e5646f75ba..5bc8edf9dca9 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -13,10 +13,11 @@ snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o snd-soc-e800-objs := e800_wm9712.o snd-soc-spitz-objs := spitz.o +snd-soc-em-x270-objs := em-x270.o obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o - +obj-$(CONFIG_SND_PXA2XX_SOC_EM_X270) += snd-soc-em-x270.o diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c index 7f32a1167572..2718eaf7895f 100644 --- a/sound/soc/pxa/corgi.c +++ b/sound/soc/pxa/corgi.c @@ -4,17 +4,13 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. - * - * Revision history - * 30th Nov 2005 Initial version. - * */ #include <linux/module.h> @@ -22,17 +18,17 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> +#include <linux/gpio.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <asm/mach-types.h> -#include <asm/hardware/scoop.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/hardware.h> -#include <asm/arch/corgi.h> -#include <asm/arch/audio.h> +#include <mach/pxa-regs.h> +#include <mach/hardware.h> +#include <mach/corgi.h> +#include <mach/audio.h> #include "../codecs/wm8731.h" #include "pxa2xx-pcm.h" @@ -54,47 +50,51 @@ static int corgi_spk_func; static void corgi_ext_control(struct snd_soc_codec *codec) { - int spk = 0, mic = 0, line = 0, hp = 0, hs = 0; - /* set up jack connection */ switch (corgi_jack_func) { case CORGI_HP: - hp = 1; /* set = unmute headphone */ - set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); - set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); + gpio_set_value(CORGI_GPIO_MUTE_L, 1); + gpio_set_value(CORGI_GPIO_MUTE_R, 1); + snd_soc_dapm_disable_pin(codec, "Mic Jack"); + snd_soc_dapm_disable_pin(codec, "Line Jack"); + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); break; case CORGI_MIC: - mic = 1; /* reset = mute headphone */ - reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); - reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); + gpio_set_value(CORGI_GPIO_MUTE_L, 0); + gpio_set_value(CORGI_GPIO_MUTE_R, 0); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + snd_soc_dapm_disable_pin(codec, "Line Jack"); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); break; case CORGI_LINE: - line = 1; - reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); - reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); + gpio_set_value(CORGI_GPIO_MUTE_L, 0); + gpio_set_value(CORGI_GPIO_MUTE_R, 0); + snd_soc_dapm_disable_pin(codec, "Mic Jack"); + snd_soc_dapm_enable_pin(codec, "Line Jack"); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); break; case CORGI_HEADSET: - hs = 1; - mic = 1; - reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); - set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); + gpio_set_value(CORGI_GPIO_MUTE_L, 0); + gpio_set_value(CORGI_GPIO_MUTE_R, 1); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + snd_soc_dapm_disable_pin(codec, "Line Jack"); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Headset Jack"); break; } if (corgi_spk_func == CORGI_SPK_ON) - spk = 1; - - /* set the enpoints to their new connetion states */ - snd_soc_dapm_set_endpoint(codec, "Ext Spk", spk); - snd_soc_dapm_set_endpoint(codec, "Mic Jack", mic); - snd_soc_dapm_set_endpoint(codec, "Line Jack", line); - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", hp); - snd_soc_dapm_set_endpoint(codec, "Headset Jack", hs); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); /* signal a DAPM event */ - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); } static int corgi_startup(struct snd_pcm_substream *substream) @@ -114,8 +114,8 @@ static int corgi_shutdown(struct snd_pcm_substream *substream) struct snd_soc_codec *codec = rtd->socdev->codec; /* set = unmute headphone */ - set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_L); - set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MUTE_R); + gpio_set_value(CORGI_GPIO_MUTE_L, 1); + gpio_set_value(CORGI_GPIO_MUTE_R, 1); return 0; } @@ -123,8 +123,8 @@ static int corgi_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_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; unsigned int clk = 0; int ret = 0; @@ -143,25 +143,25 @@ static int corgi_hw_params(struct snd_pcm_substream *substream, } /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* set cpu DAI configuration */ - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* set the codec system clock for DAC and ADC */ - ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8731_SYSCLK, clk, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, clk, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* set the I2S system clock as input (unused) */ - ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, SND_SOC_CLOCK_IN); if (ret < 0) return ret; @@ -218,22 +218,14 @@ static int corgi_set_spk(struct snd_kcontrol *kcontrol, static int corgi_amp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - if (SND_SOC_DAPM_EVENT_ON(event)) - set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_APM_ON); - else - reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_APM_ON); - + gpio_set_value(CORGI_GPIO_APM_ON, SND_SOC_DAPM_EVENT_ON(event)); return 0; } static int corgi_mic_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - if (SND_SOC_DAPM_EVENT_ON(event)) - set_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MIC_BIAS); - else - reset_scoop_gpio(&corgiscoop_device.dev, CORGI_SCP_MIC_BIAS); - + gpio_set_value(CORGI_GPIO_MIC_BIAS, SND_SOC_DAPM_EVENT_ON(event)); return 0; } @@ -247,7 +239,7 @@ SND_SOC_DAPM_HP("Headset Jack", NULL), }; /* Corgi machine audio map (connections to the codec pins) */ -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* headset Jack - in = micin, out = LHPOUT*/ {"Headset Jack", NULL, "LHPOUT"}, @@ -265,8 +257,6 @@ static const char *audio_map[][3] = { /* Same as the above but no mic bias for line signals */ {"MICIN", NULL, "Line Jack"}, - - {NULL, NULL, NULL}, }; static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", @@ -291,8 +281,8 @@ static int corgi_wm8731_init(struct snd_soc_codec *codec) { int i, err; - snd_soc_dapm_set_endpoint(codec, "LLINEIN", 0); - snd_soc_dapm_set_endpoint(codec, "RLINEIN", 0); + snd_soc_dapm_nc_pin(codec, "LLINEIN"); + snd_soc_dapm_nc_pin(codec, "RLINEIN"); /* Add corgi specific controls */ for (i = 0; i < ARRAY_SIZE(wm8731_corgi_controls); i++) { @@ -303,15 +293,13 @@ static int corgi_wm8731_init(struct snd_soc_codec *codec) } /* Add corgi specific widgets */ - for (i = 0; i < ARRAY_SIZE(wm8731_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8731_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets, + ARRAY_SIZE(wm8731_dapm_widgets)); /* Set up corgi specific audio path audio_map */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } @@ -334,6 +322,7 @@ static struct snd_soc_machine snd_soc_machine_corgi = { /* corgi audio private data */ static struct wm8731_setup_data corgi_wm8731_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/pxa/e800_wm9712.c b/sound/soc/pxa/e800_wm9712.c index 06e8afb25277..6781c5be242f 100644 --- a/sound/soc/pxa/e800_wm9712.c +++ b/sound/soc/pxa/e800_wm9712.c @@ -21,9 +21,9 @@ #include <sound/soc-dapm.h> #include <asm/mach-types.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/hardware.h> -#include <asm/arch/audio.h> +#include <mach/pxa-regs.h> +#include <mach/hardware.h> +#include <mach/audio.h> #include "../codecs/wm9712.h" #include "pxa2xx-pcm.h" diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c new file mode 100644 index 000000000000..e6ff6929ab4b --- /dev/null +++ b/sound/soc/pxa/em-x270.c @@ -0,0 +1,102 @@ +/* + * em-x270.c -- SoC audio for EM-X270 + * + * Copyright 2007 CompuLab, Ltd. + * + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Copied from tosa.c: + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> + * Richard Purdie <richard@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/pxa-regs.h> +#include <mach/hardware.h> +#include <mach/audio.h> + +#include "../codecs/wm9712.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ac97.h" + +static struct snd_soc_dai_link em_x270_dai[] = { + { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], + }, + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], + }, +}; + +static struct snd_soc_machine em_x270 = { + .name = "EM-X270", + .dai_link = em_x270_dai, + .num_links = ARRAY_SIZE(em_x270_dai), +}; + +static struct snd_soc_device em_x270_snd_devdata = { + .machine = &em_x270, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm9712, +}; + +static struct platform_device *em_x270_snd_device; + +static int __init em_x270_init(void) +{ + int ret; + + if (!machine_is_em_x270()) + return -ENODEV; + + em_x270_snd_device = platform_device_alloc("soc-audio", -1); + if (!em_x270_snd_device) + return -ENOMEM; + + platform_set_drvdata(em_x270_snd_device, &em_x270_snd_devdata); + em_x270_snd_devdata.dev = &em_x270_snd_device->dev; + ret = platform_device_add(em_x270_snd_device); + + if (ret) + platform_device_put(em_x270_snd_device); + + return ret; +} + +static void __exit em_x270_exit(void) +{ + platform_device_unregister(em_x270_snd_device); +} + +module_init(em_x270_init); +module_exit(em_x270_exit); + +/* Module information */ +MODULE_AUTHOR("Mike Rapoport"); +MODULE_DESCRIPTION("ALSA SoC EM-X270"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index 7e830b218943..4d9930c52789 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -4,7 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it @@ -26,10 +26,10 @@ #include <asm/mach-types.h> #include <asm/hardware/locomo.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/hardware.h> -#include <asm/arch/poodle.h> -#include <asm/arch/audio.h> +#include <mach/pxa-regs.h> +#include <mach/hardware.h> +#include <mach/poodle.h> +#include <mach/audio.h> #include "../codecs/wm8731.h" #include "pxa2xx-pcm.h" @@ -48,8 +48,6 @@ static int poodle_spk_func; static void poodle_ext_control(struct snd_soc_codec *codec) { - int spk = 0; - /* set up jack connection */ if (poodle_jack_func == POODLE_HP) { /* set = unmute headphone */ @@ -57,23 +55,23 @@ static void poodle_ext_control(struct snd_soc_codec *codec) POODLE_LOCOMO_GPIO_MUTE_L, 1); locomo_gpio_write(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_MUTE_R, 1); - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); } else { locomo_gpio_write(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_MUTE_L, 0); locomo_gpio_write(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_MUTE_R, 0); - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); } - if (poodle_spk_func == POODLE_SPK_ON) - spk = 1; - /* set the enpoints to their new connetion states */ - snd_soc_dapm_set_endpoint(codec, "Ext Spk", spk); + if (poodle_spk_func == POODLE_SPK_ON) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); /* signal a DAPM event */ - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); } static int poodle_startup(struct snd_pcm_substream *substream) @@ -87,25 +85,21 @@ static int poodle_startup(struct snd_pcm_substream *substream) } /* we need to unmute the HP at shutdown as the mute burns power on poodle */ -static int poodle_shutdown(struct snd_pcm_substream *substream) +static void poodle_shutdown(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->socdev->codec; - /* set = unmute headphone */ locomo_gpio_write(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_MUTE_L, 1); locomo_gpio_write(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_MUTE_R, 1); - return 0; } static int poodle_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_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; unsigned int clk = 0; int ret = 0; @@ -124,25 +118,25 @@ static int poodle_hw_params(struct snd_pcm_substream *substream, } /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* set cpu DAI configuration */ - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* set the codec system clock for DAC and ADC */ - ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8731_SYSCLK, clk, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, clk, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* set the I2S system clock as input (unused) */ - ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, SND_SOC_CLOCK_IN); if (ret < 0) return ret; @@ -215,8 +209,8 @@ SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event), }; -/* Corgi machine audio_mapnections to the codec pins */ -static const char *audio_map[][3] = { +/* Corgi machine connections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { /* headphone connected to LHPOUT1, RHPOUT1 */ {"Headphone Jack", NULL, "LHPOUT"}, @@ -225,8 +219,6 @@ static const char *audio_map[][3] = { /* speaker connected to LOUT, ROUT */ {"Ext Spk", NULL, "ROUT"}, {"Ext Spk", NULL, "LOUT"}, - - {NULL, NULL, NULL}, }; static const char *jack_function[] = {"Off", "Headphone"}; @@ -236,7 +228,7 @@ static const struct soc_enum poodle_enum[] = { SOC_ENUM_SINGLE_EXT(2, spk_function), }; -static const snd_kcontrol_new_t wm8731_poodle_controls[] = { +static const struct snd_kcontrol_new wm8731_poodle_controls[] = { SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack, poodle_set_jack), SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk, @@ -250,9 +242,9 @@ static int poodle_wm8731_init(struct snd_soc_codec *codec) { int i, err; - snd_soc_dapm_set_endpoint(codec, "LLINEIN", 0); - snd_soc_dapm_set_endpoint(codec, "RLINEIN", 0); - snd_soc_dapm_set_endpoint(codec, "MICIN", 1); + snd_soc_dapm_nc_pin(codec, "LLINEIN"); + snd_soc_dapm_nc_pin(codec, "RLINEIN"); + snd_soc_dapm_enable_pin(codec, "MICIN"); /* Add poodle specific controls */ for (i = 0; i < ARRAY_SIZE(wm8731_poodle_controls); i++) { @@ -263,15 +255,13 @@ static int poodle_wm8731_init(struct snd_soc_codec *codec) } /* Add poodle specific widgets */ - for (i = 0; i < ARRAY_SIZE(wm8731_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8731_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets, + ARRAY_SIZE(wm8731_dapm_widgets)); /* Set up poodle specific audio path audio_map */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } @@ -294,6 +284,7 @@ static struct snd_soc_machine snd_soc_machine_poodle = { /* poodle audio private data */ static struct wm8731_setup_data poodle_wm8731_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 97ec2d90547c..a7a3a9c5c6ff 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -13,225 +13,30 @@ #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/interrupt.h> -#include <linux/wait.h> -#include <linux/clk.h> -#include <linux/delay.h> #include <sound/core.h> -#include <sound/pcm.h> #include <sound/ac97_codec.h> -#include <sound/initval.h> #include <sound/soc.h> +#include <sound/pxa2xx-lib.h> -#include <asm/irq.h> -#include <linux/mutex.h> -#include <asm/hardware.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/pxa2xx-gpio.h> -#include <asm/arch/audio.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> #include "pxa2xx-pcm.h" #include "pxa2xx-ac97.h" -static DEFINE_MUTEX(car_mutex); -static DECLARE_WAIT_QUEUE_HEAD(gsr_wq); -static volatile long gsr_bits; -static struct clk *ac97_clk; -#ifdef CONFIG_PXA27x -static struct clk *ac97conf_clk; -#endif - -/* - * Beware PXA27x bugs: - * - * o Slot 12 read from modem space will hang controller. - * o CDONE, SDONE interrupt fails after any slot 12 IO. - * - * We therefore have an hybrid approach for waiting on SDONE (interrupt or - * 1 jiffy timeout if interrupt never comes). - */ - -static unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, - unsigned short reg) -{ - unsigned short val = -1; - volatile u32 *reg_addr; - - mutex_lock(&car_mutex); - - /* set up primary or secondary codec/modem space */ -#if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx) - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#else - if (reg == AC97_GPIO_STATUS) - reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE; - else - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#endif - reg_addr += (reg >> 1); - -#ifndef CONFIG_PXA27x - if (reg == AC97_GPIO_STATUS) { - /* read from controller cache */ - val = *reg_addr; - goto out; - } -#endif - - /* start read access across the ac97 link */ - GSR = GSR_CDONE | GSR_SDONE; - gsr_bits = 0; - val = *reg_addr; - - wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1); - if (!((GSR | gsr_bits) & GSR_SDONE)) { - printk(KERN_ERR "%s: read error (ac97_reg=%x GSR=%#lx)\n", - __func__, reg, GSR | gsr_bits); - val = -1; - goto out; - } - - /* valid data now */ - GSR = GSR_CDONE | GSR_SDONE; - gsr_bits = 0; - val = *reg_addr; - /* but we've just started another cycle... */ - wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1); - -out: mutex_unlock(&car_mutex); - return val; -} - -static void pxa2xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg, - unsigned short val) -{ - volatile u32 *reg_addr; - - mutex_lock(&car_mutex); - - /* set up primary or secondary codec/modem space */ -#if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx) - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#else - if (reg == AC97_GPIO_STATUS) - reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE; - else - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#endif - reg_addr += (reg >> 1); - - GSR = GSR_CDONE | GSR_SDONE; - gsr_bits = 0; - *reg_addr = val; - wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_CDONE, 1); - if (!((GSR | gsr_bits) & GSR_CDONE)) - printk(KERN_ERR "%s: write error (ac97_reg=%x GSR=%#lx)\n", - __func__, reg, GSR | gsr_bits); - - mutex_unlock(&car_mutex); -} - static void pxa2xx_ac97_warm_reset(struct snd_ac97 *ac97) { -#ifdef CONFIG_PXA3xx - int timeout = 100; -#endif - gsr_bits = 0; - -#ifdef CONFIG_PXA27x - /* warm reset broken on Bulverde, - so manually keep AC97 reset high */ - pxa_gpio_mode(113 | GPIO_OUT | GPIO_DFLT_HIGH); - udelay(10); - GCR |= GCR_WARM_RST; - pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT); - udelay(500); -#elif defined(CONFIG_PXA3xx) - /* Can't use interrupts */ - GCR |= GCR_WARM_RST; - while (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--) - mdelay(1); -#else - GCR |= GCR_WARM_RST | GCR_PRIRDY_IEN | GCR_SECRDY_IEN; - wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1); -#endif - - if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) - printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n", - __func__, gsr_bits); + pxa2xx_ac97_try_warm_reset(ac97); - GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); - GCR |= GCR_SDONE_IE|GCR_CDONE_IE; + pxa2xx_ac97_finish_reset(ac97); } static void pxa2xx_ac97_cold_reset(struct snd_ac97 *ac97) { -#ifdef CONFIG_PXA3xx - int timeout = 1000; - - /* Hold CLKBPB for 100us */ - GCR = 0; - GCR = GCR_CLKBPB; - udelay(100); - GCR = 0; -#endif + pxa2xx_ac97_try_cold_reset(ac97); - GCR &= GCR_COLD_RST; /* clear everything but nCRST */ - GCR &= ~GCR_COLD_RST; /* then assert nCRST */ - - gsr_bits = 0; -#ifdef CONFIG_PXA27x - /* PXA27x Developers Manual section 13.5.2.2.1 */ - clk_enable(ac97conf_clk); - udelay(5); - clk_disable(ac97conf_clk); - GCR = GCR_COLD_RST; - udelay(50); -#elif defined(CONFIG_PXA3xx) - /* Can't use interrupts on PXA3xx */ - GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); - - GCR = GCR_WARM_RST | GCR_COLD_RST; - while (!(GSR & (GSR_PCR | GSR_SCR)) && timeout--) - mdelay(10); -#else - GCR = GCR_COLD_RST; - GCR |= GCR_CDONE_IE|GCR_SDONE_IE; - wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1); -#endif - - if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) - printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n", - __func__, gsr_bits); - - GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); - GCR |= GCR_SDONE_IE|GCR_CDONE_IE; -} - -static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id) -{ - long status; - - status = GSR; - if (status) { - GSR = status; - gsr_bits |= status; - wake_up(&gsr_wq); - -#ifdef CONFIG_PXA27x - /* Although we don't use those we still need to clear them - since they tend to spuriously trigger when MMC is used - (hardware bug? go figure)... */ - MISR = MISR_EOC; - PISR = PISR_EOC; - MCSR = MCSR_EOC; -#endif - - return IRQ_HANDLED; - } - - return IRQ_NONE; + pxa2xx_ac97_finish_reset(ac97); } struct snd_ac97_bus_ops soc_ac97_ops = { @@ -244,7 +49,7 @@ struct snd_ac97_bus_ops soc_ac97_ops = { static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_out = { .name = "AC97 PCM Stereo out", .dev_addr = __PREG(PCDR), - .drcmr = &DRCMRTXPCDR, + .drcmr = &DRCMR(12), .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | DCMD_BURST32 | DCMD_WIDTH4, }; @@ -252,7 +57,7 @@ static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_out = { static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_in = { .name = "AC97 PCM Stereo in", .dev_addr = __PREG(PCDR), - .drcmr = &DRCMRRXPCDR, + .drcmr = &DRCMR(11), .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_BURST32 | DCMD_WIDTH4, }; @@ -260,7 +65,7 @@ static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_stereo_in = { static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_out = { .name = "AC97 Aux PCM (Slot 5) Mono out", .dev_addr = __PREG(MODR), - .drcmr = &DRCMRTXMODR, + .drcmr = &DRCMR(10), .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | DCMD_BURST16 | DCMD_WIDTH2, }; @@ -268,7 +73,7 @@ static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_out = { static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_in = { .name = "AC97 Aux PCM (Slot 5) Mono in", .dev_addr = __PREG(MODR), - .drcmr = &DRCMRRXMODR, + .drcmr = &DRCMR(9), .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_BURST16 | DCMD_WIDTH2, }; @@ -276,33 +81,22 @@ static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_aux_mono_in = { static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_mic_mono_in = { .name = "AC97 Mic PCM (Slot 6) Mono in", .dev_addr = __PREG(MCDR), - .drcmr = &DRCMRRXMCDR, + .drcmr = &DRCMR(8), .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_BURST16 | DCMD_WIDTH2, }; #ifdef CONFIG_PM static int pxa2xx_ac97_suspend(struct platform_device *pdev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { - GCR |= GCR_ACLINK_OFF; - clk_disable(ac97_clk); - return 0; + return pxa2xx_ac97_hw_suspend(); } static int pxa2xx_ac97_resume(struct platform_device *pdev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { - pxa_gpio_mode(GPIO31_SYNC_AC97_MD); - pxa_gpio_mode(GPIO30_SDATA_OUT_AC97_MD); - pxa_gpio_mode(GPIO28_BITCLK_AC97_MD); - pxa_gpio_mode(GPIO29_SDATA_IN_AC97_MD); -#ifdef CONFIG_PXA27x - /* Use GPIO 113 as AC97 Reset on Bulverde */ - pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT); -#endif - clk_enable(ac97_clk); - return 0; + return pxa2xx_ac97_hw_resume(); } #else @@ -310,69 +104,23 @@ static int pxa2xx_ac97_resume(struct platform_device *pdev, #define pxa2xx_ac97_resume NULL #endif -static int pxa2xx_ac97_probe(struct platform_device *pdev) +static int pxa2xx_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) { - int ret; - - ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, IRQF_DISABLED, "AC97", NULL); - if (ret < 0) - goto err; - - pxa_gpio_mode(GPIO31_SYNC_AC97_MD); - pxa_gpio_mode(GPIO30_SDATA_OUT_AC97_MD); - pxa_gpio_mode(GPIO28_BITCLK_AC97_MD); - pxa_gpio_mode(GPIO29_SDATA_IN_AC97_MD); -#ifdef CONFIG_PXA27x - /* Use GPIO 113 as AC97 Reset on Bulverde */ - pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT); - - ac97conf_clk = clk_get(&pdev->dev, "AC97CONFCLK"); - if (IS_ERR(ac97conf_clk)) { - ret = PTR_ERR(ac97conf_clk); - ac97conf_clk = NULL; - goto err_irq; - } -#endif - ac97_clk = clk_get(&pdev->dev, "AC97CLK"); - if (IS_ERR(ac97_clk)) { - ret = PTR_ERR(ac97_clk); - ac97_clk = NULL; - goto err_irq; - } - clk_enable(ac97_clk); - return 0; - - err_irq: - GCR |= GCR_ACLINK_OFF; -#ifdef CONFIG_PXA27x - if (ac97conf_clk) { - clk_put(ac97conf_clk); - ac97conf_clk = NULL; - } -#endif - free_irq(IRQ_AC97, NULL); - err: - return ret; + return pxa2xx_ac97_hw_probe(pdev); } -static void pxa2xx_ac97_remove(struct platform_device *pdev) +static void pxa2xx_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) { - GCR |= GCR_ACLINK_OFF; - free_irq(IRQ_AC97, NULL); -#ifdef CONFIG_PXA27x - clk_put(ac97conf_clk); - ac97conf_clk = NULL; -#endif - clk_disable(ac97_clk); - clk_put(ac97_clk); - ac97_clk = NULL; + pxa2xx_ac97_hw_remove(pdev); } static int pxa2xx_ac97_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) cpu_dai->dma_data = &pxa2xx_ac97_pcm_stereo_out; @@ -386,7 +134,7 @@ static int pxa2xx_ac97_hw_aux_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) cpu_dai->dma_data = &pxa2xx_ac97_pcm_aux_mono_out; @@ -400,7 +148,7 @@ static int pxa2xx_ac97_hw_mic_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return -ENODEV; @@ -418,7 +166,7 @@ static int pxa2xx_ac97_hw_mic_params(struct snd_pcm_substream *substream, * There is only 1 physical AC97 interface for pxa2xx, but it * has extra fifo's that can be used for aux DACs and ADCs. */ -struct snd_soc_cpu_dai pxa_ac97_dai[] = { +struct snd_soc_dai pxa_ac97_dai[] = { { .name = "pxa2xx-ac97", .id = 0, diff --git a/sound/soc/pxa/pxa2xx-ac97.h b/sound/soc/pxa/pxa2xx-ac97.h index b8ccfee095c4..e390de8edcd4 100644 --- a/sound/soc/pxa/pxa2xx-ac97.h +++ b/sound/soc/pxa/pxa2xx-ac97.h @@ -14,7 +14,7 @@ #define PXA2XX_DAI_AC97_AUX 1 #define PXA2XX_DAI_AC97_MIC 2 -extern struct snd_soc_cpu_dai pxa_ac97_dai[3]; +extern struct snd_soc_dai pxa_ac97_dai[3]; /* platform data */ extern struct snd_ac97_bus_ops pxa2xx_ac97_ops; diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 425071030970..e758034db5c3 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -3,34 +3,82 @@ * * Copyright 2005 Wolfson Microelectronics PLC. * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * lrg@slimlogic.co.uk * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. - * - * Revision history - * 12th Aug 2005 Initial version. */ #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/delay.h> +#include <linux/clk.h> +#include <linux/platform_device.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/initval.h> #include <sound/soc.h> +#include <sound/pxa2xx-lib.h> -#include <asm/hardware.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/pxa2xx-gpio.h> -#include <asm/arch/audio.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> +#include <mach/pxa2xx-gpio.h> +#include <mach/audio.h> #include "pxa2xx-pcm.h" #include "pxa2xx-i2s.h" +struct pxa2xx_gpio { + u32 sys; + u32 rx; + u32 tx; + u32 clk; + u32 frm; +}; + +/* + * I2S Controller Register and Bit Definitions + */ +#define SACR0 __REG(0x40400000) /* Global Control Register */ +#define SACR1 __REG(0x40400004) /* Serial Audio I 2 S/MSB-Justified Control Register */ +#define SASR0 __REG(0x4040000C) /* Serial Audio I 2 S/MSB-Justified Interface and FIFO Status Register */ +#define SAIMR __REG(0x40400014) /* Serial Audio Interrupt Mask Register */ +#define SAICR __REG(0x40400018) /* Serial Audio Interrupt Clear Register */ +#define SADIV __REG(0x40400060) /* Audio Clock Divider Register. */ +#define SADR __REG(0x40400080) /* Serial Audio Data Register (TX and RX FIFO access Register). */ + +#define SACR0_RFTH(x) ((x) << 12) /* Rx FIFO Interrupt or DMA Trigger Threshold */ +#define SACR0_TFTH(x) ((x) << 8) /* Tx FIFO Interrupt or DMA Trigger Threshold */ +#define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */ +#define SACR0_EFWR (1 << 4) /* Enable EFWR Function */ +#define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */ +#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */ +#define SACR0_ENB (1 << 0) /* Enable I2S Link */ +#define SACR1_ENLBF (1 << 5) /* Enable Loopback */ +#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */ +#define SACR1_DREC (1 << 3) /* Disable Recording Function */ +#define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */ + +#define SASR0_I2SOFF (1 << 7) /* Controller Status */ +#define SASR0_ROR (1 << 6) /* Rx FIFO Overrun */ +#define SASR0_TUR (1 << 5) /* Tx FIFO Underrun */ +#define SASR0_RFS (1 << 4) /* Rx FIFO Service Request */ +#define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */ +#define SASR0_BSY (1 << 2) /* I2S Busy */ +#define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */ +#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */ + +#define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */ +#define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */ + +#define SAIMR_ROR (1 << 6) /* Enable Rx FIFO Overrun Condition Interrupt */ +#define SAIMR_TUR (1 << 5) /* Enable Tx FIFO Underrun Condition Interrupt */ +#define SAIMR_RFS (1 << 4) /* Enable Rx FIFO Service Interrupt */ +#define SAIMR_TFS (1 << 3) /* Enable Tx FIFO Service Interrupt */ + struct pxa_i2s_port { u32 sadiv; u32 sacr0; @@ -40,11 +88,12 @@ struct pxa_i2s_port { u32 fmt; }; static struct pxa_i2s_port pxa_i2s; +static struct clk *clk_i2s; static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = { .name = "I2S PCM Stereo out", .dev_addr = __PREG(SADR), - .drcmr = &DRCMRTXSADR, + .drcmr = &DRCMR(3), .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | DCMD_BURST32 | DCMD_WIDTH4, }; @@ -52,7 +101,7 @@ static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = { static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_in = { .name = "I2S PCM Stereo in", .dev_addr = __PREG(SADR), - .drcmr = &DRCMRRXSADR, + .drcmr = &DRCMR(2), .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_BURST32 | DCMD_WIDTH4, }; @@ -65,11 +114,6 @@ static struct pxa2xx_gpio gpio_bus[] = { .frm = GPIO31_SYNC_I2S_MD, }, { /* I2S SoC Master */ -#ifdef CONFIG_PXA27x - .sys = GPIO113_I2S_SYSCLK_MD, -#else - .sys = GPIO32_SYSCLK_I2S_MD, -#endif .rx = GPIO29_SDATA_IN_I2S_MD, .tx = GPIO30_SDATA_OUT_I2S_MD, .clk = GPIO28_BITCLK_OUT_I2S_MD, @@ -80,7 +124,10 @@ static struct pxa2xx_gpio gpio_bus[] = { static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + if (IS_ERR(clk_i2s)) + return PTR_ERR(clk_i2s); if (!cpu_dai->active) { SACR0 |= SACR0_RST; @@ -101,7 +148,7 @@ static int pxa_i2s_wait(void) return 0; } -static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, +static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { /* interface format */ @@ -127,7 +174,7 @@ static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, return 0; } -static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, +static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { if (clk_id != PXA2XX_I2S_SYSCLK) @@ -143,13 +190,14 @@ static int pxa2xx_i2s_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; pxa_gpio_mode(gpio_bus[pxa_i2s.master].rx); pxa_gpio_mode(gpio_bus[pxa_i2s.master].tx); pxa_gpio_mode(gpio_bus[pxa_i2s.master].frm); pxa_gpio_mode(gpio_bus[pxa_i2s.master].clk); - pxa_set_cken(CKEN_I2S, 1); + BUG_ON(IS_ERR(clk_i2s)); + clk_enable(clk_i2s); pxa_i2s_wait(); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -234,13 +282,15 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream) if (SACR1 & (SACR1_DREC | SACR1_DRPL)) { SACR0 &= ~SACR0_ENB; pxa_i2s_wait(); - pxa_set_cken(CKEN_I2S, 0); + clk_disable(clk_i2s); } + + clk_put(clk_i2s); } #ifdef CONFIG_PM static int pxa2xx_i2s_suspend(struct platform_device *dev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { if (!dai->active) return 0; @@ -258,7 +308,7 @@ static int pxa2xx_i2s_suspend(struct platform_device *dev, } static int pxa2xx_i2s_resume(struct platform_device *pdev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { if (!dai->active) return 0; @@ -283,7 +333,7 @@ static int pxa2xx_i2s_resume(struct platform_device *pdev, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) -struct snd_soc_cpu_dai pxa_i2s_dai = { +struct snd_soc_dai pxa_i2s_dai = { .name = "pxa2xx-i2s", .id = 0, .type = SND_SOC_DAI_I2S, @@ -312,7 +362,49 @@ struct snd_soc_cpu_dai pxa_i2s_dai = { EXPORT_SYMBOL_GPL(pxa_i2s_dai); +static int pxa2xx_i2s_probe(struct platform_device *dev) +{ + clk_i2s = clk_get(&dev->dev, "I2SCLK"); + return IS_ERR(clk_i2s) ? PTR_ERR(clk_i2s) : 0; +} + +static int __devexit pxa2xx_i2s_remove(struct platform_device *dev) +{ + clk_put(clk_i2s); + clk_i2s = ERR_PTR(-ENOENT); + return 0; +} + +static struct platform_driver pxa2xx_i2s_driver = { + .probe = pxa2xx_i2s_probe, + .remove = __devexit_p(pxa2xx_i2s_remove), + + .driver = { + .name = "pxa2xx-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init pxa2xx_i2s_init(void) +{ + if (cpu_is_pxa27x()) + gpio_bus[1].sys = GPIO113_I2S_SYSCLK_MD; + else + gpio_bus[1].sys = GPIO32_SYSCLK_I2S_MD; + + clk_i2s = ERR_PTR(-ENOENT); + return platform_driver_register(&pxa2xx_i2s_driver); +} + +static void __exit pxa2xx_i2s_exit(void) +{ + platform_driver_unregister(&pxa2xx_i2s_driver); +} + +module_init(pxa2xx_i2s_init); +module_exit(pxa2xx_i2s_exit); + /* Module information */ -MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("pxa2xx I2S SoC Interface"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/pxa2xx-i2s.h b/sound/soc/pxa/pxa2xx-i2s.h index 4435bd9f884f..e2def441153e 100644 --- a/sound/soc/pxa/pxa2xx-i2s.h +++ b/sound/soc/pxa/pxa2xx-i2s.h @@ -15,6 +15,6 @@ /* I2S clock */ #define PXA2XX_I2S_SYSCLK 0 -extern struct snd_soc_cpu_dai pxa_i2s_dai; +extern struct snd_soc_dai pxa_i2s_dai; #endif diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 01ad7bf716b7..afcd892cd2fa 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -10,64 +10,14 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/slab.h> #include <linux/dma-mapping.h> #include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> - -#include <asm/dma.h> -#include <asm/hardware.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/audio.h> +#include <sound/pxa2xx-lib.h> #include "pxa2xx-pcm.h" - -static const struct snd_pcm_hardware pxa2xx_pcm_hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE | - SNDRV_PCM_FMTBIT_S32_LE, - .period_bytes_min = 32, - .period_bytes_max = 8192 - 32, - .periods_min = 1, - .periods_max = PAGE_SIZE/sizeof(pxa_dma_desc), - .buffer_bytes_max = 128 * 1024, - .fifo_size = 32, -}; - -struct pxa2xx_runtime_data { - int dma_ch; - struct pxa2xx_pcm_dma_params *params; - pxa_dma_desc *dma_desc_array; - dma_addr_t dma_desc_array_phys; -}; - -static void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id) -{ - struct snd_pcm_substream *substream = dev_id; - struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - int dcsr; - - dcsr = DCSR(dma_ch); - DCSR(dma_ch) = dcsr & ~DCSR_STOPIRQEN; - - if (dcsr & DCSR_ENDINTR) { - snd_pcm_period_elapsed(substream); - } else { - printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n", - prtd->params->name, dma_ch, dcsr); - } -} +#include "../../arm/pxa2xx-pcm.h" static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) @@ -76,10 +26,6 @@ static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, struct pxa2xx_runtime_data *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct pxa2xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data; - size_t totsize = params_buffer_bytes(params); - size_t period = params_period_bytes(params); - pxa_dma_desc *dma_desc; - dma_addr_t dma_buff_phys, next_desc_phys; int ret; /* return if this is a bufferless transfer e.g. @@ -106,42 +52,16 @@ static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, prtd->dma_ch = ret; } - snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); - runtime->dma_bytes = totsize; - - dma_desc = prtd->dma_desc_array; - next_desc_phys = prtd->dma_desc_array_phys; - dma_buff_phys = runtime->dma_addr; - do { - next_desc_phys += sizeof(pxa_dma_desc); - dma_desc->ddadr = next_desc_phys; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - dma_desc->dsadr = dma_buff_phys; - dma_desc->dtadr = prtd->params->dev_addr; - } else { - dma_desc->dsadr = prtd->params->dev_addr; - dma_desc->dtadr = dma_buff_phys; - } - if (period > totsize) - period = totsize; - dma_desc->dcmd = prtd->params->dcmd | period | DCMD_ENDIRQEN; - dma_desc++; - dma_buff_phys += period; - } while (totsize -= period); - dma_desc[-1].ddadr = prtd->dma_desc_array_phys; - - return 0; + return __pxa2xx_pcm_hw_params(substream, params); } static int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream) { struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - if (prtd && prtd->params) - *prtd->params->drcmr = 0; + __pxa2xx_pcm_hw_free(substream); if (prtd->dma_ch) { - snd_pcm_set_runtime_buffer(substream, NULL); pxa_free_dma(prtd->dma_ch); prtd->dma_ch = 0; } @@ -149,188 +69,21 @@ static int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream) return 0; } -static int pxa2xx_pcm_prepare(struct snd_pcm_substream *substream) -{ - struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - - DCSR(prtd->dma_ch) &= ~DCSR_RUN; - DCSR(prtd->dma_ch) = 0; - DCMD(prtd->dma_ch) = 0; - *prtd->params->drcmr = prtd->dma_ch | DRCMR_MAPVLD; - - return 0; -} - -static int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - int ret = 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys; - DCSR(prtd->dma_ch) = DCSR_RUN; - break; - - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - DCSR(prtd->dma_ch) &= ~DCSR_RUN; - break; - - case SNDRV_PCM_TRIGGER_RESUME: - DCSR(prtd->dma_ch) |= DCSR_RUN; - break; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys; - DCSR(prtd->dma_ch) |= DCSR_RUN; - break; - - default: - ret = -EINVAL; - } - - return ret; -} - -static snd_pcm_uframes_t -pxa2xx_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct pxa2xx_runtime_data *prtd = runtime->private_data; - - dma_addr_t ptr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? - DSADR(prtd->dma_ch) : DTADR(prtd->dma_ch); - snd_pcm_uframes_t x = bytes_to_frames(runtime, ptr - runtime->dma_addr); - - if (x == runtime->buffer_size) - x = 0; - return x; -} - -static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct pxa2xx_runtime_data *prtd; - int ret; - - snd_soc_set_runtime_hwparams(substream, &pxa2xx_pcm_hardware); - - /* - * For mysterious reasons (and despite what the manual says) - * playback samples are lost if the DMA count is not a multiple - * of the DMA burst size. Let's add a rule to enforce that. - */ - ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); - if (ret) - goto out; - - ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); - if (ret) - goto out; - - ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) - goto out; - - prtd = kzalloc(sizeof(struct pxa2xx_runtime_data), GFP_KERNEL); - if (prtd == NULL) { - ret = -ENOMEM; - goto out; - } - - prtd->dma_desc_array = - dma_alloc_writecombine(substream->pcm->card->dev, PAGE_SIZE, - &prtd->dma_desc_array_phys, GFP_KERNEL); - if (!prtd->dma_desc_array) { - ret = -ENOMEM; - goto err1; - } - - runtime->private_data = prtd; - return 0; - - err1: - kfree(prtd); - out: - return ret; -} - -static int pxa2xx_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct pxa2xx_runtime_data *prtd = runtime->private_data; - - dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE, - prtd->dma_desc_array, prtd->dma_desc_array_phys); - kfree(prtd); - return 0; -} - -static int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - return dma_mmap_writecombine(substream->pcm->card->dev, vma, - runtime->dma_area, - runtime->dma_addr, - runtime->dma_bytes); -} - struct snd_pcm_ops pxa2xx_pcm_ops = { - .open = pxa2xx_pcm_open, - .close = pxa2xx_pcm_close, + .open = __pxa2xx_pcm_open, + .close = __pxa2xx_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = pxa2xx_pcm_hw_params, .hw_free = pxa2xx_pcm_hw_free, - .prepare = pxa2xx_pcm_prepare, + .prepare = __pxa2xx_pcm_prepare, .trigger = pxa2xx_pcm_trigger, .pointer = pxa2xx_pcm_pointer, .mmap = pxa2xx_pcm_mmap, }; -static int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) -{ - struct snd_pcm_substream *substream = pcm->streams[stream].substream; - struct snd_dma_buffer *buf = &substream->dma_buffer; - size_t size = pxa2xx_pcm_hardware.buffer_bytes_max; - buf->dev.type = SNDRV_DMA_TYPE_DEV; - buf->dev.dev = pcm->card->dev; - buf->private_data = NULL; - buf->area = dma_alloc_writecombine(pcm->card->dev, size, - &buf->addr, GFP_KERNEL); - if (!buf->area) - return -ENOMEM; - buf->bytes = size; - return 0; -} - -static void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm) -{ - struct snd_pcm_substream *substream; - struct snd_dma_buffer *buf; - int stream; - - for (stream = 0; stream < 2; stream++) { - substream = pcm->streams[stream].substream; - if (!substream) - continue; - - buf = &substream->dma_buffer; - if (!buf->area) - continue; - - dma_free_writecombine(pcm->card->dev, buf->bytes, - buf->area, buf->addr); - buf->area = NULL; - } -} - static u64 pxa2xx_pcm_dmamask = DMA_32BIT_MASK; -int pxa2xx_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, +static int pxa2xx_soc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; @@ -360,7 +113,7 @@ int pxa2xx_pcm_new(struct snd_card *card, struct snd_soc_codec_dai *dai, struct snd_soc_platform pxa2xx_soc_platform = { .name = "pxa2xx-audio", .pcm_ops = &pxa2xx_pcm_ops, - .pcm_new = pxa2xx_pcm_new, + .pcm_new = pxa2xx_soc_pcm_new, .pcm_free = pxa2xx_pcm_free_dma_buffers, }; EXPORT_SYMBOL_GPL(pxa2xx_soc_platform); diff --git a/sound/soc/pxa/pxa2xx-pcm.h b/sound/soc/pxa/pxa2xx-pcm.h index 54c9c755e508..60c3b20aeeb4 100644 --- a/sound/soc/pxa/pxa2xx-pcm.h +++ b/sound/soc/pxa/pxa2xx-pcm.h @@ -13,21 +13,6 @@ #ifndef _PXA2XX_PCM_H #define _PXA2XX_PCM_H -struct pxa2xx_pcm_dma_params { - char *name; /* stream identifier */ - u32 dcmd; /* DMA descriptor dcmd field */ - volatile u32 *drcmr; /* the DMA request channel to use */ - u32 dev_addr; /* device physical address for DMA */ -}; - -struct pxa2xx_gpio { - u32 sys; - u32 rx; - u32 tx; - u32 clk; - u32 frm; -}; - /* platform data */ extern struct snd_soc_platform pxa2xx_soc_platform; diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index d8b8372db00e..d307b6757e95 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -4,7 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it @@ -12,9 +12,6 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 30th Nov 2005 Initial version. - * */ #include <linux/module.h> @@ -22,17 +19,16 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> +#include <linux/gpio.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <asm/mach-types.h> -#include <asm/hardware/scoop.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/hardware.h> -#include <asm/arch/akita.h> -#include <asm/arch/spitz.h> +#include <mach/pxa-regs.h> +#include <mach/hardware.h> +#include <mach/spitz.h> #include "../codecs/wm8750.h" #include "pxa2xx-pcm.h" #include "pxa2xx-i2s.h" @@ -54,60 +50,60 @@ static int spitz_spk_func; static void spitz_ext_control(struct snd_soc_codec *codec) { if (spitz_spk_func == SPITZ_SPK_ON) - snd_soc_dapm_set_endpoint(codec, "Ext Spk", 1); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); else - snd_soc_dapm_set_endpoint(codec, "Ext Spk", 0); + snd_soc_dapm_disable_pin(codec, "Ext Spk"); /* set up jack connection */ switch (spitz_jack_func) { case SPITZ_HP: /* enable and unmute hp jack, disable mic bias */ - snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Line Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); - set_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L); - set_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); + snd_soc_dapm_disable_pin(codec, "Mic Jack"); + snd_soc_dapm_disable_pin(codec, "Line Jack"); + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 1); + gpio_set_value(SPITZ_GPIO_MUTE_R, 1); break; case SPITZ_MIC: /* enable mic jack and bias, mute hp */ - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Line Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1); - reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L); - reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); + snd_soc_dapm_disable_pin(codec, "Line Jack"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 0); break; case SPITZ_LINE: /* enable line jack, disable mic bias and mute hp */ - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Line Jack", 1); - reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L); - reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); + snd_soc_dapm_disable_pin(codec, "Mic Jack"); + snd_soc_dapm_enable_pin(codec, "Line Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 0); break; case SPITZ_HEADSET: /* enable and unmute headset jack enable mic bias, mute L hp */ - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1); - snd_soc_dapm_set_endpoint(codec, "Line Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Jack", 1); - reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L); - set_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + snd_soc_dapm_disable_pin(codec, "Line Jack"); + snd_soc_dapm_enable_pin(codec, "Headset Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 1); break; case SPITZ_HP_OFF: /* jack removed, everything off */ - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0); - snd_soc_dapm_set_endpoint(codec, "Line Jack", 0); - reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L); - reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); + snd_soc_dapm_disable_pin(codec, "Mic Jack"); + snd_soc_dapm_disable_pin(codec, "Line Jack"); + gpio_set_value(SPITZ_GPIO_MUTE_L, 0); + gpio_set_value(SPITZ_GPIO_MUTE_R, 0); break; } - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); } static int spitz_startup(struct snd_pcm_substream *substream) @@ -124,8 +120,8 @@ static int spitz_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_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; unsigned int clk = 0; int ret = 0; @@ -144,25 +140,25 @@ static int spitz_hw_params(struct snd_pcm_substream *substream, } /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* set cpu DAI configuration */ - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* set the codec system clock for DAC and ADC */ - ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8750_SYSCLK, clk, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* set the I2S system clock as input (unused) */ - ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0, SND_SOC_CLOCK_IN); if (ret < 0) return ret; @@ -218,23 +214,14 @@ static int spitz_set_spk(struct snd_kcontrol *kcontrol, static int spitz_mic_bias(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - if (machine_is_borzoi() || machine_is_spitz()) { - if (SND_SOC_DAPM_EVENT_ON(event)) - set_scoop_gpio(&spitzscoop2_device.dev, - SPITZ_SCP2_MIC_BIAS); - else - reset_scoop_gpio(&spitzscoop2_device.dev, - SPITZ_SCP2_MIC_BIAS); - } + if (machine_is_borzoi() || machine_is_spitz()) + gpio_set_value(SPITZ_GPIO_MIC_BIAS, + SND_SOC_DAPM_EVENT_ON(event)); + + if (machine_is_akita()) + gpio_set_value(AKITA_GPIO_MIC_BIAS, + SND_SOC_DAPM_EVENT_ON(event)); - if (machine_is_akita()) { - if (SND_SOC_DAPM_EVENT_ON(event)) - akita_set_ioexp(&akitaioexp_device.dev, - AKITA_IOEXP_MIC_BIAS); - else - akita_reset_ioexp(&akitaioexp_device.dev, - AKITA_IOEXP_MIC_BIAS); - } return 0; } @@ -250,7 +237,7 @@ static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { }; /* Spitz machine audio_map */ -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* headphone connected to LOUT1, ROUT1 */ {"Headphone Jack", NULL, "LOUT1"}, @@ -269,8 +256,6 @@ static const char *audio_map[][3] = { /* line is connected to input 1 - no bias */ {"LINPUT1", NULL, "Line Jack"}, - - {NULL, NULL, NULL}, }; static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", @@ -296,13 +281,13 @@ static int spitz_wm8750_init(struct snd_soc_codec *codec) int i, err; /* NC codec pins */ - snd_soc_dapm_set_endpoint(codec, "RINPUT1", 0); - snd_soc_dapm_set_endpoint(codec, "LINPUT2", 0); - snd_soc_dapm_set_endpoint(codec, "RINPUT2", 0); - snd_soc_dapm_set_endpoint(codec, "LINPUT3", 0); - snd_soc_dapm_set_endpoint(codec, "RINPUT3", 0); - snd_soc_dapm_set_endpoint(codec, "OUT3", 0); - snd_soc_dapm_set_endpoint(codec, "MONO", 0); + snd_soc_dapm_nc_pin(codec, "RINPUT1"); + snd_soc_dapm_nc_pin(codec, "LINPUT2"); + snd_soc_dapm_nc_pin(codec, "RINPUT2"); + snd_soc_dapm_nc_pin(codec, "LINPUT3"); + snd_soc_dapm_nc_pin(codec, "RINPUT3"); + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "MONO1"); /* Add spitz specific controls */ for (i = 0; i < ARRAY_SIZE(wm8750_spitz_controls); i++) { @@ -313,15 +298,13 @@ static int spitz_wm8750_init(struct snd_soc_codec *codec) } /* Add spitz specific widgets */ - for (i = 0; i < ARRAY_SIZE(wm8750_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8750_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); - /* Set up spitz specific audio path audio_map */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + /* Set up spitz specific audio paths */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } @@ -344,6 +327,7 @@ static struct snd_soc_machine snd_soc_machine_spitz = { /* spitz audio private data */ static struct wm8750_setup_data spitz_wm8750_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c index 7346d7e5d066..afefe41b8c46 100644 --- a/sound/soc/pxa/tosa.c +++ b/sound/soc/pxa/tosa.c @@ -4,7 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it @@ -12,9 +12,6 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 30th Nov 2005 Initial version. - * * GPIO's * 1 - Jack Insertion * 5 - Hookswitch (headset answer/hang up switch) @@ -24,6 +21,7 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/device.h> +#include <linux/gpio.h> #include <sound/core.h> #include <sound/pcm.h> @@ -31,11 +29,10 @@ #include <sound/soc-dapm.h> #include <asm/mach-types.h> -#include <asm/hardware/tmio.h> -#include <asm/arch/pxa-regs.h> -#include <asm/arch/hardware.h> -#include <asm/arch/audio.h> -#include <asm/arch/tosa.h> +#include <mach/tosa.h> +#include <mach/pxa-regs.h> +#include <mach/hardware.h> +#include <mach/audio.h> #include "../codecs/wm9712.h" #include "pxa2xx-pcm.h" @@ -55,29 +52,31 @@ static int tosa_spk_func; static void tosa_ext_control(struct snd_soc_codec *codec) { - int spk = 0, mic_int = 0, hp = 0, hs = 0; - /* set up jack connection */ switch (tosa_jack_func) { case TOSA_HP: - hp = 1; + snd_soc_dapm_disable_pin(codec, "Mic (Internal)"); + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); break; case TOSA_MIC_INT: - mic_int = 1; + snd_soc_dapm_enable_pin(codec, "Mic (Internal)"); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); break; case TOSA_HEADSET: - hs = 1; + snd_soc_dapm_disable_pin(codec, "Mic (Internal)"); + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Headset Jack"); break; } if (tosa_spk_func == TOSA_SPK_ON) - spk = 1; + snd_soc_dapm_enable_pin(codec, "Speaker"); + else + snd_soc_dapm_disable_pin(codec, "Speaker"); - snd_soc_dapm_set_endpoint(codec, "Speaker", spk); - snd_soc_dapm_set_endpoint(codec, "Mic (Internal)", mic_int); - snd_soc_dapm_set_endpoint(codec, "Headphone Jack", hp); - snd_soc_dapm_set_endpoint(codec, "Headset Jack", hs); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); } static int tosa_startup(struct snd_pcm_substream *substream) @@ -138,10 +137,7 @@ static int tosa_set_spk(struct snd_kcontrol *kcontrol, static int tosa_hp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - if (SND_SOC_DAPM_EVENT_ON(event)) - set_tc6393_gpio(&tc6393_device.dev,TOSA_TC6393_L_MUTE); - else - reset_tc6393_gpio(&tc6393_device.dev,TOSA_TC6393_L_MUTE); + gpio_set_value(TOSA_GPIO_L_MUTE, SND_SOC_DAPM_EVENT_ON(event) ? 1 :0); return 0; } @@ -154,7 +150,7 @@ SND_SOC_DAPM_SPK("Speaker", NULL), }; /* tosa audio map */ -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* headphone connected to HPOUTL, HPOUTR */ {"Headphone Jack", NULL, "HPOUTL"}, @@ -173,8 +169,6 @@ static const char *audio_map[][3] = { {"Headset Jack", NULL, "HPOUTR"}, {"LINEINR", NULL, "Mic Bias"}, {"Mic Bias", NULL, "Headset Jack"}, - - {NULL, NULL, NULL}, }; static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset", @@ -196,8 +190,8 @@ static int tosa_ac97_init(struct snd_soc_codec *codec) { int i, err; - snd_soc_dapm_set_endpoint(codec, "OUT3", 0); - snd_soc_dapm_set_endpoint(codec, "MONOOUT", 0); + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "MONOOUT"); /* add tosa specific controls */ for (i = 0; i < ARRAY_SIZE(tosa_controls); i++) { @@ -208,17 +202,13 @@ static int tosa_ac97_init(struct snd_soc_codec *codec) } /* add tosa specific widgets */ - for (i = 0; i < ARRAY_SIZE(tosa_dapm_widgets); i++) { - snd_soc_dapm_new_control(codec, &tosa_dapm_widgets[i]); - } + snd_soc_dapm_new_controls(codec, tosa_dapm_widgets, + ARRAY_SIZE(tosa_dapm_widgets)); /* set up tosa specific audio path audio_map */ - for (i = 0; audio_map[i][0] != NULL; i++) { - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); - } + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } @@ -261,16 +251,28 @@ static int __init tosa_init(void) if (!machine_is_tosa()) return -ENODEV; + ret = gpio_request(TOSA_GPIO_L_MUTE, "Headphone Jack"); + if (ret) + return ret; + gpio_direction_output(TOSA_GPIO_L_MUTE, 0); + tosa_snd_device = platform_device_alloc("soc-audio", -1); - if (!tosa_snd_device) - return -ENOMEM; + if (!tosa_snd_device) { + ret = -ENOMEM; + goto err_alloc; + } platform_set_drvdata(tosa_snd_device, &tosa_snd_devdata); tosa_snd_devdata.dev = &tosa_snd_device->dev; ret = platform_device_add(tosa_snd_device); - if (ret) - platform_device_put(tosa_snd_device); + if (!ret) + return 0; + + platform_device_put(tosa_snd_device); + +err_alloc: + gpio_free(TOSA_GPIO_L_MUTE); return ret; } @@ -278,6 +280,7 @@ static int __init tosa_init(void) static void __exit tosa_exit(void) { platform_device_unregister(tosa_snd_device); + gpio_free(TOSA_GPIO_L_MUTE); } module_init(tosa_init); diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 1f6dbfc4caa8..b9f2353effeb 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -1,7 +1,6 @@ config SND_S3C24XX_SOC tristate "SoC Audio for the Samsung S3C24XX chips" - depends on ARCH_S3C2410 && SND_SOC - select SND_PCM + depends on ARCH_S3C2410 help Say Y or M if you want to add support for codecs attached to the S3C24XX AC97, I2S or SSP interface. You will also need @@ -16,7 +15,6 @@ config SND_S3C2412_SOC_I2S config SND_S3C2443_SOC_AC97 tristate select AC97_BUS - select SND_AC97_CODEC select SND_SOC_AC97_BUS config SND_S3C24XX_SOC_NEO1973_WM8753 diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index 0e9d1c5f2484..87ddfefcc2fb 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -10,10 +10,6 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 20th Jan 2007 Initial version. - * 05th Feb 2007 Rename all to Neo1973 - * */ #include <linux/module.h> @@ -26,15 +22,16 @@ #include <sound/pcm.h> #include <sound/soc.h> #include <sound/soc-dapm.h> +#include <sound/tlv.h> #include <asm/mach-types.h> #include <asm/hardware/scoop.h> -#include <asm/arch/regs-clock.h> -#include <asm/arch/regs-gpio.h> -#include <asm/hardware.h> -#include <asm/arch/audio.h> +#include <mach/regs-clock.h> +#include <mach/regs-gpio.h> +#include <mach/hardware.h> +#include <mach/audio.h> #include <linux/io.h> -#include <asm/arch/spi-gpio.h> +#include <mach/spi-gpio.h> #include <asm/plat-s3c24xx/regs-iis.h> @@ -43,6 +40,14 @@ #include "s3c24xx-pcm.h" #include "s3c24xx-i2s.h" +/* Debugging stuff */ +#define S3C24XX_SOC_NEO1973_WM8753_DEBUG 0 +#if S3C24XX_SOC_NEO1973_WM8753_DEBUG +#define DBG(x...) printk(KERN_DEBUG "s3c24xx-soc-neo1973-wm8753: " x) +#else +#define DBG(x...) +#endif + /* define the scenarios */ #define NEO_AUDIO_OFF 0 #define NEO_GSM_CALL_AUDIO_HANDSET 1 @@ -61,12 +66,14 @@ static int neo1973_hifi_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_codec_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; unsigned int pll_out = 0, bclk = 0; int ret = 0; unsigned long iis_clkrate; + DBG("Entered %s\n", __func__); + iis_clkrate = s3c24xx_i2s_get_clockrate(); switch (params_rate(params)) { @@ -101,44 +108,44 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, } /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); if (ret < 0) return ret; /* set cpu DAI configuration */ - ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); if (ret < 0) return ret; /* set the codec system clock for DAC and ADC */ - ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_MCLK, pll_out, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* set MCLK division for sample rate */ - ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, S3C2410_IISMOD_32FS); if (ret < 0) return ret; /* set codec BCLK division for sample rate */ - ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); if (ret < 0) return ret; /* set prescaler division for sample rate */ - ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, S3C24XX_PRESCALE(4, 4)); if (ret < 0) return ret; /* codec PLL input is PCLK/4 */ - ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, iis_clkrate / 4, pll_out); if (ret < 0) return ret; @@ -149,10 +156,12 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + + DBG("Entered %s\n", __func__); /* disable the PLL */ - return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL1, 0, 0); + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0); } /* @@ -167,11 +176,13 @@ static int neo1973_voice_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_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; unsigned int pcmdiv = 0; int ret = 0; unsigned long iis_clkrate; + DBG("Entered %s\n", __func__); + iis_clkrate = s3c24xx_i2s_get_clockrate(); if (params_rate(params) != 8000) @@ -183,24 +194,24 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, /* todo: gg check mode (DSP_B) against CSR datasheet */ /* set codec DAI configuration */ - ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; /* set the codec system clock for DAC and ADC */ - ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8753_PCMCLK, 12288000, + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000, SND_SOC_CLOCK_IN); if (ret < 0) return ret; /* set codec PCM division for sample rate */ - ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); if (ret < 0) return ret; /* configue and enable PLL for 12.288MHz output */ - ret = codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, iis_clkrate / 4, 12288000); if (ret < 0) return ret; @@ -211,10 +222,12 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + + DBG("Entered %s\n", __func__); /* disable the PLL */ - return codec_dai->dai_ops.set_pll(codec_dai, WM8753_PLL2, 0, 0); + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0); } static struct snd_soc_ops neo1973_voice_ops = { @@ -233,79 +246,81 @@ static int neo1973_get_scenario(struct snd_kcontrol *kcontrol, static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) { + DBG("Entered %s\n", __func__); + switch (neo1973_scenario) { case NEO_AUDIO_OFF: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_disable_pin(codec, "Audio Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); break; case NEO_GSM_CALL_AUDIO_HANDSET: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 1); + snd_soc_dapm_enable_pin(codec, "Audio Out"); + snd_soc_dapm_enable_pin(codec, "GSM Line Out"); + snd_soc_dapm_enable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Call Mic"); break; case NEO_GSM_CALL_AUDIO_HEADSET: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_enable_pin(codec, "Audio Out"); + snd_soc_dapm_enable_pin(codec, "GSM Line Out"); + snd_soc_dapm_enable_pin(codec, "GSM Line In"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); break; case NEO_GSM_CALL_AUDIO_BLUETOOTH: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 1); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 1); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_disable_pin(codec, "Audio Out"); + snd_soc_dapm_enable_pin(codec, "GSM Line Out"); + snd_soc_dapm_enable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); break; case NEO_STEREO_TO_SPEAKERS: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_enable_pin(codec, "Audio Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); break; case NEO_STEREO_TO_HEADPHONES: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 1); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_enable_pin(codec, "Audio Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); break; case NEO_CAPTURE_HANDSET: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 1); + snd_soc_dapm_disable_pin(codec, "Audio Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Call Mic"); break; case NEO_CAPTURE_HEADSET: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 1); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_disable_pin(codec, "Audio Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); break; case NEO_CAPTURE_BLUETOOTH: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_disable_pin(codec, "Audio Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); break; default: - snd_soc_dapm_set_endpoint(codec, "Audio Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line Out", 0); - snd_soc_dapm_set_endpoint(codec, "GSM Line In", 0); - snd_soc_dapm_set_endpoint(codec, "Headset Mic", 0); - snd_soc_dapm_set_endpoint(codec, "Call Mic", 0); + snd_soc_dapm_disable_pin(codec, "Audio Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Call Mic"); } - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } @@ -315,6 +330,8 @@ static int neo1973_set_scenario(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + DBG("Entered %s\n", __func__); + if (neo1973_scenario == ucontrol->value.integer.value[0]) return 0; @@ -327,6 +344,8 @@ static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0}; static void lm4857_write_regs(void) { + DBG("Entered %s\n", __func__); + if (i2c_master_send(i2c, lm4857_regs, 4) != 4) printk(KERN_ERR "lm4857: i2c write failed\n"); } @@ -338,6 +357,8 @@ static int lm4857_get_reg(struct snd_kcontrol *kcontrol, int shift = (kcontrol->private_value >> 8) & 0x0F; int mask = (kcontrol->private_value >> 16) & 0xFF; + DBG("Entered %s\n", __func__); + ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask; return 0; } @@ -364,6 +385,8 @@ static int lm4857_get_mode(struct snd_kcontrol *kcontrol, { u8 value = lm4857_regs[LM4857_CTRL] & 0x0F; + DBG("Entered %s\n", __func__); + if (value) value -= 5; @@ -376,6 +399,8 @@ static int lm4857_set_mode(struct snd_kcontrol *kcontrol, { u8 value = ucontrol->value.integer.value[0]; + DBG("Entered %s\n", __func__); + if (value) value += 5; @@ -397,8 +422,7 @@ static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { }; -/* example machine audio_mapnections */ -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route dapm_routes[] = { /* Connections to the lm4857 amp */ {"Audio Out", NULL, "LOUT1"}, @@ -421,8 +445,6 @@ static const char *audio_map[][3] = { /* Connect the ALC pins */ {"ACIN", NULL, "ACOP"}, - - {NULL, NULL, NULL}, }; static const char *lm4857_mode[] = { @@ -453,13 +475,16 @@ static const struct soc_enum neo_scenario_enum[] = { SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios), neo_scenarios), }; +static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); +static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); + static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { - SOC_SINGLE_EXT("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0, - lm4857_get_reg, lm4857_set_reg), - SOC_SINGLE_EXT("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0, - lm4857_get_reg, lm4857_set_reg), - SOC_SINGLE_EXT("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0, - lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT_TLV("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg, stereo_tlv), + SOC_SINGLE_EXT_TLV("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg, stereo_tlv), + SOC_SINGLE_EXT_TLV("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg, mono_tlv), SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0], lm4857_get_mode, lm4857_set_mode), SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0], @@ -483,22 +508,23 @@ static int neo1973_wm8753_init(struct snd_soc_codec *codec) { int i, err; + DBG("Entered %s\n", __func__); + /* set up NC codec pins */ - snd_soc_dapm_set_endpoint(codec, "LOUT2", 0); - snd_soc_dapm_set_endpoint(codec, "ROUT2", 0); - snd_soc_dapm_set_endpoint(codec, "OUT3", 0); - snd_soc_dapm_set_endpoint(codec, "OUT4", 0); - snd_soc_dapm_set_endpoint(codec, "LINE1", 0); - snd_soc_dapm_set_endpoint(codec, "LINE2", 0); + snd_soc_dapm_nc_pin(codec, "LOUT2"); + snd_soc_dapm_nc_pin(codec, "ROUT2"); + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "OUT4"); + snd_soc_dapm_nc_pin(codec, "LINE1"); + snd_soc_dapm_nc_pin(codec, "LINE2"); + /* Add neo1973 specific widgets */ + snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, + ARRAY_SIZE(wm8753_dapm_widgets)); /* set endpoints to default mode */ set_scenario_endpoints(codec, NEO_AUDIO_OFF); - /* Add neo1973 specific widgets */ - for (i = 0; i < ARRAY_SIZE(wm8753_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8753_dapm_widgets[i]); - /* add neo1973 specific controls */ for (i = 0; i < ARRAY_SIZE(wm8753_neo1973_controls); i++) { err = snd_ctl_add(codec->card, @@ -508,20 +534,18 @@ static int neo1973_wm8753_init(struct snd_soc_codec *codec) return err; } - /* set up neo1973 specific audio path audio_mapnects */ - for (i = 0; audio_map[i][0] != NULL; i++) { - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); - } + /* set up neo1973 specific audio routes */ + err = snd_soc_dapm_add_routes(codec, dapm_routes, + ARRAY_SIZE(dapm_routes)); - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } /* * BT Codec DAI */ -static struct snd_soc_cpu_dai bt_dai = { +static struct snd_soc_dai bt_dai = { .name = "Bluetooth", .id = 0, .type = SND_SOC_DAI_PCM, @@ -562,6 +586,7 @@ static struct snd_soc_machine neo1973 = { }; static struct wm8753_setup_data neo1973_wm8753_setup = { + .i2c_bus = 0, .i2c_address = 0x1a, }; @@ -572,54 +597,32 @@ static struct snd_soc_device neo1973_snd_devdata = { .codec_data = &neo1973_wm8753_setup, }; -static struct i2c_client client_template; - -static const unsigned short normal_i2c[] = { 0x7C, I2C_CLIENT_END }; - -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static int lm4857_amp_probe(struct i2c_adapter *adap, int addr, int kind) +static int lm4857_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - int ret; + DBG("Entered %s\n", __func__); - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) - return -ENOMEM; - - ret = i2c_attach_client(i2c); - if (ret < 0) { - printk(KERN_ERR "LM4857 failed to attach at addr %x\n", addr); - goto exit_err; - } + i2c = client; lm4857_write_regs(); - return ret; - -exit_err: - kfree(i2c); - return ret; -} - -static int lm4857_i2c_detach(struct i2c_client *client) -{ - i2c_detach_client(client); - kfree(client); return 0; } -static int lm4857_i2c_attach(struct i2c_adapter *adap) +static int lm4857_i2c_remove(struct i2c_client *client) { - return i2c_probe(adap, &addr_data, lm4857_amp_probe); + DBG("Entered %s\n", __func__); + + i2c = NULL; + + return 0; } static u8 lm4857_state; static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) { + DBG("Entered %s\n", __func__); + dev_dbg(&dev->dev, "lm4857_suspend\n"); lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf; if (lm4857_state) { @@ -631,6 +634,8 @@ static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) static int lm4857_resume(struct i2c_client *dev) { + DBG("Entered %s\n", __func__); + if (lm4857_state) { lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f); lm4857_write_regs(); @@ -640,29 +645,29 @@ static int lm4857_resume(struct i2c_client *dev) static void lm4857_shutdown(struct i2c_client *dev) { + DBG("Entered %s\n", __func__); + dev_dbg(&dev->dev, "lm4857_shutdown\n"); lm4857_regs[LM4857_CTRL] &= 0xf0; lm4857_write_regs(); } -/* corgi i2c codec control layer */ +static const struct i2c_device_id lm4857_i2c_id[] = { + { "neo1973_lm4857", 0 }, + { } +}; + static struct i2c_driver lm4857_i2c_driver = { .driver = { .name = "LM4857 I2C Amp", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_LM4857, .suspend = lm4857_suspend, .resume = lm4857_resume, .shutdown = lm4857_shutdown, - .attach_adapter = lm4857_i2c_attach, - .detach_client = lm4857_i2c_detach, - .command = NULL, -}; - -static struct i2c_client client_template = { - .name = "LM4857", - .driver = &lm4857_i2c_driver, + .probe = lm4857_i2c_probe, + .remove = lm4857_i2c_remove, + .id_table = lm4857_i2c_id, }; static struct platform_device *neo1973_snd_device; @@ -671,6 +676,14 @@ static int __init neo1973_init(void) { int ret; + DBG("Entered %s\n", __func__); + + if (!machine_is_neo1973_gta01()) { + printk(KERN_INFO + "Only GTA01 hardware supported by ASoC driver\n"); + return -ENODEV; + } + neo1973_snd_device = platform_device_alloc("soc-audio", -1); if (!neo1973_snd_device) return -ENOMEM; @@ -679,18 +692,23 @@ static int __init neo1973_init(void) neo1973_snd_devdata.dev = &neo1973_snd_device->dev; ret = platform_device_add(neo1973_snd_device); - if (ret) + if (ret) { platform_device_put(neo1973_snd_device); + return ret; + } ret = i2c_add_driver(&lm4857_i2c_driver); + if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + platform_device_unregister(neo1973_snd_device); return ret; } static void __exit neo1973_exit(void) { + DBG("Entered %s\n", __func__); + i2c_del_driver(&lm4857_i2c_driver); platform_device_unregister(neo1973_snd_device); } diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index c4a46dd589b3..ded7d995a922 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -28,16 +28,16 @@ #include <sound/pcm_params.h> #include <sound/initval.h> #include <sound/soc.h> -#include <asm/hardware.h> +#include <mach/hardware.h> #include <linux/io.h> #include <asm/dma.h> #include <asm/plat-s3c24xx/regs-s3c2412-iis.h> -#include <asm/arch/regs-gpio.h> -#include <asm/arch/audio.h> -#include <asm/arch/dma.h> +#include <mach/regs-gpio.h> +#include <mach/audio.h> +#include <mach/dma.h> #include "s3c24xx-pcm.h" #include "s3c2412-i2s.h" @@ -295,7 +295,7 @@ static inline int s3c2412_snd_is_clkmaster(void) /* * Set S3C2412 I2S DAI format */ -static int s3c2412_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, +static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { u32 iismod; @@ -500,7 +500,7 @@ EXPORT_SYMBOL_GPL(s3c2412_iis_calc_rate); /* * Set S3C2412 Clock source */ -static int s3c2412_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, +static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); @@ -528,7 +528,7 @@ static int s3c2412_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, /* * Set S3C2412 Clock dividers */ -static int s3c2412_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, +static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int div) { struct s3c2412_i2s_info *i2s = &s3c2412_i2s; @@ -601,7 +601,8 @@ struct clk *s3c2412_get_iisclk(void) EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); -static int s3c2412_i2s_probe(struct platform_device *pdev) +static int s3c2412_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) { DBG("Entered %s\n", __func__); @@ -647,7 +648,7 @@ static int s3c2412_i2s_probe(struct platform_device *pdev) #ifdef CONFIG_PM static int s3c2412_i2s_suspend(struct platform_device *dev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { struct s3c2412_i2s_info *i2s = &s3c2412_i2s; u32 iismod; @@ -675,7 +676,7 @@ static int s3c2412_i2s_suspend(struct platform_device *dev, } static int s3c2412_i2s_resume(struct platform_device *pdev, - struct snd_soc_cpu_dai *dai) + struct snd_soc_dai *dai) { struct s3c2412_i2s_info *i2s = &s3c2412_i2s; @@ -707,7 +708,7 @@ static int s3c2412_i2s_resume(struct platform_device *pdev, SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) -struct snd_soc_cpu_dai s3c2412_i2s_dai = { +struct snd_soc_dai s3c2412_i2s_dai = { .name = "s3c2412-i2s", .id = 0, .type = SND_SOC_DAI_I2S, diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h index 27f48e1ffa86..aac08a25e541 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.h +++ b/sound/soc/s3c24xx/s3c2412-i2s.h @@ -24,7 +24,7 @@ extern struct clk *s3c2412_get_iisclk(void); -extern struct snd_soc_cpu_dai s3c2412_i2s_dai; +extern struct snd_soc_dai s3c2412_i2s_dai; struct s3c2412_rate_calc { unsigned int clk_div; /* for prescaler */ diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c index e81d9a6c83da..19c5c3cf5d8c 100644 --- a/sound/soc/s3c24xx/s3c2443-ac97.c +++ b/sound/soc/s3c24xx/s3c2443-ac97.c @@ -10,9 +10,6 @@ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. - * - * Revision history - * 21st Mar 2007 Initial Version */ #include <linux/init.h> @@ -30,13 +27,13 @@ #include <sound/initval.h> #include <sound/soc.h> -#include <asm/hardware.h> +#include <mach/hardware.h> #include <asm/plat-s3c/regs-ac97.h> -#include <asm/arch/regs-gpio.h> -#include <asm/arch/regs-clock.h> -#include <asm/arch/audio.h> +#include <mach/regs-gpio.h> +#include <mach/regs-clock.h> +#include <mach/audio.h> #include <asm/dma.h> -#include <asm/arch/dma.h> +#include <mach/dma.h> #include "s3c24xx-pcm.h" #include "s3c24xx-ac97.h" @@ -212,7 +209,8 @@ static struct s3c24xx_pcm_dma_params s3c2443_ac97_mic_mono_in = { .dma_size = 4, }; -static int s3c2443_ac97_probe(struct platform_device *pdev) +static int s3c2443_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) { int ret; u32 ac_glbctrl; @@ -263,7 +261,8 @@ static int s3c2443_ac97_probe(struct platform_device *pdev) return ret; } -static void s3c2443_ac97_remove(struct platform_device *pdev) +static void s3c2443_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) { free_irq(IRQ_S3C244x_AC97, NULL); clk_disable(s3c24xx_ac97.ac97_clk); @@ -275,7 +274,7 @@ static int s3c2443_ac97_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_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) cpu_dai->dma_data = &s3c2443_ac97_pcm_stereo_out; @@ -317,7 +316,7 @@ static int s3c2443_ac97_hw_mic_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return -ENODEV; @@ -353,7 +352,7 @@ static int s3c2443_ac97_mic_trigger(struct snd_pcm_substream *substream, SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) -struct snd_soc_cpu_dai s3c2443_ac97_dai[] = { +struct snd_soc_dai s3c2443_ac97_dai[] = { { .name = "s3c2443-ac97", .id = 0, diff --git a/sound/soc/s3c24xx/s3c24xx-ac97.h b/sound/soc/s3c24xx/s3c24xx-ac97.h index bf03e8ed16c3..a96dcadf28b4 100644 --- a/sound/soc/s3c24xx/s3c24xx-ac97.h +++ b/sound/soc/s3c24xx/s3c24xx-ac97.h @@ -26,6 +26,6 @@ #define IRQ_S3C244x_AC97 IRQ_S3C2443_AC97 #endif -extern struct snd_soc_cpu_dai s3c2443_ac97_dai[]; +extern struct snd_soc_dai s3c2443_ac97_dai[]; #endif /*S3C24XXAC97_H_*/ diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c index 1ed6afd45459..ba4476b55fbc 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.c +++ b/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -12,11 +12,6 @@ * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. - * - * - * Revision history - * 11th Dec 2006 Merged with Simtec driver - * 10th Nov 2006 Initial version. */ #include <linux/init.h> @@ -32,12 +27,12 @@ #include <sound/initval.h> #include <sound/soc.h> -#include <asm/hardware.h> -#include <asm/arch/regs-gpio.h> -#include <asm/arch/regs-clock.h> -#include <asm/arch/audio.h> +#include <mach/hardware.h> +#include <mach/regs-gpio.h> +#include <mach/regs-clock.h> +#include <mach/audio.h> #include <asm/dma.h> -#include <asm/arch/dma.h> +#include <mach/dma.h> #include <asm/plat-s3c24xx/regs-iis.h> @@ -180,7 +175,7 @@ static void s3c24xx_snd_rxctrl(int on) static int s3c24xx_snd_lrsync(void) { u32 iiscon; - unsigned long timeout = jiffies + msecs_to_jiffies(5); + int timeout = 50; /* 5ms */ DBG("Entered %s\n", __func__); @@ -189,8 +184,9 @@ static int s3c24xx_snd_lrsync(void) if (iiscon & S3C2410_IISCON_LRINDEX) break; - if (time_after(jiffies, timeout)) + if (!timeout--) return -ETIMEDOUT; + udelay(100); } return 0; @@ -209,7 +205,7 @@ static inline int s3c24xx_snd_is_clkmaster(void) /* * Set S3C24xx I2S DAI format */ -static int s3c24xx_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, +static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { u32 iismod; @@ -317,7 +313,7 @@ exit_err: /* * Set S3C24xx Clock source */ -static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, +static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); @@ -343,7 +339,7 @@ static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, /* * Set S3C24xx Clock dividers */ -static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, +static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int div) { u32 reg; @@ -381,7 +377,8 @@ u32 s3c24xx_i2s_get_clockrate(void) } EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); -static int s3c24xx_i2s_probe(struct platform_device *pdev) +static int s3c24xx_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) { DBG("Entered %s\n", __func__); @@ -414,7 +411,7 @@ static int s3c24xx_i2s_probe(struct platform_device *pdev) #ifdef CONFIG_PM static int s3c24xx_i2s_suspend(struct platform_device *pdev, - struct snd_soc_cpu_dai *cpu_dai) + struct snd_soc_dai *cpu_dai) { DBG("Entered %s\n", __func__); @@ -429,7 +426,7 @@ static int s3c24xx_i2s_suspend(struct platform_device *pdev, } static int s3c24xx_i2s_resume(struct platform_device *pdev, - struct snd_soc_cpu_dai *cpu_dai) + struct snd_soc_dai *cpu_dai) { DBG("Entered %s\n", __func__); clk_enable(s3c24xx_i2s.iis_clk); @@ -452,7 +449,7 @@ static int s3c24xx_i2s_resume(struct platform_device *pdev, SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) -struct snd_soc_cpu_dai s3c24xx_i2s_dai = { +struct snd_soc_dai s3c24xx_i2s_dai = { .name = "s3c24xx-i2s", .id = 0, .type = SND_SOC_DAI_I2S, diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.h b/sound/soc/s3c24xx/s3c24xx-i2s.h index 537b4ecce8a3..726d91cf4e1c 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.h +++ b/sound/soc/s3c24xx/s3c24xx-i2s.h @@ -32,6 +32,6 @@ u32 s3c24xx_i2s_get_clockrate(void); -extern struct snd_soc_cpu_dai s3c24xx_i2s_dai; +extern struct snd_soc_dai s3c24xx_i2s_dai; #endif /*S3C24XXI2S_H_*/ diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index 7806ae614617..e13e614bada9 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -12,10 +12,6 @@ * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. - * - * Revision history - * 11th Dec 2006 Merged with Simtec driver - * 10th Nov 2006 Initial version. */ #include <linux/module.h> @@ -31,9 +27,9 @@ #include <sound/soc.h> #include <asm/dma.h> -#include <asm/hardware.h> -#include <asm/arch/dma.h> -#include <asm/arch/audio.h> +#include <mach/hardware.h> +#include <mach/dma.h> +#include <mach/audio.h> #include "s3c24xx-pcm.h" @@ -433,7 +429,7 @@ static void s3c24xx_pcm_free_dma_buffers(struct snd_pcm *pcm) static u64 s3c24xx_pcm_dmamask = DMA_32BIT_MASK; static int s3c24xx_pcm_new(struct snd_card *card, - struct snd_soc_codec_dai *dai, struct snd_pcm *pcm) + struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; diff --git a/sound/soc/s3c24xx/smdk2443_wm9710.c b/sound/soc/s3c24xx/smdk2443_wm9710.c index b4a56302b9ab..8515d6ff03f2 100644 --- a/sound/soc/s3c24xx/smdk2443_wm9710.c +++ b/sound/soc/s3c24xx/smdk2443_wm9710.c @@ -10,9 +10,6 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 8th Mar 2007 Initial version. - * */ #include <linux/module.h> diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index 4c1e013381c9..54bd604012af 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -3,7 +3,7 @@ menu "SoC Audio support for SuperH" config SND_SOC_PCM_SH7760 tristate "SoC Audio support for Renesas SH7760" - depends on CPU_SUBTYPE_SH7760 && SND_SOC && SH_DMABRG + depends on CPU_SUBTYPE_SH7760 && SH_DMABRG help Enable this option for SH7760 AC97/I2S audio support. @@ -13,10 +13,9 @@ config SND_SOC_PCM_SH7760 ## config SND_SOC_SH4_HAC + tristate select AC97_BUS select SND_SOC_AC97_BUS - select SND_AC97_CODEC - tristate config SND_SOC_SH4_SSI tristate diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c index 7a3ce80d6727..9faa12622d09 100644 --- a/sound/soc/sh/dma-sh7760.c +++ b/sound/soc/sh/dma-sh7760.c @@ -326,7 +326,7 @@ static void camelot_pcm_free(struct snd_pcm *pcm) } static int camelot_pcm_new(struct snd_card *card, - struct snd_soc_codec_dai *dai, + struct snd_soc_dai *dai, struct snd_pcm *pcm) { /* dont use SNDRV_DMA_TYPE_DEV, since it will oops the SH kernel diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c index b7b676b3d671..df7bc345c320 100644 --- a/sound/soc/sh/hac.c +++ b/sound/soc/sh/hac.c @@ -266,7 +266,7 @@ static int hac_hw_params(struct snd_pcm_substream *substream, #define AC97_FMTS \ SNDRV_PCM_FMTBIT_S16_LE -struct snd_soc_cpu_dai sh4_hac_dai[] = { +struct snd_soc_dai sh4_hac_dai[] = { { .name = "HAC0", .id = 0, diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c index 2f91de84c5c7..92bfaf4774a7 100644 --- a/sound/soc/sh/sh7760-ac97.c +++ b/sound/soc/sh/sh7760-ac97.c @@ -20,12 +20,12 @@ #define IPSEL 0xFE400034 /* platform specific structs can be declared here */ -extern struct snd_soc_cpu_dai sh4_hac_dai[2]; +extern struct snd_soc_dai sh4_hac_dai[2]; extern struct snd_soc_platform sh7760_soc_platform; static int machine_init(struct snd_soc_codec *codec) { - snd_soc_dapm_sync_endpoints(codec); + snd_soc_dapm_sync(codec); return 0; } diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c index 3388bc3d62d1..55c3464163ab 100644 --- a/sound/soc/sh/ssi.c +++ b/sound/soc/sh/ssi.c @@ -208,7 +208,7 @@ static int ssi_hw_params(struct snd_pcm_substream *substream, return 0; } -static int ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, +static int ssi_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id]; @@ -222,7 +222,7 @@ static int ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, * This divider is used to generate the SSI_SCK (I2S bitclock) from the * clock at the HAC_BIT_CLK ("oversampling clock") pin. */ -static int ssi_set_clkdiv(struct snd_soc_cpu_dai *dai, int did, int div) +static int ssi_set_clkdiv(struct snd_soc_dai *dai, int did, int div) { struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; unsigned long ssicr; @@ -245,7 +245,7 @@ static int ssi_set_clkdiv(struct snd_soc_cpu_dai *dai, int did, int div) return 0; } -static int ssi_set_fmt(struct snd_soc_cpu_dai *dai, unsigned int fmt) +static int ssi_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; unsigned long ssicr = SSIREG(SSICR); @@ -332,7 +332,7 @@ static int ssi_set_fmt(struct snd_soc_cpu_dai *dai, unsigned int fmt) SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) -struct snd_soc_cpu_dai sh4_ssi_dai[] = { +struct snd_soc_dai sh4_ssi_dai[] = { { .name = "SSI0", .id = 0, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e148db940cfc..462e635dfc74 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -4,8 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * with code, comments and ideas from :- * Richard Purdie <richard@openedhand.com> * @@ -14,10 +13,6 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 12th Aug 2005 Initial version. - * 25th Oct 2005 Working Codec, Interface and Platform registration. - * * TODO: * o Add hw rules to enforce rates, etc. * o More testing with other codecs/machines. @@ -112,9 +107,9 @@ static int soc_ac97_dev_register(struct snd_soc_codec *codec) } #endif -static inline const char* get_dai_name(int type) +static inline const char *get_dai_name(int type) { - switch(type) { + switch (type) { case SND_SOC_DAI_AC97_BUS: case SND_SOC_DAI_AC97: return "AC97"; @@ -138,8 +133,8 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; - struct snd_soc_codec_dai *codec_dai = machine->codec_dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; int ret = 0; mutex_lock(&pcm_mutex); @@ -182,9 +177,11 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) /* Check that the codec and cpu DAI's are compatible */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { runtime->hw.rate_min = - max(codec_dai->playback.rate_min, cpu_dai->playback.rate_min); + max(codec_dai->playback.rate_min, + cpu_dai->playback.rate_min); runtime->hw.rate_max = - min(codec_dai->playback.rate_max, cpu_dai->playback.rate_max); + min(codec_dai->playback.rate_max, + cpu_dai->playback.rate_max); runtime->hw.channels_min = max(codec_dai->playback.channels_min, cpu_dai->playback.channels_min); @@ -197,9 +194,11 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) codec_dai->playback.rates & cpu_dai->playback.rates; } else { runtime->hw.rate_min = - max(codec_dai->capture.rate_min, cpu_dai->capture.rate_min); + max(codec_dai->capture.rate_min, + cpu_dai->capture.rate_min); runtime->hw.rate_max = - min(codec_dai->capture.rate_max, cpu_dai->capture.rate_max); + min(codec_dai->capture.rate_max, + cpu_dai->capture.rate_max); runtime->hw.channels_min = max(codec_dai->capture.channels_min, cpu_dai->capture.channels_min); @@ -229,7 +228,7 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) goto machine_err; } - dbg("asoc: %s <-> %s info:\n",codec_dai->name, cpu_dai->name); + dbg("asoc: %s <-> %s info:\n", codec_dai->name, cpu_dai->name); dbg("asoc: rate mask 0x%x\n", runtime->hw.rates); dbg("asoc: min ch %d max ch %d\n", runtime->hw.channels_min, runtime->hw.channels_max); @@ -272,11 +271,11 @@ static void close_delayed_work(struct work_struct *work) struct snd_soc_device *socdev = container_of(work, struct snd_soc_device, delayed_work.work); struct snd_soc_codec *codec = socdev->codec; - struct snd_soc_codec_dai *codec_dai; + struct snd_soc_dai *codec_dai; int i; mutex_lock(&pcm_mutex); - for(i = 0; i < codec->num_dai; i++) { + for (i = 0; i < codec->num_dai; i++) { codec_dai = &codec->dai[i]; dbg("pop wq checking: %s status: %s waiting: %s\n", @@ -287,12 +286,12 @@ static void close_delayed_work(struct work_struct *work) /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) { - /* power down the codec to D1 if no longer active */ + /* Reduce power if no longer active */ if (codec->active == 0) { dbg("pop wq D1 %s %s\n", codec->name, codec_dai->playback.stream_name); - snd_soc_dapm_device_event(socdev, - SNDRV_CTL_POWER_D1); + snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_PREPARE); } codec_dai->pop_wait = 0; @@ -300,12 +299,12 @@ static void close_delayed_work(struct work_struct *work) codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_STOP); - /* power down the codec power domain if no longer active */ + /* Fall into standby if no longer active */ if (codec->active == 0) { dbg("pop wq D3 %s %s\n", codec->name, codec_dai->playback.stream_name); - snd_soc_dapm_device_event(socdev, - SNDRV_CTL_POWER_D3hot); + snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_STANDBY); } } } @@ -323,8 +322,8 @@ static int soc_codec_close(struct snd_pcm_substream *substream) struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; - struct snd_soc_codec_dai *codec_dai = machine->codec_dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; struct snd_soc_codec *codec = socdev->codec; mutex_lock(&pcm_mutex); @@ -340,6 +339,12 @@ static int soc_codec_close(struct snd_pcm_substream *substream) } codec->active--; + /* Muting the DAC suppresses artifacts caused during digital + * shutdown, for example from stopping clocks. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_digital_mute(codec_dai, 1); + if (cpu_dai->ops.shutdown) cpu_dai->ops.shutdown(substream); @@ -365,8 +370,8 @@ static int soc_codec_close(struct snd_pcm_substream *substream) SND_SOC_DAPM_STREAM_STOP); if (codec->active == 0 && codec_dai->pop_wait == 0) - snd_soc_dapm_device_event(socdev, - SNDRV_CTL_POWER_D3hot); + snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_STANDBY); } mutex_unlock(&pcm_mutex); @@ -384,8 +389,8 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; - struct snd_soc_codec_dai *codec_dai = machine->codec_dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; struct snd_soc_codec *codec = socdev->codec; int ret = 0; @@ -434,14 +439,14 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) else { codec_dai->pop_wait = 0; cancel_delayed_work(&socdev->delayed_work); - if (codec_dai->dai_ops.digital_mute) - codec_dai->dai_ops.digital_mute(codec_dai, 0); + snd_soc_dai_digital_mute(codec_dai, 0); } } else { /* no delayed work - do we need to power up codec */ - if (codec->dapm_state != SNDRV_CTL_POWER_D0) { + if (codec->bias_level != SND_SOC_BIAS_ON) { - snd_soc_dapm_device_event(socdev, SNDRV_CTL_POWER_D1); + snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_PREPARE); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) snd_soc_dapm_stream_event(codec, @@ -452,9 +457,8 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_START); - snd_soc_dapm_device_event(socdev, SNDRV_CTL_POWER_D0); - if (codec_dai->dai_ops.digital_mute) - codec_dai->dai_ops.digital_mute(codec_dai, 0); + snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_ON); + snd_soc_dai_digital_mute(codec_dai, 0); } else { /* codec already powered - power on widgets */ @@ -466,8 +470,8 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_START); - if (codec_dai->dai_ops.digital_mute) - codec_dai->dai_ops.digital_mute(codec_dai, 0); + + snd_soc_dai_digital_mute(codec_dai, 0); } } @@ -488,8 +492,8 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; - struct snd_soc_codec_dai *codec_dai = machine->codec_dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; int ret = 0; mutex_lock(&pcm_mutex); @@ -514,7 +518,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, if (cpu_dai->ops.hw_params) { ret = cpu_dai->ops.hw_params(substream, params); if (ret < 0) { - printk(KERN_ERR "asoc: can't set interface %s hw params\n", + printk(KERN_ERR "asoc: interface %s hw params failed\n", cpu_dai->name); goto interface_err; } @@ -523,7 +527,7 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, if (platform->pcm_ops->hw_params) { ret = platform->pcm_ops->hw_params(substream, params); if (ret < 0) { - printk(KERN_ERR "asoc: can't set platform %s hw params\n", + printk(KERN_ERR "asoc: platform %s hw params failed\n", platform->name); goto platform_err; } @@ -542,7 +546,7 @@ interface_err: codec_dai->ops.hw_free(substream); codec_err: - if(machine->ops && machine->ops->hw_free) + if (machine->ops && machine->ops->hw_free) machine->ops->hw_free(substream); mutex_unlock(&pcm_mutex); @@ -558,15 +562,15 @@ static int soc_pcm_hw_free(struct snd_pcm_substream *substream) struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; - struct snd_soc_codec_dai *codec_dai = machine->codec_dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; struct snd_soc_codec *codec = socdev->codec; mutex_lock(&pcm_mutex); /* apply codec digital mute */ - if (!codec->active && codec_dai->dai_ops.digital_mute) - codec_dai->dai_ops.digital_mute(codec_dai, 1); + if (!codec->active) + snd_soc_dai_digital_mute(codec_dai, 1); /* free any machine hw params */ if (machine->ops && machine->ops->hw_free) @@ -593,8 +597,8 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_dai_link *machine = rtd->dai; struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_cpu_dai *cpu_dai = machine->cpu_dai; - struct snd_soc_codec_dai *codec_dai = machine->codec_dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; int ret; if (codec_dai->ops.trigger) { @@ -631,16 +635,26 @@ static struct snd_pcm_ops soc_pcm_ops = { /* powers down audio subsystem for suspend */ static int soc_suspend(struct platform_device *pdev, pm_message_t state) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_machine *machine = socdev->machine; - struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_codec_device *codec_dev = socdev->codec_dev; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_machine *machine = socdev->machine; + struct snd_soc_platform *platform = socdev->platform; + struct snd_soc_codec_device *codec_dev = socdev->codec_dev; struct snd_soc_codec *codec = socdev->codec; int i; + /* Due to the resume being scheduled into a workqueue we could + * suspend before that's finished - wait for it to complete. + */ + snd_power_lock(codec->card); + snd_power_wait(codec->card, SNDRV_CTL_POWER_D0); + snd_power_unlock(codec->card); + + /* we're going to block userspace touching us until resume completes */ + snd_power_change_state(codec->card, SNDRV_CTL_POWER_D3hot); + /* mute any active DAC's */ - for(i = 0; i < machine->num_links; i++) { - struct snd_soc_codec_dai *dai = machine->dai_link[i].codec_dai; + for (i = 0; i < machine->num_links; i++) { + struct snd_soc_dai *dai = machine->dai_link[i].codec_dai; if (dai->dai_ops.digital_mute && dai->playback.active) dai->dai_ops.digital_mute(dai, 1); } @@ -652,8 +666,8 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) if (machine->suspend_pre) machine->suspend_pre(pdev, state); - for(i = 0; i < machine->num_links; i++) { - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai; + for (i = 0; i < machine->num_links; i++) { + struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai; if (cpu_dai->suspend && cpu_dai->type != SND_SOC_DAI_AC97) cpu_dai->suspend(pdev, cpu_dai); if (platform->suspend) @@ -662,9 +676,9 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) /* close any waiting streams and save state */ run_delayed_work(&socdev->delayed_work); - codec->suspend_dapm_state = codec->dapm_state; + codec->suspend_bias_level = codec->bias_level; - for(i = 0; i < codec->num_dai; i++) { + for (i = 0; i < codec->num_dai; i++) { char *stream = codec->dai[i].playback.stream_name; if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, @@ -678,8 +692,8 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) if (codec_dev->suspend) codec_dev->suspend(pdev, state); - for(i = 0; i < machine->num_links; i++) { - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai; + for (i = 0; i < machine->num_links; i++) { + struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai; if (cpu_dai->suspend && cpu_dai->type == SND_SOC_DAI_AC97) cpu_dai->suspend(pdev, cpu_dai); } @@ -690,21 +704,32 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) return 0; } -/* powers up audio subsystem after a suspend */ -static int soc_resume(struct platform_device *pdev) +/* deferred resume work, so resume can complete before we finished + * setting our codec back up, which can be very slow on I2C + */ +static void soc_resume_deferred(struct work_struct *work) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_machine *machine = socdev->machine; - struct snd_soc_platform *platform = socdev->platform; - struct snd_soc_codec_device *codec_dev = socdev->codec_dev; + struct snd_soc_device *socdev = container_of(work, + struct snd_soc_device, + deferred_resume_work); + struct snd_soc_machine *machine = socdev->machine; + struct snd_soc_platform *platform = socdev->platform; + struct snd_soc_codec_device *codec_dev = socdev->codec_dev; struct snd_soc_codec *codec = socdev->codec; + struct platform_device *pdev = to_platform_device(socdev->dev); int i; + /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time, + * so userspace apps are blocked from touching us + */ + + dev_info(socdev->dev, "starting resume work\n"); + if (machine->resume_pre) machine->resume_pre(pdev); - for(i = 0; i < machine->num_links; i++) { - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai; + for (i = 0; i < machine->num_links; i++) { + struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai; if (cpu_dai->resume && cpu_dai->type == SND_SOC_DAI_AC97) cpu_dai->resume(pdev, cpu_dai); } @@ -712,8 +737,8 @@ static int soc_resume(struct platform_device *pdev) if (codec_dev->resume) codec_dev->resume(pdev); - for(i = 0; i < codec->num_dai; i++) { - char* stream = codec->dai[i].playback.stream_name; + for (i = 0; i < codec->num_dai; i++) { + char *stream = codec->dai[i].playback.stream_name; if (stream != NULL) snd_soc_dapm_stream_event(codec, stream, SND_SOC_DAPM_STREAM_RESUME); @@ -723,15 +748,15 @@ static int soc_resume(struct platform_device *pdev) SND_SOC_DAPM_STREAM_RESUME); } - /* unmute any active DAC's */ - for(i = 0; i < machine->num_links; i++) { - struct snd_soc_codec_dai *dai = machine->dai_link[i].codec_dai; + /* unmute any active DACs */ + for (i = 0; i < machine->num_links; i++) { + struct snd_soc_dai *dai = machine->dai_link[i].codec_dai; if (dai->dai_ops.digital_mute && dai->playback.active) dai->dai_ops.digital_mute(dai, 0); } - for(i = 0; i < machine->num_links; i++) { - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai; + for (i = 0; i < machine->num_links; i++) { + struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai; if (cpu_dai->resume && cpu_dai->type != SND_SOC_DAI_AC97) cpu_dai->resume(pdev, cpu_dai); if (platform->resume) @@ -741,6 +766,22 @@ static int soc_resume(struct platform_device *pdev) if (machine->resume_post) machine->resume_post(pdev); + dev_info(socdev->dev, "resume work completed\n"); + + /* userspace can access us now we are back as we were before */ + snd_power_change_state(codec->card, SNDRV_CTL_POWER_D0); +} + +/* powers up audio subsystem after a suspend */ +static int soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + dev_info(socdev->dev, "scheduling resume work\n"); + + if (!schedule_work(&socdev->deferred_resume_work)) + dev_err(socdev->dev, "work item may be lost\n"); + return 0; } @@ -760,33 +801,38 @@ static int soc_probe(struct platform_device *pdev) if (machine->probe) { ret = machine->probe(pdev); - if(ret < 0) + if (ret < 0) return ret; } for (i = 0; i < machine->num_links; i++) { - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai; + struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai; if (cpu_dai->probe) { - ret = cpu_dai->probe(pdev); - if(ret < 0) + ret = cpu_dai->probe(pdev, cpu_dai); + if (ret < 0) goto cpu_dai_err; } } if (codec_dev->probe) { ret = codec_dev->probe(pdev); - if(ret < 0) + if (ret < 0) goto cpu_dai_err; } if (platform->probe) { ret = platform->probe(pdev); - if(ret < 0) + if (ret < 0) goto platform_err; } /* DAPM stream work */ INIT_DELAYED_WORK(&socdev->delayed_work, close_delayed_work); +#ifdef CONFIG_PM + /* deferred resume work */ + INIT_WORK(&socdev->deferred_resume_work, soc_resume_deferred); +#endif + return 0; platform_err: @@ -795,9 +841,9 @@ platform_err: cpu_dai_err: for (i--; i >= 0; i--) { - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai; + struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai; if (cpu_dai->remove) - cpu_dai->remove(pdev); + cpu_dai->remove(pdev, cpu_dai); } if (machine->remove) @@ -824,9 +870,9 @@ static int soc_remove(struct platform_device *pdev) codec_dev->remove(pdev); for (i = 0; i < machine->num_links; i++) { - struct snd_soc_cpu_dai *cpu_dai = machine->dai_link[i].cpu_dai; + struct snd_soc_dai *cpu_dai = machine->dai_link[i].cpu_dai; if (cpu_dai->remove) - cpu_dai->remove(pdev); + cpu_dai->remove(pdev, cpu_dai); } if (machine->remove) @@ -852,8 +898,8 @@ static int soc_new_pcm(struct snd_soc_device *socdev, struct snd_soc_dai_link *dai_link, int num) { struct snd_soc_codec *codec = socdev->codec; - struct snd_soc_codec_dai *codec_dai = dai_link->codec_dai; - struct snd_soc_cpu_dai *cpu_dai = dai_link->cpu_dai; + struct snd_soc_dai *codec_dai = dai_link->codec_dai; + struct snd_soc_dai *cpu_dai = dai_link->cpu_dai; struct snd_soc_pcm_runtime *rtd; struct snd_pcm *pcm; char new_name[64]; @@ -868,7 +914,7 @@ static int soc_new_pcm(struct snd_soc_device *socdev, codec_dai->codec = socdev->codec; /* check client and interface hw capabilities */ - sprintf(new_name, "%s %s-%s-%d",dai_link->stream_name, codec_dai->name, + sprintf(new_name, "%s %s-%s-%d", dai_link->stream_name, codec_dai->name, get_dai_name(cpu_dai->type), num); if (codec_dai->playback.channels_min) @@ -879,7 +925,8 @@ static int soc_new_pcm(struct snd_soc_device *socdev, ret = snd_pcm_new(codec->card, new_name, codec->pcm_devs++, playback, capture, &pcm); if (ret < 0) { - printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name); + printk(KERN_ERR "asoc: can't create pcm for codec %s\n", + codec->name); kfree(rtd); return ret; } @@ -928,8 +975,29 @@ static ssize_t codec_reg_show(struct device *dev, step = codec->reg_cache_step; count += sprintf(buf, "%s registers\n", codec->name); - for(i = 0; i < codec->reg_cache_size; i += step) - count += sprintf(buf + count, "%2x: %4x\n", i, codec->read(codec, i)); + for (i = 0; i < codec->reg_cache_size; i += step) { + count += sprintf(buf + count, "%2x: ", i); + if (count >= PAGE_SIZE - 1) + break; + + if (codec->display_register) + count += codec->display_register(codec, buf + count, + PAGE_SIZE - count, i); + else + count += snprintf(buf + count, PAGE_SIZE - count, + "%4x", codec->read(codec, i)); + + if (count >= PAGE_SIZE - 1) + break; + + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + if (count >= PAGE_SIZE - 1) + break; + } + + /* Truncate count; min() would cause a warning */ + if (count >= PAGE_SIZE) + count = PAGE_SIZE - 1; return count; } @@ -1072,7 +1140,7 @@ int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid) strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver)); /* create the pcms */ - for(i = 0; i < machine->num_links; i++) { + for (i = 0; i < machine->num_links; i++) { ret = soc_new_pcm(socdev, &machine->dai_link[i], i); if (ret < 0) { printk(KERN_ERR "asoc: can't create pcm %s\n", @@ -1102,7 +1170,7 @@ int snd_soc_register_card(struct snd_soc_device *socdev) struct snd_soc_machine *machine = socdev->machine; int ret = 0, i, ac97 = 0, err = 0; - for(i = 0; i < machine->num_links; i++) { + for (i = 0; i < machine->num_links; i++) { if (socdev->machine->dai_link[i].init) { err = socdev->machine->dai_link[i].init(codec); if (err < 0) { @@ -1111,7 +1179,7 @@ int snd_soc_register_card(struct snd_soc_device *socdev) continue; } } - if (socdev->machine->dai_link[i].codec_dai->type == + if (socdev->machine->dai_link[i].codec_dai->type == SND_SOC_DAI_AC97_BUS) ac97 = 1; } @@ -1122,7 +1190,7 @@ int snd_soc_register_card(struct snd_soc_device *socdev) ret = snd_card_register(codec->card); if (ret < 0) { - printk(KERN_ERR "asoc: failed to register soundcard for codec %s\n", + printk(KERN_ERR "asoc: failed to register soundcard for %s\n", codec->name); goto out; } @@ -1146,7 +1214,7 @@ int snd_soc_register_card(struct snd_soc_device *socdev) err = device_create_file(socdev->dev, &dev_attr_codec_reg); if (err < 0) - printk(KERN_WARNING "asoc: failed to add codec sysfs entries\n"); + printk(KERN_WARNING "asoc: failed to add codec sysfs files\n"); mutex_unlock(&codec->mutex); @@ -1166,13 +1234,13 @@ void snd_soc_free_pcms(struct snd_soc_device *socdev) { struct snd_soc_codec *codec = socdev->codec; #ifdef CONFIG_SND_SOC_AC97_BUS - struct snd_soc_codec_dai *codec_dai; + struct snd_soc_dai *codec_dai; int i; #endif mutex_lock(&codec->mutex); #ifdef CONFIG_SND_SOC_AC97_BUS - for(i = 0; i < codec->num_dai; i++) { + for (i = 0; i < codec->num_dai; i++) { codec_dai = &codec->dai[i]; if (codec_dai->type == SND_SOC_DAI_AC97_BUS && codec->ac97) { soc_ac97_dev_unregister(codec); @@ -1253,10 +1321,10 @@ int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = e->shift_l == e->shift_r ? 1 : 2; - uinfo->value.enumerated.items = e->mask; + uinfo->value.enumerated.items = e->max; - if (uinfo->value.enumerated.item > e->mask - 1) - uinfo->value.enumerated.item = e->mask - 1; + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]); return 0; @@ -1279,10 +1347,11 @@ int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(codec, e->reg); - ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1); + ucontrol->value.enumerated.item[0] + = (val >> e->shift_l) & (bitmask - 1); if (e->shift_l != e->shift_r) ucontrol->value.enumerated.item[1] = (val >> e->shift_r) & (bitmask - 1); @@ -1308,14 +1377,14 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, unsigned short val; unsigned short mask, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; - if (ucontrol->value.enumerated.item[0] > e->mask - 1) + if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; val = ucontrol->value.enumerated.item[0] << e->shift_l; mask = (bitmask - 1) << e->shift_l; if (e->shift_l != e->shift_r) { - if (ucontrol->value.enumerated.item[1] > e->mask - 1) + if (ucontrol->value.enumerated.item[1] > e->max - 1) return -EINVAL; val |= ucontrol->value.enumerated.item[1] << e->shift_r; mask |= (bitmask - 1) << e->shift_r; @@ -1342,10 +1411,10 @@ int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol, uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; - uinfo->value.enumerated.items = e->mask; + uinfo->value.enumerated.items = e->max; - if (uinfo->value.enumerated.item > e->mask - 1) - uinfo->value.enumerated.item = e->mask - 1; + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]); return 0; @@ -1390,9 +1459,11 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (kcontrol->private_value >> 16) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + unsigned int shift = mc->min; + unsigned int rshift = mc->rshift; if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1418,13 +1489,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw); int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask; @@ -1455,13 +1528,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw); int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; unsigned short val, val2, val_mask; val = (ucontrol->value.integer.value[0] & mask); @@ -1493,7 +1568,9 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw); int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (kcontrol->private_value >> 12) & 0xff; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1519,13 +1596,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int reg2 = (kcontrol->private_value >> 24) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int max = (kcontrol->private_value >> 12) & 0xff; - int mask = (1<<fls(max))-1; - int invert = (kcontrol->private_value >> 20) & 0x01; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1<<fls(max))-1; + unsigned int invert = mc->invert; ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask; @@ -1554,13 +1633,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r); int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int reg2 = (kcontrol->private_value >> 24) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int max = (kcontrol->private_value >> 12) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 20) & 0x01; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; int err; unsigned short val, val2, val_mask; @@ -1576,7 +1657,8 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, val = val << shift; val2 = val2 << shift; - if ((err = snd_soc_update_bits(codec, reg, val_mask, val)) < 0) + err = snd_soc_update_bits(codec, reg, val_mask, val); + if (err < 0) return err; err = snd_soc_update_bits(codec, reg2, val_mask, val2); @@ -1584,6 +1666,210 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r); +/** + * snd_soc_info_volsw_s8 - signed mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to provide information about a signed mixer control. + * + * Returns 0 for success. + */ +int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max-min; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8); + +/** + * snd_soc_get_volsw_s8 - signed mixer get callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to get the value of a signed mixer control. + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + int min = mc->min; + int val = snd_soc_read(codec, reg); + + ucontrol->value.integer.value[0] = + ((signed char)(val & 0xff))-min; + ucontrol->value.integer.value[1] = + ((signed char)((val >> 8) & 0xff))-min; + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8); + +/** + * snd_soc_put_volsw_sgn - signed mixer put callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Callback to set the value of a signed mixer control. + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + int min = mc->min; + unsigned short val; + + val = (ucontrol->value.integer.value[0]+min) & 0xff; + val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8; + + return snd_soc_update_bits(codec, reg, 0xffff, val); +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8); + +/** + * snd_soc_dai_set_sysclk - configure DAI system or master clock. + * @dai: DAI + * @clk_id: DAI specific clock ID + * @freq: new clock frequency in Hz + * @dir: new clock direction - input/output. + * + * Configures the DAI master (MCLK) or system (SYSCLK) clocking. + */ +int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + if (dai->dai_ops.set_sysclk) + return dai->dai_ops.set_sysclk(dai, clk_id, freq, dir); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); + +/** + * snd_soc_dai_set_clkdiv - configure DAI clock dividers. + * @dai: DAI + * @clk_id: DAI specific clock divider ID + * @div: new clock divisor. + * + * Configures the clock dividers. This is used to derive the best DAI bit and + * frame clocks from the system or master clock. It's best to set the DAI bit + * and frame clocks as low as possible to save system power. + */ +int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, + int div_id, int div) +{ + if (dai->dai_ops.set_clkdiv) + return dai->dai_ops.set_clkdiv(dai, div_id, div); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); + +/** + * snd_soc_dai_set_pll - configure DAI PLL. + * @dai: DAI + * @pll_id: DAI specific PLL ID + * @freq_in: PLL input clock frequency in Hz + * @freq_out: requested PLL output clock frequency in Hz + * + * Configures and enables PLL to generate output clock based on input clock. + */ +int snd_soc_dai_set_pll(struct snd_soc_dai *dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + if (dai->dai_ops.set_pll) + return dai->dai_ops.set_pll(dai, pll_id, freq_in, freq_out); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); + +/** + * snd_soc_dai_set_fmt - configure DAI hardware audio format. + * @dai: DAI + * @clk_id: DAI specific clock ID + * @fmt: SND_SOC_DAIFMT_ format value. + * + * Configures the DAI hardware format and clocking. + */ +int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + if (dai->dai_ops.set_fmt) + return dai->dai_ops.set_fmt(dai, fmt); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); + +/** + * snd_soc_dai_set_tdm_slot - configure DAI TDM. + * @dai: DAI + * @mask: DAI specific mask representing used slots. + * @slots: Number of slots in use. + * + * Configures a DAI for TDM operation. Both mask and slots are codec and DAI + * specific. + */ +int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int mask, int slots) +{ + if (dai->dai_ops.set_sysclk) + return dai->dai_ops.set_tdm_slot(dai, mask, slots); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); + +/** + * snd_soc_dai_set_tristate - configure DAI system or master clock. + * @dai: DAI + * @tristate: tristate enable + * + * Tristates the DAI so that others can use it. + */ +int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + if (dai->dai_ops.set_sysclk) + return dai->dai_ops.set_tristate(dai, tristate); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); + +/** + * snd_soc_dai_digital_mute - configure DAI system or master clock. + * @dai: DAI + * @mute: mute enable + * + * Mutes the DAI DAC. + */ +int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute) +{ + if (dai->dai_ops.digital_mute) + return dai->dai_ops.digital_mute(dai, mute); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); + static int __devinit snd_soc_init(void) { printk(KERN_INFO "ASoC version %s\n", SND_SOC_VERSION); @@ -1592,14 +1878,14 @@ static int __devinit snd_soc_init(void) static void snd_soc_exit(void) { - platform_driver_unregister(&soc_driver); + platform_driver_unregister(&soc_driver); } module_init(snd_soc_init); module_exit(snd_soc_exit); /* Module information */ -MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("ALSA SoC Core"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:soc-audio"); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index af3326c63504..efbd0b37810a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2,19 +2,13 @@ * soc-dapm.c -- ALSA SoC Dynamic Audio Power Management * * Copyright 2005 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 12th Aug 2005 Initial version. - * 25th Oct 2005 Implemented path power domain. - * 18th Dec 2005 Implemented machine and stream level power domain. - * * Features: * o Changes power status of internal codec blocks depending on the * dynamic configuration of codec internal audio paths and active @@ -43,6 +37,7 @@ #include <linux/bitops.h> #include <linux/platform_device.h> #include <linux/jiffies.h> +#include <linux/debugfs.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -50,23 +45,10 @@ #include <sound/initval.h> /* debug */ -#define DAPM_DEBUG 0 -#if DAPM_DEBUG +#ifdef DEBUG #define dump_dapm(codec, action) dbg_dump_dapm(codec, action) -#define dbg(format, arg...) printk(format, ## arg) #else #define dump_dapm(codec, action) -#define dbg(format, arg...) -#endif - -#define POP_DEBUG 0 -#if POP_DEBUG -#define POP_TIME 500 /* 500 msecs - change if pop debug is too fast */ -#define pop_wait(time) schedule_timeout_uninterruptible(msecs_to_jiffies(time)) -#define pop_dbg(format, arg...) printk(format, ## arg); pop_wait(POP_TIME) -#else -#define pop_dbg(format, arg...) -#define pop_wait(time) #endif /* dapm power sequences - make this per codec in the future */ @@ -85,6 +67,30 @@ static int dapm_status = 1; module_param(dapm_status, int, 0); MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries"); +static struct dentry *asoc_debugfs; + +static u32 pop_time; + +static void pop_wait(void) +{ + if (pop_time) + schedule_timeout_uninterruptible(msecs_to_jiffies(pop_time)); +} + +static void pop_dbg(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + if (pop_time) { + vprintk(fmt, args); + pop_wait(); + } + + va_end(args); +} + /* create a new dapm widget */ static inline struct snd_soc_dapm_widget *dapm_cnew_widget( const struct snd_soc_dapm_widget *_widget) @@ -100,10 +106,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_switch: case snd_soc_dapm_mixer: { int val; - int reg = w->kcontrols[i].private_value & 0xff; - int shift = (w->kcontrols[i].private_value >> 8) & 0x0f; - int mask = (w->kcontrols[i].private_value >> 16) & 0xff; - int invert = (w->kcontrols[i].private_value >> 24) & 0x01; + struct soc_mixer_control *mc = (struct soc_mixer_control *) + w->kcontrols[i].private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; val = snd_soc_read(w->codec, reg); val = (val >> shift) & mask; @@ -118,13 +127,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value; int val, item, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(w->codec, e->reg); item = (val >> e->shift_l) & (bitmask - 1); p->connect = 0; - for (i = 0; i < e->mask; i++) { + for (i = 0; i < e->max; i++) { if (!(strcmp(p->name, e->texts[i])) && item == i) p->connect = 1; } @@ -161,7 +170,7 @@ static int dapm_connect_mux(struct snd_soc_codec *codec, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; int i; - for (i = 0; i < e->mask; i++) { + for (i = 0; i < e->max; i++) { if (!(strcmp(control_name, e->texts[i]))) { list_add(&path->list, &codec->dapm_paths); list_add(&path->list_sink, &dest->sources); @@ -222,11 +231,12 @@ static int dapm_update_bits(struct snd_soc_dapm_widget *widget) change = old != new; if (change) { pop_dbg("pop test %s : %s in %d ms\n", widget->name, - widget->power ? "on" : "off", POP_TIME); + widget->power ? "on" : "off", pop_time); snd_soc_write(codec, widget->reg, new); - pop_wait(POP_TIME); + pop_wait(); } - dbg("reg %x old %x new %x change %d\n", widget->reg, old, new, change); + pr_debug("reg %x old %x new %x change %d\n", widget->reg, + old, new, change); return change; } @@ -242,16 +252,19 @@ static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power) return 0; if (widget->num_kcontrols && k) { - int reg = k->private_value & 0xff; - int shift = (k->private_value >> 8) & 0x0f; - int mask = (k->private_value >> 16) & 0xff; - int invert = (k->private_value >> 24) & 0x01; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)k->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; if (power) { int i; /* power up has happended, increase volume to last level */ if (invert) { - for (i = mask; i > widget->saved_value; i--) + for (i = max; i > widget->saved_value; i--) snd_soc_update_bits(widget->codec, reg, mask, i); } else { for (i = 0; i < widget->saved_value; i++) @@ -448,6 +461,26 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) } /* + * Handler for generic register modifier widget. + */ +int dapm_reg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + unsigned int val; + + if (SND_SOC_DAPM_EVENT_ON(event)) + val = w->on_val; + else + val = w->off_val; + + snd_soc_update_bits(w->codec, -(w->reg + 1), + w->mask << w->shift, val << w->shift); + + return 0; +} +EXPORT_SYMBOL_GPL(dapm_reg_event); + +/* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- * @@ -499,24 +532,6 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; } - /* programmable gain/attenuation */ - if (w->id == snd_soc_dapm_pga) { - int on; - in = is_connected_input_ep(w); - dapm_clear_walk(w->codec); - out = is_connected_output_ep(w); - dapm_clear_walk(w->codec); - w->power = on = (out != 0 && in != 0) ? 1 : 0; - - if (!on) - dapm_set_pga(w, on); /* lower volume to reduce pops */ - dapm_update_bits(w); - if (on) - dapm_set_pga(w, on); /* restore volume from zero */ - - continue; - } - /* pre and post event widgets */ if (w->id == snd_soc_dapm_pre) { if (!w->event) @@ -562,45 +577,56 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) power_change = (w->power == power) ? 0: 1; w->power = power; + if (!power_change) + continue; + /* call any power change event handlers */ - if (power_change) { - if (w->event) { - dbg("power %s event for %s flags %x\n", - w->power ? "on" : "off", w->name, w->event_flags); - if (power) { - /* power up event */ - if (w->event_flags & SND_SOC_DAPM_PRE_PMU) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - return ret; - } - dapm_update_bits(w); - if (w->event_flags & SND_SOC_DAPM_POST_PMU){ - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - return ret; - } - } else { - /* power down event */ - if (w->event_flags & SND_SOC_DAPM_PRE_PMD) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - return ret; - } - dapm_update_bits(w); - if (w->event_flags & SND_SOC_DAPM_POST_PMD) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - return ret; - } - } - } else - /* no event handler */ - dapm_update_bits(w); + if (w->event) + pr_debug("power %s event for %s flags %x\n", + w->power ? "on" : "off", + w->name, w->event_flags); + + /* power up pre event */ + if (power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + return ret; + } + + /* power down pre event */ + if (!power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + return ret; + } + + /* Lower PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && !power) + dapm_set_pga(w, power); + + dapm_update_bits(w); + + /* Raise PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && power) + dapm_set_pga(w, power); + + /* power up post event */ + if (power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMU)) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + return ret; + } + + /* power down post event */ + if (!power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMD)) { + ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + return ret; } } } @@ -608,7 +634,7 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) return ret; } -#if DAPM_DEBUG +#ifdef DEBUG static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) { struct snd_soc_dapm_widget *w; @@ -666,7 +692,7 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) /* test and update the power status of a mux widget */ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int mask, - int val, struct soc_enum* e) + int mux, int val, struct soc_enum *e) { struct snd_soc_dapm_path *path; int found = 0; @@ -682,19 +708,21 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, if (path->kcontrol != kcontrol) continue; - if (!path->name || ! e->texts[val]) + if (!path->name || !e->texts[mux]) continue; found = 1; /* we now need to match the string in the enum to the path */ - if (!(strcmp(path->name, e->texts[val]))) + if (!(strcmp(path->name, e->texts[mux]))) path->connect = 1; /* new connection */ else path->connect = 0; /* old connection must be powered down */ } - if (found) + if (found) { dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP); + dump_dapm(widget->codec, "mux power update"); + } return 0; } @@ -730,8 +758,10 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, break; } - if (found) + if (found) { dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP); + dump_dapm(widget->codec, "mixer power update"); + } return 0; } @@ -768,21 +798,18 @@ static ssize_t dapm_widget_show(struct device *dev, } } - switch(codec->dapm_state){ - case SNDRV_CTL_POWER_D0: - state = "D0"; + switch (codec->bias_level) { + case SND_SOC_BIAS_ON: + state = "On"; break; - case SNDRV_CTL_POWER_D1: - state = "D1"; + case SND_SOC_BIAS_PREPARE: + state = "Prepare"; break; - case SNDRV_CTL_POWER_D2: - state = "D2"; + case SND_SOC_BIAS_STANDBY: + state = "Standby"; break; - case SNDRV_CTL_POWER_D3hot: - state = "D3hot"; - break; - case SNDRV_CTL_POWER_D3cold: - state = "D3cold"; + case SND_SOC_BIAS_OFF: + state = "Off"; break; } count += sprintf(buf + count, "PM State: %s\n", state); @@ -796,16 +823,31 @@ int snd_soc_dapm_sys_add(struct device *dev) { int ret = 0; - if (dapm_status) - ret = device_create_file(dev, &dev_attr_dapm_widget); + if (!dapm_status) + return 0; + + ret = device_create_file(dev, &dev_attr_dapm_widget); + if (ret != 0) + return ret; - return ret; + asoc_debugfs = debugfs_create_dir("asoc", NULL); + if (!IS_ERR(asoc_debugfs)) + debugfs_create_u32("dapm_pop_time", 0744, asoc_debugfs, + &pop_time); + else + asoc_debugfs = NULL; + + return 0; } static void snd_soc_dapm_sys_remove(struct device *dev) { - if (dapm_status) + if (dapm_status) { device_remove_file(dev, &dev_attr_dapm_widget); + } + + if (asoc_debugfs) + debugfs_remove_recursive(asoc_debugfs); } /* free all dapm widgets and resources */ @@ -826,8 +868,25 @@ static void dapm_free_widgets(struct snd_soc_codec *codec) } } +static int snd_soc_dapm_set_pin(struct snd_soc_codec *codec, + char *pin, int status) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (!strcmp(w->name, pin)) { + pr_debug("dapm: %s: pin %s\n", codec->name, pin); + w->connected = status; + return 0; + } + } + + pr_err("dapm: %s: configuring unknown pin %s\n", codec->name, pin); + return -EINVAL; +} + /** - * snd_soc_dapm_sync_endpoints - scan and power dapm paths + * snd_soc_dapm_sync - scan and power dapm paths * @codec: audio codec * * Walks all dapm audio paths and powers widgets according to their @@ -835,27 +894,16 @@ static void dapm_free_widgets(struct snd_soc_codec *codec) * * Returns 0 for success. */ -int snd_soc_dapm_sync_endpoints(struct snd_soc_codec *codec) +int snd_soc_dapm_sync(struct snd_soc_codec *codec) { - return dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP); + int ret = dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP); + dump_dapm(codec, "sync"); + return ret; } -EXPORT_SYMBOL_GPL(snd_soc_dapm_sync_endpoints); +EXPORT_SYMBOL_GPL(snd_soc_dapm_sync); -/** - * snd_soc_dapm_connect_input - connect dapm widgets - * @codec: audio codec - * @sink: name of target widget - * @control: mixer control name - * @source: name of source name - * - * Connects 2 dapm widgets together via a named audio path. The sink is - * the widget receiving the audio signal, whilst the source is the sender - * of the audio signal. - * - * Returns 0 for success else error. - */ -int snd_soc_dapm_connect_input(struct snd_soc_codec *codec, const char *sink, - const char * control, const char *source) +static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, + const char *sink, const char *control, const char *source) { struct snd_soc_dapm_path *path; struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w; @@ -957,9 +1005,64 @@ err: kfree(path); return ret; } + +/** + * snd_soc_dapm_connect_input - connect dapm widgets + * @codec: audio codec + * @sink: name of target widget + * @control: mixer control name + * @source: name of source name + * + * Connects 2 dapm widgets together via a named audio path. The sink is + * the widget receiving the audio signal, whilst the source is the sender + * of the audio signal. + * + * This function has been deprecated in favour of snd_soc_dapm_add_routes(). + * + * Returns 0 for success else error. + */ +int snd_soc_dapm_connect_input(struct snd_soc_codec *codec, const char *sink, + const char *control, const char *source) +{ + return snd_soc_dapm_add_route(codec, sink, control, source); +} EXPORT_SYMBOL_GPL(snd_soc_dapm_connect_input); /** + * snd_soc_dapm_add_routes - Add routes between DAPM widgets + * @codec: codec + * @route: audio routes + * @num: number of routes + * + * Connects 2 dapm widgets together via a named audio path. The sink is + * the widget receiving the audio signal, whilst the source is the sender + * of the audio signal. + * + * Returns 0 for success else error. On error all resources can be freed + * with a call to snd_soc_card_free(). + */ +int snd_soc_dapm_add_routes(struct snd_soc_codec *codec, + const struct snd_soc_dapm_route *route, int num) +{ + int i, ret; + + for (i = 0; i < num; i++) { + ret = snd_soc_dapm_add_route(codec, route->sink, + route->control, route->source); + if (ret < 0) { + printk(KERN_ERR "Failed to add route %s->%s\n", + route->source, + route->sink); + return ret; + } + route++; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_add_routes); + +/** * snd_soc_dapm_new_widgets - add new dapm widgets * @codec: audio codec * @@ -1022,12 +1125,14 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int invert = (kcontrol->private_value >> 24) & 0x01; - int mask = (1 << fls(max)) - 1; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int invert = mc->invert; + unsigned int mask = (1 << fls(max)) - 1; /* return the saved value if we are powered down */ if (widget->id == snd_soc_dapm_pga && !widget->power) { @@ -1065,12 +1170,14 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; unsigned short val, val2, val_mask; int ret; @@ -1137,7 +1244,7 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(widget->codec, e->reg); ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1); @@ -1167,15 +1274,15 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, unsigned short mask, bitmask; int ret = 0; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; - if (ucontrol->value.enumerated.item[0] > e->mask - 1) + if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; mux = ucontrol->value.enumerated.item[0]; val = mux << e->shift_l; mask = (bitmask - 1) << e->shift_l; if (e->shift_l != e->shift_r) { - if (ucontrol->value.enumerated.item[1] > e->mask - 1) + if (ucontrol->value.enumerated.item[1] > e->max - 1) return -EINVAL; val |= ucontrol->value.enumerated.item[1] << e->shift_r; mask |= (bitmask - 1) << e->shift_r; @@ -1183,7 +1290,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mutex_lock(&widget->codec->mutex); widget->value = val; - dapm_mux_update_power(widget, kcontrol, mask, mux, e); + dapm_mux_update_power(widget, kcontrol, mask, mux, val, e); if (widget->event) { if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { ret = widget->event(widget, @@ -1234,6 +1341,33 @@ int snd_soc_dapm_new_control(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control); /** + * snd_soc_dapm_new_controls - create new dapm controls + * @codec: audio codec + * @widget: widget array + * @num: number of widgets + * + * Creates new DAPM controls based upon the templates. + * + * Returns 0 for success else error. + */ +int snd_soc_dapm_new_controls(struct snd_soc_codec *codec, + const struct snd_soc_dapm_widget *widget, + int num) +{ + int i, ret; + + for (i = 0; i < num; i++) { + ret = snd_soc_dapm_new_control(codec, widget); + if (ret < 0) + return ret; + widget++; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_new_controls); + + +/** * snd_soc_dapm_stream_event - send a stream event to the dapm core * @codec: audio codec * @stream: stream name @@ -1257,8 +1391,8 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, { if (!w->sname) continue; - dbg("widget %s\n %s stream %s event %d\n", w->name, w->sname, - stream, event); + pr_debug("widget %s\n %s stream %s event %d\n", + w->name, w->sname, stream, event); if (strstr(w->sname, stream)) { switch(event) { case SND_SOC_DAPM_STREAM_START: @@ -1294,53 +1428,101 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event); /** - * snd_soc_dapm_device_event - send a device event to the dapm core + * snd_soc_dapm_set_bias_level - set the bias level for the system * @socdev: audio device - * @event: device event + * @level: level to configure * - * Sends a device event to the dapm core. The core then makes any - * necessary machine or codec power changes.. + * Configure the bias (power) levels for the SoC audio device. * * Returns 0 for success else error. */ -int snd_soc_dapm_device_event(struct snd_soc_device *socdev, int event) +int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, + enum snd_soc_bias_level level) { struct snd_soc_codec *codec = socdev->codec; struct snd_soc_machine *machine = socdev->machine; + int ret = 0; - if (machine->dapm_event) - machine->dapm_event(machine, event); - if (codec->dapm_event) - codec->dapm_event(codec, event); - return 0; + if (machine->set_bias_level) + ret = machine->set_bias_level(machine, level); + if (ret == 0 && codec->set_bias_level) + ret = codec->set_bias_level(codec, level); + + return ret; +} + +/** + * snd_soc_dapm_enable_pin - enable pin. + * @snd_soc_codec: SoC codec + * @pin: pin name + * + * Enables input/output pin and it's parents or children widgets iff there is + * a valid audio route and active audio stream. + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, char *pin) +{ + return snd_soc_dapm_set_pin(codec, pin, 1); } -EXPORT_SYMBOL_GPL(snd_soc_dapm_device_event); +EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin); /** - * snd_soc_dapm_set_endpoint - set audio endpoint status + * snd_soc_dapm_disable_pin - disable pin. + * @codec: SoC codec + * @pin: pin name + * + * Disables input/output pin and it's parents or children widgets. + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, char *pin) +{ + return snd_soc_dapm_set_pin(codec, pin, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin); + +/** + * snd_soc_dapm_nc_pin - permanently disable pin. + * @codec: SoC codec + * @pin: pin name + * + * Marks the specified pin as being not connected, disabling it along + * any parent or child widgets. At present this is identical to + * snd_soc_dapm_disable_pin() but in future it will be extended to do + * additional things such as disabling controls which only affect + * paths through the pin. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, char *pin) +{ + return snd_soc_dapm_set_pin(codec, pin, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_nc_pin); + +/** + * snd_soc_dapm_get_pin_status - get audio pin status * @codec: audio codec - * @endpoint: audio signal endpoint (or start point) - * @status: point status + * @pin: audio signal pin endpoint (or start point) * - * Set audio endpoint status - connected or disconnected. + * Get audio pin status - connected or disconnected. * - * Returns 0 for success else error. + * Returns 1 for connected otherwise 0. */ -int snd_soc_dapm_set_endpoint(struct snd_soc_codec *codec, - char *endpoint, int status) +int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, char *pin) { struct snd_soc_dapm_widget *w; list_for_each_entry(w, &codec->dapm_widgets, list) { - if (!strcmp(w->name, endpoint)) { - w->connected = status; - return 0; - } + if (!strcmp(w->name, pin)) + return w->connected; } - return -ENODEV; + return 0; } -EXPORT_SYMBOL_GPL(snd_soc_dapm_set_endpoint); +EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_status); /** * snd_soc_dapm_free - free dapm resources @@ -1358,6 +1540,6 @@ void snd_soc_dapm_free(struct snd_soc_device *socdev) EXPORT_SYMBOL_GPL(snd_soc_dapm_free); /* Module information */ -MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); MODULE_LICENSE("GPL"); |