diff options
author | Takashi Iwai <tiwai@suse.de> | 2016-07-26 10:35:31 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2016-07-26 10:35:31 +0200 |
commit | 4a6baf1b35891ebc877e91a803877d69b703e086 (patch) | |
tree | 3a1368712165fb4fb18741b3496b514b2661b998 /sound | |
parent | cf81d6b583444cb6f5e656f050e43413b236354e (diff) | |
parent | e7ca8fcd15049b1e48ae2ef1434a68a51ef0ead5 (diff) | |
download | linux-4a6baf1b35891ebc877e91a803877d69b703e086.tar.gz linux-4a6baf1b35891ebc877e91a803877d69b703e086.tar.bz2 linux-4a6baf1b35891ebc877e91a803877d69b703e086.zip |
Merge tag 'asoc-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-linus
ASoC: Updates for v4.8
Not really any framework work this time around (though we have seen one
of the Analog Devices drivers move more to the clock API which is good
to see) but rather a lot of new drivers:
- Lots of updates for the Intel drivers, mostly board support and bug
fixing, and to the NAU8825 driver.
- Work on generalizing bits of simple-card to allow more code sharing
with the Renesas rsrc-card (which can't use simple-card due to DPCM).
- Removal of the Odroid X2 driver due to replacement with simple-card.
- Support for several new Mediatek platforms and associated boards.
- New drivers for Allwinner A10, Analog Devices ADAU7002, Broadcom
Cygnus, Cirrus Logic CS35L33 and CS53L30, Maxim MAX8960 and MAX98504,
Realtek RT5514 and Wolfson WM8758
Diffstat (limited to 'sound')
169 files changed, 18406 insertions, 2925 deletions
diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 9b3334be9df2..2c498488af6c 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -67,6 +67,8 @@ struct snd_compr_file { struct snd_compr_stream stream; }; +static void error_delayed_work(struct work_struct *work); + /* * a note on stream states used: * we use following states in the compressed core @@ -123,6 +125,9 @@ static int snd_compr_open(struct inode *inode, struct file *f) snd_card_unref(compr->card); return -ENOMEM; } + + INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); + data->stream.ops = compr->ops; data->stream.direction = dirn; data->stream.private_data = compr->private_data; @@ -153,6 +158,8 @@ static int snd_compr_free(struct inode *inode, struct file *f) struct snd_compr_file *data = f->private_data; struct snd_compr_runtime *runtime = data->stream.runtime; + cancel_delayed_work_sync(&data->stream.error_work); + switch (runtime->state) { case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_DRAINING: @@ -237,6 +244,15 @@ snd_compr_ioctl_avail(struct snd_compr_stream *stream, unsigned long arg) avail = snd_compr_calc_avail(stream, &ioctl_avail); ioctl_avail.avail = avail; + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + return -EBADFD; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + if (copy_to_user((__u64 __user *)arg, &ioctl_avail, sizeof(ioctl_avail))) return -EFAULT; @@ -346,11 +362,13 @@ static ssize_t snd_compr_read(struct file *f, char __user *buf, switch (stream->runtime->state) { case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_PREPARED: - case SNDRV_PCM_STATE_XRUN: case SNDRV_PCM_STATE_SUSPENDED: case SNDRV_PCM_STATE_DISCONNECTED: retval = -EBADFD; goto out; + case SNDRV_PCM_STATE_XRUN: + retval = -EPIPE; + goto out; } avail = snd_compr_get_avail(stream); @@ -399,10 +417,16 @@ static unsigned int snd_compr_poll(struct file *f, poll_table *wait) stream = &data->stream; mutex_lock(&stream->device->lock); - if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_XRUN: retval = snd_compr_get_poll(stream) | POLLERR; goto out; + default: + break; } + poll_wait(f, &stream->runtime->sleep, wait); avail = snd_compr_get_avail(stream); @@ -697,6 +721,45 @@ static int snd_compr_stop(struct snd_compr_stream *stream) return retval; } +static void error_delayed_work(struct work_struct *work) +{ + struct snd_compr_stream *stream; + + stream = container_of(work, struct snd_compr_stream, error_work.work); + + mutex_lock(&stream->device->lock); + + stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + wake_up(&stream->runtime->sleep); + + mutex_unlock(&stream->device->lock); +} + +/* + * snd_compr_stop_error: Report a fatal error on a stream + * @stream: pointer to stream + * @state: state to transition the stream to + * + * Stop the stream and set its state. + * + * Should be called with compressed device lock held. + */ +int snd_compr_stop_error(struct snd_compr_stream *stream, + snd_pcm_state_t state) +{ + if (stream->runtime->state == state) + return 0; + + stream->runtime->state = state; + + pr_debug("Changing state to: %d\n", state); + + queue_delayed_work(system_power_efficient_wq, &stream->error_work, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_compr_stop_error); + static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) { int ret; diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index 06e099e802df..22aec9a1e9a4 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -10,6 +10,7 @@ if SND_ATMEL_SOC config SND_ATMEL_SOC_PDC tristate + depends on HAS_DMA default m if SND_ATMEL_SOC_SSC_PDC=m && SND_ATMEL_SOC_SSC=m default y if SND_ATMEL_SOC_SSC_PDC=y || (SND_ATMEL_SOC_SSC_PDC=m && SND_ATMEL_SOC_SSC=y) diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c index 6107de9c538b..6d9b8b44e2da 100644 --- a/sound/soc/atmel/atmel-classd.c +++ b/sound/soc/atmel/atmel-classd.c @@ -593,11 +593,6 @@ static int atmel_classd_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "no memory resource\n"); - return -ENXIO; - } - io_base = devm_ioremap_resource(dev, res); if (IS_ERR(io_base)) { ret = PTR_ERR(io_base); diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c index aee4787a0b89..5f56da60c92f 100644 --- a/sound/soc/atmel/atmel-pdmic.c +++ b/sound/soc/atmel/atmel-pdmic.c @@ -624,11 +624,6 @@ static int atmel_pdmic_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "no memory resource\n"); - return -ENXIO; - } - io_base = devm_ioremap_resource(dev, res); if (IS_ERR(io_base)) { ret = PTR_ERR(io_base); diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c index 1267e1af0fae..54c09acd3fed 100644 --- a/sound/soc/atmel/atmel_ssc_dai.c +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -321,7 +321,7 @@ static int atmel_ssc_startup(struct snd_pcm_substream *substream, return ret; } - dma_params = &ssc_dma_params[dai->id][dir]; + dma_params = &ssc_dma_params[pdev->id][dir]; dma_params->ssc = ssc_p->ssc; dma_params->substream = substream; diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index 6a834e109f1d..d528aaceaad9 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -7,3 +7,12 @@ config SND_BCM2835_SOC_I2S Say Y or M if you want to add support for codecs attached to the BCM2835 I2S interface. You will also need to select the audio interfaces to support below. + +config SND_SOC_CYGNUS + tristate "SoC platform audio for Broadcom Cygnus chips" + depends on ARCH_BCM_CYGNUS || COMPILE_TEST + help + Say Y if you want to add support for ASoC audio on Broadcom + Cygnus chips (bcm958300, bcm958305, bcm911360) + + If you don't know what to do here, say N.
\ No newline at end of file diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index bc816b71e5a4..fc739d007884 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -3,3 +3,8 @@ snd-soc-bcm2835-i2s-objs := bcm2835-i2s.o obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o +# CYGNUS Platform Support +snd-soc-cygnus-objs := cygnus-pcm.o cygnus-ssp.o + +obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o + diff --git a/sound/soc/bcm/cygnus-pcm.c b/sound/soc/bcm/cygnus-pcm.c new file mode 100644 index 000000000000..d616e096462e --- /dev/null +++ b/sound/soc/bcm/cygnus-pcm.c @@ -0,0 +1,861 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/debugfs.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "cygnus-ssp.h" + +/* Register offset needed for ASoC PCM module */ + +#define INTH_R5F_STATUS_OFFSET 0x040 +#define INTH_R5F_CLEAR_OFFSET 0x048 +#define INTH_R5F_MASK_SET_OFFSET 0x050 +#define INTH_R5F_MASK_CLEAR_OFFSET 0x054 + +#define BF_REARM_FREE_MARK_OFFSET 0x344 +#define BF_REARM_FULL_MARK_OFFSET 0x348 + +/* Ring Buffer Ctrl Regs --- Start */ +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_RDADDR_REG_BASE */ +#define SRC_RBUF_0_RDADDR_OFFSET 0x500 +#define SRC_RBUF_1_RDADDR_OFFSET 0x518 +#define SRC_RBUF_2_RDADDR_OFFSET 0x530 +#define SRC_RBUF_3_RDADDR_OFFSET 0x548 +#define SRC_RBUF_4_RDADDR_OFFSET 0x560 +#define SRC_RBUF_5_RDADDR_OFFSET 0x578 +#define SRC_RBUF_6_RDADDR_OFFSET 0x590 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_WRADDR_REG_BASE */ +#define SRC_RBUF_0_WRADDR_OFFSET 0x504 +#define SRC_RBUF_1_WRADDR_OFFSET 0x51c +#define SRC_RBUF_2_WRADDR_OFFSET 0x534 +#define SRC_RBUF_3_WRADDR_OFFSET 0x54c +#define SRC_RBUF_4_WRADDR_OFFSET 0x564 +#define SRC_RBUF_5_WRADDR_OFFSET 0x57c +#define SRC_RBUF_6_WRADDR_OFFSET 0x594 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_BASEADDR_REG_BASE */ +#define SRC_RBUF_0_BASEADDR_OFFSET 0x508 +#define SRC_RBUF_1_BASEADDR_OFFSET 0x520 +#define SRC_RBUF_2_BASEADDR_OFFSET 0x538 +#define SRC_RBUF_3_BASEADDR_OFFSET 0x550 +#define SRC_RBUF_4_BASEADDR_OFFSET 0x568 +#define SRC_RBUF_5_BASEADDR_OFFSET 0x580 +#define SRC_RBUF_6_BASEADDR_OFFSET 0x598 + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_ENDADDR_REG_BASE */ +#define SRC_RBUF_0_ENDADDR_OFFSET 0x50c +#define SRC_RBUF_1_ENDADDR_OFFSET 0x524 +#define SRC_RBUF_2_ENDADDR_OFFSET 0x53c +#define SRC_RBUF_3_ENDADDR_OFFSET 0x554 +#define SRC_RBUF_4_ENDADDR_OFFSET 0x56c +#define SRC_RBUF_5_ENDADDR_OFFSET 0x584 +#define SRC_RBUF_6_ENDADDR_OFFSET 0x59c + +/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_FREE_MARK_REG_BASE */ +#define SRC_RBUF_0_FREE_MARK_OFFSET 0x510 +#define SRC_RBUF_1_FREE_MARK_OFFSET 0x528 +#define SRC_RBUF_2_FREE_MARK_OFFSET 0x540 +#define SRC_RBUF_3_FREE_MARK_OFFSET 0x558 +#define SRC_RBUF_4_FREE_MARK_OFFSET 0x570 +#define SRC_RBUF_5_FREE_MARK_OFFSET 0x588 +#define SRC_RBUF_6_FREE_MARK_OFFSET 0x5a0 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_RDADDR_REG_BASE */ +#define DST_RBUF_0_RDADDR_OFFSET 0x5c0 +#define DST_RBUF_1_RDADDR_OFFSET 0x5d8 +#define DST_RBUF_2_RDADDR_OFFSET 0x5f0 +#define DST_RBUF_3_RDADDR_OFFSET 0x608 +#define DST_RBUF_4_RDADDR_OFFSET 0x620 +#define DST_RBUF_5_RDADDR_OFFSET 0x638 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_WRADDR_REG_BASE */ +#define DST_RBUF_0_WRADDR_OFFSET 0x5c4 +#define DST_RBUF_1_WRADDR_OFFSET 0x5dc +#define DST_RBUF_2_WRADDR_OFFSET 0x5f4 +#define DST_RBUF_3_WRADDR_OFFSET 0x60c +#define DST_RBUF_4_WRADDR_OFFSET 0x624 +#define DST_RBUF_5_WRADDR_OFFSET 0x63c + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_BASEADDR_REG_BASE */ +#define DST_RBUF_0_BASEADDR_OFFSET 0x5c8 +#define DST_RBUF_1_BASEADDR_OFFSET 0x5e0 +#define DST_RBUF_2_BASEADDR_OFFSET 0x5f8 +#define DST_RBUF_3_BASEADDR_OFFSET 0x610 +#define DST_RBUF_4_BASEADDR_OFFSET 0x628 +#define DST_RBUF_5_BASEADDR_OFFSET 0x640 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_ENDADDR_REG_BASE */ +#define DST_RBUF_0_ENDADDR_OFFSET 0x5cc +#define DST_RBUF_1_ENDADDR_OFFSET 0x5e4 +#define DST_RBUF_2_ENDADDR_OFFSET 0x5fc +#define DST_RBUF_3_ENDADDR_OFFSET 0x614 +#define DST_RBUF_4_ENDADDR_OFFSET 0x62c +#define DST_RBUF_5_ENDADDR_OFFSET 0x644 + +/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_FULL_MARK_REG_BASE */ +#define DST_RBUF_0_FULL_MARK_OFFSET 0x5d0 +#define DST_RBUF_1_FULL_MARK_OFFSET 0x5e8 +#define DST_RBUF_2_FULL_MARK_OFFSET 0x600 +#define DST_RBUF_3_FULL_MARK_OFFSET 0x618 +#define DST_RBUF_4_FULL_MARK_OFFSET 0x630 +#define DST_RBUF_5_FULL_MARK_OFFSET 0x648 +/* Ring Buffer Ctrl Regs --- End */ + +/* Error Status Regs --- Start */ +/* AUD_FMM_BF_ESR_ESRX_STATUS_REG_BASE */ +#define ESR0_STATUS_OFFSET 0x900 +#define ESR1_STATUS_OFFSET 0x918 +#define ESR2_STATUS_OFFSET 0x930 +#define ESR3_STATUS_OFFSET 0x948 +#define ESR4_STATUS_OFFSET 0x960 + +/* AUD_FMM_BF_ESR_ESRX_STATUS_CLEAR_REG_BASE */ +#define ESR0_STATUS_CLR_OFFSET 0x908 +#define ESR1_STATUS_CLR_OFFSET 0x920 +#define ESR2_STATUS_CLR_OFFSET 0x938 +#define ESR3_STATUS_CLR_OFFSET 0x950 +#define ESR4_STATUS_CLR_OFFSET 0x968 + +/* AUD_FMM_BF_ESR_ESRX_MASK_REG_BASE */ +#define ESR0_MASK_STATUS_OFFSET 0x90c +#define ESR1_MASK_STATUS_OFFSET 0x924 +#define ESR2_MASK_STATUS_OFFSET 0x93c +#define ESR3_MASK_STATUS_OFFSET 0x954 +#define ESR4_MASK_STATUS_OFFSET 0x96c + +/* AUD_FMM_BF_ESR_ESRX_MASK_SET_REG_BASE */ +#define ESR0_MASK_SET_OFFSET 0x910 +#define ESR1_MASK_SET_OFFSET 0x928 +#define ESR2_MASK_SET_OFFSET 0x940 +#define ESR3_MASK_SET_OFFSET 0x958 +#define ESR4_MASK_SET_OFFSET 0x970 + +/* AUD_FMM_BF_ESR_ESRX_MASK_CLEAR_REG_BASE */ +#define ESR0_MASK_CLR_OFFSET 0x914 +#define ESR1_MASK_CLR_OFFSET 0x92c +#define ESR2_MASK_CLR_OFFSET 0x944 +#define ESR3_MASK_CLR_OFFSET 0x95c +#define ESR4_MASK_CLR_OFFSET 0x974 +/* Error Status Regs --- End */ + +#define R5F_ESR0_SHIFT 0 /* esr0 = fifo underflow */ +#define R5F_ESR1_SHIFT 1 /* esr1 = ringbuf underflow */ +#define R5F_ESR2_SHIFT 2 /* esr2 = ringbuf overflow */ +#define R5F_ESR3_SHIFT 3 /* esr3 = freemark */ +#define R5F_ESR4_SHIFT 4 /* esr4 = fullmark */ + + +/* Mask for R5F register. Set all relevant interrupt for playback handler */ +#define ANY_PLAYBACK_IRQ (BIT(R5F_ESR0_SHIFT) | \ + BIT(R5F_ESR1_SHIFT) | \ + BIT(R5F_ESR3_SHIFT)) + +/* Mask for R5F register. Set all relevant interrupt for capture handler */ +#define ANY_CAPTURE_IRQ (BIT(R5F_ESR2_SHIFT) | BIT(R5F_ESR4_SHIFT)) + +/* + * PERIOD_BYTES_MIN is the number of bytes to at which the interrupt will tick. + * This number should be a multiple of 256. Minimum value is 256 + */ +#define PERIOD_BYTES_MIN 0x100 + +static const struct snd_pcm_hardware cygnus_pcm_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + + /* A period is basically an interrupt */ + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = 0x10000, + + /* period_min/max gives range of approx interrupts per buffer */ + .periods_min = 2, + .periods_max = 8, + + /* + * maximum buffer size in bytes = period_bytes_max * periods_max + * We allocate this amount of data for each enabled channel + */ + .buffer_bytes_max = 4 * 0x8000, +}; + +static u64 cygnus_dma_dmamask = DMA_BIT_MASK(32); + +static struct cygnus_aio_port *cygnus_dai_get_dma_data( + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + + return snd_soc_dai_get_dma_data(soc_runtime->cpu_dai, substream); +} + +static void ringbuf_set_initial(void __iomem *audio_io, + struct ringbuf_regs *p_rbuf, + bool is_playback, + u32 start, + u32 periodsize, + u32 bufsize) +{ + u32 initial_rd; + u32 initial_wr; + u32 end; + u32 fmark_val; /* free or full mark */ + + p_rbuf->period_bytes = periodsize; + p_rbuf->buf_size = bufsize; + + if (is_playback) { + /* Set the pointers to indicate full (flip uppermost bit) */ + initial_rd = start; + initial_wr = initial_rd ^ BIT(31); + } else { + /* Set the pointers to indicate empty */ + initial_wr = start; + initial_rd = initial_wr; + } + + end = start + bufsize - 1; + + /* + * The interrupt will fire when free/full mark is *exceeded* + * The fmark value must be multiple of PERIOD_BYTES_MIN so set fmark + * to be PERIOD_BYTES_MIN less than the period size. + */ + fmark_val = periodsize - PERIOD_BYTES_MIN; + + writel(start, audio_io + p_rbuf->baseaddr); + writel(end, audio_io + p_rbuf->endaddr); + writel(fmark_val, audio_io + p_rbuf->fmark); + writel(initial_rd, audio_io + p_rbuf->rdaddr); + writel(initial_wr, audio_io + p_rbuf->wraddr); +} + +static int configure_ringbuf_regs(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf; + int status = 0; + + aio = cygnus_dai_get_dma_data(substream); + + /* Map the ssp portnum to a set of ring buffers. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + p_rbuf = &aio->play_rb_regs; + + switch (aio->portnum) { + case 0: + *p_rbuf = RINGBUF_REG_PLAYBACK(0); + break; + case 1: + *p_rbuf = RINGBUF_REG_PLAYBACK(2); + break; + case 2: + *p_rbuf = RINGBUF_REG_PLAYBACK(4); + break; + case 3: /* SPDIF */ + *p_rbuf = RINGBUF_REG_PLAYBACK(6); + break; + default: + status = -EINVAL; + } + } else { + p_rbuf = &aio->capture_rb_regs; + + switch (aio->portnum) { + case 0: + *p_rbuf = RINGBUF_REG_CAPTURE(0); + break; + case 1: + *p_rbuf = RINGBUF_REG_CAPTURE(2); + break; + case 2: + *p_rbuf = RINGBUF_REG_CAPTURE(4); + break; + default: + status = -EINVAL; + } + } + + return status; +} + +static struct ringbuf_regs *get_ringbuf(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_rbuf = &aio->play_rb_regs; + else + p_rbuf = &aio->capture_rb_regs; + + return p_rbuf; +} + +static void enable_intr(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + u32 clear_mask; + + aio = cygnus_dai_get_dma_data(substream); + + /* The port number maps to the bit position to be cleared */ + clear_mask = BIT(aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Clear interrupt status before enabling them */ + writel(clear_mask, aio->cygaud->audio + ESR0_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR1_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR3_STATUS_CLR_OFFSET); + /* Unmask the interrupts of the given port*/ + writel(clear_mask, aio->cygaud->audio + ESR0_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR1_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR3_MASK_CLR_OFFSET); + + writel(ANY_PLAYBACK_IRQ, + aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); + } else { + writel(clear_mask, aio->cygaud->audio + ESR2_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR4_STATUS_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR2_MASK_CLR_OFFSET); + writel(clear_mask, aio->cygaud->audio + ESR4_MASK_CLR_OFFSET); + + writel(ANY_CAPTURE_IRQ, + aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET); + } + +} + +static void disable_intr(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + u32 set_mask; + + aio = cygnus_dai_get_dma_data(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s on port %d\n", __func__, aio->portnum); + + /* The port number maps to the bit position to be set */ + set_mask = BIT(aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Mask the interrupts of the given port*/ + writel(set_mask, aio->cygaud->audio + ESR0_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR1_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR3_MASK_SET_OFFSET); + } else { + writel(set_mask, aio->cygaud->audio + ESR2_MASK_SET_OFFSET); + writel(set_mask, aio->cygaud->audio + ESR4_MASK_SET_OFFSET); + } + +} + +static int cygnus_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + enable_intr(substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + disable_intr(substream); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void cygnus_pcm_period_elapsed(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + struct ringbuf_regs *p_rbuf = NULL; + u32 regval; + + aio = cygnus_dai_get_dma_data(substream); + + p_rbuf = get_ringbuf(substream); + + /* + * If free/full mark interrupt occurs, provide timestamp + * to ALSA and update appropriate idx by period_bytes + */ + snd_pcm_period_elapsed(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Set the ring buffer to full */ + regval = readl(aio->cygaud->audio + p_rbuf->rdaddr); + regval = regval ^ BIT(31); + writel(regval, aio->cygaud->audio + p_rbuf->wraddr); + } else { + /* Set the ring buffer to empty */ + regval = readl(aio->cygaud->audio + p_rbuf->wraddr); + writel(regval, aio->cygaud->audio + p_rbuf->rdaddr); + } +} + +/* + * ESR0/1/3 status Description + * 0x1 I2S0_out port caused interrupt + * 0x2 I2S1_out port caused interrupt + * 0x4 I2S2_out port caused interrupt + * 0x8 SPDIF_out port caused interrupt + */ +static void handle_playback_irq(struct cygnus_audio *cygaud) +{ + void __iomem *audio_io; + u32 port; + u32 esr_status0, esr_status1, esr_status3; + + audio_io = cygaud->audio; + + /* + * ESR status gets updates with/without interrupts enabled. + * So, check the ESR mask, which provides interrupt enable/ + * disable status and use it to determine which ESR status + * should be serviced. + */ + esr_status0 = readl(audio_io + ESR0_STATUS_OFFSET); + esr_status0 &= ~readl(audio_io + ESR0_MASK_STATUS_OFFSET); + esr_status1 = readl(audio_io + ESR1_STATUS_OFFSET); + esr_status1 &= ~readl(audio_io + ESR1_MASK_STATUS_OFFSET); + esr_status3 = readl(audio_io + ESR3_STATUS_OFFSET); + esr_status3 &= ~readl(audio_io + ESR3_MASK_STATUS_OFFSET); + + for (port = 0; port < CYGNUS_MAX_PLAYBACK_PORTS; port++) { + u32 esrmask = BIT(port); + + /* + * Ringbuffer or FIFO underflow + * If we get this interrupt then, it is also true that we have + * not yet responded to the freemark interrupt. + * Log a debug message. The freemark handler below will + * handle getting everything going again. + */ + if ((esrmask & esr_status1) || (esrmask & esr_status0)) { + dev_dbg(cygaud->dev, + "Underrun: esr0=0x%x, esr1=0x%x esr3=0x%x\n", + esr_status0, esr_status1, esr_status3); + } + + /* + * Freemark is hit. This is the normal interrupt. + * In typical operation the read and write regs will be equal + */ + if (esrmask & esr_status3) { + struct snd_pcm_substream *playstr; + + playstr = cygaud->portinfo[port].play_stream; + cygnus_pcm_period_elapsed(playstr); + } + } + + /* Clear ESR interrupt */ + writel(esr_status0, audio_io + ESR0_STATUS_CLR_OFFSET); + writel(esr_status1, audio_io + ESR1_STATUS_CLR_OFFSET); + writel(esr_status3, audio_io + ESR3_STATUS_CLR_OFFSET); + /* Rearm freemark logic by writing 1 to the correct bit */ + writel(esr_status3, audio_io + BF_REARM_FREE_MARK_OFFSET); +} + +/* + * ESR2/4 status Description + * 0x1 I2S0_in port caused interrupt + * 0x2 I2S1_in port caused interrupt + * 0x4 I2S2_in port caused interrupt + */ +static void handle_capture_irq(struct cygnus_audio *cygaud) +{ + void __iomem *audio_io; + u32 port; + u32 esr_status2, esr_status4; + + audio_io = cygaud->audio; + + /* + * ESR status gets updates with/without interrupts enabled. + * So, check the ESR mask, which provides interrupt enable/ + * disable status and use it to determine which ESR status + * should be serviced. + */ + esr_status2 = readl(audio_io + ESR2_STATUS_OFFSET); + esr_status2 &= ~readl(audio_io + ESR2_MASK_STATUS_OFFSET); + esr_status4 = readl(audio_io + ESR4_STATUS_OFFSET); + esr_status4 &= ~readl(audio_io + ESR4_MASK_STATUS_OFFSET); + + for (port = 0; port < CYGNUS_MAX_CAPTURE_PORTS; port++) { + u32 esrmask = BIT(port); + + /* + * Ringbuffer or FIFO overflow + * If we get this interrupt then, it is also true that we have + * not yet responded to the fullmark interrupt. + * Log a debug message. The fullmark handler below will + * handle getting everything going again. + */ + if (esrmask & esr_status2) + dev_dbg(cygaud->dev, + "Overflow: esr2=0x%x\n", esr_status2); + + if (esrmask & esr_status4) { + struct snd_pcm_substream *capstr; + + capstr = cygaud->portinfo[port].capture_stream; + cygnus_pcm_period_elapsed(capstr); + } + } + + writel(esr_status2, audio_io + ESR2_STATUS_CLR_OFFSET); + writel(esr_status4, audio_io + ESR4_STATUS_CLR_OFFSET); + /* Rearm fullmark logic by writing 1 to the correct bit */ + writel(esr_status4, audio_io + BF_REARM_FULL_MARK_OFFSET); +} + +static irqreturn_t cygnus_dma_irq(int irq, void *data) +{ + u32 r5_status; + struct cygnus_audio *cygaud = data; + + /* + * R5 status bits Description + * 0 ESR0 (playback FIFO interrupt) + * 1 ESR1 (playback rbuf interrupt) + * 2 ESR2 (capture rbuf interrupt) + * 3 ESR3 (Freemark play. interrupt) + * 4 ESR4 (Fullmark capt. interrupt) + */ + r5_status = readl(cygaud->audio + INTH_R5F_STATUS_OFFSET); + + if (!(r5_status & (ANY_PLAYBACK_IRQ | ANY_CAPTURE_IRQ))) + return IRQ_NONE; + + /* If playback interrupt happened */ + if (ANY_PLAYBACK_IRQ & r5_status) { + handle_playback_irq(cygaud); + writel(ANY_PLAYBACK_IRQ & r5_status, + cygaud->audio + INTH_R5F_CLEAR_OFFSET); + } + + /* If capture interrupt happened */ + if (ANY_CAPTURE_IRQ & r5_status) { + handle_capture_irq(cygaud); + writel(ANY_CAPTURE_IRQ & r5_status, + cygaud->audio + INTH_R5F_CLEAR_OFFSET); + } + + return IRQ_HANDLED; +} + +static int cygnus_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + int ret; + + aio = cygnus_dai_get_dma_data(substream); + if (!aio) + return -ENODEV; + + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_soc_set_runtime_hwparams(substream, &cygnus_pcm_hw); + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, PERIOD_BYTES_MIN); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, PERIOD_BYTES_MIN); + if (ret < 0) + return ret; + /* + * Keep track of which substream belongs to which port. + * This info is needed by snd_pcm_period_elapsed() in irq_handler + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->play_stream = substream; + else + aio->capture_stream = substream; + + return 0; +} + +static int cygnus_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + + aio = cygnus_dai_get_dma_data(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->play_stream = NULL; + else + aio->capture_stream = NULL; + + if (!aio->play_stream && !aio->capture_stream) + dev_dbg(rtd->cpu_dai->dev, "freed port %d\n", aio->portnum); + + return 0; +} + +static int cygnus_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_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + int ret = 0; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + return ret; +} + +static int cygnus_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct cygnus_aio_port *aio; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int cygnus_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct cygnus_aio_port *aio; + unsigned long bufsize, periodsize; + int ret = 0; + bool is_play; + u32 start; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + dev_dbg(rtd->cpu_dai->dev, "%s port %d\n", __func__, aio->portnum); + + bufsize = snd_pcm_lib_buffer_bytes(substream); + periodsize = snd_pcm_lib_period_bytes(substream); + + dev_dbg(rtd->cpu_dai->dev, "%s (buf_size %lu) (period_size %lu)\n", + __func__, bufsize, periodsize); + + configure_ringbuf_regs(substream); + + p_rbuf = get_ringbuf(substream); + + start = runtime->dma_addr; + + is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0; + + ringbuf_set_initial(aio->cygaud->audio, p_rbuf, is_play, start, + periodsize, bufsize); + + return ret; +} + +static snd_pcm_uframes_t cygnus_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct cygnus_aio_port *aio; + unsigned int res = 0, cur = 0, base = 0; + struct ringbuf_regs *p_rbuf = NULL; + + aio = cygnus_dai_get_dma_data(substream); + + /* + * Get the offset of the current read (for playack) or write + * index (for capture). Report this value back to the asoc framework. + */ + p_rbuf = get_ringbuf(substream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cur = readl(aio->cygaud->audio + p_rbuf->rdaddr); + else + cur = readl(aio->cygaud->audio + p_rbuf->wraddr); + + base = readl(aio->cygaud->audio + p_rbuf->baseaddr); + + /* + * Mask off the MSB of the rdaddr,wraddr and baseaddr + * since MSB is not part of the address + */ + res = (cur & 0x7fffffff) - (base & 0x7fffffff); + + return bytes_to_frames(substream->runtime, res); +} + +static int cygnus_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + + size = cygnus_pcm_hw.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); + + dev_dbg(rtd->cpu_dai->dev, "%s: size 0x%zx @ %pK\n", + __func__, size, buf->area); + + if (!buf->area) { + dev_err(rtd->cpu_dai->dev, "%s: dma_alloc failed\n", __func__); + return -ENOMEM; + } + buf->bytes = size; + + return 0; +} + + +static const struct snd_pcm_ops cygnus_pcm_ops = { + .open = cygnus_pcm_open, + .close = cygnus_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = cygnus_pcm_hw_params, + .hw_free = cygnus_pcm_hw_free, + .prepare = cygnus_pcm_prepare, + .trigger = cygnus_pcm_trigger, + .pointer = cygnus_pcm_pointer, +}; + +static void cygnus_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + buf = &substream->dma_buffer; + if (buf->area) { + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } + } + + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + buf = &substream->dma_buffer; + if (buf->area) { + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } + } +} + +static int cygnus_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &cygnus_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = cygnus_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = cygnus_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) { + cygnus_dma_free_dma_buffers(pcm); + return ret; + } + } + + return 0; +} + +static struct snd_soc_platform_driver cygnus_soc_platform = { + .ops = &cygnus_pcm_ops, + .pcm_new = cygnus_dma_new, + .pcm_free = cygnus_dma_free_dma_buffers, +}; + +int cygnus_soc_platform_register(struct device *dev, + struct cygnus_audio *cygaud) +{ + int rc = 0; + + dev_dbg(dev, "%s Enter\n", __func__); + + rc = devm_request_irq(dev, cygaud->irq_num, cygnus_dma_irq, + IRQF_SHARED, "cygnus-audio", cygaud); + if (rc) { + dev_err(dev, "%s request_irq error %d\n", __func__, rc); + return rc; + } + + rc = snd_soc_register_platform(dev, &cygnus_soc_platform); + if (rc) { + dev_err(dev, "%s failed\n", __func__); + return rc; + } + + return 0; +} + +int cygnus_soc_platform_unregister(struct device *dev) +{ + snd_soc_unregister_platform(dev); + + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Cygnus ASoC PCM module"); diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c new file mode 100644 index 000000000000..e710bb0c5637 --- /dev/null +++ b/sound/soc/bcm/cygnus-ssp.c @@ -0,0 +1,1529 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "cygnus-ssp.h" + +#define DEFAULT_VCO 1354750204 + +#define CYGNUS_TDM_RATE \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define CAPTURE_FCI_ID_BASE 0x180 +#define CYGNUS_SSP_TRISTATE_MASK 0x001fff +#define CYGNUS_PLLCLKSEL_MASK 0xf + +/* Used with stream_on field to indicate which streams are active */ +#define PLAYBACK_STREAM_MASK BIT(0) +#define CAPTURE_STREAM_MASK BIT(1) + +#define I2S_STREAM_CFG_MASK 0xff003ff +#define I2S_CAP_STREAM_CFG_MASK 0xf0 +#define SPDIF_STREAM_CFG_MASK 0x3ff +#define CH_GRP_STEREO 0x1 + +/* Begin register offset defines */ +#define AUD_MISC_SEROUT_OE_REG_BASE 0x01c +#define AUD_MISC_SEROUT_SPDIF_OE 12 +#define AUD_MISC_SEROUT_MCLK_OE 3 +#define AUD_MISC_SEROUT_LRCK_OE 2 +#define AUD_MISC_SEROUT_SCLK_OE 1 +#define AUD_MISC_SEROUT_SDAT_OE 0 + +/* AUD_FMM_BF_CTRL_xxx regs */ +#define BF_DST_CFG0_OFFSET 0x100 +#define BF_DST_CFG1_OFFSET 0x104 +#define BF_DST_CFG2_OFFSET 0x108 + +#define BF_DST_CTRL0_OFFSET 0x130 +#define BF_DST_CTRL1_OFFSET 0x134 +#define BF_DST_CTRL2_OFFSET 0x138 + +#define BF_SRC_CFG0_OFFSET 0x148 +#define BF_SRC_CFG1_OFFSET 0x14c +#define BF_SRC_CFG2_OFFSET 0x150 +#define BF_SRC_CFG3_OFFSET 0x154 + +#define BF_SRC_CTRL0_OFFSET 0x1c0 +#define BF_SRC_CTRL1_OFFSET 0x1c4 +#define BF_SRC_CTRL2_OFFSET 0x1c8 +#define BF_SRC_CTRL3_OFFSET 0x1cc + +#define BF_SRC_GRP0_OFFSET 0x1fc +#define BF_SRC_GRP1_OFFSET 0x200 +#define BF_SRC_GRP2_OFFSET 0x204 +#define BF_SRC_GRP3_OFFSET 0x208 + +#define BF_SRC_GRP_EN_OFFSET 0x320 +#define BF_SRC_GRP_FLOWON_OFFSET 0x324 +#define BF_SRC_GRP_SYNC_DIS_OFFSET 0x328 + +/* AUD_FMM_IOP_OUT_I2S_xxx regs */ +#define OUT_I2S_0_STREAM_CFG_OFFSET 0xa00 +#define OUT_I2S_0_CFG_OFFSET 0xa04 +#define OUT_I2S_0_MCLK_CFG_OFFSET 0xa0c + +#define OUT_I2S_1_STREAM_CFG_OFFSET 0xa40 +#define OUT_I2S_1_CFG_OFFSET 0xa44 +#define OUT_I2S_1_MCLK_CFG_OFFSET 0xa4c + +#define OUT_I2S_2_STREAM_CFG_OFFSET 0xa80 +#define OUT_I2S_2_CFG_OFFSET 0xa84 +#define OUT_I2S_2_MCLK_CFG_OFFSET 0xa8c + +/* AUD_FMM_IOP_OUT_SPDIF_xxx regs */ +#define SPDIF_STREAM_CFG_OFFSET 0xac0 +#define SPDIF_CTRL_OFFSET 0xac4 +#define SPDIF_FORMAT_CFG_OFFSET 0xad8 +#define SPDIF_MCLK_CFG_OFFSET 0xadc + +/* AUD_FMM_IOP_PLL_0_xxx regs */ +#define IOP_PLL_0_MACRO_OFFSET 0xb00 +#define IOP_PLL_0_MDIV_Ch0_OFFSET 0xb14 +#define IOP_PLL_0_MDIV_Ch1_OFFSET 0xb18 +#define IOP_PLL_0_MDIV_Ch2_OFFSET 0xb1c + +#define IOP_PLL_0_ACTIVE_MDIV_Ch0_OFFSET 0xb30 +#define IOP_PLL_0_ACTIVE_MDIV_Ch1_OFFSET 0xb34 +#define IOP_PLL_0_ACTIVE_MDIV_Ch2_OFFSET 0xb38 + +/* AUD_FMM_IOP_xxx regs */ +#define IOP_PLL_0_CONTROL_OFFSET 0xb04 +#define IOP_PLL_0_USER_NDIV_OFFSET 0xb08 +#define IOP_PLL_0_ACTIVE_NDIV_OFFSET 0xb20 +#define IOP_PLL_0_RESET_OFFSET 0xb5c + +/* AUD_FMM_IOP_IN_I2S_xxx regs */ +#define IN_I2S_0_STREAM_CFG_OFFSET 0x00 +#define IN_I2S_0_CFG_OFFSET 0x04 +#define IN_I2S_1_STREAM_CFG_OFFSET 0x40 +#define IN_I2S_1_CFG_OFFSET 0x44 +#define IN_I2S_2_STREAM_CFG_OFFSET 0x80 +#define IN_I2S_2_CFG_OFFSET 0x84 + +/* AUD_FMM_IOP_MISC_xxx regs */ +#define IOP_SW_INIT_LOGIC 0x1c0 + +/* End register offset defines */ + + +/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_0_REG */ +#define I2S_OUT_MCLKRATE_SHIFT 16 + +/* AUD_FMM_IOP_OUT_I2S_x_MCLK_CFG_REG */ +#define I2S_OUT_PLLCLKSEL_SHIFT 0 + +/* AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG */ +#define I2S_OUT_STREAM_ENA 31 +#define I2S_OUT_STREAM_CFG_GROUP_ID 20 +#define I2S_OUT_STREAM_CFG_CHANNEL_GROUPING 24 + +/* AUD_FMM_IOP_IN_I2S_x_CAP */ +#define I2S_IN_STREAM_CFG_CAP_ENA 31 +#define I2S_IN_STREAM_CFG_0_GROUP_ID 4 + +/* AUD_FMM_IOP_OUT_I2S_x_I2S_CFG_REG */ +#define I2S_OUT_CFGX_CLK_ENA 0 +#define I2S_OUT_CFGX_DATA_ENABLE 1 +#define I2S_OUT_CFGX_DATA_ALIGNMENT 6 +#define I2S_OUT_CFGX_BITS_PER_SLOT 13 +#define I2S_OUT_CFGX_VALID_SLOT 14 +#define I2S_OUT_CFGX_FSYNC_WIDTH 18 +#define I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32 26 +#define I2S_OUT_CFGX_SLAVE_MODE 30 +#define I2S_OUT_CFGX_TDM_MODE 31 + +/* AUD_FMM_BF_CTRL_SOURCECH_CFGx_REG */ +#define BF_SRC_CFGX_SFIFO_ENA 0 +#define BF_SRC_CFGX_BUFFER_PAIR_ENABLE 1 +#define BF_SRC_CFGX_SAMPLE_CH_MODE 2 +#define BF_SRC_CFGX_SFIFO_SZ_DOUBLE 5 +#define BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY 10 +#define BF_SRC_CFGX_BIT_RES 20 +#define BF_SRC_CFGX_PROCESS_SEQ_ID_VALID 31 + +/* AUD_FMM_BF_CTRL_DESTCH_CFGx_REG */ +#define BF_DST_CFGX_CAP_ENA 0 +#define BF_DST_CFGX_BUFFER_PAIR_ENABLE 1 +#define BF_DST_CFGX_DFIFO_SZ_DOUBLE 2 +#define BF_DST_CFGX_NOT_PAUSE_WHEN_FULL 11 +#define BF_DST_CFGX_FCI_ID 12 +#define BF_DST_CFGX_CAP_MODE 24 +#define BF_DST_CFGX_PROC_SEQ_ID_VALID 31 + +/* AUD_FMM_IOP_OUT_SPDIF_xxx */ +#define SPDIF_0_OUT_DITHER_ENA 3 +#define SPDIF_0_OUT_STREAM_ENA 31 + +/* AUD_FMM_IOP_PLL_0_USER */ +#define IOP_PLL_0_USER_NDIV_FRAC 10 + +/* AUD_FMM_IOP_PLL_0_ACTIVE */ +#define IOP_PLL_0_ACTIVE_NDIV_FRAC 10 + + +#define INIT_SSP_REGS(num) (struct cygnus_ssp_regs){ \ + .i2s_stream_cfg = OUT_I2S_ ##num## _STREAM_CFG_OFFSET, \ + .i2s_cap_stream_cfg = IN_I2S_ ##num## _STREAM_CFG_OFFSET, \ + .i2s_cfg = OUT_I2S_ ##num## _CFG_OFFSET, \ + .i2s_cap_cfg = IN_I2S_ ##num## _CFG_OFFSET, \ + .i2s_mclk_cfg = OUT_I2S_ ##num## _MCLK_CFG_OFFSET, \ + .bf_destch_ctrl = BF_DST_CTRL ##num## _OFFSET, \ + .bf_destch_cfg = BF_DST_CFG ##num## _OFFSET, \ + .bf_sourcech_ctrl = BF_SRC_CTRL ##num## _OFFSET, \ + .bf_sourcech_cfg = BF_SRC_CFG ##num## _OFFSET, \ + .bf_sourcech_grp = BF_SRC_GRP ##num## _OFFSET \ +} + +struct pll_macro_entry { + u32 mclk; + u32 pll_ch_num; +}; + +/* + * PLL has 3 output channels (1x, 2x, and 4x). Below are + * the common MCLK frequencies used by audio driver + */ +static const struct pll_macro_entry pll_predef_mclk[] = { + { 4096000, 0}, + { 8192000, 1}, + {16384000, 2}, + + { 5644800, 0}, + {11289600, 1}, + {22579200, 2}, + + { 6144000, 0}, + {12288000, 1}, + {24576000, 2}, + + {12288000, 0}, + {24576000, 1}, + {49152000, 2}, + + {22579200, 0}, + {45158400, 1}, + {90316800, 2}, + + {24576000, 0}, + {49152000, 1}, + {98304000, 2}, +}; + +/* List of valid frame sizes for tdm mode */ +static const int ssp_valid_tdm_framesize[] = {32, 64, 128, 256, 512}; + +/* + * Use this relationship to derive the sampling rate (lrclk) + * lrclk = (mclk) / ((2*mclk_to_sclk_ratio) * (32 * SCLK))). + * + * Use mclk and pll_ch from the table above + * + * Valid SCLK = 0/1/2/4/8/12 + * + * mclk_to_sclk_ratio = number of MCLK per SCLK. Division is twice the + * value programmed in this field. + * Valid mclk_to_sclk_ratio = 1 through to 15 + * + * eg: To set lrclk = 48khz, set mclk = 12288000, mclk_to_sclk_ratio = 2, + * SCLK = 64 + */ +struct _ssp_clk_coeff { + u32 mclk; + u32 sclk_rate; + u32 rate; + u32 mclk_rate; +}; + +static const struct _ssp_clk_coeff ssp_clk_coeff[] = { + { 4096000, 32, 16000, 4}, + { 4096000, 32, 32000, 2}, + { 4096000, 64, 8000, 4}, + { 4096000, 64, 16000, 2}, + { 4096000, 64, 32000, 1}, + { 4096000, 128, 8000, 2}, + { 4096000, 128, 16000, 1}, + { 4096000, 256, 8000, 1}, + + { 6144000, 32, 16000, 6}, + { 6144000, 32, 32000, 3}, + { 6144000, 32, 48000, 2}, + { 6144000, 32, 96000, 1}, + { 6144000, 64, 8000, 6}, + { 6144000, 64, 16000, 3}, + { 6144000, 64, 48000, 1}, + { 6144000, 128, 8000, 3}, + + { 8192000, 32, 32000, 4}, + { 8192000, 64, 16000, 4}, + { 8192000, 64, 32000, 2}, + { 8192000, 128, 8000, 4}, + { 8192000, 128, 16000, 2}, + { 8192000, 128, 32000, 1}, + { 8192000, 256, 8000, 2}, + { 8192000, 256, 16000, 1}, + { 8192000, 512, 8000, 1}, + + {12288000, 32, 32000, 6}, + {12288000, 32, 48000, 4}, + {12288000, 32, 96000, 2}, + {12288000, 32, 192000, 1}, + {12288000, 64, 16000, 6}, + {12288000, 64, 32000, 3}, + {12288000, 64, 48000, 2}, + {12288000, 64, 96000, 1}, + {12288000, 128, 8000, 6}, + {12288000, 128, 16000, 3}, + {12288000, 128, 48000, 1}, + {12288000, 256, 8000, 3}, + + {16384000, 64, 32000, 4}, + {16384000, 128, 16000, 4}, + {16384000, 128, 32000, 2}, + {16384000, 256, 8000, 4}, + {16384000, 256, 16000, 2}, + {16384000, 256, 32000, 1}, + {16384000, 512, 8000, 2}, + {16384000, 512, 16000, 1}, + + {24576000, 32, 96000, 4}, + {24576000, 32, 192000, 2}, + {24576000, 64, 32000, 6}, + {24576000, 64, 48000, 4}, + {24576000, 64, 96000, 2}, + {24576000, 64, 192000, 1}, + {24576000, 128, 16000, 6}, + {24576000, 128, 32000, 3}, + {24576000, 128, 48000, 2}, + {24576000, 256, 8000, 6}, + {24576000, 256, 16000, 3}, + {24576000, 256, 48000, 1}, + {24576000, 512, 8000, 3}, + + {49152000, 32, 192000, 4}, + {49152000, 64, 96000, 4}, + {49152000, 64, 192000, 2}, + {49152000, 128, 32000, 6}, + {49152000, 128, 48000, 4}, + {49152000, 128, 96000, 2}, + {49152000, 128, 192000, 1}, + {49152000, 256, 16000, 6}, + {49152000, 256, 32000, 3}, + {49152000, 256, 48000, 2}, + {49152000, 256, 96000, 1}, + {49152000, 512, 8000, 6}, + {49152000, 512, 16000, 3}, + {49152000, 512, 48000, 1}, + + { 5644800, 32, 22050, 4}, + { 5644800, 32, 44100, 2}, + { 5644800, 32, 88200, 1}, + { 5644800, 64, 11025, 4}, + { 5644800, 64, 22050, 2}, + { 5644800, 64, 44100, 1}, + + {11289600, 32, 44100, 4}, + {11289600, 32, 88200, 2}, + {11289600, 32, 176400, 1}, + {11289600, 64, 22050, 4}, + {11289600, 64, 44100, 2}, + {11289600, 64, 88200, 1}, + {11289600, 128, 11025, 4}, + {11289600, 128, 22050, 2}, + {11289600, 128, 44100, 1}, + + {22579200, 32, 88200, 4}, + {22579200, 32, 176400, 2}, + {22579200, 64, 44100, 4}, + {22579200, 64, 88200, 2}, + {22579200, 64, 176400, 1}, + {22579200, 128, 22050, 4}, + {22579200, 128, 44100, 2}, + {22579200, 128, 88200, 1}, + {22579200, 256, 11025, 4}, + {22579200, 256, 22050, 2}, + {22579200, 256, 44100, 1}, + + {45158400, 32, 176400, 4}, + {45158400, 64, 88200, 4}, + {45158400, 64, 176400, 2}, + {45158400, 128, 44100, 4}, + {45158400, 128, 88200, 2}, + {45158400, 128, 176400, 1}, + {45158400, 256, 22050, 4}, + {45158400, 256, 44100, 2}, + {45158400, 256, 88200, 1}, + {45158400, 512, 11025, 4}, + {45158400, 512, 22050, 2}, + {45158400, 512, 44100, 1}, +}; + +static struct cygnus_aio_port *cygnus_dai_get_portinfo(struct snd_soc_dai *dai) +{ + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + return &cygaud->portinfo[dai->id]; +} + +static int audio_ssp_init_portregs(struct cygnus_aio_port *aio) +{ + u32 value, fci_id; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value &= ~I2S_STREAM_CFG_MASK; + + /* Set Group ID */ + writel(aio->portnum, + aio->cygaud->audio + aio->regs.bf_sourcech_grp); + + /* Configure the AUD_FMM_IOP_OUT_I2S_x_STREAM_CFG reg */ + value |= aio->portnum << I2S_OUT_STREAM_CFG_GROUP_ID; + value |= aio->portnum; /* FCI ID is the port num */ + value |= CH_GRP_STEREO << I2S_OUT_STREAM_CFG_CHANNEL_GROUPING; + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + /* Configure the AUD_FMM_BF_CTRL_SOURCECH_CFGX reg */ + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY); + value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE); + value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* Configure the AUD_FMM_IOP_IN_I2S_x_CAP_STREAM_CFG_0 reg */ + value = readl(aio->cygaud->i2s_in + + aio->regs.i2s_cap_stream_cfg); + value &= ~I2S_CAP_STREAM_CFG_MASK; + value |= aio->portnum << I2S_IN_STREAM_CFG_0_GROUP_ID; + writel(value, aio->cygaud->i2s_in + + aio->regs.i2s_cap_stream_cfg); + + /* Configure the AUD_FMM_BF_CTRL_DESTCH_CFGX_REG_BASE reg */ + fci_id = CAPTURE_FCI_ID_BASE + aio->portnum; + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_DFIFO_SZ_DOUBLE); + value &= ~BIT(BF_DST_CFGX_NOT_PAUSE_WHEN_FULL); + value |= (fci_id << BF_DST_CFGX_FCI_ID); + value |= BIT(BF_DST_CFGX_PROC_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); + + /* Enable the transmit pin for this port */ + value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + value &= ~BIT((aio->portnum * 4) + AUD_MISC_SEROUT_SDAT_OE); + writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + break; + case PORT_SPDIF: + writel(aio->portnum, aio->cygaud->audio + BF_SRC_GRP3_OFFSET); + + value = readl(aio->cygaud->audio + SPDIF_CTRL_OFFSET); + value |= BIT(SPDIF_0_OUT_DITHER_ENA); + writel(value, aio->cygaud->audio + SPDIF_CTRL_OFFSET); + + /* Enable and set the FCI ID for the SPDIF channel */ + value = readl(aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET); + value &= ~SPDIF_STREAM_CFG_MASK; + value |= aio->portnum; /* FCI ID is the port num */ + value |= BIT(SPDIF_0_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + SPDIF_STREAM_CFG_OFFSET); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_NOT_PAUSE_WHEN_EMPTY); + value |= BIT(BF_SRC_CFGX_SFIFO_SZ_DOUBLE); + value |= BIT(BF_SRC_CFGX_PROCESS_SEQ_ID_VALID); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* Enable the spdif output pin */ + value = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + value &= ~BIT(AUD_MISC_SEROUT_SPDIF_OE); + writel(value, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + break; + default: + dev_err(aio->cygaud->dev, "Port not supported\n"); + status = -EINVAL; + } + + return status; +} + +static void audio_ssp_in_enable(struct cygnus_aio_port *aio) +{ + u32 value; + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_CAP_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); + + writel(0x1, aio->cygaud->audio + aio->regs.bf_destch_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value |= BIT(I2S_OUT_CFGX_CLK_ENA); + value |= BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + value |= BIT(I2S_IN_STREAM_CFG_CAP_ENA); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + + aio->streams_on |= CAPTURE_STREAM_MASK; +} + +static void audio_ssp_in_disable(struct cygnus_aio_port *aio) +{ + u32 value; + + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + value &= ~BIT(I2S_IN_STREAM_CFG_CAP_ENA); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_stream_cfg); + + aio->streams_on &= ~CAPTURE_STREAM_MASK; + + /* If both playback and capture are off */ + if (!aio->streams_on) { + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~BIT(I2S_OUT_CFGX_CLK_ENA); + value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + } + + writel(0x0, aio->cygaud->audio + aio->regs.bf_destch_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_destch_cfg); + value &= ~BIT(BF_DST_CFGX_CAP_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_destch_cfg); +} + +static int audio_ssp_out_enable(struct cygnus_aio_port *aio) +{ + u32 value; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value |= BIT(I2S_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value |= BIT(I2S_OUT_CFGX_CLK_ENA); + value |= BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value |= BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + aio->streams_on |= PLAYBACK_STREAM_MASK; + break; + case PORT_SPDIF: + value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + value |= 0x3; + writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + + writel(1, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value |= BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + break; + default: + dev_err(aio->cygaud->dev, + "Port not supported %d\n", aio->portnum); + status = -EINVAL; + } + + return status; +} + +static int audio_ssp_out_disable(struct cygnus_aio_port *aio) +{ + u32 value; + int status = 0; + + switch (aio->port_type) { + case PORT_TDM: + aio->streams_on &= ~PLAYBACK_STREAM_MASK; + + /* If both playback and capture are off */ + if (!aio->streams_on) { + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~BIT(I2S_OUT_CFGX_CLK_ENA); + value &= ~BIT(I2S_OUT_CFGX_DATA_ENABLE); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + } + + /* set group_sync_dis = 1 */ + value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + value |= BIT(aio->portnum); + writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + + writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + /* set group_sync_dis = 0 */ + value = readl(aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + value &= ~BIT(aio->portnum); + writel(value, aio->cygaud->audio + BF_SRC_GRP_SYNC_DIS_OFFSET); + + value = readl(aio->cygaud->audio + aio->regs.i2s_stream_cfg); + value &= ~BIT(I2S_OUT_STREAM_ENA); + writel(value, aio->cygaud->audio + aio->regs.i2s_stream_cfg); + + /* IOP SW INIT on OUT_I2S_x */ + value = readl(aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + value |= BIT(aio->portnum); + writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + value &= ~BIT(aio->portnum); + writel(value, aio->cygaud->i2s_in + IOP_SW_INIT_LOGIC); + break; + case PORT_SPDIF: + value = readl(aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + value &= ~0x3; + writel(value, aio->cygaud->audio + SPDIF_FORMAT_CFG_OFFSET); + writel(0, aio->cygaud->audio + aio->regs.bf_sourcech_ctrl); + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_SFIFO_ENA); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + break; + default: + dev_err(aio->cygaud->dev, + "Port not supported %d\n", aio->portnum); + status = -EINVAL; + } + + return status; +} + +static int pll_configure_mclk(struct cygnus_audio *cygaud, u32 mclk, + struct cygnus_aio_port *aio) +{ + int i = 0, error; + bool found = false; + const struct pll_macro_entry *p_entry; + struct clk *ch_clk; + + for (i = 0; i < ARRAY_SIZE(pll_predef_mclk); i++) { + p_entry = &pll_predef_mclk[i]; + if (p_entry->mclk == mclk) { + found = true; + break; + } + } + if (!found) { + dev_err(cygaud->dev, + "%s No valid mclk freq (%u) found!\n", __func__, mclk); + return -EINVAL; + } + + ch_clk = cygaud->audio_clk[p_entry->pll_ch_num]; + + if ((aio->clk_trace.cap_en) && (!aio->clk_trace.cap_clk_en)) { + error = clk_prepare_enable(ch_clk); + if (error) { + dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", + __func__, error); + return error; + } + aio->clk_trace.cap_clk_en = true; + } + + if ((aio->clk_trace.play_en) && (!aio->clk_trace.play_clk_en)) { + error = clk_prepare_enable(ch_clk); + if (error) { + dev_err(cygaud->dev, "%s clk_prepare_enable failed %d\n", + __func__, error); + return error; + } + aio->clk_trace.play_clk_en = true; + } + + error = clk_set_rate(ch_clk, mclk); + if (error) { + dev_err(cygaud->dev, "%s Set MCLK rate failed: %d\n", + __func__, error); + return error; + } + + return p_entry->pll_ch_num; +} + +static int cygnus_ssp_set_clocks(struct cygnus_aio_port *aio, + struct cygnus_audio *cygaud) +{ + u32 value, i = 0; + u32 mask = 0xf; + u32 sclk; + bool found = false; + const struct _ssp_clk_coeff *p_entry = NULL; + + for (i = 0; i < ARRAY_SIZE(ssp_clk_coeff); i++) { + p_entry = &ssp_clk_coeff[i]; + if ((p_entry->rate == aio->lrclk) && + (p_entry->sclk_rate == aio->bit_per_frame) && + (p_entry->mclk == aio->mclk)) { + found = true; + break; + } + } + if (!found) { + dev_err(aio->cygaud->dev, + "No valid match found in ssp_clk_coeff array\n"); + dev_err(aio->cygaud->dev, "lrclk = %u, bits/frame = %u, mclk = %u\n", + aio->lrclk, aio->bit_per_frame, aio->mclk); + return -EINVAL; + } + + sclk = aio->bit_per_frame; + if (sclk == 512) + sclk = 0; + /* sclks_per_1fs_div = sclk cycles/32 */ + sclk /= 32; + /* Set sclk rate */ + switch (aio->port_type) { + case PORT_TDM: + /* Set number of bitclks per frame */ + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~(mask << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32); + value |= sclk << I2S_OUT_CFGX_SCLKS_PER_1FS_DIV32; + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + dev_dbg(aio->cygaud->dev, + "SCLKS_PER_1FS_DIV32 = 0x%x\n", value); + break; + case PORT_SPDIF: + break; + default: + dev_err(aio->cygaud->dev, "Unknown port type\n"); + return -EINVAL; + } + + /* Set MCLK_RATE ssp port (spdif and ssp are the same) */ + value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + value &= ~(0xf << I2S_OUT_MCLKRATE_SHIFT); + value |= (p_entry->mclk_rate << I2S_OUT_MCLKRATE_SHIFT); + writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + + dev_dbg(aio->cygaud->dev, "mclk cfg reg = 0x%x\n", value); + dev_dbg(aio->cygaud->dev, "bits per frame = %u, mclk = %u Hz, lrclk = %u Hz\n", + aio->bit_per_frame, aio->mclk, aio->lrclk); + return 0; +} + +static int cygnus_ssp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + int rate, bitres; + u32 value; + u32 mask = 0x1f; + int ret = 0; + + dev_dbg(aio->cygaud->dev, "%s port = %d\n", __func__, aio->portnum); + dev_dbg(aio->cygaud->dev, "params_channels %d\n", + params_channels(params)); + dev_dbg(aio->cygaud->dev, "rate %d\n", params_rate(params)); + dev_dbg(aio->cygaud->dev, "format %d\n", params_format(params)); + + rate = params_rate(params); + + switch (aio->mode) { + case CYGNUS_SSPMODE_TDM: + if ((rate == 192000) && (params_channels(params) > 4)) { + dev_err(aio->cygaud->dev, "Cannot run %d channels at %dHz\n", + params_channels(params), rate); + return -EINVAL; + } + break; + case CYGNUS_SSPMODE_I2S: + aio->bit_per_frame = 64; /* I2S must be 64 bit per frame */ + break; + default: + dev_err(aio->cygaud->dev, + "%s port running in unknown mode\n", __func__); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~BIT(BF_SRC_CFGX_BUFFER_PAIR_ENABLE); + /* Configure channels as mono or stereo/TDM */ + if (params_channels(params) == 1) + value |= BIT(BF_SRC_CFGX_SAMPLE_CH_MODE); + else + value &= ~BIT(BF_SRC_CFGX_SAMPLE_CH_MODE); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + if (aio->port_type == PORT_SPDIF) { + dev_err(aio->cygaud->dev, + "SPDIF does not support 8bit format\n"); + return -EINVAL; + } + bitres = 8; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + bitres = 16; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + /* 32 bit mode is coded as 0 */ + bitres = 0; + break; + + default: + return -EINVAL; + } + + value = readl(aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + value &= ~(mask << BF_SRC_CFGX_BIT_RES); + value |= (bitres << BF_SRC_CFGX_BIT_RES); + writel(value, aio->cygaud->audio + aio->regs.bf_sourcech_cfg); + + } else { + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + value = readl(aio->cygaud->audio + + aio->regs.bf_destch_cfg); + value |= BIT(BF_DST_CFGX_CAP_MODE); + writel(value, aio->cygaud->audio + + aio->regs.bf_destch_cfg); + break; + + case SNDRV_PCM_FORMAT_S32_LE: + value = readl(aio->cygaud->audio + + aio->regs.bf_destch_cfg); + value &= ~BIT(BF_DST_CFGX_CAP_MODE); + writel(value, aio->cygaud->audio + + aio->regs.bf_destch_cfg); + break; + + default: + return -EINVAL; + } + } + + aio->lrclk = rate; + + if (!aio->is_slave) + ret = cygnus_ssp_set_clocks(aio, cygaud); + + return ret; +} + +/* + * This function sets the mclk frequency for pll clock + */ +static int cygnus_ssp_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + int sel; + u32 value; + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + dev_dbg(aio->cygaud->dev, + "%s Enter port = %d\n", __func__, aio->portnum); + sel = pll_configure_mclk(cygaud, freq, aio); + if (sel < 0) { + dev_err(aio->cygaud->dev, + "%s Setting mclk failed.\n", __func__); + return -EINVAL; + } + + aio->mclk = freq; + + dev_dbg(aio->cygaud->dev, "%s Setting MCLKSEL to %d\n", __func__, sel); + value = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + value &= ~(0xf << I2S_OUT_PLLCLKSEL_SHIFT); + value |= (sel << I2S_OUT_PLLCLKSEL_SHIFT); + writel(value, aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + + return 0; +} + +static int cygnus_ssp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + + snd_soc_dai_set_dma_data(dai, substream, aio); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->clk_trace.play_en = true; + else + aio->clk_trace.cap_en = true; + + return 0; +} + +static void cygnus_ssp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aio->clk_trace.play_en = false; + else + aio->clk_trace.cap_en = false; + + if (!aio->is_slave) { + u32 val; + + val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + val &= CYGNUS_PLLCLKSEL_MASK; + if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { + dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", + val); + return; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (aio->clk_trace.play_clk_en) { + clk_disable_unprepare(aio->cygaud-> + audio_clk[val]); + aio->clk_trace.play_clk_en = false; + } + } else { + if (aio->clk_trace.cap_clk_en) { + clk_disable_unprepare(aio->cygaud-> + audio_clk[val]); + aio->clk_trace.cap_clk_en = false; + } + } + } +} + +/* + * Bit Update Notes + * 31 Yes TDM Mode (1 = TDM, 0 = i2s) + * 30 Yes Slave Mode (1 = Slave, 0 = Master) + * 29:26 No Sclks per frame + * 25:18 Yes FS Width + * 17:14 No Valid Slots + * 13 No Bits (1 = 16 bits, 0 = 32 bits) + * 12:08 No Bits per samp + * 07 Yes Justifcation (1 = LSB, 0 = MSB) + * 06 Yes Alignment (1 = Delay 1 clk, 0 = no delay + * 05 Yes SCLK polarity (1 = Rising, 0 = Falling) + * 04 Yes LRCLK Polarity (1 = High for left, 0 = Low for left) + * 03:02 Yes Reserved - write as zero + * 01 No Data Enable + * 00 No CLK Enable + */ +#define I2S_OUT_CFG_REG_UPDATE_MASK 0x3C03FF03 + +/* Input cfg is same as output, but the FS width is not a valid field */ +#define I2S_IN_CFG_REG_UPDATE_MASK (I2S_OUT_CFG_REG_UPDATE_MASK | 0x03FC0000) + +int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, int len) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + + if ((len > 0) && (len < 256)) { + aio->fsync_width = len; + return 0; + } else { + return -EINVAL; + } +} + +static int cygnus_ssp_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + u32 ssp_curcfg; + u32 ssp_newcfg; + u32 ssp_outcfg; + u32 ssp_incfg; + u32 val; + u32 mask; + + dev_dbg(aio->cygaud->dev, "%s Enter fmt: %x\n", __func__, fmt); + + if (aio->port_type == PORT_SPDIF) + return -EINVAL; + + ssp_newcfg = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + ssp_newcfg |= BIT(I2S_OUT_CFGX_SLAVE_MODE); + aio->is_slave = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + ssp_newcfg &= ~BIT(I2S_OUT_CFGX_SLAVE_MODE); + aio->is_slave = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT); + ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH); + aio->mode = CYGNUS_SSPMODE_I2S; + break; + + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + ssp_newcfg |= BIT(I2S_OUT_CFGX_TDM_MODE); + + /* DSP_A = data after FS, DSP_B = data during FS */ + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) + ssp_newcfg |= BIT(I2S_OUT_CFGX_DATA_ALIGNMENT); + + if ((aio->fsync_width > 0) && (aio->fsync_width < 256)) + ssp_newcfg |= + (aio->fsync_width << I2S_OUT_CFGX_FSYNC_WIDTH); + else + ssp_newcfg |= BIT(I2S_OUT_CFGX_FSYNC_WIDTH); + + aio->mode = CYGNUS_SSPMODE_TDM; + break; + + default: + return -EINVAL; + } + + /* + * SSP out cfg. + * Retain bits we do not want to update, then OR in new bits + */ + ssp_curcfg = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + ssp_outcfg = (ssp_curcfg & I2S_OUT_CFG_REG_UPDATE_MASK) | ssp_newcfg; + writel(ssp_outcfg, aio->cygaud->audio + aio->regs.i2s_cfg); + + /* + * SSP in cfg. + * Retain bits we do not want to update, then OR in new bits + */ + ssp_curcfg = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + ssp_incfg = (ssp_curcfg & I2S_IN_CFG_REG_UPDATE_MASK) | ssp_newcfg; + writel(ssp_incfg, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + + val = readl(aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + /* + * Configure the word clk and bit clk as output or tristate + * Each port has 4 bits for controlling its pins. + * Shift the mask based upon port number. + */ + mask = BIT(AUD_MISC_SEROUT_LRCK_OE) + | BIT(AUD_MISC_SEROUT_SCLK_OE) + | BIT(AUD_MISC_SEROUT_MCLK_OE); + mask = mask << (aio->portnum * 4); + if (aio->is_slave) + /* Set bit for tri-state */ + val |= mask; + else + /* Clear bit for drive */ + val &= ~mask; + + dev_dbg(aio->cygaud->dev, "%s Set OE bits 0x%x\n", __func__, val); + writel(val, aio->cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + return 0; +} + +static int cygnus_ssp_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(dai); + struct cygnus_audio *cygaud = snd_soc_dai_get_drvdata(dai); + + dev_dbg(aio->cygaud->dev, + "%s cmd %d at port = %d\n", __func__, cmd, aio->portnum); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_ssp_out_enable(aio); + else + audio_ssp_in_enable(aio); + cygaud->active_ports++; + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_ssp_out_disable(aio); + else + audio_ssp_in_disable(aio); + cygaud->active_ports--; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int cygnus_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + u32 value; + int bits_per_slot = 0; /* default to 32-bits per slot */ + int frame_bits; + unsigned int active_slots; + bool found = false; + int i; + + if (tx_mask != rx_mask) { + dev_err(aio->cygaud->dev, + "%s tx_mask must equal rx_mask\n", __func__); + return -EINVAL; + } + + active_slots = hweight32(tx_mask); + + if ((active_slots < 0) || (active_slots > 16)) + return -EINVAL; + + /* Slot value must be even */ + if (active_slots % 2) + return -EINVAL; + + /* We encode 16 slots as 0 in the reg */ + if (active_slots == 16) + active_slots = 0; + + /* Slot Width is either 16 or 32 */ + switch (slot_width) { + case 16: + bits_per_slot = 1; + break; + case 32: + bits_per_slot = 0; + break; + default: + bits_per_slot = 0; + dev_warn(aio->cygaud->dev, + "%s Defaulting Slot Width to 32\n", __func__); + } + + frame_bits = slots * slot_width; + + for (i = 0; i < ARRAY_SIZE(ssp_valid_tdm_framesize); i++) { + if (ssp_valid_tdm_framesize[i] == frame_bits) { + found = true; + break; + } + } + + if (!found) { + dev_err(aio->cygaud->dev, + "%s In TDM mode, frame bits INVALID (%d)\n", + __func__, frame_bits); + return -EINVAL; + } + + aio->bit_per_frame = frame_bits; + + dev_dbg(aio->cygaud->dev, "%s active_slots %u, bits per frame %d\n", + __func__, active_slots, frame_bits); + + /* Set capture side of ssp port */ + value = readl(aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT); + value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT); + value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT); + value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT); + writel(value, aio->cygaud->i2s_in + aio->regs.i2s_cap_cfg); + + /* Set playback side of ssp port */ + value = readl(aio->cygaud->audio + aio->regs.i2s_cfg); + value &= ~(0xf << I2S_OUT_CFGX_VALID_SLOT); + value |= (active_slots << I2S_OUT_CFGX_VALID_SLOT); + value &= ~BIT(I2S_OUT_CFGX_BITS_PER_SLOT); + value |= (bits_per_slot << I2S_OUT_CFGX_BITS_PER_SLOT); + writel(value, aio->cygaud->audio + aio->regs.i2s_cfg); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int cygnus_ssp_suspend(struct snd_soc_dai *cpu_dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + + if (!aio->is_slave) { + u32 val; + + val = readl(aio->cygaud->audio + aio->regs.i2s_mclk_cfg); + val &= CYGNUS_PLLCLKSEL_MASK; + if (val >= ARRAY_SIZE(aio->cygaud->audio_clk)) { + dev_err(aio->cygaud->dev, "Clk index %u is out of bounds\n", + val); + return -EINVAL; + } + + if (aio->clk_trace.cap_clk_en) + clk_disable_unprepare(aio->cygaud->audio_clk[val]); + if (aio->clk_trace.play_clk_en) + clk_disable_unprepare(aio->cygaud->audio_clk[val]); + + aio->pll_clk_num = val; + } + + return 0; +} + +static int cygnus_ssp_resume(struct snd_soc_dai *cpu_dai) +{ + struct cygnus_aio_port *aio = cygnus_dai_get_portinfo(cpu_dai); + int error; + + if (!aio->is_slave) { + if (aio->clk_trace.cap_clk_en) { + error = clk_prepare_enable(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + if (error) { + dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", + __func__); + return -EINVAL; + } + } + if (aio->clk_trace.play_clk_en) { + error = clk_prepare_enable(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + if (error) { + if (aio->clk_trace.cap_clk_en) + clk_disable_unprepare(aio->cygaud-> + audio_clk[aio->pll_clk_num]); + dev_err(aio->cygaud->dev, "%s clk_prepare_enable failed\n", + __func__); + return -EINVAL; + } + } + } + + return 0; +} +#else +#define cygnus_ssp_suspend NULL +#define cygnus_ssp_resume NULL +#endif + +static const struct snd_soc_dai_ops cygnus_ssp_dai_ops = { + .startup = cygnus_ssp_startup, + .shutdown = cygnus_ssp_shutdown, + .trigger = cygnus_ssp_trigger, + .hw_params = cygnus_ssp_hw_params, + .set_fmt = cygnus_ssp_set_fmt, + .set_sysclk = cygnus_ssp_set_sysclk, + .set_tdm_slot = cygnus_set_dai_tdm_slot, +}; + + +#define INIT_CPU_DAI(num) { \ + .name = "cygnus-ssp" #num, \ + .playback = { \ + .channels_min = 1, \ + .channels_max = 16, \ + .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000, \ + .formats = SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .capture = { \ + .channels_min = 2, \ + .channels_max = 16, \ + .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + }, \ + .ops = &cygnus_ssp_dai_ops, \ + .suspend = cygnus_ssp_suspend, \ + .resume = cygnus_ssp_resume, \ +} + +static const struct snd_soc_dai_driver cygnus_ssp_dai_info[] = { + INIT_CPU_DAI(0), + INIT_CPU_DAI(1), + INIT_CPU_DAI(2), +}; + +static struct snd_soc_dai_driver cygnus_spdif_dai_info = { + .name = "cygnus-spdif", + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = CYGNUS_TDM_RATE | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &cygnus_ssp_dai_ops, + .suspend = cygnus_ssp_suspend, + .resume = cygnus_ssp_resume, +}; + +static struct snd_soc_dai_driver cygnus_ssp_dai[CYGNUS_MAX_PORTS]; + +static const struct snd_soc_component_driver cygnus_ssp_component = { + .name = "cygnus-audio", +}; + +/* + * Return < 0 if error + * Return 0 if disabled + * Return 1 if enabled and node is parsed successfully + */ +static int parse_ssp_child_node(struct platform_device *pdev, + struct device_node *dn, + struct cygnus_audio *cygaud, + struct snd_soc_dai_driver *p_dai) +{ + struct cygnus_aio_port *aio; + struct cygnus_ssp_regs ssp_regs[3]; + u32 rawval; + int portnum = -1; + enum cygnus_audio_port_type port_type; + + if (of_property_read_u32(dn, "reg", &rawval)) { + dev_err(&pdev->dev, "Missing reg property\n"); + return -EINVAL; + } + + portnum = rawval; + switch (rawval) { + case 0: + ssp_regs[0] = INIT_SSP_REGS(0); + port_type = PORT_TDM; + break; + case 1: + ssp_regs[1] = INIT_SSP_REGS(1); + port_type = PORT_TDM; + break; + case 2: + ssp_regs[2] = INIT_SSP_REGS(2); + port_type = PORT_TDM; + break; + case 3: + port_type = PORT_SPDIF; + break; + default: + dev_err(&pdev->dev, "Bad value for reg %u\n", rawval); + return -EINVAL; + } + + aio = &cygaud->portinfo[portnum]; + aio->cygaud = cygaud; + aio->portnum = portnum; + aio->port_type = port_type; + aio->fsync_width = -1; + + switch (port_type) { + case PORT_TDM: + aio->regs = ssp_regs[portnum]; + *p_dai = cygnus_ssp_dai_info[portnum]; + aio->mode = CYGNUS_SSPMODE_UNKNOWN; + break; + + case PORT_SPDIF: + aio->regs.bf_sourcech_cfg = BF_SRC_CFG3_OFFSET; + aio->regs.bf_sourcech_ctrl = BF_SRC_CTRL3_OFFSET; + aio->regs.i2s_mclk_cfg = SPDIF_MCLK_CFG_OFFSET; + aio->regs.i2s_stream_cfg = SPDIF_STREAM_CFG_OFFSET; + *p_dai = cygnus_spdif_dai_info; + + /* For the purposes of this code SPDIF can be I2S mode */ + aio->mode = CYGNUS_SSPMODE_I2S; + break; + default: + dev_err(&pdev->dev, "Bad value for port_type %d\n", port_type); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "%s portnum = %d\n", __func__, aio->portnum); + aio->streams_on = 0; + aio->cygaud->dev = &pdev->dev; + aio->clk_trace.play_en = false; + aio->clk_trace.cap_en = false; + + audio_ssp_init_portregs(aio); + return 0; +} + +static int audio_clk_init(struct platform_device *pdev, + struct cygnus_audio *cygaud) +{ + int i; + char clk_name[PROP_LEN_MAX]; + + for (i = 0; i < ARRAY_SIZE(cygaud->audio_clk); i++) { + snprintf(clk_name, PROP_LEN_MAX, "ch%d_audio", i); + + cygaud->audio_clk[i] = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(cygaud->audio_clk[i])) + return PTR_ERR(cygaud->audio_clk[i]); + } + + return 0; +} + +static int cygnus_ssp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child_node; + struct resource *res = pdev->resource; + struct cygnus_audio *cygaud; + int err = -EINVAL; + int node_count; + int active_port_count; + + cygaud = devm_kzalloc(dev, sizeof(struct cygnus_audio), GFP_KERNEL); + if (!cygaud) + return -ENOMEM; + + dev_set_drvdata(dev, cygaud); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aud"); + cygaud->audio = devm_ioremap_resource(dev, res); + if (IS_ERR(cygaud->audio)) + return PTR_ERR(cygaud->audio); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "i2s_in"); + cygaud->i2s_in = devm_ioremap_resource(dev, res); + if (IS_ERR(cygaud->i2s_in)) + return PTR_ERR(cygaud->i2s_in); + + /* Tri-state all controlable pins until we know that we need them */ + writel(CYGNUS_SSP_TRISTATE_MASK, + cygaud->audio + AUD_MISC_SEROUT_OE_REG_BASE); + + node_count = of_get_child_count(pdev->dev.of_node); + if ((node_count < 1) || (node_count > CYGNUS_MAX_PORTS)) { + dev_err(dev, "child nodes is %d. Must be between 1 and %d\n", + node_count, CYGNUS_MAX_PORTS); + return -EINVAL; + } + + active_port_count = 0; + + for_each_available_child_of_node(pdev->dev.of_node, child_node) { + err = parse_ssp_child_node(pdev, child_node, cygaud, + &cygnus_ssp_dai[active_port_count]); + + /* negative is err, 0 is active and good, 1 is disabled */ + if (err < 0) + return err; + else if (!err) { + dev_dbg(dev, "Activating DAI: %s\n", + cygnus_ssp_dai[active_port_count].name); + active_port_count++; + } + } + + cygaud->dev = dev; + cygaud->active_ports = 0; + + dev_dbg(dev, "Registering %d DAIs\n", active_port_count); + err = snd_soc_register_component(dev, &cygnus_ssp_component, + cygnus_ssp_dai, active_port_count); + if (err) { + dev_err(dev, "snd_soc_register_dai failed\n"); + return err; + } + + cygaud->irq_num = platform_get_irq(pdev, 0); + if (cygaud->irq_num <= 0) { + dev_err(dev, "platform_get_irq failed\n"); + err = cygaud->irq_num; + goto err_irq; + } + + err = audio_clk_init(pdev, cygaud); + if (err) { + dev_err(dev, "audio clock initialization failed\n"); + goto err_irq; + } + + err = cygnus_soc_platform_register(dev, cygaud); + if (err) { + dev_err(dev, "platform reg error %d\n", err); + goto err_irq; + } + + return 0; + +err_irq: + snd_soc_unregister_component(dev); + return err; +} + +static int cygnus_ssp_remove(struct platform_device *pdev) +{ + cygnus_soc_platform_unregister(&pdev->dev); + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static const struct of_device_id cygnus_ssp_of_match[] = { + { .compatible = "brcm,cygnus-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, cygnus_ssp_of_match); + +static struct platform_driver cygnus_ssp_driver = { + .probe = cygnus_ssp_probe, + .remove = cygnus_ssp_remove, + .driver = { + .name = "cygnus-ssp", + .of_match_table = cygnus_ssp_of_match, + }, +}; + +module_platform_driver(cygnus_ssp_driver); + +MODULE_ALIAS("platform:cygnus-ssp"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Cygnus ASoC SSP Interface"); diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h new file mode 100644 index 000000000000..33dd34305928 --- /dev/null +++ b/sound/soc/bcm/cygnus-ssp.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __CYGNUS_SSP_H__ +#define __CYGNUS_SSP_H__ + +#define CYGNUS_TDM_DAI_MAX_SLOTS 16 + +#define CYGNUS_MAX_PLAYBACK_PORTS 4 +#define CYGNUS_MAX_CAPTURE_PORTS 3 +#define CYGNUS_MAX_I2S_PORTS 3 +#define CYGNUS_MAX_PORTS CYGNUS_MAX_PLAYBACK_PORTS +#define CYGNUS_AUIDO_MAX_NUM_CLKS 3 + +#define CYGNUS_SSP_FRAMEBITS_DIV 1 + +#define CYGNUS_SSPMODE_I2S 0 +#define CYGNUS_SSPMODE_TDM 1 +#define CYGNUS_SSPMODE_UNKNOWN -1 + +#define CYGNUS_SSP_CLKSRC_PLL 0 + +/* Max string length of our dt property names */ +#define PROP_LEN_MAX 40 + +struct ringbuf_regs { + unsigned rdaddr; + unsigned wraddr; + unsigned baseaddr; + unsigned endaddr; + unsigned fmark; /* freemark for play, fullmark for caputure */ + unsigned period_bytes; + unsigned buf_size; +}; + +#define RINGBUF_REG_PLAYBACK(num) ((struct ringbuf_regs) { \ + .rdaddr = SRC_RBUF_ ##num## _RDADDR_OFFSET, \ + .wraddr = SRC_RBUF_ ##num## _WRADDR_OFFSET, \ + .baseaddr = SRC_RBUF_ ##num## _BASEADDR_OFFSET, \ + .endaddr = SRC_RBUF_ ##num## _ENDADDR_OFFSET, \ + .fmark = SRC_RBUF_ ##num## _FREE_MARK_OFFSET, \ + .period_bytes = 0, \ + .buf_size = 0, \ +}) + +#define RINGBUF_REG_CAPTURE(num) ((struct ringbuf_regs) { \ + .rdaddr = DST_RBUF_ ##num## _RDADDR_OFFSET, \ + .wraddr = DST_RBUF_ ##num## _WRADDR_OFFSET, \ + .baseaddr = DST_RBUF_ ##num## _BASEADDR_OFFSET, \ + .endaddr = DST_RBUF_ ##num## _ENDADDR_OFFSET, \ + .fmark = DST_RBUF_ ##num## _FULL_MARK_OFFSET, \ + .period_bytes = 0, \ + .buf_size = 0, \ +}) + +enum cygnus_audio_port_type { + PORT_TDM, + PORT_SPDIF, +}; + +struct cygnus_ssp_regs { + u32 i2s_stream_cfg; + u32 i2s_cfg; + u32 i2s_cap_stream_cfg; + u32 i2s_cap_cfg; + u32 i2s_mclk_cfg; + + u32 bf_destch_ctrl; + u32 bf_destch_cfg; + u32 bf_sourcech_ctrl; + u32 bf_sourcech_cfg; + u32 bf_sourcech_grp; +}; + +struct cygnus_track_clk { + bool cap_en; + bool play_en; + bool cap_clk_en; + bool play_clk_en; +}; + +struct cygnus_aio_port { + int portnum; + int mode; + bool is_slave; + int streams_on; /* will be 0 if both capture and play are off */ + int fsync_width; + int port_type; + + u32 mclk; + u32 lrclk; + u32 bit_per_frame; + u32 pll_clk_num; + + struct cygnus_audio *cygaud; + struct cygnus_ssp_regs regs; + + struct ringbuf_regs play_rb_regs; + struct ringbuf_regs capture_rb_regs; + + struct snd_pcm_substream *play_stream; + struct snd_pcm_substream *capture_stream; + + struct cygnus_track_clk clk_trace; +}; + + +struct cygnus_audio { + struct cygnus_aio_port portinfo[CYGNUS_MAX_PORTS]; + + int irq_num; + void __iomem *audio; + struct device *dev; + void __iomem *i2s_in; + + struct clk *audio_clk[CYGNUS_AUIDO_MAX_NUM_CLKS]; + int active_ports; + unsigned long vco_rate; +}; + +extern int cygnus_ssp_get_mode(struct snd_soc_dai *cpu_dai); +extern int cygnus_ssp_add_pll_tweak_controls(struct snd_soc_pcm_runtime *rtd); +extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, + int len); +extern int cygnus_soc_platform_register(struct device *dev, + struct cygnus_audio *cygaud); +extern int cygnus_soc_platform_unregister(struct device *dev); +extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai, + int len); +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f3fb98f0a995..1cd6ab344d67 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -32,6 +32,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ADAU1977_SPI if SPI_MASTER select SND_SOC_ADAU1977_I2C if I2C select SND_SOC_ADAU1701 if I2C + select SND_SOC_ADAU7002 select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C @@ -46,6 +47,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_BT_SCO select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS35L32 if I2C + select SND_SOC_CS35L33 if I2C select SND_SOC_CS42L51_I2C if I2C select SND_SOC_CS42L52 if I2C && INPUT select SND_SOC_CS42L56 if I2C && INPUT @@ -57,6 +59,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS42XX8_I2C if I2C select SND_SOC_CS4349 if I2C select SND_SOC_CS47L24 if MFD_CS47L24 + select SND_SOC_CS53L30 if I2C select SND_SOC_CX20442 if TTY select SND_SOC_DA7210 if SND_SOC_I2C_AND_SPI select SND_SOC_DA7213 if I2C @@ -84,6 +87,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98925 if I2C select SND_SOC_MAX98926 if I2C select SND_SOC_MAX9850 if I2C + select SND_SOC_MAX9860 if I2C select SND_SOC_MAX9768 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_MC13783 if MFD_MC13XXX @@ -269,8 +273,12 @@ config SND_SOC_AD1980 config SND_SOC_AD73311 tristate +config SND_SOC_ADAU_UTILS + tristate + config SND_SOC_ADAU1373 tristate + select SND_SOC_ADAU_UTILS config SND_SOC_ADAU1701 tristate "Analog Devices ADAU1701 CODEC" @@ -280,6 +288,7 @@ config SND_SOC_ADAU1701 config SND_SOC_ADAU17X1 tristate select SND_SOC_SIGMADSP_REGMAP + select SND_SOC_ADAU_UTILS config SND_SOC_ADAU1761 tristate @@ -322,6 +331,9 @@ config SND_SOC_ADAU1977_I2C select SND_SOC_ADAU1977 select REGMAP_I2C +config SND_SOC_ADAU7002 + tristate "Analog Devices ADAU7002 Stereo PDM-to-I2S/TDM Converter" + config SND_SOC_ADAV80X tristate @@ -371,7 +383,7 @@ config SND_SOC_ALC5632 tristate config SND_SOC_BT_SCO - tristate + tristate "Dummy BT SCO codec driver" config SND_SOC_CQ0093VC tristate @@ -380,6 +392,10 @@ config SND_SOC_CS35L32 tristate "Cirrus Logic CS35L32 CODEC" depends on I2C +config SND_SOC_CS35L33 + tristate "Cirrus Logic CS35L33 CODEC" + depends on I2C + config SND_SOC_CS42L51 tristate @@ -450,6 +466,11 @@ config SND_SOC_CS4349 config SND_SOC_CS47L24 tristate +# Cirrus Logic Quad-Channel ADC +config SND_SOC_CS53L30 + tristate "Cirrus Logic CS53L30 CODEC" + depends on I2C + config SND_SOC_CX20442 tristate depends on TTY @@ -536,6 +557,10 @@ config SND_SOC_MAX98357A config SND_SOC_MAX98371 tristate +config SND_SOC_MAX98504 + tristate "Maxim MAX98504 speaker amplifier" + depends on I2C + config SND_SOC_MAX9867 tristate @@ -548,6 +573,11 @@ config SND_SOC_MAX98926 config SND_SOC_MAX9850 tristate +config SND_SOC_MAX9860 + tristate "Maxim MAX9860 Mono Audio Voice Codec" + depends on I2C + select REGMAP_I2C + config SND_SOC_PCM1681 tristate "Texas Instruments PCM1681 CODEC" depends on I2C @@ -644,6 +674,9 @@ config SND_SOC_RT298 config SND_SOC_RT5514 tristate +config SND_SOC_RT5514_SPI + tristate + config SND_SOC_RT5616 tristate "Realtek RT5616 CODEC" depends on I2C @@ -969,7 +1002,8 @@ config SND_SOC_WM8983 tristate config SND_SOC_WM8985 - tristate + tristate "Wolfson Microelectronics WM8985 and WM8758 codec driver" + depends on SND_SOC_I2C_AND_SPI config SND_SOC_WM8988 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 0f548fd34ca3..58036af2c7d9 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -7,6 +7,7 @@ snd-soc-ad193x-spi-objs := ad193x-spi.o snd-soc-ad193x-i2c-objs := ad193x-i2c.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o +snd-soc-adau-utils-objs := adau-utils.o snd-soc-adau1373-objs := adau1373.o snd-soc-adau1701-objs := adau1701.o snd-soc-adau17x1-objs := adau17x1.o @@ -19,6 +20,7 @@ snd-soc-adau1781-spi-objs := adau1781-spi.o snd-soc-adau1977-objs := adau1977.o snd-soc-adau1977-spi-objs := adau1977-spi.o snd-soc-adau1977-i2c-objs := adau1977-i2c.o +snd-soc-adau7002-objs := adau7002.o snd-soc-adav80x-objs := adav80x.o snd-soc-adav801-objs := adav801.o snd-soc-adav803-objs := adav803.o @@ -35,6 +37,7 @@ snd-soc-arizona-objs := arizona.o snd-soc-bt-sco-objs := bt-sco.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o +snd-soc-cs35l33-objs := cs35l33.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o snd-soc-cs42l52-objs := cs42l52.o @@ -49,6 +52,7 @@ snd-soc-cs42xx8-objs := cs42xx8.o snd-soc-cs42xx8-i2c-objs := cs42xx8-i2c.o snd-soc-cs4349-objs := cs4349.o snd-soc-cs47l24-objs := cs47l24.o +snd-soc-cs53l30-objs := cs53l30.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o snd-soc-da7213-objs := da7213.o @@ -79,6 +83,7 @@ snd-soc-max9867-objs := max9867.o snd-soc-max98925-objs := max98925.o snd-soc-max98926-objs := max98926.o snd-soc-max9850-objs := max9850.o +snd-soc-max9860-objs := max9860.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o snd-soc-nau8825-objs := nau8825.o @@ -100,6 +105,7 @@ snd-soc-rl6347a-objs := rl6347a.o snd-soc-rt286-objs := rt286.o snd-soc-rt298-objs := rt298.o snd-soc-rt5514-objs := rt5514.o +snd-soc-rt5514-spi-objs := rt5514-spi.o snd-soc-rt5616-objs := rt5616.o snd-soc-rt5631-objs := rt5631.o snd-soc-rt5640-objs := rt5640.o @@ -208,6 +214,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o # Amp snd-soc-max9877-objs := max9877.o +snd-soc-max98504-objs := max98504.o snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-tas2552-objs := tas2552.o @@ -220,6 +227,7 @@ obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o obj-$(CONFIG_SND_SOC_AD193X_I2C) += snd-soc-ad193x-i2c.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_ADAU_UTILS) += snd-soc-adau-utils.o obj-$(CONFIG_SND_SOC_ADAU1373) += snd-soc-adau1373.o obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o obj-$(CONFIG_SND_SOC_ADAU17X1) += snd-soc-adau17x1.o @@ -232,6 +240,7 @@ obj-$(CONFIG_SND_SOC_ADAU1781_SPI) += snd-soc-adau1781-spi.o obj-$(CONFIG_SND_SOC_ADAU1977) += snd-soc-adau1977.o obj-$(CONFIG_SND_SOC_ADAU1977_SPI) += snd-soc-adau1977-spi.o obj-$(CONFIG_SND_SOC_ADAU1977_I2C) += snd-soc-adau1977-i2c.o +obj-$(CONFIG_SND_SOC_ADAU7002) += snd-soc-adau7002.o obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o @@ -250,6 +259,7 @@ obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o +obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o @@ -264,6 +274,7 @@ obj-$(CONFIG_SND_SOC_CS42XX8) += snd-soc-cs42xx8.o obj-$(CONFIG_SND_SOC_CS42XX8_I2C) += snd-soc-cs42xx8-i2c.o obj-$(CONFIG_SND_SOC_CS4349) += snd-soc-cs4349.o obj-$(CONFIG_SND_SOC_CS47L24) += snd-soc-cs47l24.o +obj-$(CONFIG_SND_SOC_CS53L30) += snd-soc-cs53l30.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o @@ -293,6 +304,7 @@ obj-$(CONFIG_SND_SOC_MAX9867) += snd-soc-max9867.o obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX98926) += snd-soc-max98926.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o +obj-$(CONFIG_SND_SOC_MAX9860) += snd-soc-max9860.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o @@ -314,6 +326,7 @@ obj-$(CONFIG_SND_SOC_RL6347A) += snd-soc-rl6347a.o obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o obj-$(CONFIG_SND_SOC_RT298) += snd-soc-rt298.o obj-$(CONFIG_SND_SOC_RT5514) += snd-soc-rt5514.o +obj-$(CONFIG_SND_SOC_RT5514_SPI) += snd-soc-rt5514-spi.o obj-$(CONFIG_SND_SOC_RT5616) += snd-soc-rt5616.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o @@ -419,4 +432,5 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o +obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o diff --git a/sound/soc/codecs/adau-utils.c b/sound/soc/codecs/adau-utils.c new file mode 100644 index 000000000000..19d6a6f41b12 --- /dev/null +++ b/sound/soc/codecs/adau-utils.c @@ -0,0 +1,61 @@ +/* + * Shared helper functions for devices from the ADAU family + * + * Copyright 2011-2016 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/gcd.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "adau-utils.h" + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]) +{ + unsigned int r, n, m, i, j; + unsigned int div; + + if (!freq_out) { + r = 0; + n = 0; + m = 0; + div = 0; + } else { + if (freq_out % freq_in != 0) { + div = DIV_ROUND_UP(freq_in, 13500000); + freq_in /= div; + r = freq_out / freq_in; + i = freq_out % freq_in; + j = gcd(i, freq_in); + n = i / j; + m = freq_in / j; + div--; + } else { + r = freq_out / freq_in; + n = 0; + m = 0; + div = 0; + } + if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) + return -EINVAL; + } + + regs[0] = m >> 8; + regs[1] = m & 0xff; + regs[2] = n >> 8; + regs[3] = n & 0xff; + regs[4] = (r << 3) | (div << 1); + if (m != 0) + regs[4] |= 1; /* Fractional mode */ + + return 0; +} +EXPORT_SYMBOL_GPL(adau_calc_pll_cfg); + +MODULE_DESCRIPTION("ASoC ADAU audio CODECs shared helper functions"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/adau-utils.h b/sound/soc/codecs/adau-utils.h new file mode 100644 index 000000000000..939b5f37762f --- /dev/null +++ b/sound/soc/codecs/adau-utils.h @@ -0,0 +1,7 @@ +#ifndef SOUND_SOC_CODECS_ADAU_PLL_H +#define SOUND_SOC_CODECS_ADAU_PLL_H + +int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out, + uint8_t regs[5]); + +#endif diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c index fe1353a797b9..1556b360fa15 100644 --- a/sound/soc/codecs/adau1373.c +++ b/sound/soc/codecs/adau1373.c @@ -23,6 +23,7 @@ #include <sound/adau1373.h> #include "adau1373.h" +#include "adau-utils.h" struct adau1373_dai { unsigned int clk_src; @@ -1254,7 +1255,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, { struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); unsigned int dpll_div = 0; - unsigned int x, r, n, m, i, j, mode; + uint8_t pll_regs[5]; + int ret; switch (pll_id) { case ADAU1373_PLL1: @@ -1295,27 +1297,8 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, dpll_div++; } - if (freq_out % freq_in != 0) { - /* fout = fin * (r + (n/m)) / x */ - x = DIV_ROUND_UP(freq_in, 13500000); - freq_in /= x; - r = freq_out / freq_in; - i = freq_out % freq_in; - j = gcd(i, freq_in); - n = i / j; - m = freq_in / j; - x--; - mode = 1; - } else { - /* fout = fin / r */ - r = freq_out / freq_in; - n = 0; - m = 0; - x = 0; - mode = 0; - } - - if (r < 2 || r > 8 || x > 3 || m > 0xffff || n > 0xffff) + ret = adau_calc_pll_cfg(freq_in, freq_out, pll_regs); + if (ret) return -EINVAL; if (dpll_div) { @@ -1330,12 +1313,11 @@ static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, regmap_write(adau1373->regmap, ADAU1373_DPLL_CTRL(pll_id), (source << 4) | dpll_div); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), (m >> 8) & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), m & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), (n >> 8) & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), n & 0xff); - regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), - (r << 3) | (x << 1) | mode); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL1(pll_id), pll_regs[0]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL2(pll_id), pll_regs[1]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL3(pll_id), pll_regs[2]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL4(pll_id), pll_regs[3]); + regmap_write(adau1373->regmap, ADAU1373_PLL_CTRL5(pll_id), pll_regs[4]); /* Set sysclk to pll_rate / 4 */ regmap_update_bits(adau1373->regmap, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09); diff --git a/sound/soc/codecs/adau1761-i2c.c b/sound/soc/codecs/adau1761-i2c.c index 8de010f758cd..9e7f257f17f8 100644 --- a/sound/soc/codecs/adau1761-i2c.c +++ b/sound/soc/codecs/adau1761-i2c.c @@ -31,7 +31,7 @@ static int adau1761_i2c_probe(struct i2c_client *client, static int adau1761_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); + adau17x1_remove(&client->dev); return 0; } diff --git a/sound/soc/codecs/adau1761-spi.c b/sound/soc/codecs/adau1761-spi.c index d9171245bd9f..a0b214be759a 100644 --- a/sound/soc/codecs/adau1761-spi.c +++ b/sound/soc/codecs/adau1761-spi.c @@ -48,7 +48,7 @@ static int adau1761_spi_probe(struct spi_device *spi) static int adau1761_spi_remove(struct spi_device *spi) { - snd_soc_unregister_codec(&spi->dev); + adau17x1_remove(&spi->dev); return 0; } diff --git a/sound/soc/codecs/adau1781-i2c.c b/sound/soc/codecs/adau1781-i2c.c index 06cbca84cf02..7b9d1802d159 100644 --- a/sound/soc/codecs/adau1781-i2c.c +++ b/sound/soc/codecs/adau1781-i2c.c @@ -31,7 +31,7 @@ static int adau1781_i2c_probe(struct i2c_client *client, static int adau1781_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); + adau17x1_remove(&client->dev); return 0; } diff --git a/sound/soc/codecs/adau1781-spi.c b/sound/soc/codecs/adau1781-spi.c index 3d965a01b99c..9b233544d2e8 100644 --- a/sound/soc/codecs/adau1781-spi.c +++ b/sound/soc/codecs/adau1781-spi.c @@ -48,7 +48,7 @@ static int adau1781_spi_probe(struct spi_device *spi) static int adau1781_spi_remove(struct spi_device *spi) { - snd_soc_unregister_codec(&spi->dev); + adau17x1_remove(&spi->dev); return 0; } diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index fcf05b254ecd..439aa3ff1f99 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -9,6 +9,7 @@ #include <linux/module.h> #include <linux/init.h> +#include <linux/clk.h> #include <linux/delay.h> #include <linux/slab.h> #include <sound/core.h> @@ -23,6 +24,7 @@ #include "sigmadsp.h" #include "adau17x1.h" +#include "adau-utils.h" static const char * const adau17x1_capture_mixer_boost_text[] = { "Normal operation", "Boost Level 1", "Boost Level 2", "Boost Level 3", @@ -302,6 +304,116 @@ bool adau17x1_has_dsp(struct adau *adau) } EXPORT_SYMBOL_GPL(adau17x1_has_dsp); +static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = dai->codec; + struct adau *adau = snd_soc_codec_get_drvdata(codec); + int ret; + + if (freq_in < 8000000 || freq_in > 27000000) + return -EINVAL; + + ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs); + if (ret < 0) + return ret; + + /* The PLL register is 6 bytes long and can only be written at once. */ + ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, + adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); + if (ret) + return ret; + + adau->pll_freq = freq_out; + + return 0; +} + +static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec); + struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); + bool is_pll; + bool was_pll; + + switch (clk_id) { + case ADAU17X1_CLK_SRC_MCLK: + is_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL_AUTO: + if (!adau->mclk) + return -EINVAL; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: + is_pll = true; + break; + default: + return -EINVAL; + } + + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_MCLK: + was_pll = false; + break; + case ADAU17X1_CLK_SRC_PLL: + case ADAU17X1_CLK_SRC_PLL_AUTO: + was_pll = true; + break; + default: + return -EINVAL; + } + + adau->sysclk = freq; + + if (is_pll != was_pll) { + if (is_pll) { + snd_soc_dapm_add_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } else { + snd_soc_dapm_del_routes(dapm, + &adau17x1_dapm_pll_route, 1); + } + } + + adau->clk_src = clk_id; + + return 0; +} + +static int adau17x1_auto_pll(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + struct adau *adau = snd_soc_dai_get_drvdata(dai); + unsigned int pll_rate; + + switch (params_rate(params)) { + case 48000: + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 96000: + pll_rate = 48000 * 1024; + break; + case 44100: + case 7350: + case 11025: + case 14700: + case 22050: + case 29400: + case 88200: + pll_rate = 44100 * 1024; + break; + default: + return -EINVAL; + } + + return adau17x1_set_dai_pll(dai, ADAU17X1_PLL, ADAU17X1_PLL_SRC_MCLK, + clk_get_rate(adau->mclk), pll_rate); +} + static int adau17x1_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { @@ -311,10 +423,19 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, unsigned int freq; int ret; - if (adau->clk_src == ADAU17X1_CLK_SRC_PLL) + switch (adau->clk_src) { + case ADAU17X1_CLK_SRC_PLL_AUTO: + ret = adau17x1_auto_pll(dai, params); + if (ret) + return ret; + /* Fall-through */ + case ADAU17X1_CLK_SRC_PLL: freq = adau->pll_freq; - else + break; + default: freq = adau->sysclk; + break; + } if (freq % params_rate(params) != 0) return -EINVAL; @@ -386,93 +507,6 @@ static int adau17x1_hw_params(struct snd_pcm_substream *substream, ADAU17X1_SERIAL_PORT1_DELAY_MASK, val); } -static int adau17x1_set_dai_pll(struct snd_soc_dai *dai, int pll_id, - int source, unsigned int freq_in, unsigned int freq_out) -{ - struct snd_soc_codec *codec = dai->codec; - struct adau *adau = snd_soc_codec_get_drvdata(codec); - unsigned int r, n, m, i, j; - unsigned int div; - int ret; - - if (freq_in < 8000000 || freq_in > 27000000) - return -EINVAL; - - if (!freq_out) { - r = 0; - n = 0; - m = 0; - div = 0; - } else { - if (freq_out % freq_in != 0) { - div = DIV_ROUND_UP(freq_in, 13500000); - freq_in /= div; - r = freq_out / freq_in; - i = freq_out % freq_in; - j = gcd(i, freq_in); - n = i / j; - m = freq_in / j; - div--; - } else { - r = freq_out / freq_in; - n = 0; - m = 0; - div = 0; - } - if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2) - return -EINVAL; - } - - adau->pll_regs[0] = m >> 8; - adau->pll_regs[1] = m & 0xff; - adau->pll_regs[2] = n >> 8; - adau->pll_regs[3] = n & 0xff; - adau->pll_regs[4] = (r << 3) | (div << 1); - if (m != 0) - adau->pll_regs[4] |= 1; /* Fractional mode */ - - /* The PLL register is 6 bytes long and can only be written at once. */ - ret = regmap_raw_write(adau->regmap, ADAU17X1_PLL_CONTROL, - adau->pll_regs, ARRAY_SIZE(adau->pll_regs)); - if (ret) - return ret; - - adau->pll_freq = freq_out; - - return 0; -} - -static int adau17x1_set_dai_sysclk(struct snd_soc_dai *dai, - int clk_id, unsigned int freq, int dir) -{ - struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(dai->codec); - struct adau *adau = snd_soc_codec_get_drvdata(dai->codec); - - switch (clk_id) { - case ADAU17X1_CLK_SRC_MCLK: - case ADAU17X1_CLK_SRC_PLL: - break; - default: - return -EINVAL; - } - - adau->sysclk = freq; - - if (adau->clk_src != clk_id) { - if (clk_id == ADAU17X1_CLK_SRC_PLL) { - snd_soc_dapm_add_routes(dapm, - &adau17x1_dapm_pll_route, 1); - } else { - snd_soc_dapm_del_routes(dapm, - &adau17x1_dapm_pll_route, 1); - } - } - - adau->clk_src = clk_id; - - return 0; -} - static int adau17x1_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { @@ -857,6 +891,10 @@ int adau17x1_add_routes(struct snd_soc_codec *codec) ret = snd_soc_dapm_add_routes(dapm, adau17x1_no_dsp_dapm_routes, ARRAY_SIZE(adau17x1_no_dsp_dapm_routes)); } + + if (adau->clk_src != ADAU17X1_CLK_SRC_MCLK) + snd_soc_dapm_add_routes(dapm, &adau17x1_dapm_pll_route, 1); + return ret; } EXPORT_SYMBOL_GPL(adau17x1_add_routes); @@ -879,6 +917,7 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, const char *firmware_name) { struct adau *adau; + int ret; if (IS_ERR(regmap)) return PTR_ERR(regmap); @@ -887,6 +926,30 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, if (!adau) return -ENOMEM; + adau->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(adau->mclk)) { + if (PTR_ERR(adau->mclk) != -ENOENT) + return PTR_ERR(adau->mclk); + /* Clock is optional (for the driver) */ + adau->mclk = NULL; + } else if (adau->mclk) { + adau->clk_src = ADAU17X1_CLK_SRC_PLL_AUTO; + + /* + * Any valid PLL output rate will work at this point, use one + * that is likely to be chosen later as well. The register will + * be written when the PLL is powered up for the first time. + */ + ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024, + adau->pll_regs); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(adau->mclk); + if (ret) + return ret; + } + adau->regmap = regmap; adau->switch_mode = switch_mode; adau->type = type; @@ -910,6 +973,16 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, } EXPORT_SYMBOL_GPL(adau17x1_probe); +void adau17x1_remove(struct device *dev) +{ + struct adau *adau = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + if (adau->mclk) + clk_disable_unprepare(adau->mclk); +} +EXPORT_SYMBOL_GPL(adau17x1_remove); + MODULE_DESCRIPTION("ASoC ADAU1X61/ADAU1X81 common code"); MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau17x1.h b/sound/soc/codecs/adau17x1.h index 5ae87a084d97..bf04b7efee40 100644 --- a/sound/soc/codecs/adau17x1.h +++ b/sound/soc/codecs/adau17x1.h @@ -22,13 +22,18 @@ enum adau17x1_pll_src { }; enum adau17x1_clk_src { + /* Automatically configure PLL based on the sample rate */ + ADAU17X1_CLK_SRC_PLL_AUTO, ADAU17X1_CLK_SRC_MCLK, ADAU17X1_CLK_SRC_PLL, }; +struct clk; + struct adau { unsigned int sysclk; unsigned int pll_freq; + struct clk *mclk; enum adau17x1_clk_src clk_src; enum adau17x1_type type; @@ -52,6 +57,7 @@ int adau17x1_add_routes(struct snd_soc_codec *codec); int adau17x1_probe(struct device *dev, struct regmap *regmap, enum adau17x1_type type, void (*switch_mode)(struct device *dev), const char *firmware_name); +void adau17x1_remove(struct device *dev); int adau17x1_set_micbias_voltage(struct snd_soc_codec *codec, enum adau17x1_micbias_voltage micbias); bool adau17x1_readable_register(struct device *dev, unsigned int reg); diff --git a/sound/soc/codecs/adau7002.c b/sound/soc/codecs/adau7002.c new file mode 100644 index 000000000000..9df72c6adcca --- /dev/null +++ b/sound/soc/codecs/adau7002.c @@ -0,0 +1,80 @@ +/* + * ADAU7002 Stereo PDM-to-I2S/TDM converter driver + * + * Copyright 2014-2016 Analog Devices + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <sound/soc.h> + +static const struct snd_soc_dapm_widget adau7002_widgets[] = { + SND_SOC_DAPM_INPUT("PDM_DAT"), + SND_SOC_DAPM_REGULATOR_SUPPLY("IOVDD", 0, 0), +}; + +static const struct snd_soc_dapm_route adau7002_routes[] = { + { "Capture", NULL, "PDM_DAT" }, + { "Capture", NULL, "IOVDD" }, +}; + +static struct snd_soc_dai_driver adau7002_dai = { + .name = "adau7002-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE, + .sig_bits = 20, + }, +}; + +static const struct snd_soc_codec_driver adau7002_codec_driver = { + .dapm_widgets = adau7002_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau7002_widgets), + .dapm_routes = adau7002_routes, + .num_dapm_routes = ARRAY_SIZE(adau7002_routes), +}; + +static int adau7002_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &adau7002_codec_driver, + &adau7002_dai, 1); +} + +static int adau7002_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id adau7002_dt_ids[] = { + { .compatible = "adi,adau7002", }, + { } +}; +MODULE_DEVICE_TABLE(of, adau7002_dt_ids); +#endif + +static struct platform_driver adau7002_driver = { + .driver = { + .name = "adau7002", + .of_match_table = of_match_ptr(adau7002_dt_ids), + }, + .probe = adau7002_probe, + .remove = adau7002_remove, +}; +module_platform_driver(adau7002_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("ADAU7002 Stereo PDM-to-I2S/TDM Converter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c index 5013d2ba0c10..97798d250f08 100644 --- a/sound/soc/codecs/ak4613.c +++ b/sound/soc/codecs/ak4613.c @@ -437,15 +437,25 @@ static struct snd_soc_dai_driver ak4613_dai = { .symmetric_rates = 1, }; -static int ak4613_resume(struct snd_soc_codec *codec) +static int ak4613_suspend(struct snd_soc_codec *codec) { struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + regcache_cache_only(regmap, true); regcache_mark_dirty(regmap); + return 0; +} + +static int ak4613_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_cache_only(regmap, false); return regcache_sync(regmap); } static struct snd_soc_codec_driver soc_codec_dev_ak4613 = { + .suspend = ak4613_suspend, .resume = ak4613_resume, .set_bias_level = ak4613_set_bias_level, .controls = ak4613_snd_controls, diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 4d8b9e49e8d6..cc941d66ec3d 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -523,15 +523,23 @@ static struct snd_soc_dai_driver ak4642_dai = { .symmetric_rates = 1, }; -static int ak4642_resume(struct snd_soc_codec *codec) +static int ak4642_suspend(struct snd_soc_codec *codec) { struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + regcache_cache_only(regmap, true); regcache_mark_dirty(regmap); - regcache_sync(regmap); return 0; } +static int ak4642_resume(struct snd_soc_codec *codec) +{ + struct regmap *regmap = dev_get_regmap(codec->dev, NULL); + + regcache_cache_only(regmap, false); + regcache_sync(regmap); + return 0; +} static int ak4642_probe(struct snd_soc_codec *codec) { struct ak4642_priv *priv = snd_soc_codec_get_drvdata(codec); @@ -544,6 +552,7 @@ static int ak4642_probe(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_ak4642 = { .probe = ak4642_probe, + .suspend = ak4642_suspend, .resume = ak4642_resume, .set_bias_level = ak4642_set_bias_level, .controls = ak4642_snd_controls, diff --git a/sound/soc/codecs/arizona.c b/sound/soc/codecs/arizona.c index 664a8c044ffb..ecfdbfcae366 100644 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@ -85,30 +85,9 @@ static int arizona_spk_ev(struct snd_soc_dapm_widget *w, { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct arizona *arizona = dev_get_drvdata(codec->dev->parent); - struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); - bool manual_ena = false; int val; - switch (arizona->type) { - case WM5102: - switch (arizona->rev) { - case 0: - break; - default: - manual_ena = true; - break; - } - default: - break; - } - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - if (!priv->spk_ena && manual_ena) { - regmap_write_async(arizona->regmap, 0x4f5, 0x25a); - priv->spk_ena_pending = true; - } - break; case SND_SOC_DAPM_POST_PMU: val = snd_soc_read(codec, ARIZONA_INTERRUPT_RAW_STATUS_3); if (val & ARIZONA_SPK_OVERHEAT_STS) { @@ -120,33 +99,12 @@ static int arizona_spk_ev(struct snd_soc_dapm_widget *w, regmap_update_bits_async(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, 1 << w->shift, 1 << w->shift); - - if (priv->spk_ena_pending) { - msleep(75); - regmap_write_async(arizona->regmap, 0x4f5, 0xda); - priv->spk_ena_pending = false; - priv->spk_ena++; - } break; case SND_SOC_DAPM_PRE_PMD: - if (manual_ena) { - priv->spk_ena--; - if (!priv->spk_ena) - regmap_write_async(arizona->regmap, - 0x4f5, 0x25a); - } - regmap_update_bits_async(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, 1 << w->shift, 0); break; - case SND_SOC_DAPM_POST_PMD: - if (manual_ena) { - if (!priv->spk_ena) - regmap_write_async(arizona->regmap, - 0x4f5, 0x0da); - } - break; default: break; } @@ -324,6 +282,17 @@ int arizona_init_gpio(struct snd_soc_codec *codec) } EXPORT_SYMBOL_GPL(arizona_init_gpio); +int arizona_init_notifiers(struct snd_soc_codec *codec) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + BLOCKING_INIT_NOTIFIER_HEAD(&arizona->notifier); + + return 0; +} +EXPORT_SYMBOL_GPL(arizona_init_notifiers); + const char * const arizona_mixer_texts[ARIZONA_NUM_MIXER_INPUTS] = { "None", "Tone Generator 1", @@ -619,7 +588,7 @@ const struct soc_enum arizona_asrc_rate1 = arizona_rate_text, arizona_rate_val); EXPORT_SYMBOL_GPL(arizona_asrc_rate1); -static const char *arizona_vol_ramp_text[] = { +static const char * const arizona_vol_ramp_text[] = { "0ms/6dB", "0.5ms/6dB", "1ms/6dB", "2ms/6dB", "4ms/6dB", "8ms/6dB", "15ms/6dB", "30ms/6dB", }; @@ -648,7 +617,7 @@ SOC_ENUM_SINGLE_DECL(arizona_out_vi_ramp, arizona_vol_ramp_text); EXPORT_SYMBOL_GPL(arizona_out_vi_ramp); -static const char *arizona_lhpf_mode_text[] = { +static const char * const arizona_lhpf_mode_text[] = { "Low-pass", "High-pass" }; @@ -676,7 +645,7 @@ SOC_ENUM_SINGLE_DECL(arizona_lhpf4_mode, arizona_lhpf_mode_text); EXPORT_SYMBOL_GPL(arizona_lhpf4_mode); -static const char *arizona_ng_hold_text[] = { +static const char * const arizona_ng_hold_text[] = { "30ms", "120ms", "250ms", "500ms", }; @@ -810,6 +779,14 @@ const struct soc_enum arizona_output_anc_src[] = { }; EXPORT_SYMBOL_GPL(arizona_output_anc_src); +const struct snd_kcontrol_new arizona_voice_trigger_switch[] = { + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 1, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 2, 1, 0), + SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 3, 1, 0), +}; +EXPORT_SYMBOL_GPL(arizona_voice_trigger_switch); + static void arizona_in_set_vu(struct snd_soc_codec *codec, int ena) { struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); @@ -2573,6 +2550,30 @@ int arizona_lhpf_coeff_put(struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL_GPL(arizona_lhpf_coeff_put); +int arizona_register_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb, + int (*notify)(struct notifier_block *nb, + unsigned long action, void *data)) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + nb->notifier_call = notify; + + return blocking_notifier_chain_register(&arizona->notifier, nb); +} +EXPORT_SYMBOL_GPL(arizona_register_notifier); + +int arizona_unregister_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb) +{ + struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec); + struct arizona *arizona = priv->arizona; + + return blocking_notifier_chain_unregister(&arizona->notifier, nb); +} +EXPORT_SYMBOL_GPL(arizona_unregister_notifier); + MODULE_DESCRIPTION("ASoC Wolfson Arizona class device support"); MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/arizona.h b/sound/soc/codecs/arizona.h index ce0531b8c632..69da1ef3a17c 100644 --- a/sound/soc/codecs/arizona.h +++ b/sound/soc/codecs/arizona.h @@ -63,6 +63,9 @@ #define ARIZONA_DVFS_SR1_RQ 0x001 #define ARIZONA_DVFS_ADSP1_RQ 0x100 +/* Notifier events */ +#define ARIZONA_NOTIFY_VOICE_TRIGGER 0x1 + struct arizona; struct wm_adsp; @@ -87,14 +90,15 @@ struct arizona_priv { unsigned int out_down_pending; unsigned int out_down_delay; - unsigned int spk_ena:2; - unsigned int spk_ena_pending:1; - unsigned int dvfs_reqs; struct mutex dvfs_lock; bool dvfs_cached; }; +struct arizona_voice_trigger_info { + int core; +}; + #define ARIZONA_NUM_MIXER_INPUTS 104 extern const unsigned int arizona_mixer_tlv[]; @@ -248,6 +252,8 @@ extern const struct soc_enum arizona_anc_input_src[]; extern const struct soc_enum arizona_anc_ng_enum; extern const struct soc_enum arizona_output_anc_src[]; +extern const struct snd_kcontrol_new arizona_voice_trigger_switch[]; + extern int arizona_in_ev(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event); @@ -306,6 +312,7 @@ extern int arizona_set_fll(struct arizona_fll *fll, int source, extern int arizona_init_spk(struct snd_soc_codec *codec); extern int arizona_init_gpio(struct snd_soc_codec *codec); extern int arizona_init_mono(struct snd_soc_codec *codec); +extern int arizona_init_notifiers(struct snd_soc_codec *codec); extern int arizona_free_spk(struct snd_soc_codec *codec); @@ -317,4 +324,13 @@ int arizona_set_output_mode(struct snd_soc_codec *codec, int output, extern bool arizona_input_analog(struct snd_soc_codec *codec, int shift); extern const char *arizona_sample_rate_val_to_name(unsigned int rate_val); + +extern int arizona_register_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb, + int (*notify)(struct notifier_block *nb, + unsigned long action, + void *data)); +extern int arizona_unregister_notifier(struct snd_soc_codec *codec, + struct notifier_block *nb); + #endif diff --git a/sound/soc/codecs/bt-sco.c b/sound/soc/codecs/bt-sco.c index b084ad113e96..2a8d0ee141d4 100644 --- a/sound/soc/codecs/bt-sco.c +++ b/sound/soc/codecs/bt-sco.c @@ -25,22 +25,41 @@ static const struct snd_soc_dapm_route bt_sco_routes[] = { { "TX", NULL, "Playback" }, }; -static struct snd_soc_dai_driver bt_sco_dai = { - .name = "bt-sco-pcm", - .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, +static struct snd_soc_dai_driver bt_sco_dai[] = { + { + .name = "bt-sco-pcm", + .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, + }, }, + { + .name = "bt-sco-pcm-wb", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + } }; static struct snd_soc_codec_driver soc_codec_dev_bt_sco = { @@ -53,7 +72,7 @@ static struct snd_soc_codec_driver soc_codec_dev_bt_sco = { static int bt_sco_probe(struct platform_device *pdev) { return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_bt_sco, - &bt_sco_dai, 1); + bt_sco_dai, ARRAY_SIZE(bt_sco_dai)); } static int bt_sco_remove(struct platform_device *pdev) @@ -77,6 +96,7 @@ MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids); #if defined(CONFIG_OF) static const struct of_device_id bt_sco_codec_of_match[] = { { .compatible = "delta,dfbmcs320", }, + { .compatible = "linux,bt-sco", }, {}, }; MODULE_DEVICE_TABLE(of, bt_sco_codec_of_match); diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c new file mode 100644 index 000000000000..6f9c1addcd7f --- /dev/null +++ b/sound/soc/codecs/cs35l33.c @@ -0,0 +1,1303 @@ +/* + * cs35l33.c -- CS35L33 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan <paul.handrigan@cirrus.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. + * + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/workqueue.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 <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <sound/cs35l33.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/machine.h> +#include <linux/of_gpio.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> + +#include "cs35l33.h" + +#define CS35L33_BOOT_DELAY 50 + +struct cs35l33_private { + struct snd_soc_codec *codec; + struct cs35l33_pdata pdata; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + bool amp_cal; + int mclk_int; + struct regulator_bulk_data core_supplies[2]; + int num_core_supplies; + bool is_tdm_mode; + bool enable_soft_ramp; +}; + +static const struct reg_default cs35l33_reg[] = { + {CS35L33_PWRCTL1, 0x85}, + {CS35L33_PWRCTL2, 0xFE}, + {CS35L33_CLK_CTL, 0x0C}, + {CS35L33_BST_PEAK_CTL, 0x90}, + {CS35L33_PROTECT_CTL, 0x55}, + {CS35L33_BST_CTL1, 0x00}, + {CS35L33_BST_CTL2, 0x01}, + {CS35L33_ADSP_CTL, 0x00}, + {CS35L33_ADC_CTL, 0xC8}, + {CS35L33_DAC_CTL, 0x14}, + {CS35L33_DIG_VOL_CTL, 0x00}, + {CS35L33_CLASSD_CTL, 0x04}, + {CS35L33_AMP_CTL, 0x90}, + {CS35L33_INT_MASK_1, 0xFF}, + {CS35L33_INT_MASK_2, 0xFF}, + {CS35L33_DIAG_LOCK, 0x00}, + {CS35L33_DIAG_CTRL_1, 0x40}, + {CS35L33_DIAG_CTRL_2, 0x00}, + {CS35L33_HG_MEMLDO_CTL, 0x62}, + {CS35L33_HG_REL_RATE, 0x03}, + {CS35L33_LDO_DEL, 0x12}, + {CS35L33_HG_HEAD, 0x0A}, + {CS35L33_HG_EN, 0x05}, + {CS35L33_TX_VMON, 0x00}, + {CS35L33_TX_IMON, 0x03}, + {CS35L33_TX_VPMON, 0x02}, + {CS35L33_TX_VBSTMON, 0x05}, + {CS35L33_TX_FLAG, 0x06}, + {CS35L33_TX_EN1, 0x00}, + {CS35L33_TX_EN2, 0x00}, + {CS35L33_TX_EN3, 0x00}, + {CS35L33_TX_EN4, 0x00}, + {CS35L33_RX_AUD, 0x40}, + {CS35L33_RX_SPLY, 0x03}, + {CS35L33_RX_ALIVE, 0x04}, + {CS35L33_BST_CTL4, 0x63}, +}; + +static const struct reg_sequence cs35l33_patch[] = { + { 0x00, 0x99, 0 }, + { 0x59, 0x02, 0 }, + { 0x52, 0x30, 0 }, + { 0x39, 0x45, 0 }, + { 0x57, 0x30, 0 }, + { 0x2C, 0x68, 0 }, + { 0x00, 0x00, 0 }, +}; + +static bool cs35l33_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_HG_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l33_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + /* these are read only registers */ + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_HG_STATUS: + return false; + default: + return true; + } +} + +static bool cs35l33_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L33_DEVID_AB: + case CS35L33_DEVID_CD: + case CS35L33_DEVID_E: + case CS35L33_REV_ID: + case CS35L33_PWRCTL1: + case CS35L33_PWRCTL2: + case CS35L33_CLK_CTL: + case CS35L33_BST_PEAK_CTL: + case CS35L33_PROTECT_CTL: + case CS35L33_BST_CTL1: + case CS35L33_BST_CTL2: + case CS35L33_ADSP_CTL: + case CS35L33_ADC_CTL: + case CS35L33_DAC_CTL: + case CS35L33_DIG_VOL_CTL: + case CS35L33_CLASSD_CTL: + case CS35L33_AMP_CTL: + case CS35L33_INT_MASK_1: + case CS35L33_INT_MASK_2: + case CS35L33_INT_STATUS_1: + case CS35L33_INT_STATUS_2: + case CS35L33_DIAG_LOCK: + case CS35L33_DIAG_CTRL_1: + case CS35L33_DIAG_CTRL_2: + case CS35L33_HG_MEMLDO_CTL: + case CS35L33_HG_REL_RATE: + case CS35L33_LDO_DEL: + case CS35L33_HG_HEAD: + case CS35L33_HG_EN: + case CS35L33_TX_VMON: + case CS35L33_TX_IMON: + case CS35L33_TX_VPMON: + case CS35L33_TX_VBSTMON: + case CS35L33_TX_FLAG: + case CS35L33_TX_EN1: + case CS35L33_TX_EN2: + case CS35L33_TX_EN3: + case CS35L33_TX_EN4: + case CS35L33_RX_AUD: + case CS35L33_RX_SPLY: + case CS35L33_RX_ALIVE: + case CS35L33_BST_CTL4: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(classd_ctl_tlv, 900, 100, 0); +static DECLARE_TLV_DB_SCALE(dac_tlv, -10200, 50, 0); + +static const struct snd_kcontrol_new cs35l33_snd_controls[] = { + + SOC_SINGLE_TLV("SPK Amp Volume", CS35L33_AMP_CTL, + 4, 0x09, 0, classd_ctl_tlv), + SOC_SINGLE_SX_TLV("DAC Volume", CS35L33_DIG_VOL_CTL, + 0, 0x34, 0xE4, dac_tlv), +}; + +static int cs35l33_spkrdrv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (!priv->amp_cal) { + usleep_range(8000, 9000); + priv->amp_cal = true; + regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_CAL, 0); + dev_dbg(codec->dev, "Amp calibration done\n"); + } + dev_dbg(codec->dev, "Amp turned on\n"); + break; + case SND_SOC_DAPM_POST_PMD: + dev_dbg(codec->dev, "Amp turned off\n"); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + break; + } + + return 0; +} + +static int cs35l33_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_BST, 0); + val = priv->is_tdm_mode ? 0 : CS35L33_PDN_TDM; + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_PDN_TDM, val); + dev_dbg(codec->dev, "BST turned on\n"); + break; + case SND_SOC_DAPM_POST_PMU: + dev_dbg(codec->dev, "SDIN turned on\n"); + if (!priv->amp_cal) { + regmap_update_bits(priv->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_CAL, CS35L33_AMP_CAL); + dev_dbg(codec->dev, "Amp calibration started\n"); + usleep_range(10000, 11000); + } + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_PDN_TDM, CS35L33_PDN_TDM); + usleep_range(4000, 4100); + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_BST, CS35L33_PDN_BST); + dev_dbg(codec->dev, "BST and SDIN turned off\n"); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + + } + + return 0; +} + +static int cs35l33_sdout_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int mask = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM; + unsigned int mask2 = CS35L33_SDOUT_3ST_TDM; + unsigned int val, val2; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (priv->is_tdm_mode) { + /* set sdout_3st_i2s and reset pdn_tdm */ + val = CS35L33_SDOUT_3ST_I2S; + /* reset sdout_3st_tdm */ + val2 = 0; + } else { + /* reset sdout_3st_i2s and set pdn_tdm */ + val = CS35L33_PDN_TDM; + /* set sdout_3st_tdm */ + val2 = CS35L33_SDOUT_3ST_TDM; + } + dev_dbg(codec->dev, "SDOUT turned on\n"); + break; + case SND_SOC_DAPM_PRE_PMD: + val = CS35L33_SDOUT_3ST_I2S | CS35L33_PDN_TDM; + val2 = CS35L33_SDOUT_3ST_TDM; + dev_dbg(codec->dev, "SDOUT turned off\n"); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + return 0; + } + + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + mask, val); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + mask2, val2); + + return 0; +} + +static const struct snd_soc_dapm_widget cs35l33_dapm_widgets[] = { + + SND_SOC_DAPM_OUTPUT("SPK"), + SND_SOC_DAPM_OUT_DRV_E("SPKDRV", CS35L33_PWRCTL1, 7, 1, NULL, 0, + cs35l33_spkrdrv_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L33_PWRCTL2, + 2, 1, cs35l33_sdin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("MON"), + + SND_SOC_DAPM_ADC("VMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VMON_SHIFT, 1), + SND_SOC_DAPM_ADC("IMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_IMON_SHIFT, 1), + SND_SOC_DAPM_ADC("VPMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VPMON_SHIFT, 1), + SND_SOC_DAPM_ADC("VBSTMON", NULL, + CS35L33_PWRCTL2, CS35L33_PDN_VBSTMON_SHIFT, 1), + + SND_SOC_DAPM_AIF_OUT_E("SDOUT", NULL, 0, SND_SOC_NOPM, 0, 0, + cs35l33_sdout_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_PRE_PMD), +}; + +static const struct snd_soc_dapm_route cs35l33_audio_map[] = { + {"SDIN", NULL, "CS35L33 Playback"}, + {"SPKDRV", NULL, "SDIN"}, + {"SPK", NULL, "SPKDRV"}, + + {"VMON", NULL, "MON"}, + {"IMON", NULL, "MON"}, + + {"SDOUT", NULL, "VMON"}, + {"SDOUT", NULL, "IMON"}, + {"CS35L33 Capture", NULL, "SDOUT"}, +}; + +static const struct snd_soc_dapm_route cs35l33_vphg_auto_route[] = { + {"SPKDRV", NULL, "VPMON"}, + {"VPMON", NULL, "CS35L33 Playback"}, +}; + +static const struct snd_soc_dapm_route cs35l33_vp_vbst_mon_route[] = { + {"SDOUT", NULL, "VPMON"}, + {"VPMON", NULL, "MON"}, + {"SDOUT", NULL, "VBSTMON"}, + {"VBSTMON", NULL, "MON"}, +}; + +static int cs35l33_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int val; + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_ALL, 0); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS, 0); + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(priv->regmap, CS35L33_PWRCTL1, + CS35L33_PDN_ALL, CS35L33_PDN_ALL); + regmap_read(priv->regmap, CS35L33_INT_STATUS_2, &val); + usleep_range(1000, 1100); + if (val & CS35L33_PDN_DONE) + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS, CS35L33_MCLKDIS); + break; + case SND_SOC_BIAS_OFF: + break; + default: + return -EINVAL; + } + + return 0; +} + +struct cs35l33_mclk_div { + int mclk; + int srate; + u8 adsp_rate; + u8 int_fs_ratio; +}; + +static const struct cs35l33_mclk_div cs35l33_mclk_coeffs[] = { + /* MCLK, Sample Rate, adsp_rate, int_fs_ratio */ + {5644800, 11025, 0x4, CS35L33_INT_FS_RATE}, + {5644800, 22050, 0x8, CS35L33_INT_FS_RATE}, + {5644800, 44100, 0xC, CS35L33_INT_FS_RATE}, + + {6000000, 8000, 0x1, 0}, + {6000000, 11025, 0x2, 0}, + {6000000, 11029, 0x3, 0}, + {6000000, 12000, 0x4, 0}, + {6000000, 16000, 0x5, 0}, + {6000000, 22050, 0x6, 0}, + {6000000, 22059, 0x7, 0}, + {6000000, 24000, 0x8, 0}, + {6000000, 32000, 0x9, 0}, + {6000000, 44100, 0xA, 0}, + {6000000, 44118, 0xB, 0}, + {6000000, 48000, 0xC, 0}, + + {6144000, 8000, 0x1, CS35L33_INT_FS_RATE}, + {6144000, 12000, 0x4, CS35L33_INT_FS_RATE}, + {6144000, 16000, 0x5, CS35L33_INT_FS_RATE}, + {6144000, 24000, 0x8, CS35L33_INT_FS_RATE}, + {6144000, 32000, 0x9, CS35L33_INT_FS_RATE}, + {6144000, 48000, 0xC, CS35L33_INT_FS_RATE}, +}; + +static int cs35l33_get_mclk_coeff(int mclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l33_mclk_coeffs); i++) { + if (cs35l33_mclk_coeffs[i].mclk == mclk && + cs35l33_mclk_coeffs[i].srate == srate) + return i; + } + return -EINVAL; +} + +static int cs35l33_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL, + CS35L33_MS_MASK, CS35L33_MS_MASK); + dev_dbg(codec->dev, "Audio port in master mode\n"); + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(priv->regmap, CS35L33_ADSP_CTL, + CS35L33_MS_MASK, 0); + dev_dbg(codec->dev, "Audio port in slave mode\n"); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + /* + * tdm mode in cs35l33 resembles dsp-a mode very + * closely, it is dsp-a with fsync shifted left by half bclk + */ + priv->is_tdm_mode = true; + dev_dbg(codec->dev, "Audio port in TDM mode\n"); + break; + case SND_SOC_DAIFMT_I2S: + priv->is_tdm_mode = false; + dev_dbg(codec->dev, "Audio port in I2S mode\n"); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cs35l33_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + int sample_size = params_width(params); + int coeff = cs35l33_get_mclk_coeff(priv->mclk_int, params_rate(params)); + + if (coeff < 0) + return coeff; + + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_ADSP_FS | CS35L33_INT_FS_RATE, + cs35l33_mclk_coeffs[coeff].int_fs_ratio + | cs35l33_mclk_coeffs[coeff].adsp_rate); + + if (priv->is_tdm_mode) { + sample_size = (sample_size / 8) - 1; + if (sample_size > 2) + sample_size = 2; + regmap_update_bits(priv->regmap, CS35L33_RX_AUD, + CS35L33_AUDIN_RX_DEPTH, + sample_size << CS35L33_AUDIN_RX_DEPTH_SHIFT); + } + + dev_dbg(codec->dev, "sample rate=%d, bits per sample=%d\n", + params_rate(params), params_width(params)); + + return 0; +} + +static const unsigned int cs35l33_src_rates[] = { + 8000, 11025, 11029, 12000, 16000, 22050, + 22059, 24000, 32000, 44100, 44118, 48000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l33_constraints = { + .count = ARRAY_SIZE(cs35l33_src_rates), + .list = cs35l33_src_rates, +}; + +static int cs35l33_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l33_constraints); + return 0; +} + +static int cs35l33_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + if (tristate) { + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_SDOUT_3ST_I2S, CS35L33_SDOUT_3ST_I2S); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_SDOUT_3ST_TDM, CS35L33_SDOUT_3ST_TDM); + } else { + regmap_update_bits(priv->regmap, CS35L33_PWRCTL2, + CS35L33_SDOUT_3ST_I2S, 0); + regmap_update_bits(priv->regmap, CS35L33_CLK_CTL, + CS35L33_SDOUT_3ST_TDM, 0); + } + + return 0; +} + +static int cs35l33_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int reg, bit_pos, i; + int slot, slot_num; + + if (slot_width != 8) + return -EINVAL; + + /* scan rx_mask for aud slot */ + slot = ffs(rx_mask) - 1; + if (slot >= 0) { + regmap_update_bits(priv->regmap, CS35L33_RX_AUD, + CS35L33_X_LOC, slot); + dev_dbg(codec->dev, "Audio starts from slots %d", slot); + } + + /* + * scan tx_mask: vmon(2 slots); imon (2 slots); + * vpmon (1 slot) vbstmon (1 slot) + */ + slot = ffs(tx_mask) - 1; + slot_num = 0; + + for (i = 0; i < 2 ; i++) { + /* disable vpmon/vbstmon: enable later if set in tx_mask */ + regmap_update_bits(priv->regmap, CS35L33_TX_VPMON + i, + CS35L33_X_STATE | CS35L33_X_LOC, CS35L33_X_STATE + | CS35L33_X_LOC); + } + + /* disconnect {vp,vbst}_mon routes: eanble later if set in tx_mask*/ + snd_soc_dapm_del_routes(dapm, cs35l33_vp_vbst_mon_route, + ARRAY_SIZE(cs35l33_vp_vbst_mon_route)); + + while (slot >= 0) { + /* configure VMON_TX_LOC */ + if (slot_num == 0) { + regmap_update_bits(priv->regmap, CS35L33_TX_VMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + dev_dbg(codec->dev, "VMON enabled in slots %d-%d", + slot, slot + 1); + } + + /* configure IMON_TX_LOC */ + if (slot_num == 3) { + regmap_update_bits(priv->regmap, CS35L33_TX_IMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + dev_dbg(codec->dev, "IMON enabled in slots %d-%d", + slot, slot + 1); + } + + /* configure VPMON_TX_LOC */ + if (slot_num == 4) { + regmap_update_bits(priv->regmap, CS35L33_TX_VPMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + snd_soc_dapm_add_routes(dapm, + &cs35l33_vp_vbst_mon_route[0], 2); + dev_dbg(codec->dev, "VPMON enabled in slots %d", slot); + } + + /* configure VBSTMON_TX_LOC */ + if (slot_num == 5) { + regmap_update_bits(priv->regmap, CS35L33_TX_VBSTMON, + CS35L33_X_STATE | CS35L33_X_LOC, slot); + snd_soc_dapm_add_routes(dapm, + &cs35l33_vp_vbst_mon_route[2], 2); + dev_dbg(codec->dev, + "VBSTMON enabled in slots %d", slot); + } + + /* Enable the relevant tx slot */ + reg = CS35L33_TX_EN4 - (slot/8); + bit_pos = slot - ((slot / 8) * (8)); + regmap_update_bits(priv->regmap, reg, + 1 << bit_pos, 1 << bit_pos); + + tx_mask &= ~(1 << slot); + slot = ffs(tx_mask) - 1; + slot_num++; + } + + return 0; +} + +static int cs35l33_codec_set_sysclk(struct snd_soc_codec *codec, + int clk_id, int source, unsigned int freq, int dir) +{ + struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec); + + switch (freq) { + case CS35L33_MCLK_5644: + case CS35L33_MCLK_6: + case CS35L33_MCLK_6144: + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIV2, 0); + cs35l33->mclk_int = freq; + break; + case CS35L33_MCLK_11289: + case CS35L33_MCLK_12: + case CS35L33_MCLK_12288: + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIV2, CS35L33_MCLKDIV2); + cs35l33->mclk_int = freq/2; + break; + default: + cs35l33->mclk_int = 0; + return -EINVAL; + } + + dev_dbg(codec->dev, "external mclk freq=%d, internal mclk freq=%d\n", + freq, cs35l33->mclk_int); + + return 0; +} + +static const struct snd_soc_dai_ops cs35l33_ops = { + .startup = cs35l33_pcm_startup, + .set_tristate = cs35l33_set_tristate, + .set_fmt = cs35l33_set_dai_fmt, + .hw_params = cs35l33_pcm_hw_params, + .set_tdm_slot = cs35l33_set_tdm_slot, +}; + +static struct snd_soc_dai_driver cs35l33_dai = { + .name = "cs35l33-dai", + .id = 0, + .playback = { + .stream_name = "CS35L33 Playback", + .channels_min = 1, + .channels_max = 1, + .rates = CS35L33_RATES, + .formats = CS35L33_FORMATS, + }, + .capture = { + .stream_name = "CS35L33 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = CS35L33_RATES, + .formats = CS35L33_FORMATS, + }, + .ops = &cs35l33_ops, + .symmetric_rates = 1, +}; + +static int cs35l33_set_hg_data(struct snd_soc_codec *codec, + struct cs35l33_pdata *pdata) +{ + struct cs35l33_hg *hg_config = &pdata->hg_config; + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct cs35l33_private *priv = snd_soc_codec_get_drvdata(codec); + + if (hg_config->enable_hg_algo) { + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_MEM_DEPTH_MASK, + hg_config->mem_depth << CS35L33_MEM_DEPTH_SHIFT); + regmap_write(priv->regmap, CS35L33_HG_REL_RATE, + hg_config->release_rate); + regmap_update_bits(priv->regmap, CS35L33_HG_HEAD, + CS35L33_HD_RM_MASK, + hg_config->hd_rm << CS35L33_HD_RM_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_LDO_THLD_MASK, + hg_config->ldo_thld << CS35L33_LDO_THLD_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_MEMLDO_CTL, + CS35L33_LDO_DISABLE_MASK, + hg_config->ldo_path_disable << + CS35L33_LDO_DISABLE_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_LDO_ENTRY_DELAY_MASK, + hg_config->ldo_entry_delay << + CS35L33_LDO_ENTRY_DELAY_SHIFT); + if (hg_config->vp_hg_auto) { + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_VP_HG_AUTO_MASK, + CS35L33_VP_HG_AUTO_MASK); + snd_soc_dapm_add_routes(dapm, cs35l33_vphg_auto_route, + ARRAY_SIZE(cs35l33_vphg_auto_route)); + } + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_VP_HG_MASK, + hg_config->vp_hg << CS35L33_VP_HG_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_VP_HG_RATE_MASK, + hg_config->vp_hg_rate << CS35L33_VP_HG_RATE_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_LDO_DEL, + CS35L33_VP_HG_VA_MASK, + hg_config->vp_hg_va << CS35L33_VP_HG_VA_SHIFT); + regmap_update_bits(priv->regmap, CS35L33_HG_EN, + CS35L33_CLASS_HG_EN_MASK, CS35L33_CLASS_HG_EN_MASK); + } + return 0; +} + +static int cs35l33_set_bst_ipk(struct snd_soc_codec *codec, unsigned int bst) +{ + struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec); + int ret = 0, steps = 0; + + /* Boost current in uA */ + if (bst > 3600000 || bst < 1850000) { + dev_err(codec->dev, "Invalid boost current %d\n", bst); + ret = -EINVAL; + goto err; + } + + if (bst % 15625) { + dev_err(codec->dev, "Current not a multiple of 15625uA (%d)\n", + bst); + ret = -EINVAL; + goto err; + } + + while (bst > 1850000) { + bst -= 15625; + steps++; + } + + regmap_write(cs35l33->regmap, CS35L33_BST_PEAK_CTL, + steps+0x70); + +err: + return ret; +} + +static int cs35l33_probe(struct snd_soc_codec *codec) +{ + struct cs35l33_private *cs35l33 = snd_soc_codec_get_drvdata(codec); + + cs35l33->codec = codec; + pm_runtime_get_sync(codec->dev); + + regmap_update_bits(cs35l33->regmap, CS35L33_PROTECT_CTL, + CS35L33_ALIVE_WD_DIS, 0x8); + regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL2, + CS35L33_ALIVE_WD_DIS2, + CS35L33_ALIVE_WD_DIS2); + + /* Set Platform Data */ + regmap_update_bits(cs35l33->regmap, CS35L33_BST_CTL1, + CS35L33_BST_CTL_MASK, cs35l33->pdata.boost_ctl); + regmap_update_bits(cs35l33->regmap, CS35L33_CLASSD_CTL, + CS35L33_AMP_DRV_SEL_MASK, + cs35l33->pdata.amp_drv_sel << CS35L33_AMP_DRV_SEL_SHIFT); + + if (cs35l33->pdata.boost_ipk) + cs35l33_set_bst_ipk(codec, cs35l33->pdata.boost_ipk); + + if (cs35l33->enable_soft_ramp) { + snd_soc_update_bits(codec, CS35L33_DAC_CTL, + CS35L33_DIGSFT, CS35L33_DIGSFT); + snd_soc_update_bits(codec, CS35L33_DAC_CTL, + CS35L33_DSR_RATE, cs35l33->pdata.ramp_rate); + } else { + snd_soc_update_bits(codec, CS35L33_DAC_CTL, + CS35L33_DIGSFT, 0); + } + + /* update IMON scaling rate if different from default of 0x8 */ + if (cs35l33->pdata.imon_adc_scale != 0x8) + snd_soc_update_bits(codec, CS35L33_ADC_CTL, + CS35L33_IMON_SCALE, cs35l33->pdata.imon_adc_scale); + + cs35l33_set_hg_data(codec, &(cs35l33->pdata)); + + /* + * unmask important interrupts that causes the chip to enter + * speaker safe mode and hence deserves user attention + */ + regmap_update_bits(cs35l33->regmap, CS35L33_INT_MASK_1, + CS35L33_M_OTE | CS35L33_M_OTW | CS35L33_M_AMP_SHORT | + CS35L33_M_CAL_ERR, 0); + + pm_runtime_put_sync(codec->dev); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs35l33 = { + .probe = cs35l33_probe, + + .set_bias_level = cs35l33_set_bias_level, + .set_sysclk = cs35l33_codec_set_sysclk, + + .dapm_widgets = cs35l33_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l33_dapm_widgets), + .dapm_routes = cs35l33_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l33_audio_map), + .controls = cs35l33_snd_controls, + .num_controls = ARRAY_SIZE(cs35l33_snd_controls), + + .idle_bias_off = true, +}; + +static const struct regmap_config cs35l33_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L33_MAX_REGISTER, + .reg_defaults = cs35l33_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l33_reg), + .volatile_reg = cs35l33_volatile_register, + .readable_reg = cs35l33_readable_register, + .writeable_reg = cs35l33_writeable_register, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static int __maybe_unused cs35l33_runtime_resume(struct device *dev) +{ + struct cs35l33_private *cs35l33 = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "%s\n", __func__); + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 0); + + ret = regulator_bulk_enable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(dev, "Failed to enable core supplies: %d\n", ret); + return ret; + } + + regcache_cache_only(cs35l33->regmap, false); + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 1); + + msleep(CS35L33_BOOT_DELAY); + + ret = regcache_sync(cs35l33->regmap); + if (ret != 0) { + dev_err(dev, "Failed to restore register cache\n"); + goto err; + } + + return 0; + +err: + regcache_cache_only(cs35l33->regmap, true); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return ret; +} + +static int __maybe_unused cs35l33_runtime_suspend(struct device *dev) +{ + struct cs35l33_private *cs35l33 = dev_get_drvdata(dev); + + dev_dbg(dev, "%s\n", __func__); + + /* redo the calibration in next power up */ + cs35l33->amp_cal = false; + + regcache_cache_only(cs35l33->regmap, true); + regcache_mark_dirty(cs35l33->regmap); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return 0; +} + +static const struct dev_pm_ops cs35l33_pm_ops = { + SET_RUNTIME_PM_OPS(cs35l33_runtime_suspend, + cs35l33_runtime_resume, + NULL) +}; + +static int cs35l33_get_hg_data(const struct device_node *np, + struct cs35l33_pdata *pdata) +{ + struct device_node *hg; + struct cs35l33_hg *hg_config = &pdata->hg_config; + u32 val32; + + hg = of_get_child_by_name(np, "cirrus,hg-algo"); + hg_config->enable_hg_algo = hg ? true : false; + + if (hg_config->enable_hg_algo) { + if (of_property_read_u32(hg, "cirrus,mem-depth", &val32) >= 0) + hg_config->mem_depth = val32; + if (of_property_read_u32(hg, "cirrus,release-rate", + &val32) >= 0) + hg_config->release_rate = val32; + if (of_property_read_u32(hg, "cirrus,ldo-thld", &val32) >= 0) + hg_config->ldo_thld = val32; + if (of_property_read_u32(hg, "cirrus,ldo-path-disable", + &val32) >= 0) + hg_config->ldo_path_disable = val32; + if (of_property_read_u32(hg, "cirrus,ldo-entry-delay", + &val32) >= 0) + hg_config->ldo_entry_delay = val32; + + hg_config->vp_hg_auto = of_property_read_bool(hg, + "cirrus,vp-hg-auto"); + + if (of_property_read_u32(hg, "cirrus,vp-hg", &val32) >= 0) + hg_config->vp_hg = val32; + if (of_property_read_u32(hg, "cirrus,vp-hg-rate", &val32) >= 0) + hg_config->vp_hg_rate = val32; + if (of_property_read_u32(hg, "cirrus,vp-hg-va", &val32) >= 0) + hg_config->vp_hg_va = val32; + } + + of_node_put(hg); + + return 0; +} + +static irqreturn_t cs35l33_irq_thread(int irq, void *data) +{ + struct cs35l33_private *cs35l33 = data; + struct snd_soc_codec *codec = cs35l33->codec; + unsigned int sticky_val1, sticky_val2, current_val, mask1, mask2; + + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_2, + &sticky_val2); + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1, + &sticky_val1); + regmap_read(cs35l33->regmap, CS35L33_INT_MASK_2, &mask2); + regmap_read(cs35l33->regmap, CS35L33_INT_MASK_1, &mask1); + + /* Check to see if the unmasked bits are active, + * if not then exit. + */ + if (!(sticky_val1 & ~mask1) && !(sticky_val2 & ~mask2)) + return IRQ_NONE; + + regmap_read(cs35l33->regmap, CS35L33_INT_STATUS_1, + ¤t_val); + + /* handle the interrupts */ + + if (sticky_val1 & CS35L33_AMP_SHORT) { + dev_crit(codec->dev, "Amp short error\n"); + if (!(current_val & CS35L33_AMP_SHORT)) { + dev_dbg(codec->dev, + "Amp short error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, + CS35L33_AMP_SHORT_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, + CS35L33_AMP_SHORT_RLS, + CS35L33_AMP_SHORT_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_AMP_SHORT_RLS, + 0); + } + } + + if (sticky_val1 & CS35L33_CAL_ERR) { + dev_err(codec->dev, "Cal error\n"); + + /* redo the calibration in next power up */ + cs35l33->amp_cal = false; + + if (!(current_val & CS35L33_CAL_ERR)) { + dev_dbg(codec->dev, "Cal error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + CS35L33_CAL_ERR_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_CAL_ERR_RLS, + 0); + } + } + + if (sticky_val1 & CS35L33_OTE) { + dev_crit(codec->dev, "Over temperature error\n"); + if (!(current_val & CS35L33_OTE)) { + dev_dbg(codec->dev, + "Over temperature error release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, + CS35L33_OTE_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTE_RLS, 0); + } + } + + if (sticky_val1 & CS35L33_OTW) { + dev_err(codec->dev, "Over temperature warning\n"); + if (!(current_val & CS35L33_OTW)) { + dev_dbg(codec->dev, + "Over temperature warning release\n"); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, + CS35L33_OTW_RLS); + regmap_update_bits(cs35l33->regmap, + CS35L33_AMP_CTL, CS35L33_OTW_RLS, 0); + } + } + if (CS35L33_ALIVE_ERR & sticky_val1) + dev_err(codec->dev, "ERROR: ADSPCLK Interrupt\n"); + + if (CS35L33_MCLK_ERR & sticky_val1) + dev_err(codec->dev, "ERROR: MCLK Interrupt\n"); + + if (CS35L33_VMON_OVFL & sticky_val2) + dev_err(codec->dev, + "ERROR: VMON Overflow Interrupt\n"); + + if (CS35L33_IMON_OVFL & sticky_val2) + dev_err(codec->dev, + "ERROR: IMON Overflow Interrupt\n"); + + if (CS35L33_VPMON_OVFL & sticky_val2) + dev_err(codec->dev, + "ERROR: VPMON Overflow Interrupt\n"); + + return IRQ_HANDLED; +} + +static const char * const cs35l33_core_supplies[] = { + "VA", + "VP", +}; + +static int cs35l33_of_get_pdata(struct device *dev, + struct cs35l33_private *cs35l33) +{ + struct device_node *np = dev->of_node; + struct cs35l33_pdata *pdata = &cs35l33->pdata; + u32 val32; + + if (!np) + return 0; + + if (of_property_read_u32(np, "cirrus,boost-ctl", &val32) >= 0) { + pdata->boost_ctl = val32; + pdata->amp_drv_sel = 1; + } + + if (of_property_read_u32(np, "cirrus,ramp-rate", &val32) >= 0) { + pdata->ramp_rate = val32; + cs35l33->enable_soft_ramp = true; + } + + if (of_property_read_u32(np, "cirrus,boost-ipk", &val32) >= 0) + pdata->boost_ipk = val32; + + if (of_property_read_u32(np, "cirrus,imon-adc-scale", &val32) >= 0) { + if ((val32 == 0x0) || (val32 == 0x7) || (val32 == 0x6)) + pdata->imon_adc_scale = val32; + else + /* use default value */ + pdata->imon_adc_scale = 0x8; + } else { + /* use default value */ + pdata->imon_adc_scale = 0x8; + } + + cs35l33_get_hg_data(np, pdata); + + return 0; +} + +static int cs35l33_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l33_private *cs35l33; + struct cs35l33_pdata *pdata = dev_get_platdata(&i2c_client->dev); + int ret, devid, i; + unsigned int reg; + + cs35l33 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs35l33_private), + GFP_KERNEL); + if (!cs35l33) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs35l33); + cs35l33->regmap = devm_regmap_init_i2c(i2c_client, &cs35l33_regmap); + if (IS_ERR(cs35l33->regmap)) { + ret = PTR_ERR(cs35l33->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + return ret; + } + + regcache_cache_only(cs35l33->regmap, true); + + for (i = 0; i < ARRAY_SIZE(cs35l33_core_supplies); i++) + cs35l33->core_supplies[i].supply + = cs35l33_core_supplies[i]; + cs35l33->num_core_supplies = ARRAY_SIZE(cs35l33_core_supplies); + + ret = devm_regulator_bulk_get(&i2c_client->dev, + cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request core supplies: %d\n", + ret); + return ret; + } + + if (pdata) { + cs35l33->pdata = *pdata; + } else { + cs35l33_of_get_pdata(&i2c_client->dev, cs35l33); + pdata = &cs35l33->pdata; + } + + ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL, + cs35l33_irq_thread, IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs35l33", cs35l33); + if (ret != 0) + dev_warn(&i2c_client->dev, "Failed to request IRQ: %d\n", ret); + + /* We could issue !RST or skip it based on AMP topology */ + cs35l33->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset-gpios", GPIOD_OUT_HIGH); + if (IS_ERR(cs35l33->reset_gpio)) { + dev_err(&i2c_client->dev, "%s ERROR: Can't get reset GPIO\n", + __func__); + return PTR_ERR(cs35l33->reset_gpio); + } + + ret = regulator_bulk_enable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable core supplies: %d\n", + ret); + return ret; + } + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 1); + + msleep(CS35L33_BOOT_DELAY); + regcache_cache_only(cs35l33->regmap, false); + + /* initialize codec */ + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_AB, ®); + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l33->regmap, CS35L33_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L33_CHIP_ID) { + dev_err(&i2c_client->dev, + "CS35L33 Device ID (%X). Expected ID %X\n", + devid, CS35L33_CHIP_ID); + goto err_enable; + } + + ret = regmap_read(cs35l33->regmap, CS35L33_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed\n"); + goto err_enable; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS35L33, Revision: %02X\n", reg & 0xFF); + + ret = regmap_register_patch(cs35l33->regmap, + cs35l33_patch, ARRAY_SIZE(cs35l33_patch)); + if (ret < 0) { + dev_err(&i2c_client->dev, + "Error in applying regmap patch: %d\n", ret); + goto err_enable; + } + + /* disable mclk and tdm */ + regmap_update_bits(cs35l33->regmap, CS35L33_CLK_CTL, + CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM, + CS35L33_MCLKDIS | CS35L33_SDOUT_3ST_TDM); + + pm_runtime_set_autosuspend_delay(&i2c_client->dev, 100); + pm_runtime_use_autosuspend(&i2c_client->dev); + pm_runtime_set_active(&i2c_client->dev); + pm_runtime_enable(&i2c_client->dev); + + ret = snd_soc_register_codec(&i2c_client->dev, + &soc_codec_dev_cs35l33, &cs35l33_dai, 1); + if (ret < 0) { + dev_err(&i2c_client->dev, "%s: Register codec failed\n", + __func__); + goto err_enable; + } + + return 0; + +err_enable: + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return ret; +} + +static int cs35l33_i2c_remove(struct i2c_client *client) +{ + struct cs35l33_private *cs35l33 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + + if (cs35l33->reset_gpio) + gpiod_set_value_cansleep(cs35l33->reset_gpio, 0); + + pm_runtime_disable(&client->dev); + regulator_bulk_disable(cs35l33->num_core_supplies, + cs35l33->core_supplies); + + return 0; +} + +static const struct of_device_id cs35l33_of_match[] = { + { .compatible = "cirrus,cs35l33", }, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l33_of_match); + +static const struct i2c_device_id cs35l33_id[] = { + {"cs35l33", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l33_id); + +static struct i2c_driver cs35l33_i2c_driver = { + .driver = { + .name = "cs35l33", + .pm = &cs35l33_pm_ops, + .of_match_table = cs35l33_of_match, + + }, + .id_table = cs35l33_id, + .probe = cs35l33_i2c_probe, + .remove = cs35l33_i2c_remove, + +}; +module_i2c_driver(cs35l33_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L33 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, <paul.handrigan@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l33.h b/sound/soc/codecs/cs35l33.h new file mode 100644 index 000000000000..c045737d1a5f --- /dev/null +++ b/sound/soc/codecs/cs35l33.h @@ -0,0 +1,221 @@ +/* + * cs35l33.h -- CS35L33 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Paul Handrigan <paul.handrigan@cirrus.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. + * + */ + +#ifndef __CS35L33_H__ +#define __CS35L33_H__ + +#define CS35L33_CHIP_ID 0x00035A33 +#define CS35L33_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L33_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L33_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L33_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L33_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L33_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L33_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L33_CLK_CTL 0x08 /* Clock Ctl */ +#define CS35L33_BST_PEAK_CTL 0x09 /* Max Current for Boost */ +#define CS35L33_PROTECT_CTL 0x0A /* Amp Protection Parameters */ +#define CS35L33_BST_CTL1 0x0B /* Boost Converter CTL1 */ +#define CS35L33_BST_CTL2 0x0C /* Boost Converter CTL2 */ +#define CS35L33_ADSP_CTL 0x0D /* Serial Port Control */ +#define CS35L33_ADC_CTL 0x0E /* ADC Control */ +#define CS35L33_DAC_CTL 0x0F /* DAC Control */ +#define CS35L33_DIG_VOL_CTL 0x10 /* Digital Volume CTL */ +#define CS35L33_CLASSD_CTL 0x11 /* Class D Amp CTL */ +#define CS35L33_AMP_CTL 0x12 /* Amp Gain/Protecton Release CTL */ +#define CS35L33_INT_MASK_1 0x13 /* Interrupt Mask 1 */ +#define CS35L33_INT_MASK_2 0x14 /* Interrupt Mask 2 */ +#define CS35L33_INT_STATUS_1 0x15 /* Interrupt Status 1 [RO] */ +#define CS35L33_INT_STATUS_2 0x16 /* Interrupt Status 2 [RO] */ +#define CS35L33_DIAG_LOCK 0x17 /* Diagnostic Mode Register Lock */ +#define CS35L33_DIAG_CTRL_1 0x18 /* Diagnostic Mode Register Control */ +#define CS35L33_DIAG_CTRL_2 0x19 /* Diagnostic Mode Register Control 2 */ +#define CS35L33_HG_MEMLDO_CTL 0x23 /* H/G Memory/LDO CTL */ +#define CS35L33_HG_REL_RATE 0x24 /* H/G Release Rate */ +#define CS35L33_LDO_DEL 0x25 /* LDO Entry Delay/VPhg Control 1 */ +#define CS35L33_HG_HEAD 0x29 /* H/G Headroom */ +#define CS35L33_HG_EN 0x2A /* H/G Enable/VPhg CNT2 */ +#define CS35L33_TX_VMON 0x2D /* TDM TX Control 1 (VMON) */ +#define CS35L33_TX_IMON 0x2E /* TDM TX Control 2 (IMON) */ +#define CS35L33_TX_VPMON 0x2F /* TDM TX Control 3 (VPMON) */ +#define CS35L33_TX_VBSTMON 0x30 /* TDM TX Control 4 (VBSTMON) */ +#define CS35L33_TX_FLAG 0x31 /* TDM TX Control 5 (FLAG) */ +#define CS35L33_TX_EN1 0x32 /* TDM TX Enable 1 */ +#define CS35L33_TX_EN2 0x33 /* TDM TX Enable 2 */ +#define CS35L33_TX_EN3 0x34 /* TDM TX Enable 3 */ +#define CS35L33_TX_EN4 0x35 /* TDM TX Enable 4 */ +#define CS35L33_RX_AUD 0x36 /* TDM RX Control 1 */ +#define CS35L33_RX_SPLY 0x37 /* TDM RX Control 2 */ +#define CS35L33_RX_ALIVE 0x38 /* TDM RX Control 3 */ +#define CS35L33_BST_CTL4 0x39 /* Boost Converter Control 4 */ +#define CS35L33_HG_STATUS 0x3F /* H/G Status */ +#define CS35L33_MAX_REGISTER 0x59 + +#define CS35L33_MCLK_5644 5644800 +#define CS35L33_MCLK_6144 6144000 +#define CS35L33_MCLK_6 6000000 +#define CS35L33_MCLK_11289 11289600 +#define CS35L33_MCLK_12 12000000 +#define CS35L33_MCLK_12288 12288000 + +/* CS35L33_PWRCTL1 */ +#define CS35L33_PDN_AMP (1 << 7) +#define CS35L33_PDN_BST (1 << 2) +#define CS35L33_PDN_ALL 1 + +/* CS35L33_PWRCTL2 */ +#define CS35L33_PDN_VMON_SHIFT 7 +#define CS35L33_PDN_VMON (1 << CS35L33_PDN_VMON_SHIFT) +#define CS35L33_PDN_IMON_SHIFT 6 +#define CS35L33_PDN_IMON (1 << CS35L33_PDN_IMON_SHIFT) +#define CS35L33_PDN_VPMON_SHIFT 5 +#define CS35L33_PDN_VPMON (1 << CS35L33_PDN_VPMON_SHIFT) +#define CS35L33_PDN_VBSTMON_SHIFT 4 +#define CS35L33_PDN_VBSTMON (1 << CS35L33_PDN_VBSTMON_SHIFT) +#define CS35L33_SDOUT_3ST_I2S_SHIFT 3 +#define CS35L33_SDOUT_3ST_I2S (1 << CS35L33_SDOUT_3ST_I2S_SHIFT) +#define CS35L33_PDN_SDIN_SHIFT 2 +#define CS35L33_PDN_SDIN (1 << CS35L33_PDN_SDIN_SHIFT) +#define CS35L33_PDN_TDM_SHIFT 1 +#define CS35L33_PDN_TDM (1 << CS35L33_PDN_TDM_SHIFT) + +/* CS35L33_CLK_CTL */ +#define CS35L33_MCLKDIS (1 << 7) +#define CS35L33_MCLKDIV2 (1 << 6) +#define CS35L33_SDOUT_3ST_TDM (1 << 5) +#define CS35L33_INT_FS_RATE (1 << 4) +#define CS35L33_ADSP_FS 0xF + +/* CS35L33_PROTECT_CTL */ +#define CS35L33_ALIVE_WD_DIS (3 << 2) + +/* CS35L33_BST_CTL1 */ +#define CS35L33_BST_CTL_SRC (1 << 6) +#define CS35L33_BST_CTL_SHIFT (1 << 5) +#define CS35L33_BST_CTL_MASK 0x3F + +/* CS35L33_BST_CTL2 */ +#define CS35L33_TDM_WD_SEL (1 << 4) +#define CS35L33_ALIVE_WD_DIS2 (1 << 3) +#define CS35L33_VBST_SR_STEP 0x3 + +/* CS35L33_ADSP_CTL */ +#define CS35L33_ADSP_DRIVE (1 << 7) +#define CS35L33_MS_MASK (1 << 6) +#define CS35L33_SDIN_LOC (3 << 4) +#define CS35L33_ALIVE_RATE 0x3 + +/* CS35L33_ADC_CTL */ +#define CS35L33_INV_VMON (1 << 7) +#define CS35L33_INV_IMON (1 << 6) +#define CS35L33_ADC_NOTCH_DIS (1 << 5) +#define CS35L33_IMON_SCALE 0xF + +/* CS35L33_DAC_CTL */ +#define CS35L33_INV_DAC (1 << 7) +#define CS35L33_DAC_NOTCH_DIS (1 << 5) +#define CS35L33_DIGSFT (1 << 4) +#define CS35L33_DSR_RATE 0xF + +/* CS35L33_CLASSD_CTL */ +#define CS35L33_AMP_SD (1 << 6) +#define CS35L33_AMP_DRV_SEL_SRC (1 << 5) +#define CS35L33_AMP_DRV_SEL_MASK 0x10 +#define CS35L33_AMP_DRV_SEL_SHIFT 4 +#define CS35L33_AMP_CAL (1 << 3) +#define CS35L33_GAIN_CHG_ZC_MASK 0x04 +#define CS35L33_GAIN_CHG_ZC_SHIFT 2 +#define CS35L33_CLASS_D_CTL_MASK 0x3F + +/* CS35L33_AMP_CTL */ +#define CS35L33_AMP_GAIN 0xF0 +#define CS35L33_CAL_ERR_RLS (1 << 3) +#define CS35L33_AMP_SHORT_RLS (1 << 2) +#define CS35L33_OTW_RLS (1 << 1) +#define CS35L33_OTE_RLS 1 + +/* CS35L33_INT_MASK_1 */ +#define CS35L33_M_CAL_ERR_SHIFT 6 +#define CS35L33_M_CAL_ERR (1 << CS35L33_M_CAL_ERR_SHIFT) +#define CS35L33_M_ALIVE_ERR_SHIFT 5 +#define CS35L33_M_ALIVE_ERR (1 << CS35L33_M_ALIVE_ERR_SHIFT) +#define CS35L33_M_AMP_SHORT_SHIFT 2 +#define CS35L33_M_AMP_SHORT (1 << CS35L33_M_AMP_SHORT_SHIFT) +#define CS35L33_M_OTW_SHIFT 1 +#define CS35L33_M_OTW (1 << CS35L33_M_OTW_SHIFT) +#define CS35L33_M_OTE_SHIFT 0 +#define CS35L33_M_OTE (1 << CS35L33_M_OTE_SHIFT) + +/* CS35L33_INT_STATUS_1 */ +#define CS35L33_CAL_ERR (1 << 6) +#define CS35L33_ALIVE_ERR (1 << 5) +#define CS35L33_ADSPCLK_ERR (1 << 4) +#define CS35L33_MCLK_ERR (1 << 3) +#define CS35L33_AMP_SHORT (1 << 2) +#define CS35L33_OTW (1 << 1) +#define CS35L33_OTE (1 << 0) + +/* CS35L33_INT_STATUS_2 */ +#define CS35L33_VMON_OVFL (1 << 7) +#define CS35L33_IMON_OVFL (1 << 6) +#define CS35L33_VPMON_OVFL (1 << 5) +#define CS35L33_VBSTMON_OVFL (1 << 4) +#define CS35L33_PDN_DONE 1 + +/* CS35L33_BST_CTL4 */ +#define CS35L33_BST_RGS 0x70 +#define CS35L33_BST_COEFF3 0xF + +/* CS35L33_HG_MEMLDO_CTL */ +#define CS35L33_MEM_DEPTH_SHIFT 5 +#define CS35L33_MEM_DEPTH_MASK (0x3 << CS35L33_MEM_DEPTH_SHIFT) +#define CS35L33_LDO_THLD_SHIFT 1 +#define CS35L33_LDO_THLD_MASK (0xF << CS35L33_LDO_THLD_SHIFT) +#define CS35L33_LDO_DISABLE_SHIFT 0 +#define CS35L33_LDO_DISABLE_MASK (0x1 << CS35L33_LDO_DISABLE_SHIFT) + +/* CS35L33_LDO_DEL */ +#define CS35L33_VP_HG_VA_SHIFT 5 +#define CS35L33_VP_HG_VA_MASK (0x7 << CS35L33_VP_HG_VA_SHIFT) +#define CS35L33_LDO_ENTRY_DELAY_SHIFT 2 +#define CS35L33_LDO_ENTRY_DELAY_MASK (0x7 << CS35L33_LDO_ENTRY_DELAY_SHIFT) +#define CS35L33_VP_HG_RATE_SHIFT 0 +#define CS35L33_VP_HG_RATE_MASK (0x3 << CS35L33_VP_HG_RATE_SHIFT) + +/* CS35L33_HG_HEAD */ +#define CS35L33_HD_RM_SHIFT 0 +#define CS35L33_HD_RM_MASK (0x7F << CS35L33_HD_RM_SHIFT) + +/* CS35L33_HG_EN */ +#define CS35L33_CLASS_HG_ENA_SHIFT 7 +#define CS35L33_CLASS_HG_EN_MASK (0x1 << CS35L33_CLASS_HG_ENA_SHIFT) +#define CS35L33_VP_HG_AUTO_SHIFT 6 +#define CS35L33_VP_HG_AUTO_MASK (0x1 << 6) +#define CS35L33_VP_HG_SHIFT 0 +#define CS35L33_VP_HG_MASK (0x1F << CS35L33_VP_HG_SHIFT) + +#define CS35L33_RATES (SNDRV_PCM_RATE_8000_48000) +#define CS35L33_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +/* CS35L33_{RX,TX}_X */ +#define CS35L33_X_STATE_SHIFT 7 +#define CS35L33_X_STATE (1 << CS35L33_X_STATE_SHIFT) +#define CS35L33_X_LOC_SHIFT 0 +#define CS35L33_X_LOC (0x1F << CS35L33_X_LOC_SHIFT) + +/* CS35L33_RX_AUD */ +#define CS35L33_AUDIN_RX_DEPTH_SHIFT 5 +#define CS35L33_AUDIN_RX_DEPTH (0x7 << CS35L33_AUDIN_RX_DEPTH_SHIFT) + +#endif diff --git a/sound/soc/codecs/cs47l24.c b/sound/soc/codecs/cs47l24.c index 5ec5a682d186..954a4f5d3338 100644 --- a/sound/soc/codecs/cs47l24.c +++ b/sound/soc/codecs/cs47l24.c @@ -359,6 +359,11 @@ SND_SOC_DAPM_INPUT("IN2R"), SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"), +SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"), + +SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0, + &arizona_voice_trigger_switch[2]), + SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, 0, NULL, 0, arizona_in_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | @@ -899,10 +904,16 @@ static const struct snd_soc_dapm_route cs47l24_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC2 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, { "DRC2 Signal Activity", NULL, "DRC2L" }, { "DRC2 Signal Activity", NULL, "DRC2R" }, + + { "DSP Voice Trigger", NULL, "SYSCLK" }, + { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" }, + { "DSP3 Voice Trigger", "Switch", "DSP3" }, }; static int cs47l24_set_fll(struct snd_soc_codec *codec, int fll_id, int source, @@ -1067,6 +1078,7 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data) { struct cs47l24_priv *priv = data; struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; int serviced = 0; int i, ret; @@ -1074,6 +1086,12 @@ static irqreturn_t cs47l24_adsp2_irq(int irq, void *data) ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); if (ret != -ENODEV) serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } } if (!serviced) { @@ -1096,6 +1114,7 @@ static int cs47l24_codec_probe(struct snd_soc_codec *codec) arizona_init_spk(codec); arizona_init_gpio(codec); arizona_init_mono(codec); + arizona_init_notifiers(codec); ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, "ADSP2 Compressed IRQ", cs47l24_adsp2_irq, diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c new file mode 100644 index 000000000000..2c0d9c430a8c --- /dev/null +++ b/sound/soc/codecs/cs53l30.c @@ -0,0 +1,1143 @@ +/* + * cs53l30.c -- CS53l30 ALSA Soc Audio driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Authors: Paul Handrigan <Paul.Handrigan@cirrus.com>, + * Tim Howe <Tim.Howe@cirrus.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. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include "cs53l30.h" + +#define CS53L30_NUM_SUPPLIES 2 +static const char *const cs53l30_supply_names[CS53L30_NUM_SUPPLIES] = { + "VA", + "VP", +}; + +struct cs53l30_private { + struct regulator_bulk_data supplies[CS53L30_NUM_SUPPLIES]; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + struct gpio_desc *mute_gpio; + struct clk *mclk; + bool use_sdout2; + u32 mclk_rate; +}; + +static const struct reg_default cs53l30_reg_defaults[] = { + { CS53L30_PWRCTL, CS53L30_PWRCTL_DEFAULT }, + { CS53L30_MCLKCTL, CS53L30_MCLKCTL_DEFAULT }, + { CS53L30_INT_SR_CTL, CS53L30_INT_SR_CTL_DEFAULT }, + { CS53L30_MICBIAS_CTL, CS53L30_MICBIAS_CTL_DEFAULT }, + { CS53L30_ASPCFG_CTL, CS53L30_ASPCFG_CTL_DEFAULT }, + { CS53L30_ASP_CTL1, CS53L30_ASP_CTL1_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL1, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL2, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL3, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_CTL4, CS53L30_ASP_TDMTX_CTLx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN1, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN2, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN3, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN4, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN5, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_TDMTX_EN6, CS53L30_ASP_TDMTX_ENx_DEFAULT }, + { CS53L30_ASP_CTL2, CS53L30_ASP_CTL2_DEFAULT }, + { CS53L30_SFT_RAMP, CS53L30_SFT_RMP_DEFAULT }, + { CS53L30_LRCK_CTL1, CS53L30_LRCK_CTLx_DEFAULT }, + { CS53L30_LRCK_CTL2, CS53L30_LRCK_CTLx_DEFAULT }, + { CS53L30_MUTEP_CTL1, CS53L30_MUTEP_CTL1_DEFAULT }, + { CS53L30_MUTEP_CTL2, CS53L30_MUTEP_CTL2_DEFAULT }, + { CS53L30_INBIAS_CTL1, CS53L30_INBIAS_CTL1_DEFAULT }, + { CS53L30_INBIAS_CTL2, CS53L30_INBIAS_CTL2_DEFAULT }, + { CS53L30_DMIC1_STR_CTL, CS53L30_DMIC1_STR_CTL_DEFAULT }, + { CS53L30_DMIC2_STR_CTL, CS53L30_DMIC2_STR_CTL_DEFAULT }, + { CS53L30_ADCDMIC1_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT }, + { CS53L30_ADCDMIC1_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT }, + { CS53L30_ADC1_CTL3, CS53L30_ADCx_CTL3_DEFAULT }, + { CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT }, + { CS53L30_ADC1A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC1A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADC1B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADCDMIC2_CTL1, CS53L30_ADCDMICx_CTL1_DEFAULT }, + { CS53L30_ADCDMIC2_CTL2, CS53L30_ADCDMIC1_CTL2_DEFAULT }, + { CS53L30_ADC2_CTL3, CS53L30_ADCx_CTL3_DEFAULT }, + { CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_CTL_DEFAULT }, + { CS53L30_ADC2A_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_AFE_CTL_DEFAULT }, + { CS53L30_ADC2A_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_ADC2B_DIG_VOL, CS53L30_ADCxy_DIG_VOL_DEFAULT }, + { CS53L30_INT_MASK, CS53L30_DEVICE_INT_MASK }, +}; + +static bool cs53l30_volatile_register(struct device *dev, unsigned int reg) +{ + if (reg == CS53L30_IS) + return true; + else + return false; +} + +static bool cs53l30_writeable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS53L30_DEVID_AB: + case CS53L30_DEVID_CD: + case CS53L30_DEVID_E: + case CS53L30_REVID: + case CS53L30_IS: + return false; + default: + return true; + } +} + +static bool cs53l30_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS53L30_DEVID_AB: + case CS53L30_DEVID_CD: + case CS53L30_DEVID_E: + case CS53L30_REVID: + case CS53L30_PWRCTL: + case CS53L30_MCLKCTL: + case CS53L30_INT_SR_CTL: + case CS53L30_MICBIAS_CTL: + case CS53L30_ASPCFG_CTL: + case CS53L30_ASP_CTL1: + case CS53L30_ASP_TDMTX_CTL1: + case CS53L30_ASP_TDMTX_CTL2: + case CS53L30_ASP_TDMTX_CTL3: + case CS53L30_ASP_TDMTX_CTL4: + case CS53L30_ASP_TDMTX_EN1: + case CS53L30_ASP_TDMTX_EN2: + case CS53L30_ASP_TDMTX_EN3: + case CS53L30_ASP_TDMTX_EN4: + case CS53L30_ASP_TDMTX_EN5: + case CS53L30_ASP_TDMTX_EN6: + case CS53L30_ASP_CTL2: + case CS53L30_SFT_RAMP: + case CS53L30_LRCK_CTL1: + case CS53L30_LRCK_CTL2: + case CS53L30_MUTEP_CTL1: + case CS53L30_MUTEP_CTL2: + case CS53L30_INBIAS_CTL1: + case CS53L30_INBIAS_CTL2: + case CS53L30_DMIC1_STR_CTL: + case CS53L30_DMIC2_STR_CTL: + case CS53L30_ADCDMIC1_CTL1: + case CS53L30_ADCDMIC1_CTL2: + case CS53L30_ADC1_CTL3: + case CS53L30_ADC1_NG_CTL: + case CS53L30_ADC1A_AFE_CTL: + case CS53L30_ADC1B_AFE_CTL: + case CS53L30_ADC1A_DIG_VOL: + case CS53L30_ADC1B_DIG_VOL: + case CS53L30_ADCDMIC2_CTL1: + case CS53L30_ADCDMIC2_CTL2: + case CS53L30_ADC2_CTL3: + case CS53L30_ADC2_NG_CTL: + case CS53L30_ADC2A_AFE_CTL: + case CS53L30_ADC2B_AFE_CTL: + case CS53L30_ADC2A_DIG_VOL: + case CS53L30_ADC2B_DIG_VOL: + case CS53L30_INT_MASK: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(adc_boost_tlv, 0, 2000, 0); +static DECLARE_TLV_DB_SCALE(adc_ng_boost_tlv, 0, 3000, 0); +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); +static DECLARE_TLV_DB_SCALE(dig_tlv, -9600, 100, 1); +static DECLARE_TLV_DB_SCALE(pga_preamp_tlv, 0, 10000, 0); + +static const char * const input1_sel_text[] = { + "DMIC1 On AB In", + "DMIC1 On A In", + "DMIC1 On B In", + "ADC1 On AB In", + "ADC1 On A In", + "ADC1 On B In", + "DMIC1 Off ADC1 Off", +}; + +static unsigned int const input1_sel_values[] = { + CS53L30_CH_TYPE, + CS53L30_ADCxB_PDN | CS53L30_CH_TYPE, + CS53L30_ADCxA_PDN | CS53L30_CH_TYPE, + CS53L30_DMICx_PDN, + CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, +}; + +static const char * const input2_sel_text[] = { + "DMIC2 On AB In", + "DMIC2 On A In", + "DMIC2 On B In", + "ADC2 On AB In", + "ADC2 On A In", + "ADC2 On B In", + "DMIC2 Off ADC2 Off", +}; + +static unsigned int const input2_sel_values[] = { + 0x0, + CS53L30_ADCxB_PDN, + CS53L30_ADCxA_PDN, + CS53L30_DMICx_PDN, + CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_DMICx_PDN, + CS53L30_ADCxA_PDN | CS53L30_ADCxB_PDN | CS53L30_DMICx_PDN, +}; + +static const char * const input1_route_sel_text[] = { + "ADC1_SEL", "DMIC1_SEL", +}; + +static const struct soc_enum input1_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, CS53L30_CH_TYPE_SHIFT, + ARRAY_SIZE(input1_route_sel_text), + input1_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input1_sel_enum, CS53L30_ADCDMIC1_CTL1, 0, + CS53L30_ADCDMICx_PDN_MASK, input1_sel_text, + input1_sel_values); + +static const struct snd_kcontrol_new input1_route_sel_mux = + SOC_DAPM_ENUM("Input 1 Route", input1_route_sel_enum); + +static const char * const input2_route_sel_text[] = { + "ADC2_SEL", "DMIC2_SEL", +}; + +/* Note: CS53L30_ADCDMIC1_CTL1 CH_TYPE controls inputs 1 and 2 */ +static const struct soc_enum input2_route_sel_enum = + SOC_ENUM_SINGLE(CS53L30_ADCDMIC1_CTL1, 0, + ARRAY_SIZE(input2_route_sel_text), + input2_route_sel_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(input2_sel_enum, CS53L30_ADCDMIC2_CTL1, 0, + CS53L30_ADCDMICx_PDN_MASK, input2_sel_text, + input2_sel_values); + +static const struct snd_kcontrol_new input2_route_sel_mux = + SOC_DAPM_ENUM("Input 2 Route", input2_route_sel_enum); + +/* + * TB = 6144*(MCLK(int) scaling factor)/MCLK(internal) + * TB - Time base + * NOTE: If MCLK_INT_SCALE = 0, then TB=1 + */ +static const char * const cs53l30_ng_delay_text[] = { + "TB*50ms", "TB*100ms", "TB*150ms", "TB*200ms", +}; + +static const struct soc_enum adc1_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT, + ARRAY_SIZE(cs53l30_ng_delay_text), + cs53l30_ng_delay_text); + +static const struct soc_enum adc2_ng_delay_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_DELAY_SHIFT, + ARRAY_SIZE(cs53l30_ng_delay_text), + cs53l30_ng_delay_text); + +/* The noise gate threshold selected will depend on NG Boost */ +static const char * const cs53l30_ng_thres_text[] = { + "-64dB/-34dB", "-66dB/-36dB", "-70dB/-40dB", "-73dB/-43dB", + "-76dB/-46dB", "-82dB/-52dB", "-58dB", "-64dB", +}; + +static const struct soc_enum adc1_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT, + ARRAY_SIZE(cs53l30_ng_thres_text), + cs53l30_ng_thres_text); + +static const struct soc_enum adc2_ng_thres_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_NG_CTL, CS53L30_ADCx_NG_THRESH_SHIFT, + ARRAY_SIZE(cs53l30_ng_thres_text), + cs53l30_ng_thres_text); + +/* Corner frequencies are with an Fs of 48kHz. */ +static const char * const hpf_corner_freq_text[] = { + "1.86Hz", "120Hz", "235Hz", "466Hz", +}; + +static const struct soc_enum adc1_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC1_CTL3, CS53L30_ADCx_HPF_CF_SHIFT, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct soc_enum adc2_hpf_enum = + SOC_ENUM_SINGLE(CS53L30_ADC2_CTL3, CS53L30_ADCx_HPF_CF_SHIFT, + ARRAY_SIZE(hpf_corner_freq_text), hpf_corner_freq_text); + +static const struct snd_kcontrol_new cs53l30_snd_controls[] = { + SOC_SINGLE("Digital Soft-Ramp Switch", CS53L30_SFT_RAMP, + CS53L30_DIGSFT_SHIFT, 1, 0), + SOC_SINGLE("ADC1 Noise Gate Ganging Switch", CS53L30_ADC1_CTL3, + CS53L30_ADCx_NG_ALL_SHIFT, 1, 0), + SOC_SINGLE("ADC2 Noise Gate Ganging Switch", CS53L30_ADC2_CTL3, + CS53L30_ADCx_NG_ALL_SHIFT, 1, 0), + SOC_SINGLE("ADC1A Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL, + CS53L30_ADCxA_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC1B Noise Gate Enable Switch", CS53L30_ADC1_NG_CTL, + CS53L30_ADCxB_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC2A Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL, + CS53L30_ADCxA_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC2B Noise Gate Enable Switch", CS53L30_ADC2_NG_CTL, + CS53L30_ADCxB_NG_SHIFT, 1, 0), + SOC_SINGLE("ADC1 Notch Filter Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1), + SOC_SINGLE("ADC2 Notch Filter Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCx_NOTCH_DIS_SHIFT, 1, 1), + SOC_SINGLE("ADC1A Invert Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxA_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC1B Invert Switch", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxB_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC2A Invert Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxA_INV_SHIFT, 1, 0), + SOC_SINGLE("ADC2B Invert Switch", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxB_INV_SHIFT, 1, 0), + + SOC_SINGLE_TLV("ADC1A Digital Boost Volume", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1B Digital Boost Volume", CS53L30_ADCDMIC1_CTL2, + CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2A Digital Boost Volume", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxA_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC2B Digital Boost Volume", CS53L30_ADCDMIC2_CTL2, + CS53L30_ADCxB_DIG_BOOST_SHIFT, 1, 0, adc_boost_tlv), + SOC_SINGLE_TLV("ADC1 NG Boost Volume", CS53L30_ADC1_NG_CTL, + CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv), + SOC_SINGLE_TLV("ADC2 NG Boost Volume", CS53L30_ADC2_NG_CTL, + CS53L30_ADCx_NG_BOOST_SHIFT, 1, 0, adc_ng_boost_tlv), + + SOC_DOUBLE_R_TLV("ADC1 Preamplifier Volume", CS53L30_ADC1A_AFE_CTL, + CS53L30_ADC1B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT, + 2, 0, pga_preamp_tlv), + SOC_DOUBLE_R_TLV("ADC2 Preamplifier Volume", CS53L30_ADC2A_AFE_CTL, + CS53L30_ADC2B_AFE_CTL, CS53L30_ADCxy_PREAMP_SHIFT, + 2, 0, pga_preamp_tlv), + + SOC_ENUM("Input 1 Channel Select", input1_sel_enum), + SOC_ENUM("Input 2 Channel Select", input2_sel_enum), + + SOC_ENUM("ADC1 HPF Select", adc1_hpf_enum), + SOC_ENUM("ADC2 HPF Select", adc2_hpf_enum), + SOC_ENUM("ADC1 NG Threshold", adc1_ng_thres_enum), + SOC_ENUM("ADC2 NG Threshold", adc2_ng_thres_enum), + SOC_ENUM("ADC1 NG Delay", adc1_ng_delay_enum), + SOC_ENUM("ADC2 NG Delay", adc2_ng_delay_enum), + + SOC_SINGLE_SX_TLV("ADC1A PGA Volume", + CS53L30_ADC1A_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC1B PGA Volume", + CS53L30_ADC1B_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2A PGA Volume", + CS53L30_ADC2A_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + SOC_SINGLE_SX_TLV("ADC2B PGA Volume", + CS53L30_ADC2B_AFE_CTL, 0, 0x34, 0x18, pga_tlv), + + SOC_SINGLE_SX_TLV("ADC1A Digital Volume", + CS53L30_ADC1A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC1B Digital Volume", + CS53L30_ADC1B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2A Digital Volume", + CS53L30_ADC2A_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), + SOC_SINGLE_SX_TLV("ADC2B Digital Volume", + CS53L30_ADC2B_DIG_VOL, 0, 0xA0, 0x0C, dig_tlv), +}; + +static const struct snd_soc_dapm_widget cs53l30_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN1_DMIC1"), + SND_SOC_DAPM_INPUT("IN2"), + SND_SOC_DAPM_INPUT("IN3_DMIC2"), + SND_SOC_DAPM_INPUT("IN4"), + SND_SOC_DAPM_SUPPLY("MIC1 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC1_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC2_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC3 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC3_BIAS_PDN_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC4 Bias", CS53L30_MICBIAS_CTL, + CS53L30_MIC4_BIAS_PDN_SHIFT, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("ASP_SDOUT1", NULL, 0, CS53L30_ASP_CTL1, + CS53L30_ASP_SDOUTx_PDN_SHIFT, 1), + SND_SOC_DAPM_AIF_OUT("ASP_SDOUT2", NULL, 0, CS53L30_ASP_CTL2, + CS53L30_ASP_SDOUTx_PDN_SHIFT, 1), + + SND_SOC_DAPM_MUX("Input Mux 1", SND_SOC_NOPM, 0, 0, + &input1_route_sel_mux), + SND_SOC_DAPM_MUX("Input Mux 2", SND_SOC_NOPM, 0, 0, + &input2_route_sel_mux), + + SND_SOC_DAPM_ADC("ADC1A", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_ADCxA_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC1B", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_ADCxB_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2A", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_ADCxA_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("ADC2B", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_ADCxB_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("DMIC1", NULL, CS53L30_ADCDMIC1_CTL1, + CS53L30_DMICx_PDN_SHIFT, 1), + SND_SOC_DAPM_ADC("DMIC2", NULL, CS53L30_ADCDMIC2_CTL1, + CS53L30_DMICx_PDN_SHIFT, 1), +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes[] = { + /* ADC Input Paths */ + {"ADC1A", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "ADC1_SEL", "ADC1A"}, + {"ADC1B", NULL, "IN2"}, + + {"ADC2A", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "ADC2_SEL", "ADC2A"}, + {"ADC2B", NULL, "IN4"}, + + /* MIC Bias Paths */ + {"ADC1A", NULL, "MIC1 Bias"}, + {"ADC1B", NULL, "MIC2 Bias"}, + {"ADC2A", NULL, "MIC3 Bias"}, + {"ADC2B", NULL, "MIC4 Bias"}, + + /* DMIC Paths */ + {"DMIC1", NULL, "IN1_DMIC1"}, + {"Input Mux 1", "DMIC1_SEL", "DMIC1"}, + + {"DMIC2", NULL, "IN3_DMIC2"}, + {"Input Mux 2", "DMIC2_SEL", "DMIC2"}, +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout1[] = { + /* Output Paths when using SDOUT1 only */ + {"ASP_SDOUT1", NULL, "ADC1A" }, + {"ASP_SDOUT1", NULL, "Input Mux 1"}, + {"ASP_SDOUT1", NULL, "ADC1B"}, + + {"ASP_SDOUT1", NULL, "ADC2A"}, + {"ASP_SDOUT1", NULL, "Input Mux 2"}, + {"ASP_SDOUT1", NULL, "ADC2B"}, + + {"Capture", NULL, "ASP_SDOUT1"}, +}; + +static const struct snd_soc_dapm_route cs53l30_dapm_routes_sdout2[] = { + /* Output Paths when using both SDOUT1 and SDOUT2 */ + {"ASP_SDOUT1", NULL, "ADC1A" }, + {"ASP_SDOUT1", NULL, "Input Mux 1"}, + {"ASP_SDOUT1", NULL, "ADC1B"}, + + {"ASP_SDOUT2", NULL, "ADC2A"}, + {"ASP_SDOUT2", NULL, "Input Mux 2"}, + {"ASP_SDOUT2", NULL, "ADC2B"}, + + {"Capture", NULL, "ASP_SDOUT1"}, + {"Capture", NULL, "ASP_SDOUT2"}, +}; + +struct cs53l30_mclk_div { + u32 mclk_rate; + u32 srate; + u8 asp_rate; + u8 internal_fs_ratio; + u8 mclk_int_scale; +}; + +static struct cs53l30_mclk_div cs53l30_mclk_coeffs[] = { + /* NOTE: Enable MCLK_INT_SCALE to save power. */ + + /* MCLK, Sample Rate, asp_rate, internal_fs_ratio, mclk_int_scale */ + {5644800, 11025, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {5644800, 22050, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {5644800, 44100, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + + {6000000, 8000, 0x1, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 11025, 0x2, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 12000, 0x4, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 16000, 0x5, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 22050, 0x6, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 24000, 0x8, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 32000, 0x9, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 44100, 0xA, 0, CS53L30_MCLK_INT_SCALE}, + {6000000, 48000, 0xC, 0, CS53L30_MCLK_INT_SCALE}, + + {6144000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6144000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + + {6400000, 8000, 0x1, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 11025, 0x2, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 12000, 0x4, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 16000, 0x5, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 22050, 0x6, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 24000, 0x8, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 32000, 0x9, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 44100, 0xA, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, + {6400000, 48000, 0xC, CS53L30_INTRNL_FS_RATIO, CS53L30_MCLK_INT_SCALE}, +}; + +struct cs53l30_mclkx_div { + u32 mclkx; + u8 ratio; + u8 mclkdiv; +}; + +static struct cs53l30_mclkx_div cs53l30_mclkx_coeffs[] = { + {5644800, 1, CS53L30_MCLK_DIV_BY_1}, + {6000000, 1, CS53L30_MCLK_DIV_BY_1}, + {6144000, 1, CS53L30_MCLK_DIV_BY_1}, + {11289600, 2, CS53L30_MCLK_DIV_BY_2}, + {12288000, 2, CS53L30_MCLK_DIV_BY_2}, + {12000000, 2, CS53L30_MCLK_DIV_BY_2}, + {19200000, 3, CS53L30_MCLK_DIV_BY_3}, +}; + +static int cs53l30_get_mclkx_coeff(int mclkx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclkx_coeffs); i++) { + if (cs53l30_mclkx_coeffs[i].mclkx == mclkx) + return i; + } + + return -EINVAL; +} + +static int cs53l30_get_mclk_coeff(int mclk_rate, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs53l30_mclk_coeffs); i++) { + if (cs53l30_mclk_coeffs[i].mclk_rate == mclk_rate && + cs53l30_mclk_coeffs[i].srate == srate) + return i; + } + + return -EINVAL; +} + +static int cs53l30_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + int mclkx_coeff; + u32 mclk_rate; + + /* MCLKX -> MCLK */ + mclkx_coeff = cs53l30_get_mclkx_coeff(freq); + if (mclkx_coeff < 0) + return mclkx_coeff; + + mclk_rate = cs53l30_mclkx_coeffs[mclkx_coeff].mclkx / + cs53l30_mclkx_coeffs[mclkx_coeff].ratio; + + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIV_MASK, + cs53l30_mclkx_coeffs[mclkx_coeff].mclkdiv); + + priv->mclk_rate = mclk_rate; + + return 0; +} + +static int cs53l30_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + u8 aspcfg = 0, aspctl1 = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aspcfg |= CS53L30_ASP_MS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* Set TDM_PDN to turn off TDM mode -- Reset default */ + aspctl1 |= CS53L30_ASP_TDM_PDN; + break; + case SND_SOC_DAIFMT_DSP_A: + /* + * Clear TDM_PDN to turn on TDM mode; Use ASP_SCLK_INV = 0 + * with SHIFT_LEFT = 1 combination as Figure 4-13 shows in + * the CS53L30 datasheet + */ + aspctl1 |= CS53L30_SHIFT_LEFT; + break; + default: + return -EINVAL; + } + + /* Check to see if the SCLK is inverted */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: + aspcfg ^= CS53L30_ASP_SCLK_INV; + break; + default: + break; + } + + regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL, + CS53L30_ASP_MS | CS53L30_ASP_SCLK_INV, aspcfg); + + regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1, + CS53L30_ASP_TDM_PDN | CS53L30_SHIFT_LEFT, aspctl1); + + return 0; +} + +static int cs53l30_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + int srate = params_rate(params); + int mclk_coeff; + + /* MCLK -> srate */ + mclk_coeff = cs53l30_get_mclk_coeff(priv->mclk_rate, srate); + if (mclk_coeff < 0) + return -EINVAL; + + regmap_update_bits(priv->regmap, CS53L30_INT_SR_CTL, + CS53L30_INTRNL_FS_RATIO_MASK, + cs53l30_mclk_coeffs[mclk_coeff].internal_fs_ratio); + + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_INT_SCALE_MASK, + cs53l30_mclk_coeffs[mclk_coeff].mclk_int_scale); + + regmap_update_bits(priv->regmap, CS53L30_ASPCFG_CTL, + CS53L30_ASP_RATE_MASK, + cs53l30_mclk_coeffs[mclk_coeff].asp_rate); + + return 0; +} + +static int cs53l30_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + unsigned int reg; + int i, inter_max_check, ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_LP_MASK, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (dapm->bias_level == SND_SOC_BIAS_OFF) { + ret = clk_prepare_enable(priv->mclk); + if (ret) { + dev_err(codec->dev, + "failed to enable MCLK: %d\n", ret); + return ret; + } + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS_MASK, 0); + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, 0); + msleep(50); + } else { + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, + CS53L30_PDN_ULP); + } + break; + case SND_SOC_BIAS_OFF: + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, 0); + /* + * If digital softramp is set, the amount of time required + * for power down increases and depends on the digital + * volume setting. + */ + + /* Set the max possible time if digsft is set */ + regmap_read(priv->regmap, CS53L30_SFT_RAMP, ®); + if (reg & CS53L30_DIGSFT_MASK) + inter_max_check = CS53L30_PDN_POLL_MAX; + else + inter_max_check = 10; + + regmap_update_bits(priv->regmap, CS53L30_PWRCTL, + CS53L30_PDN_ULP_MASK, + CS53L30_PDN_ULP); + /* PDN_DONE will take a min of 20ms to be set.*/ + msleep(20); + /* Clr status */ + regmap_read(priv->regmap, CS53L30_IS, ®); + for (i = 0; i < inter_max_check; i++) { + if (inter_max_check < 10) { + usleep_range(1000, 1100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } else { + usleep_range(10000, 10100); + regmap_read(priv->regmap, CS53L30_IS, ®); + if (reg & CS53L30_PDN_DONE) + break; + } + } + /* PDN_DONE is set. We now can disable the MCLK */ + regmap_update_bits(priv->regmap, CS53L30_INT_MASK, + CS53L30_PDN_DONE, CS53L30_PDN_DONE); + regmap_update_bits(priv->regmap, CS53L30_MCLKCTL, + CS53L30_MCLK_DIS_MASK, + CS53L30_MCLK_DIS); + clk_disable_unprepare(priv->mclk); + break; + } + + return 0; +} + +static int cs53l30_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + u8 val = tristate ? CS53L30_ASP_3ST : 0; + + return regmap_update_bits(priv->regmap, CS53L30_ASP_CTL1, + CS53L30_ASP_3ST_MASK, val); +} + +static unsigned int const cs53l30_src_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list src_constraints = { + .count = ARRAY_SIZE(cs53l30_src_rates), + .list = cs53l30_src_rates, +}; + +static int cs53l30_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &src_constraints); + + return 0; +} + +/* + * Note: CS53L30 counts the slot number per byte while ASoC counts the slot + * number per slot_width. So there is a difference between the slots of ASoC + * and the slots of CS53L30. + */ +static int cs53l30_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + unsigned int loc[CS53L30_TDM_SLOT_MAX] = {48, 48, 48, 48}; + unsigned int slot_next, slot_step; + u64 tx_enable = 0; + int i; + + if (!rx_mask) { + dev_err(dai->dev, "rx masks must not be 0\n"); + return -EINVAL; + } + + /* Assuming slot_width is not supposed to be greater than 64 */ + if (slots <= 0 || slot_width <= 0 || slot_width > 64) { + dev_err(dai->dev, "invalid slot number or slot width\n"); + return -EINVAL; + } + + if (slot_width & 0x7) { + dev_err(dai->dev, "slot width must count in byte\n"); + return -EINVAL; + } + + /* How many bytes in each ASoC slot */ + slot_step = slot_width >> 3; + + for (i = 0; rx_mask && i < CS53L30_TDM_SLOT_MAX; i++) { + /* Find the first slot from LSB */ + slot_next = __ffs(rx_mask); + /* Save the slot location by converting to CS53L30 slot */ + loc[i] = slot_next * slot_step; + /* Create the mask of CS53L30 slot */ + tx_enable |= (u64)((u64)(1 << slot_step) - 1) << (u64)loc[i]; + /* Clear this slot from rx_mask */ + rx_mask &= ~(1 << slot_next); + } + + /* Error out to avoid slot shift */ + if (rx_mask && i == CS53L30_TDM_SLOT_MAX) { + dev_err(dai->dev, "rx_mask exceeds max slot number: %d\n", + CS53L30_TDM_SLOT_MAX); + return -EINVAL; + } + + /* Validate the last active CS53L30 slot */ + slot_next = loc[i - 1] + slot_step - 1; + if (slot_next > 47) { + dev_err(dai->dev, "slot selection out of bounds: %u\n", + slot_next); + return -EINVAL; + } + + for (i = 0; i < CS53L30_TDM_SLOT_MAX && loc[i] != 48; i++) { + regmap_update_bits(priv->regmap, CS53L30_ASP_TDMTX_CTL(i), + CS53L30_ASP_CHx_TX_LOC_MASK, loc[i]); + dev_dbg(dai->dev, "loc[%d]=%x\n", i, loc[i]); + } + + for (i = 0; i < CS53L30_ASP_TDMTX_ENx_MAX && tx_enable; i++) { + regmap_write(priv->regmap, CS53L30_ASP_TDMTX_ENx(i), + tx_enable & 0xff); + tx_enable >>= 8; + dev_dbg(dai->dev, "en_reg=%x, tx_enable=%llx\n", + CS53L30_ASP_TDMTX_ENx(i), tx_enable & 0xff); + } + + return 0; +} + +static int cs53l30_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(dai->codec); + + if (priv->mute_gpio) + gpiod_set_value_cansleep(priv->mute_gpio, mute); + + return 0; +} + +/* SNDRV_PCM_RATE_KNOT -> 12000, 24000 Hz, limit with constraint list */ +#define CS53L30_RATES (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) + +#define CS53L30_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops cs53l30_ops = { + .startup = cs53l30_pcm_startup, + .hw_params = cs53l30_pcm_hw_params, + .set_fmt = cs53l30_set_dai_fmt, + .set_sysclk = cs53l30_set_sysclk, + .set_tristate = cs53l30_set_tristate, + .set_tdm_slot = cs53l30_set_dai_tdm_slot, + .mute_stream = cs53l30_mute_stream, +}; + +static struct snd_soc_dai_driver cs53l30_dai = { + .name = "cs53l30", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = CS53L30_RATES, + .formats = CS53L30_FORMATS, + }, + .ops = &cs53l30_ops, + .symmetric_rates = 1, +}; + +static int cs53l30_codec_probe(struct snd_soc_codec *codec) +{ + struct cs53l30_private *priv = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + if (priv->use_sdout2) + snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout2, + ARRAY_SIZE(cs53l30_dapm_routes_sdout2)); + else + snd_soc_dapm_add_routes(dapm, cs53l30_dapm_routes_sdout1, + ARRAY_SIZE(cs53l30_dapm_routes_sdout1)); + + return 0; +} + +static struct snd_soc_codec_driver cs53l30_driver = { + .probe = cs53l30_codec_probe, + .set_bias_level = cs53l30_set_bias_level, + .idle_bias_off = true, + + .dapm_widgets = cs53l30_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs53l30_dapm_widgets), + .dapm_routes = cs53l30_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(cs53l30_dapm_routes), + + .controls = cs53l30_snd_controls, + .num_controls = ARRAY_SIZE(cs53l30_snd_controls), +}; + +static struct regmap_config cs53l30_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS53L30_MAX_REGISTER, + .reg_defaults = cs53l30_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs53l30_reg_defaults), + .volatile_reg = cs53l30_volatile_register, + .writeable_reg = cs53l30_writeable_register, + .readable_reg = cs53l30_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs53l30_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct device_node *np = client->dev.of_node; + struct device *dev = &client->dev; + struct cs53l30_private *cs53l30; + unsigned int devid = 0; + unsigned int reg; + int ret = 0, i; + u8 val; + + cs53l30 = devm_kzalloc(dev, sizeof(*cs53l30), GFP_KERNEL); + if (!cs53l30) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(cs53l30->supplies); i++) + cs53l30->supplies[i].supply = cs53l30_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to get supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + /* Reset the Device */ + cs53l30->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(cs53l30->reset_gpio)) { + ret = PTR_ERR(cs53l30->reset_gpio); + goto error; + } + + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + i2c_set_clientdata(client, cs53l30); + + cs53l30->mclk_rate = 0; + + cs53l30->regmap = devm_regmap_init_i2c(client, &cs53l30_regmap); + if (IS_ERR(cs53l30->regmap)) { + ret = PTR_ERR(cs53l30->regmap); + dev_err(dev, "regmap_init() failed: %d\n", ret); + goto error; + } + + /* Initialize codec */ + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_AB, ®); + devid = reg << 12; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_CD, ®); + devid |= reg << 4; + + ret = regmap_read(cs53l30->regmap, CS53L30_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS53L30_DEVID) { + ret = -ENODEV; + dev_err(dev, "Device ID (%X). Expected %X\n", + devid, CS53L30_DEVID); + goto error; + } + + ret = regmap_read(cs53l30->regmap, CS53L30_REVID, ®); + if (ret < 0) { + dev_err(dev, "failed to get Revision ID: %d\n", ret); + goto error; + } + + /* Check if MCLK provided */ + cs53l30->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(cs53l30->mclk)) { + if (PTR_ERR(cs53l30->mclk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto error; + } + /* Otherwise mark the mclk pointer to NULL */ + cs53l30->mclk = NULL; + } + + /* Fetch the MUTE control */ + cs53l30->mute_gpio = devm_gpiod_get_optional(dev, "mute", + GPIOD_OUT_HIGH); + if (IS_ERR(cs53l30->mute_gpio)) { + ret = PTR_ERR(cs53l30->mute_gpio); + goto error; + } + + if (cs53l30->mute_gpio) { + /* Enable MUTE controls via MUTE pin */ + regmap_write(cs53l30->regmap, CS53L30_MUTEP_CTL1, + CS53L30_MUTEP_CTL1_MUTEALL); + /* Flip the polarity of MUTE pin */ + if (gpiod_is_active_low(cs53l30->mute_gpio)) + regmap_update_bits(cs53l30->regmap, CS53L30_MUTEP_CTL2, + CS53L30_MUTE_PIN_POLARITY, 0); + } + + if (!of_property_read_u8(np, "cirrus,micbias-lvl", &val)) + regmap_update_bits(cs53l30->regmap, CS53L30_MICBIAS_CTL, + CS53L30_MIC_BIAS_CTRL_MASK, val); + + if (of_property_read_bool(np, "cirrus,use-sdout2")) + cs53l30->use_sdout2 = true; + + dev_info(dev, "Cirrus Logic CS53L30, Revision: %02X\n", reg & 0xFF); + + ret = snd_soc_register_codec(dev, &cs53l30_driver, &cs53l30_dai, 1); + if (ret) { + dev_err(dev, "failed to register codec: %d\n", ret); + goto error; + } + + return 0; + +error: + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + return ret; +} + +static int cs53l30_i2c_remove(struct i2c_client *client) +{ + struct cs53l30_private *cs53l30 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + + /* Hold down reset */ + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + + return 0; +} + +#ifdef CONFIG_PM +static int cs53l30_runtime_suspend(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + + regcache_cache_only(cs53l30->regmap, true); + + /* Hold down reset */ + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 0); + + regulator_bulk_disable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + + return 0; +} + +static int cs53l30_runtime_resume(struct device *dev) +{ + struct cs53l30_private *cs53l30 = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(cs53l30->supplies), + cs53l30->supplies); + if (ret) { + dev_err(dev, "failed to enable supplies: %d\n", ret); + return ret; + } + + if (cs53l30->reset_gpio) + gpiod_set_value_cansleep(cs53l30->reset_gpio, 1); + + regcache_cache_only(cs53l30->regmap, false); + ret = regcache_sync(cs53l30->regmap); + if (ret) { + dev_err(dev, "failed to synchronize regcache: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops cs53l30_runtime_pm = { + SET_RUNTIME_PM_OPS(cs53l30_runtime_suspend, cs53l30_runtime_resume, + NULL) +}; + +static const struct of_device_id cs53l30_of_match[] = { + { .compatible = "cirrus,cs53l30", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cs53l30_of_match); + +static const struct i2c_device_id cs53l30_id[] = { + { "cs53l30", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs53l30_id); + +static struct i2c_driver cs53l30_i2c_driver = { + .driver = { + .name = "cs53l30", + .pm = &cs53l30_runtime_pm, + }, + .id_table = cs53l30_id, + .probe = cs53l30_i2c_probe, + .remove = cs53l30_i2c_remove, +}; + +module_i2c_driver(cs53l30_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS53L30 driver"); +MODULE_AUTHOR("Paul Handrigan, Cirrus Logic Inc, <Paul.Handrigan@cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs53l30.h b/sound/soc/codecs/cs53l30.h new file mode 100644 index 000000000000..5e39da568749 --- /dev/null +++ b/sound/soc/codecs/cs53l30.h @@ -0,0 +1,459 @@ +/* + * ALSA SoC CS53L30 codec driver + * + * Copyright 2015 Cirrus Logic, Inc. + * + * Author: Paul Handrigan <Paul.Handrigan@cirrus.com>, + * Tim Howe <Tim.Howe@cirrus.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. + * + */ + +#ifndef __CS53L30_H__ +#define __CS53L30_H__ + +/* I2C Registers */ +#define CS53L30_DEVID_AB 0x01 /* Device ID A & B [RO]. */ +#define CS53L30_DEVID_CD 0x02 /* Device ID C & D [RO]. */ +#define CS53L30_DEVID_E 0x03 /* Device ID E [RO]. */ +#define CS53L30_REVID 0x05 /* Revision ID [RO]. */ +#define CS53L30_PWRCTL 0x06 /* Power Control. */ +#define CS53L30_MCLKCTL 0x07 /* MCLK Control. */ +#define CS53L30_INT_SR_CTL 0x08 /* Internal Sample Rate Control. */ +#define CS53L30_MICBIAS_CTL 0x0A /* Mic Bias Control. */ +#define CS53L30_ASPCFG_CTL 0x0C /* ASP Config Control. */ +#define CS53L30_ASP_CTL1 0x0D /* ASP1 Control. */ +#define CS53L30_ASP_TDMTX_CTL1 0x0E /* ASP1 TDM TX Control 1 */ +#define CS53L30_ASP_TDMTX_CTL2 0x0F /* ASP1 TDM TX Control 2 */ +#define CS53L30_ASP_TDMTX_CTL3 0x10 /* ASP1 TDM TX Control 3 */ +#define CS53L30_ASP_TDMTX_CTL4 0x11 /* ASP1 TDM TX Control 4 */ +#define CS53L30_ASP_TDMTX_EN1 0x12 /* ASP1 TDM TX Enable 1 */ +#define CS53L30_ASP_TDMTX_EN2 0x13 /* ASP1 TDM TX Enable 2 */ +#define CS53L30_ASP_TDMTX_EN3 0x14 /* ASP1 TDM TX Enable 3 */ +#define CS53L30_ASP_TDMTX_EN4 0x15 /* ASP1 TDM TX Enable 4 */ +#define CS53L30_ASP_TDMTX_EN5 0x16 /* ASP1 TDM TX Enable 5 */ +#define CS53L30_ASP_TDMTX_EN6 0x17 /* ASP1 TDM TX Enable 6 */ +#define CS53L30_ASP_CTL2 0x18 /* ASP2 Control. */ +#define CS53L30_SFT_RAMP 0x1A /* Soft Ramp Control. */ +#define CS53L30_LRCK_CTL1 0x1B /* LRCK Control 1. */ +#define CS53L30_LRCK_CTL2 0x1C /* LRCK Control 2. */ +#define CS53L30_MUTEP_CTL1 0x1F /* Mute Pin Control 1. */ +#define CS53L30_MUTEP_CTL2 0x20 /* Mute Pin Control 2. */ +#define CS53L30_INBIAS_CTL1 0x21 /* Input Bias Control 1. */ +#define CS53L30_INBIAS_CTL2 0x22 /* Input Bias Control 2. */ +#define CS53L30_DMIC1_STR_CTL 0x23 /* DMIC1 Stereo Control. */ +#define CS53L30_DMIC2_STR_CTL 0x24 /* DMIC2 Stereo Control. */ +#define CS53L30_ADCDMIC1_CTL1 0x25 /* ADC1/DMIC1 Control 1. */ +#define CS53L30_ADCDMIC1_CTL2 0x26 /* ADC1/DMIC1 Control 2. */ +#define CS53L30_ADC1_CTL3 0x27 /* ADC1 Control 3. */ +#define CS53L30_ADC1_NG_CTL 0x28 /* ADC1 Noise Gate Control. */ +#define CS53L30_ADC1A_AFE_CTL 0x29 /* ADC1A AFE Control. */ +#define CS53L30_ADC1B_AFE_CTL 0x2A /* ADC1B AFE Control. */ +#define CS53L30_ADC1A_DIG_VOL 0x2B /* ADC1A Digital Volume. */ +#define CS53L30_ADC1B_DIG_VOL 0x2C /* ADC1B Digital Volume. */ +#define CS53L30_ADCDMIC2_CTL1 0x2D /* ADC2/DMIC2 Control 1. */ +#define CS53L30_ADCDMIC2_CTL2 0x2E /* ADC2/DMIC2 Control 2. */ +#define CS53L30_ADC2_CTL3 0x2F /* ADC2 Control 3. */ +#define CS53L30_ADC2_NG_CTL 0x30 /* ADC2 Noise Gate Control. */ +#define CS53L30_ADC2A_AFE_CTL 0x31 /* ADC2A AFE Control. */ +#define CS53L30_ADC2B_AFE_CTL 0x32 /* ADC2B AFE Control. */ +#define CS53L30_ADC2A_DIG_VOL 0x33 /* ADC2A Digital Volume. */ +#define CS53L30_ADC2B_DIG_VOL 0x34 /* ADC2B Digital Volume. */ +#define CS53L30_INT_MASK 0x35 /* Interrupt Mask. */ +#define CS53L30_IS 0x36 /* Interrupt Status. */ +#define CS53L30_MAX_REGISTER 0x36 + +#define CS53L30_TDM_SLOT_MAX 4 +#define CS53L30_ASP_TDMTX_CTL(x) (CS53L30_ASP_TDMTX_CTL1 + (x)) +/* x : index for registers; n : index for slot; 8 slots per register */ +#define CS53L30_ASP_TDMTX_ENx(x) (CS53L30_ASP_TDMTX_EN6 - (x)) +#define CS53L30_ASP_TDMTX_ENn(n) CS53L30_ASP_TDMTX_ENx((n) >> 3) +#define CS53L30_ASP_TDMTX_ENx_MAX 6 + +/* Device ID */ +#define CS53L30_DEVID 0x53A30 + +/* PDN_DONE Poll Maximum + * If soft ramp is set it will take much longer to power down + * the system. + */ +#define CS53L30_PDN_POLL_MAX 90 + +/* Bitfield Definitions */ + +/* R6 (0x06) CS53L30_PWRCTL - Power Control */ +#define CS53L30_PDN_ULP_SHIFT 7 +#define CS53L30_PDN_ULP_MASK (1 << CS53L30_PDN_ULP_SHIFT) +#define CS53L30_PDN_ULP (1 << CS53L30_PDN_ULP_SHIFT) +#define CS53L30_PDN_LP_SHIFT 6 +#define CS53L30_PDN_LP_MASK (1 << CS53L30_PDN_LP_SHIFT) +#define CS53L30_PDN_LP (1 << CS53L30_PDN_LP_SHIFT) +#define CS53L30_DISCHARGE_FILT_SHIFT 5 +#define CS53L30_DISCHARGE_FILT_MASK (1 << CS53L30_DISCHARGE_FILT_SHIFT) +#define CS53L30_DISCHARGE_FILT (1 << CS53L30_DISCHARGE_FILT_SHIFT) +#define CS53L30_THMS_PDN_SHIFT 4 +#define CS53L30_THMS_PDN_MASK (1 << CS53L30_THMS_PDN_SHIFT) +#define CS53L30_THMS_PDN (1 << CS53L30_THMS_PDN_SHIFT) + +#define CS53L30_PWRCTL_DEFAULT (CS53L30_THMS_PDN) + +/* R7 (0x07) CS53L30_MCLKCTL - MCLK Control */ +#define CS53L30_MCLK_DIS_SHIFT 7 +#define CS53L30_MCLK_DIS_MASK (1 << CS53L30_MCLK_DIS_SHIFT) +#define CS53L30_MCLK_DIS (1 << CS53L30_MCLK_DIS_SHIFT) +#define CS53L30_MCLK_INT_SCALE_SHIFT 6 +#define CS53L30_MCLK_INT_SCALE_MASK (1 << CS53L30_MCLK_INT_SCALE_SHIFT) +#define CS53L30_MCLK_INT_SCALE (1 << CS53L30_MCLK_INT_SCALE_SHIFT) +#define CS53L30_DMIC_DRIVE_SHIFT 5 +#define CS53L30_DMIC_DRIVE_MASK (1 << CS53L30_DMIC_DRIVE_SHIFT) +#define CS53L30_DMIC_DRIVE (1 << CS53L30_DMIC_DRIVE_SHIFT) +#define CS53L30_MCLK_DIV_SHIFT 2 +#define CS53L30_MCLK_DIV_WIDTH 2 +#define CS53L30_MCLK_DIV_MASK (((1 << CS53L30_MCLK_DIV_WIDTH) - 1) << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_1 (0x0 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_2 (0x1 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_MCLK_DIV_BY_3 (0x2 << CS53L30_MCLK_DIV_SHIFT) +#define CS53L30_SYNC_EN_SHIFT 1 +#define CS53L30_SYNC_EN_MASK (1 << CS53L30_SYNC_EN_SHIFT) +#define CS53L30_SYNC_EN (1 << CS53L30_SYNC_EN_SHIFT) + +#define CS53L30_MCLKCTL_DEFAULT (CS53L30_MCLK_DIV_BY_2) + +/* R8 (0x08) CS53L30_INT_SR_CTL - Internal Sample Rate Control */ +#define CS53L30_INTRNL_FS_RATIO_SHIFT 4 +#define CS53L30_INTRNL_FS_RATIO_MASK (1 << CS53L30_INTRNL_FS_RATIO_SHIFT) +#define CS53L30_INTRNL_FS_RATIO (1 << CS53L30_INTRNL_FS_RATIO_SHIFT) +#define CS53L30_MCLK_19MHZ_EN_SHIFT 0 +#define CS53L30_MCLK_19MHZ_EN_MASK (1 << CS53L30_MCLK_19MHZ_EN_SHIFT) +#define CS53L30_MCLK_19MHZ_EN (1 << CS53L30_MCLK_19MHZ_EN_SHIFT) + +/* 0x6 << 1 is reserved bits */ +#define CS53L30_INT_SR_CTL_DEFAULT (CS53L30_INTRNL_FS_RATIO | 0x6 << 1) + +/* R10 (0x0A) CS53L30_MICBIAS_CTL - Mic Bias Control */ +#define CS53L30_MIC4_BIAS_PDN_SHIFT 7 +#define CS53L30_MIC4_BIAS_PDN_MASK (1 << CS53L30_MIC4_BIAS_PDN_SHIFT) +#define CS53L30_MIC4_BIAS_PDN (1 << CS53L30_MIC4_BIAS_PDN_SHIFT) +#define CS53L30_MIC3_BIAS_PDN_SHIFT 6 +#define CS53L30_MIC3_BIAS_PDN_MASK (1 << CS53L30_MIC3_BIAS_PDN_SHIFT) +#define CS53L30_MIC3_BIAS_PDN (1 << CS53L30_MIC3_BIAS_PDN_SHIFT) +#define CS53L30_MIC2_BIAS_PDN_SHIFT 5 +#define CS53L30_MIC2_BIAS_PDN_MASK (1 << CS53L30_MIC2_BIAS_PDN_SHIFT) +#define CS53L30_MIC2_BIAS_PDN (1 << CS53L30_MIC2_BIAS_PDN_SHIFT) +#define CS53L30_MIC1_BIAS_PDN_SHIFT 4 +#define CS53L30_MIC1_BIAS_PDN_MASK (1 << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_MIC1_BIAS_PDN (1 << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_MICx_BIAS_PDN (0xf << CS53L30_MIC1_BIAS_PDN_SHIFT) +#define CS53L30_VP_MIN_SHIFT 2 +#define CS53L30_VP_MIN_MASK (1 << CS53L30_VP_MIN_SHIFT) +#define CS53L30_VP_MIN (1 << CS53L30_VP_MIN_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_SHIFT 0 +#define CS53L30_MIC_BIAS_CTRL_WIDTH 2 +#define CS53L30_MIC_BIAS_CTRL_MASK (((1 << CS53L30_MIC_BIAS_CTRL_WIDTH) - 1) << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_HIZ (0 << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_1V8 (1 << CS53L30_MIC_BIAS_CTRL_SHIFT) +#define CS53L30_MIC_BIAS_CTRL_2V75 (2 << CS53L30_MIC_BIAS_CTRL_SHIFT) + +#define CS53L30_MICBIAS_CTL_DEFAULT (CS53L30_MICx_BIAS_PDN | CS53L30_VP_MIN) + +/* R12 (0x0C) CS53L30_ASPCFG_CTL - ASP Configuration Control */ +#define CS53L30_ASP_MS_SHIFT 7 +#define CS53L30_ASP_MS_MASK (1 << CS53L30_ASP_MS_SHIFT) +#define CS53L30_ASP_MS (1 << CS53L30_ASP_MS_SHIFT) +#define CS53L30_ASP_SCLK_INV_SHIFT 4 +#define CS53L30_ASP_SCLK_INV_MASK (1 << CS53L30_ASP_SCLK_INV_SHIFT) +#define CS53L30_ASP_SCLK_INV (1 << CS53L30_ASP_SCLK_INV_SHIFT) +#define CS53L30_ASP_RATE_SHIFT 0 +#define CS53L30_ASP_RATE_WIDTH 4 +#define CS53L30_ASP_RATE_MASK (((1 << CS53L30_ASP_RATE_WIDTH) - 1) << CS53L30_ASP_RATE_SHIFT) +#define CS53L30_ASP_RATE_48K (0xc << CS53L30_ASP_RATE_SHIFT) + +#define CS53L30_ASPCFG_CTL_DEFAULT (CS53L30_ASP_RATE_48K) + +/* R13/R24 (0x0D/0x18) CS53L30_ASP_CTL1 & CS53L30_ASP_CTL2 - ASP Control 1~2 */ +#define CS53L30_ASP_TDM_PDN_SHIFT 7 +#define CS53L30_ASP_TDM_PDN_MASK (1 << CS53L30_ASP_TDM_PDN_SHIFT) +#define CS53L30_ASP_TDM_PDN (1 << CS53L30_ASP_TDM_PDN_SHIFT) +#define CS53L30_ASP_SDOUTx_PDN_SHIFT 6 +#define CS53L30_ASP_SDOUTx_PDN_MASK (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT) +#define CS53L30_ASP_SDOUTx_PDN (1 << CS53L30_ASP_SDOUTx_PDN_SHIFT) +#define CS53L30_ASP_3ST_SHIFT 5 +#define CS53L30_ASP_3ST_MASK (1 << CS53L30_ASP_3ST_SHIFT) +#define CS53L30_ASP_3ST (1 << CS53L30_ASP_3ST_SHIFT) +#define CS53L30_SHIFT_LEFT_SHIFT 4 +#define CS53L30_SHIFT_LEFT_MASK (1 << CS53L30_SHIFT_LEFT_SHIFT) +#define CS53L30_SHIFT_LEFT (1 << CS53L30_SHIFT_LEFT_SHIFT) +#define CS53L30_ASP_SDOUTx_DRIVE_SHIFT 0 +#define CS53L30_ASP_SDOUTx_DRIVE_MASK (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT) +#define CS53L30_ASP_SDOUTx_DRIVE (1 << CS53L30_ASP_SDOUTx_DRIVE_SHIFT) + +#define CS53L30_ASP_CTL1_DEFAULT (CS53L30_ASP_TDM_PDN) +#define CS53L30_ASP_CTL2_DEFAULT (0) + +/* R14 (0x0E) ~ R17 (0x11) CS53L30_ASP_TDMTX_CTLx - ASP TDM TX Control 1~4 */ +#define CS53L30_ASP_CHx_TX_STATE_SHIFT 7 +#define CS53L30_ASP_CHx_TX_STATE_MASK (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT) +#define CS53L30_ASP_CHx_TX_STATE (1 << CS53L30_ASP_CHx_TX_STATE_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC_SHIFT 0 +#define CS53L30_ASP_CHx_TX_LOC_WIDTH 6 +#define CS53L30_ASP_CHx_TX_LOC_MASK (((1 << CS53L30_ASP_CHx_TX_LOC_WIDTH) - 1) << CS53L30_ASP_CHx_TX_LOC_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC_MAX (47 << CS53L30_ASP_CHx_TX_LOC_SHIFT) +#define CS53L30_ASP_CHx_TX_LOC(x) ((x) << CS53L30_ASP_CHx_TX_LOC_SHIFT) + +#define CS53L30_ASP_TDMTX_CTLx_DEFAULT (CS53L30_ASP_CHx_TX_LOC_MAX) + +/* R18 (0x12) ~ R23 (0x17) CS53L30_ASP_TDMTX_ENx - ASP TDM TX Enable 1~6 */ +#define CS53L30_ASP_TDMTX_ENx_DEFAULT (0) + +/* R26 (0x1A) CS53L30_SFT_RAMP - Soft Ramp Control */ +#define CS53L30_DIGSFT_SHIFT 5 +#define CS53L30_DIGSFT_MASK (1 << CS53L30_DIGSFT_SHIFT) +#define CS53L30_DIGSFT (1 << CS53L30_DIGSFT_SHIFT) + +#define CS53L30_SFT_RMP_DEFAULT (0) + +/* R28 (0x1C) CS53L30_LRCK_CTL2 - LRCK Control 2 */ +#define CS53L30_LRCK_50_NPW_SHIFT 3 +#define CS53L30_LRCK_50_NPW_MASK (1 << CS53L30_LRCK_50_NPW_SHIFT) +#define CS53L30_LRCK_50_NPW (1 << CS53L30_LRCK_50_NPW_SHIFT) +#define CS53L30_LRCK_TPWH_SHIFT 0 +#define CS53L30_LRCK_TPWH_WIDTH 3 +#define CS53L30_LRCK_TPWH_MASK (((1 << CS53L30_LRCK_TPWH_WIDTH) - 1) << CS53L30_LRCK_TPWH_SHIFT) +#define CS53L30_LRCK_TPWH(x) (((x) << CS53L30_LRCK_TPWH_SHIFT) & CS53L30_LRCK_TPWH_MASK) + +#define CS53L30_LRCK_CTLx_DEFAULT (0) + +/* R31 (0x1F) CS53L30_MUTEP_CTL1 - MUTE Pin Control 1 */ +#define CS53L30_MUTE_PDN_ULP_SHIFT 7 +#define CS53L30_MUTE_PDN_ULP_MASK (1 << CS53L30_MUTE_PDN_ULP_SHIFT) +#define CS53L30_MUTE_PDN_ULP (1 << CS53L30_MUTE_PDN_ULP_SHIFT) +#define CS53L30_MUTE_PDN_LP_SHIFT 6 +#define CS53L30_MUTE_PDN_LP_MASK (1 << CS53L30_MUTE_PDN_LP_SHIFT) +#define CS53L30_MUTE_PDN_LP (1 << CS53L30_MUTE_PDN_LP_SHIFT) +#define CS53L30_MUTE_M4B_PDN_SHIFT 4 +#define CS53L30_MUTE_M4B_PDN_MASK (1 << CS53L30_MUTE_M4B_PDN_SHIFT) +#define CS53L30_MUTE_M4B_PDN (1 << CS53L30_MUTE_M4B_PDN_SHIFT) +#define CS53L30_MUTE_M3B_PDN_SHIFT 3 +#define CS53L30_MUTE_M3B_PDN_MASK (1 << CS53L30_MUTE_M3B_PDN_SHIFT) +#define CS53L30_MUTE_M3B_PDN (1 << CS53L30_MUTE_M3B_PDN_SHIFT) +#define CS53L30_MUTE_M2B_PDN_SHIFT 2 +#define CS53L30_MUTE_M2B_PDN_MASK (1 << CS53L30_MUTE_M2B_PDN_SHIFT) +#define CS53L30_MUTE_M2B_PDN (1 << CS53L30_MUTE_M2B_PDN_SHIFT) +#define CS53L30_MUTE_M1B_PDN_SHIFT 1 +#define CS53L30_MUTE_M1B_PDN_MASK (1 << CS53L30_MUTE_M1B_PDN_SHIFT) +#define CS53L30_MUTE_M1B_PDN (1 << CS53L30_MUTE_M1B_PDN_SHIFT) +/* Note: be careful - x starts from 0 */ +#define CS53L30_MUTE_MxB_PDN_SHIFT(x) (CS53L30_MUTE_M1B_PDN_SHIFT + (x)) +#define CS53L30_MUTE_MxB_PDN_MASK(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x)) +#define CS53L30_MUTE_MxB_PDN(x) (1 << CS53L30_MUTE_MxB_PDN_SHIFT(x)) +#define CS53L30_MUTE_MB_ALL_PDN_SHIFT 0 +#define CS53L30_MUTE_MB_ALL_PDN_MASK (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT) +#define CS53L30_MUTE_MB_ALL_PDN (1 << CS53L30_MUTE_MB_ALL_PDN_SHIFT) + +#define CS53L30_MUTEP_CTL1_MUTEALL (0xdf) +#define CS53L30_MUTEP_CTL1_DEFAULT (0) + +/* R32 (0x20) CS53L30_MUTEP_CTL2 - MUTE Pin Control 2 */ +#define CS53L30_MUTE_PIN_POLARITY_SHIFT 7 +#define CS53L30_MUTE_PIN_POLARITY_MASK (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT) +#define CS53L30_MUTE_PIN_POLARITY (1 << CS53L30_MUTE_PIN_POLARITY_SHIFT) +#define CS53L30_MUTE_ASP_TDM_PDN_SHIFT 6 +#define CS53L30_MUTE_ASP_TDM_PDN_MASK (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT) +#define CS53L30_MUTE_ASP_TDM_PDN (1 << CS53L30_MUTE_ASP_TDM_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT 5 +#define CS53L30_MUTE_ASP_SDOUT2_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT2_PDN (1 << CS53L30_MUTE_ASP_SDOUT2_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT 4 +#define CS53L30_MUTE_ASP_SDOUT1_PDN_MASK (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUT1_PDN (1 << CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +/* Note: be careful - x starts from 0 */ +#define CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x) ((x) + CS53L30_MUTE_ASP_SDOUT1_PDN_SHIFT) +#define CS53L30_MUTE_ASP_SDOUTx_PDN_MASK(x) (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x)) +#define CS53L30_MUTE_ASP_SDOUTx_PDN (1 << CS53L30_MUTE_ASP_SDOUTx_PDN_SHIFT(x)) +#define CS53L30_MUTE_ADC2B_PDN_SHIFT 3 +#define CS53L30_MUTE_ADC2B_PDN_MASK (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT) +#define CS53L30_MUTE_ADC2B_PDN (1 << CS53L30_MUTE_ADC2B_PDN_SHIFT) +#define CS53L30_MUTE_ADC2A_PDN_SHIFT 2 +#define CS53L30_MUTE_ADC2A_PDN_MASK (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT) +#define CS53L30_MUTE_ADC2A_PDN (1 << CS53L30_MUTE_ADC2A_PDN_SHIFT) +#define CS53L30_MUTE_ADC1B_PDN_SHIFT 1 +#define CS53L30_MUTE_ADC1B_PDN_MASK (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT) +#define CS53L30_MUTE_ADC1B_PDN (1 << CS53L30_MUTE_ADC1B_PDN_SHIFT) +#define CS53L30_MUTE_ADC1A_PDN_SHIFT 0 +#define CS53L30_MUTE_ADC1A_PDN_MASK (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT) +#define CS53L30_MUTE_ADC1A_PDN (1 << CS53L30_MUTE_ADC1A_PDN_SHIFT) + +#define CS53L30_MUTEP_CTL2_DEFAULT (CS53L30_MUTE_PIN_POLARITY) + +/* R33 (0x21) CS53L30_INBIAS_CTL1 - Input Bias Control 1 */ +#define CS53L30_IN4M_BIAS_SHIFT 6 +#define CS53L30_IN4M_BIAS_WIDTH 2 +#define CS53L30_IN4M_BIAS_MASK (((1 << CS53L30_IN4M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_OPEN (0 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_PULL_DOWN (1 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4M_BIAS_VCM (2 << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_SHIFT 4 +#define CS53L30_IN4P_BIAS_WIDTH 2 +#define CS53L30_IN4P_BIAS_MASK (((1 << CS53L30_IN4P_BIAS_WIDTH) - 1) << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_OPEN (0 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_PULL_DOWN (1 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN4P_BIAS_VCM (2 << CS53L30_IN4P_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_SHIFT 2 +#define CS53L30_IN3M_BIAS_WIDTH 2 +#define CS53L30_IN3M_BIAS_MASK (((1 << CS53L30_IN3M_BIAS_WIDTH) - 1) << CS53L30_IN4M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_OPEN (0 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_PULL_DOWN (1 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3M_BIAS_VCM (2 << CS53L30_IN3M_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_SHIFT 0 +#define CS53L30_IN3P_BIAS_WIDTH 2 +#define CS53L30_IN3P_BIAS_MASK (((1 << CS53L30_IN3P_BIAS_WIDTH) - 1) << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_OPEN (0 << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_PULL_DOWN (1 << CS53L30_IN3P_BIAS_SHIFT) +#define CS53L30_IN3P_BIAS_VCM (2 << CS53L30_IN3P_BIAS_SHIFT) + +#define CS53L30_INBIAS_CTL1_DEFAULT (CS53L30_IN4M_BIAS_VCM | CS53L30_IN4P_BIAS_VCM |\ + CS53L30_IN3M_BIAS_VCM | CS53L30_IN3P_BIAS_VCM) + +/* R34 (0x22) CS53L30_INBIAS_CTL2 - Input Bias Control 2 */ +#define CS53L30_IN2M_BIAS_SHIFT 6 +#define CS53L30_IN2M_BIAS_WIDTH 2 +#define CS53L30_IN2M_BIAS_MASK (((1 << CS53L30_IN2M_BIAS_WIDTH) - 1) << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_OPEN (0 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_PULL_DOWN (1 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2M_BIAS_VCM (2 << CS53L30_IN2M_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_SHIFT 4 +#define CS53L30_IN2P_BIAS_WIDTH 2 +#define CS53L30_IN2P_BIAS_MASK (((1 << CS53L30_IN2P_BIAS_WIDTH) - 1) << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_OPEN (0 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_PULL_DOWN (1 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN2P_BIAS_VCM (2 << CS53L30_IN2P_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_SHIFT 2 +#define CS53L30_IN1M_BIAS_WIDTH 2 +#define CS53L30_IN1M_BIAS_MASK (((1 << CS53L30_IN1M_BIAS_WIDTH) - 1) << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_OPEN (0 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_PULL_DOWN (1 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1M_BIAS_VCM (2 << CS53L30_IN1M_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_SHIFT 0 +#define CS53L30_IN1P_BIAS_WIDTH 2 +#define CS53L30_IN1P_BIAS_MASK (((1 << CS53L30_IN1P_BIAS_WIDTH) - 1) << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_OPEN (0 << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_PULL_DOWN (1 << CS53L30_IN1P_BIAS_SHIFT) +#define CS53L30_IN1P_BIAS_VCM (2 << CS53L30_IN1P_BIAS_SHIFT) + +#define CS53L30_INBIAS_CTL2_DEFAULT (CS53L30_IN2M_BIAS_VCM | CS53L30_IN2P_BIAS_VCM |\ + CS53L30_IN1M_BIAS_VCM | CS53L30_IN1P_BIAS_VCM) + +/* R35 (0x23) & R36 (0x24) CS53L30_DMICx_STR_CTL - DMIC1 & DMIC2 Stereo Control */ +#define CS53L30_DMICx_STEREO_ENB_SHIFT 5 +#define CS53L30_DMICx_STEREO_ENB_MASK (1 << CS53L30_DMICx_STEREO_ENB_SHIFT) +#define CS53L30_DMICx_STEREO_ENB (1 << CS53L30_DMICx_STEREO_ENB_SHIFT) + +/* 0x88 and 0xCC are reserved bits */ +#define CS53L30_DMIC1_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0x88) +#define CS53L30_DMIC2_STR_CTL_DEFAULT (CS53L30_DMICx_STEREO_ENB | 0xCC) + +/* R37/R45 (0x25/0x2D) CS53L30_ADCDMICx_CTL1 - ADC1/DMIC1 & ADC2/DMIC2 Control 1 */ +#define CS53L30_ADCxB_PDN_SHIFT 7 +#define CS53L30_ADCxB_PDN_MASK (1 << CS53L30_ADCxB_PDN_SHIFT) +#define CS53L30_ADCxB_PDN (1 << CS53L30_ADCxB_PDN_SHIFT) +#define CS53L30_ADCxA_PDN_SHIFT 6 +#define CS53L30_ADCxA_PDN_MASK (1 << CS53L30_ADCxA_PDN_SHIFT) +#define CS53L30_ADCxA_PDN (1 << CS53L30_ADCxA_PDN_SHIFT) +#define CS53L30_DMICx_PDN_SHIFT 2 +#define CS53L30_DMICx_PDN_MASK (1 << CS53L30_DMICx_PDN_SHIFT) +#define CS53L30_DMICx_PDN (1 << CS53L30_DMICx_PDN_SHIFT) +#define CS53L30_DMICx_SCLK_DIV_SHIFT 1 +#define CS53L30_DMICx_SCLK_DIV_MASK (1 << CS53L30_DMICx_SCLK_DIV_SHIFT) +#define CS53L30_DMICx_SCLK_DIV (1 << CS53L30_DMICx_SCLK_DIV_SHIFT) +#define CS53L30_CH_TYPE_SHIFT 0 +#define CS53L30_CH_TYPE_MASK (1 << CS53L30_CH_TYPE_SHIFT) +#define CS53L30_CH_TYPE (1 << CS53L30_CH_TYPE_SHIFT) + +#define CS53L30_ADCDMICx_PDN_MASK 0xFF +#define CS53L30_ADCDMICx_CTL1_DEFAULT (CS53L30_DMICx_PDN) + +/* R38/R46 (0x26/0x2E) CS53L30_ADCDMICx_CTL2 - ADC1/DMIC1 & ADC2/DMIC2 Control 2 */ +#define CS53L30_ADCx_NOTCH_DIS_SHIFT 7 +#define CS53L30_ADCx_NOTCH_DIS_MASK (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT) +#define CS53L30_ADCx_NOTCH_DIS (1 << CS53L30_ADCx_NOTCH_DIS_SHIFT) +#define CS53L30_ADCxB_INV_SHIFT 5 +#define CS53L30_ADCxB_INV_MASK (1 << CS53L30_ADCxB_INV_SHIFT) +#define CS53L30_ADCxB_INV (1 << CS53L30_ADCxB_INV_SHIFT) +#define CS53L30_ADCxA_INV_SHIFT 4 +#define CS53L30_ADCxA_INV_MASK (1 << CS53L30_ADCxA_INV_SHIFT) +#define CS53L30_ADCxA_INV (1 << CS53L30_ADCxA_INV_SHIFT) +#define CS53L30_ADCxB_DIG_BOOST_SHIFT 1 +#define CS53L30_ADCxB_DIG_BOOST_MASK (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT) +#define CS53L30_ADCxB_DIG_BOOST (1 << CS53L30_ADCxB_DIG_BOOST_SHIFT) +#define CS53L30_ADCxA_DIG_BOOST_SHIFT 0 +#define CS53L30_ADCxA_DIG_BOOST_MASK (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT) +#define CS53L30_ADCxA_DIG_BOOST (1 << CS53L30_ADCxA_DIG_BOOST_SHIFT) + +#define CS53L30_ADCDMIC1_CTL2_DEFAULT (0) + +/* R39/R47 (0x27/0x2F) CS53L30_ADCx_CTL3 - ADC1/ADC2 Control 3 */ +#define CS53L30_ADCx_HPF_EN_SHIFT 3 +#define CS53L30_ADCx_HPF_EN_MASK (1 << CS53L30_ADCx_HPF_EN_SHIFT) +#define CS53L30_ADCx_HPF_EN (1 << CS53L30_ADCx_HPF_EN_SHIFT) +#define CS53L30_ADCx_HPF_CF_SHIFT 1 +#define CS53L30_ADCx_HPF_CF_WIDTH 2 +#define CS53L30_ADCx_HPF_CF_MASK (((1 << CS53L30_ADCx_HPF_CF_WIDTH) - 1) << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_1HZ86 (0 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_120HZ (1 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_235HZ (2 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_HPF_CF_466HZ (3 << CS53L30_ADCx_HPF_CF_SHIFT) +#define CS53L30_ADCx_NG_ALL_SHIFT 0 +#define CS53L30_ADCx_NG_ALL_MASK (1 << CS53L30_ADCx_NG_ALL_SHIFT) +#define CS53L30_ADCx_NG_ALL (1 << CS53L30_ADCx_NG_ALL_SHIFT) + +#define CS53L30_ADCx_CTL3_DEFAULT (CS53L30_ADCx_HPF_EN) + +/* R40/R48 (0x28/0x30) CS53L30_ADCx_NG_CTL - ADC1/ADC2 Noise Gate Control */ +#define CS53L30_ADCxB_NG_SHIFT 7 +#define CS53L30_ADCxB_NG_MASK (1 << CS53L30_ADCxB_NG_SHIFT) +#define CS53L30_ADCxB_NG (1 << CS53L30_ADCxB_NG_SHIFT) +#define CS53L30_ADCxA_NG_SHIFT 6 +#define CS53L30_ADCxA_NG_MASK (1 << CS53L30_ADCxA_NG_SHIFT) +#define CS53L30_ADCxA_NG (1 << CS53L30_ADCxA_NG_SHIFT) +#define CS53L30_ADCx_NG_BOOST_SHIFT 5 +#define CS53L30_ADCx_NG_BOOST_MASK (1 << CS53L30_ADCx_NG_BOOST_SHIFT) +#define CS53L30_ADCx_NG_BOOST (1 << CS53L30_ADCx_NG_BOOST_SHIFT) +#define CS53L30_ADCx_NG_THRESH_SHIFT 2 +#define CS53L30_ADCx_NG_THRESH_WIDTH 3 +#define CS53L30_ADCx_NG_THRESH_MASK (((1 << CS53L30_ADCx_NG_THRESH_WIDTH) - 1) << CS53L30_ADCx_NG_THRESH_SHIFT) +#define CS53L30_ADCx_NG_DELAY_SHIFT 0 +#define CS53L30_ADCx_NG_DELAY_WIDTH 2 +#define CS53L30_ADCx_NG_DELAY_MASK (((1 << CS53L30_ADCx_NG_DELAY_WIDTH) - 1) << CS53L30_ADCx_NG_DELAY_SHIFT) + +#define CS53L30_ADCx_NG_CTL_DEFAULT (0) + +/* R41/R42/R49/R50 (0x29/0x2A/0x31/0x32) CS53L30_ADCxy_AFE_CTL - ADC1A/1B/2A/2B AFE Control */ +#define CS53L30_ADCxy_PREAMP_SHIFT 6 +#define CS53L30_ADCxy_PREAMP_WIDTH 2 +#define CS53L30_ADCxy_PREAMP_MASK (((1 << CS53L30_ADCxy_PREAMP_WIDTH) - 1) << CS53L30_ADCxy_PREAMP_SHIFT) +#define CS53L30_ADCxy_PGA_VOL_SHIFT 0 +#define CS53L30_ADCxy_PGA_VOL_WIDTH 6 +#define CS53L30_ADCxy_PGA_VOL_MASK (((1 << CS53L30_ADCxy_PGA_VOL_WIDTH) - 1) << CS53L30_ADCxy_PGA_VOL_SHIFT) + +#define CS53L30_ADCxy_AFE_CTL_DEFAULT (0) + +/* R43/R44/R51/R52 (0x2B/0x2C/0x33/0x34) CS53L30_ADCxy_DIG_VOL - ADC1A/1B/2A/2B Digital Volume */ +#define CS53L30_ADCxy_VOL_MUTE (0x80) + +#define CS53L30_ADCxy_DIG_VOL_DEFAULT (0x0) + +/* CS53L30_INT */ +#define CS53L30_PDN_DONE (1 << 7) +#define CS53L30_THMS_TRIP (1 << 6) +#define CS53L30_SYNC_DONE (1 << 5) +#define CS53L30_ADC2B_OVFL (1 << 4) +#define CS53L30_ADC2A_OVFL (1 << 3) +#define CS53L30_ADC1B_OVFL (1 << 2) +#define CS53L30_ADC1A_OVFL (1 << 1) +#define CS53L30_MUTE_PIN (1 << 0) +#define CS53L30_DEVICE_INT_MASK 0xFF + +#endif /* __CS53L30_H__ */ diff --git a/sound/soc/codecs/da7219-aad.c b/sound/soc/codecs/da7219-aad.c index 9459593eef13..f0057cd223a4 100644 --- a/sound/soc/codecs/da7219-aad.c +++ b/sound/soc/codecs/da7219-aad.c @@ -13,8 +13,8 @@ #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/of_device.h> -#include <linux/of_irq.h> +#include <linux/i2c.h> +#include <linux/property.h> #include <linux/pm_wakeirq.h> #include <linux/slab.h> #include <linux/delay.h> @@ -382,11 +382,11 @@ static irqreturn_t da7219_aad_irq_thread(int irq, void *data) } /* - * DT to pdata conversion + * DT/ACPI to pdata conversion */ static enum da7219_aad_micbias_pulse_lvl - da7219_aad_of_micbias_pulse_lvl(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_micbias_pulse_lvl(struct snd_soc_codec *codec, u32 val) { switch (val) { case 2800: @@ -400,7 +400,7 @@ static enum da7219_aad_micbias_pulse_lvl } static enum da7219_aad_btn_cfg - da7219_aad_of_btn_cfg(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_btn_cfg(struct snd_soc_codec *codec, u32 val) { switch (val) { case 2: @@ -424,7 +424,7 @@ static enum da7219_aad_btn_cfg } static enum da7219_aad_mic_det_thr - da7219_aad_of_mic_det_thr(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_mic_det_thr(struct snd_soc_codec *codec, u32 val) { switch (val) { case 200: @@ -442,7 +442,7 @@ static enum da7219_aad_mic_det_thr } static enum da7219_aad_jack_ins_deb - da7219_aad_of_jack_ins_deb(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_jack_ins_deb(struct snd_soc_codec *codec, u32 val) { switch (val) { case 5: @@ -468,7 +468,7 @@ static enum da7219_aad_jack_ins_deb } static enum da7219_aad_jack_det_rate - da7219_aad_of_jack_det_rate(struct snd_soc_codec *codec, const char *str) + da7219_aad_fw_jack_det_rate(struct snd_soc_codec *codec, const char *str) { if (!strcmp(str, "32ms_64ms")) { return DA7219_AAD_JACK_DET_RATE_32_64MS; @@ -485,7 +485,7 @@ static enum da7219_aad_jack_det_rate } static enum da7219_aad_jack_rem_deb - da7219_aad_of_jack_rem_deb(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_jack_rem_deb(struct snd_soc_codec *codec, u32 val) { switch (val) { case 1: @@ -503,7 +503,7 @@ static enum da7219_aad_jack_rem_deb } static enum da7219_aad_btn_avg - da7219_aad_of_btn_avg(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_btn_avg(struct snd_soc_codec *codec, u32 val) { switch (val) { case 1: @@ -521,7 +521,7 @@ static enum da7219_aad_btn_avg } static enum da7219_aad_adc_1bit_rpt - da7219_aad_of_adc_1bit_rpt(struct snd_soc_codec *codec, u32 val) + da7219_aad_fw_adc_1bit_rpt(struct snd_soc_codec *codec, u32 val) { switch (val) { case 1: @@ -538,97 +538,96 @@ static enum da7219_aad_adc_1bit_rpt } } -static struct da7219_aad_pdata *da7219_aad_of_to_pdata(struct snd_soc_codec *codec) +static struct da7219_aad_pdata *da7219_aad_fw_to_pdata(struct snd_soc_codec *codec) { - struct device_node *np = codec->dev->of_node; - struct device_node *aad_np = of_find_node_by_name(np, "da7219_aad"); + struct device *dev = codec->dev; + struct i2c_client *i2c = to_i2c_client(dev); + struct fwnode_handle *aad_np; struct da7219_aad_pdata *aad_pdata; - const char *of_str; - u32 of_val32; + const char *fw_str; + u32 fw_val32; + aad_np = device_get_named_child_node(dev, "da7219_aad"); if (!aad_np) return NULL; aad_pdata = devm_kzalloc(codec->dev, sizeof(*aad_pdata), GFP_KERNEL); if (!aad_pdata) - goto out; + return NULL; - aad_pdata->irq = irq_of_parse_and_map(np, 0); + aad_pdata->irq = i2c->irq; - if (of_property_read_u32(aad_np, "dlg,micbias-pulse-lvl", - &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-lvl", + &fw_val32) >= 0) aad_pdata->micbias_pulse_lvl = - da7219_aad_of_micbias_pulse_lvl(codec, of_val32); + da7219_aad_fw_micbias_pulse_lvl(codec, fw_val32); else aad_pdata->micbias_pulse_lvl = DA7219_AAD_MICBIAS_PULSE_LVL_OFF; - if (of_property_read_u32(aad_np, "dlg,micbias-pulse-time", - &of_val32) >= 0) - aad_pdata->micbias_pulse_time = of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-time", + &fw_val32) >= 0) + aad_pdata->micbias_pulse_time = fw_val32; - if (of_property_read_u32(aad_np, "dlg,btn-cfg", &of_val32) >= 0) - aad_pdata->btn_cfg = da7219_aad_of_btn_cfg(codec, of_val32); + if (fwnode_property_read_u32(aad_np, "dlg,btn-cfg", &fw_val32) >= 0) + aad_pdata->btn_cfg = da7219_aad_fw_btn_cfg(codec, fw_val32); else aad_pdata->btn_cfg = DA7219_AAD_BTN_CFG_10MS; - if (of_property_read_u32(aad_np, "dlg,mic-det-thr", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,mic-det-thr", &fw_val32) >= 0) aad_pdata->mic_det_thr = - da7219_aad_of_mic_det_thr(codec, of_val32); + da7219_aad_fw_mic_det_thr(codec, fw_val32); else aad_pdata->mic_det_thr = DA7219_AAD_MIC_DET_THR_500_OHMS; - if (of_property_read_u32(aad_np, "dlg,jack-ins-deb", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,jack-ins-deb", &fw_val32) >= 0) aad_pdata->jack_ins_deb = - da7219_aad_of_jack_ins_deb(codec, of_val32); + da7219_aad_fw_jack_ins_deb(codec, fw_val32); else aad_pdata->jack_ins_deb = DA7219_AAD_JACK_INS_DEB_20MS; - if (!of_property_read_string(aad_np, "dlg,jack-det-rate", &of_str)) + if (!fwnode_property_read_string(aad_np, "dlg,jack-det-rate", &fw_str)) aad_pdata->jack_det_rate = - da7219_aad_of_jack_det_rate(codec, of_str); + da7219_aad_fw_jack_det_rate(codec, fw_str); else aad_pdata->jack_det_rate = DA7219_AAD_JACK_DET_RATE_256_512MS; - if (of_property_read_u32(aad_np, "dlg,jack-rem-deb", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,jack-rem-deb", &fw_val32) >= 0) aad_pdata->jack_rem_deb = - da7219_aad_of_jack_rem_deb(codec, of_val32); + da7219_aad_fw_jack_rem_deb(codec, fw_val32); else aad_pdata->jack_rem_deb = DA7219_AAD_JACK_REM_DEB_1MS; - if (of_property_read_u32(aad_np, "dlg,a-d-btn-thr", &of_val32) >= 0) - aad_pdata->a_d_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,a-d-btn-thr", &fw_val32) >= 0) + aad_pdata->a_d_btn_thr = (u8) fw_val32; else aad_pdata->a_d_btn_thr = 0xA; - if (of_property_read_u32(aad_np, "dlg,d-b-btn-thr", &of_val32) >= 0) - aad_pdata->d_b_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,d-b-btn-thr", &fw_val32) >= 0) + aad_pdata->d_b_btn_thr = (u8) fw_val32; else aad_pdata->d_b_btn_thr = 0x16; - if (of_property_read_u32(aad_np, "dlg,b-c-btn-thr", &of_val32) >= 0) - aad_pdata->b_c_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,b-c-btn-thr", &fw_val32) >= 0) + aad_pdata->b_c_btn_thr = (u8) fw_val32; else aad_pdata->b_c_btn_thr = 0x21; - if (of_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &of_val32) >= 0) - aad_pdata->c_mic_btn_thr = (u8) of_val32; + if (fwnode_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &fw_val32) >= 0) + aad_pdata->c_mic_btn_thr = (u8) fw_val32; else aad_pdata->c_mic_btn_thr = 0x3E; - if (of_property_read_u32(aad_np, "dlg,btn-avg", &of_val32) >= 0) - aad_pdata->btn_avg = da7219_aad_of_btn_avg(codec, of_val32); + if (fwnode_property_read_u32(aad_np, "dlg,btn-avg", &fw_val32) >= 0) + aad_pdata->btn_avg = da7219_aad_fw_btn_avg(codec, fw_val32); else aad_pdata->btn_avg = DA7219_AAD_BTN_AVG_2; - if (of_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &of_val32) >= 0) + if (fwnode_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &fw_val32) >= 0) aad_pdata->adc_1bit_rpt = - da7219_aad_of_adc_1bit_rpt(codec, of_val32); + da7219_aad_fw_adc_1bit_rpt(codec, fw_val32); else aad_pdata->adc_1bit_rpt = DA7219_AAD_ADC_1BIT_RPT_1; -out: - of_node_put(aad_np); - return aad_pdata; } @@ -769,9 +768,9 @@ int da7219_aad_init(struct snd_soc_codec *codec) da7219->aad = da7219_aad; da7219_aad->codec = codec; - /* Handle any DT/platform data */ - if ((codec->dev->of_node) && (da7219->pdata)) - da7219->pdata->aad_pdata = da7219_aad_of_to_pdata(codec); + /* Handle any DT/ACPI/platform data */ + if (da7219->pdata && !da7219->pdata->aad_pdata) + da7219->pdata->aad_pdata = da7219_aad_fw_to_pdata(codec); da7219_aad_handle_pdata(codec); diff --git a/sound/soc/codecs/da7219.c b/sound/soc/codecs/da7219.c index 5c93899f1f0e..50ea94317cb3 100644 --- a/sound/soc/codecs/da7219.c +++ b/sound/soc/codecs/da7219.c @@ -15,6 +15,7 @@ #include <linux/clk.h> #include <linux/i2c.h> #include <linux/of_device.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/slab.h> #include <linux/pm.h> @@ -1418,7 +1419,7 @@ static struct snd_soc_dai_driver da7219_dai = { /* - * DT + * DT/ACPI */ static const struct of_device_id da7219_of_match[] = { @@ -1434,7 +1435,7 @@ static const struct acpi_device_id da7219_acpi_match[] = { MODULE_DEVICE_TABLE(acpi, da7219_acpi_match); static enum da7219_micbias_voltage - da7219_of_micbias_lvl(struct snd_soc_codec *codec, u32 val) + da7219_fw_micbias_lvl(struct device *dev, u32 val) { switch (val) { case 1600: @@ -1450,13 +1451,13 @@ static enum da7219_micbias_voltage case 2600: return DA7219_MICBIAS_2_6V; default: - dev_warn(codec->dev, "Invalid micbias level"); + dev_warn(dev, "Invalid micbias level"); return DA7219_MICBIAS_2_2V; } } static enum da7219_mic_amp_in_sel - da7219_of_mic_amp_in_sel(struct snd_soc_codec *codec, const char *str) + da7219_fw_mic_amp_in_sel(struct device *dev, const char *str) { if (!strcmp(str, "diff")) { return DA7219_MIC_AMP_IN_SEL_DIFF; @@ -1465,29 +1466,29 @@ static enum da7219_mic_amp_in_sel } else if (!strcmp(str, "se_n")) { return DA7219_MIC_AMP_IN_SEL_SE_N; } else { - dev_warn(codec->dev, "Invalid mic input type selection"); + dev_warn(dev, "Invalid mic input type selection"); return DA7219_MIC_AMP_IN_SEL_DIFF; } } -static struct da7219_pdata *da7219_of_to_pdata(struct snd_soc_codec *codec) +static struct da7219_pdata *da7219_fw_to_pdata(struct snd_soc_codec *codec) { - struct device_node *np = codec->dev->of_node; + struct device *dev = codec->dev; struct da7219_pdata *pdata; const char *of_str; u32 of_val32; - pdata = devm_kzalloc(codec->dev, sizeof(*pdata), GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return NULL; - if (of_property_read_u32(np, "dlg,micbias-lvl", &of_val32) >= 0) - pdata->micbias_lvl = da7219_of_micbias_lvl(codec, of_val32); + if (device_property_read_u32(dev, "dlg,micbias-lvl", &of_val32) >= 0) + pdata->micbias_lvl = da7219_fw_micbias_lvl(dev, of_val32); else pdata->micbias_lvl = DA7219_MICBIAS_2_2V; - if (!of_property_read_string(np, "dlg,mic-amp-in-sel", &of_str)) - pdata->mic_amp_in_sel = da7219_of_mic_amp_in_sel(codec, of_str); + if (!device_property_read_string(dev, "dlg,mic-amp-in-sel", &of_str)) + pdata->mic_amp_in_sel = da7219_fw_mic_amp_in_sel(dev, of_str); else pdata->mic_amp_in_sel = DA7219_MIC_AMP_IN_SEL_DIFF; @@ -1662,11 +1663,10 @@ static int da7219_probe(struct snd_soc_codec *codec) break; } - /* Handle DT/Platform data */ - if (codec->dev->of_node) - da7219->pdata = da7219_of_to_pdata(codec); - else - da7219->pdata = dev_get_platdata(codec->dev); + /* Handle DT/ACPI/Platform data */ + da7219->pdata = dev_get_platdata(codec->dev); + if (!da7219->pdata) + da7219->pdata = da7219_fw_to_pdata(codec); da7219_handle_pdata(codec); diff --git a/sound/soc/codecs/hdac_hdmi.c b/sound/soc/codecs/hdac_hdmi.c index 2abb742fc47b..4e181b270d95 100644 --- a/sound/soc/codecs/hdac_hdmi.c +++ b/sound/soc/codecs/hdac_hdmi.c @@ -1124,8 +1124,10 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll) } hdac_hdmi_parse_eld(edev, pin); - print_hex_dump_bytes("ELD: ", DUMP_PREFIX_OFFSET, - pin->eld.eld_buffer, pin->eld.eld_size); + print_hex_dump_debug("ELD: ", + DUMP_PREFIX_OFFSET, 16, 1, + pin->eld.eld_buffer, pin->eld.eld_size, + true); } else { pin->eld.monitor_present = false; pin->eld.eld_valid = false; @@ -1816,6 +1818,7 @@ static const struct dev_pm_ops hdac_hdmi_pm = { static const struct hda_device_id hdmi_list[] = { HDA_CODEC_EXT_ENTRY(0x80862809, 0x100000, "Skylake HDMI", 0), HDA_CODEC_EXT_ENTRY(0x8086280a, 0x100000, "Broxton HDMI", 0), + HDA_CODEC_EXT_ENTRY(0x8086280b, 0x100000, "Kabylake HDMI", 0), {} }; diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 8e36e883e453..f27d115626db 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -112,7 +112,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, return ret; if (hcp->hcd.ops->audio_startup) { - ret = hcp->hcd.ops->audio_startup(dai->dev->parent); + ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); if (ret) { mutex_lock(&hcp->current_stream_lock); hcp->current_stream = NULL; @@ -122,8 +122,8 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, } if (hcp->hcd.ops->get_eld) { - ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->eld, - sizeof(hcp->eld)); + ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, + hcp->eld, sizeof(hcp->eld)); if (!ret) { ret = snd_pcm_hw_constraint_eld(substream->runtime, @@ -144,7 +144,7 @@ static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, WARN_ON(hcp->current_stream != substream); - hcp->hcd.ops->audio_shutdown(dai->dev->parent); + hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); mutex_lock(&hcp->current_stream_lock); hcp->current_stream = NULL; @@ -195,8 +195,8 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, hp.sample_rate = params_rate(params); hp.channels = params_channels(params); - return hcp->hcd.ops->hw_params(dai->dev->parent, &hcp->daifmt[dai->id], - &hp); + return hcp->hcd.ops->hw_params(dai->dev->parent, hcp->hcd.data, + &hcp->daifmt[dai->id], &hp); } static int hdmi_codec_set_fmt(struct snd_soc_dai *dai, @@ -280,7 +280,8 @@ static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute) dev_dbg(dai->dev, "%s()\n", __func__); if (hcp->hcd.ops->digital_mute) - return hcp->hcd.ops->digital_mute(dai->dev->parent, mute); + return hcp->hcd.ops->digital_mute(dai->dev->parent, + hcp->hcd.data, mute); return 0; } diff --git a/sound/soc/codecs/max98504.c b/sound/soc/codecs/max98504.c new file mode 100644 index 000000000000..a7320e709890 --- /dev/null +++ b/sound/soc/codecs/max98504.c @@ -0,0 +1,383 @@ +/* + * MAX98504 ALSA SoC Audio driver + * + * Copyright 2013 - 2014 Maxim Integrated Products + * Copyright 2016 Samsung Electronics Co., 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. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <sound/soc.h> + +#include "max98504.h" + +static const char * const max98504_supply_names[] = { + "DVDD", + "DIOVDD", + "PVDD", +}; +#define MAX98504_NUM_SUPPLIES ARRAY_SIZE(max98504_supply_names) + +struct max98504_priv { + struct regmap *regmap; + struct regulator_bulk_data supplies[MAX98504_NUM_SUPPLIES]; + unsigned int pcm_rx_channels; + bool brownout_enable; + unsigned int brownout_threshold; + unsigned int brownout_attenuation; + unsigned int brownout_attack_hold; + unsigned int brownout_timed_hold; + unsigned int brownout_release_rate; +}; + +static struct reg_default max98504_reg_defaults[] = { + { 0x01, 0}, + { 0x02, 0}, + { 0x03, 0}, + { 0x04, 0}, + { 0x10, 0}, + { 0x11, 0}, + { 0x12, 0}, + { 0x13, 0}, + { 0x14, 0}, + { 0x15, 0}, + { 0x16, 0}, + { 0x17, 0}, + { 0x18, 0}, + { 0x19, 0}, + { 0x1A, 0}, + { 0x20, 0}, + { 0x21, 0}, + { 0x22, 0}, + { 0x23, 0}, + { 0x24, 0}, + { 0x25, 0}, + { 0x26, 0}, + { 0x27, 0}, + { 0x28, 0}, + { 0x30, 0}, + { 0x31, 0}, + { 0x32, 0}, + { 0x33, 0}, + { 0x34, 0}, + { 0x35, 0}, + { 0x36, 0}, + { 0x37, 0}, + { 0x38, 0}, + { 0x39, 0}, + { 0x40, 0}, + { 0x41, 0}, +}; + +static bool max98504_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98504_INTERRUPT_STATUS: + case MAX98504_INTERRUPT_FLAGS: + case MAX98504_INTERRUPT_FLAG_CLEARS: + case MAX98504_WATCHDOG_CLEAR: + case MAX98504_GLOBAL_ENABLE: + case MAX98504_SOFTWARE_RESET: + return true; + default: + return false; + } +} + +static bool max98504_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98504_SOFTWARE_RESET: + case MAX98504_WATCHDOG_CLEAR: + case MAX98504_INTERRUPT_FLAG_CLEARS: + return false; + default: + return true; + } +} + +static int max98504_pcm_rx_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, + max98504->pcm_rx_channels); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_write(max98504->regmap, MAX98504_PCM_RX_ENABLE, 0); + break; + } + + return 0; +} + +static int max98504_component_probe(struct snd_soc_component *c) +{ + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + struct regmap *map = max98504->regmap; + int ret; + + ret = regulator_bulk_enable(MAX98504_NUM_SUPPLIES, max98504->supplies); + if (ret < 0) + return ret; + + regmap_write(map, MAX98504_SOFTWARE_RESET, 0x1); + msleep(20); + + if (!max98504->brownout_enable) + return 0; + + regmap_write(map, MAX98504_PVDD_BROWNOUT_ENABLE, 0x1); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_1, + (max98504->brownout_threshold & 0x1f) << 3 | + (max98504->brownout_attenuation & 0x3)); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_2, + max98504->brownout_attack_hold & 0xff); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_3, + max98504->brownout_timed_hold & 0xff); + + regmap_write(map, MAX98504_PVDD_BROWNOUT_CONFIG_4, + max98504->brownout_release_rate & 0xff); + + return 0; +} + +static void max98504_component_remove(struct snd_soc_component *c) +{ + struct max98504_priv *max98504 = snd_soc_component_get_drvdata(c); + + regulator_bulk_disable(MAX98504_NUM_SUPPLIES, max98504->supplies); +} + +static const char *spk_source_mux_text[] = { + "PCM Monomix", "Analog In", "PDM Left", "PDM Right" +}; + +static const struct soc_enum spk_source_mux_enum = + SOC_ENUM_SINGLE(MAX98504_SPEAKER_SOURCE_SELECT, + 0, ARRAY_SIZE(spk_source_mux_text), + spk_source_mux_text); + +static const struct snd_kcontrol_new spk_source_mux = + SOC_DAPM_ENUM("SPK Source", spk_source_mux_enum); + +static const struct snd_soc_dapm_route max98504_dapm_routes[] = { + { "SPKOUT", NULL, "Global Enable" }, + { "SPK Source", "PCM Monomix", "DAC PCM" }, + { "SPK Source", "Analog In", "AIN" }, + { "SPK Source", "PDM Left", "DAC PDM" }, + { "SPK Source", "PDM Right", "DAC PDM" }, +}; + +static const struct snd_soc_dapm_widget max98504_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("Global Enable", MAX98504_GLOBAL_ENABLE, + 0, 0, NULL, 0), + SND_SOC_DAPM_INPUT("AIN"), + SND_SOC_DAPM_AIF_OUT("AIF2OUTL", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2OUTR", "AIF2 Capture", 1, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC_E("DAC PCM", NULL, SND_SOC_NOPM, 0, 0, + max98504_pcm_rx_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC("DAC PDM", NULL, MAX98504_PDM_RX_ENABLE, 0, 0), + SND_SOC_DAPM_MUX("SPK Source", SND_SOC_NOPM, 0, 0, &spk_source_mux), + SND_SOC_DAPM_REG(snd_soc_dapm_spk, "SPKOUT", + MAX98504_SPEAKER_ENABLE, 0, 1, 1, 0), +}; + +static int max98504_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai); + struct regmap *map = max98504->regmap; + + + switch (dai->id) { + case MAX98504_DAI_ID_PCM: + regmap_write(map, MAX98504_PCM_TX_ENABLE, tx_mask); + max98504->pcm_rx_channels = rx_mask; + break; + + case MAX98504_DAI_ID_PDM: + regmap_write(map, MAX98504_PDM_TX_ENABLE, tx_mask); + break; + default: + WARN_ON(1); + } + + return 0; +} +static int max98504_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct max98504_priv *max98504 = snd_soc_dai_get_drvdata(dai); + struct regmap *map = max98504->regmap; + unsigned int i, sources = 0; + + for (i = 0; i < tx_num; i++) + if (tx_slot[i]) + sources |= (1 << i); + + switch (dai->id) { + case MAX98504_DAI_ID_PCM: + regmap_write(map, MAX98504_PCM_TX_CHANNEL_SOURCES, + sources); + break; + + case MAX98504_DAI_ID_PDM: + regmap_write(map, MAX98504_PDM_TX_CONTROL, sources); + break; + default: + WARN_ON(1); + } + + regmap_write(map, MAX98504_MEASUREMENT_ENABLE, sources ? 0x3 : 0x01); + + return 0; +} + +static const struct snd_soc_dai_ops max98504_dai_ops = { + .set_tdm_slot = max98504_set_tdm_slot, + .set_channel_map = max98504_set_channel_map, +}; + +#define MAX98504_FORMATS (SNDRV_PCM_FMTBIT_S8|SNDRV_PCM_FMTBIT_S16_LE|\ + SNDRV_PCM_FMTBIT_S24_LE|SNDRV_PCM_FMTBIT_S32_LE) +#define MAX98504_PDM_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\ + SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|\ + SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|\ + SNDRV_PCM_RATE_96000) + +static struct snd_soc_dai_driver max98504_dai[] = { + /* TODO: Add the PCM interface definitions */ + { + .name = "max98504-aif2", + .id = MAX98504_DAI_ID_PDM, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98504_PDM_RATES, + .formats = MAX98504_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98504_PDM_RATES, + .formats = MAX98504_FORMATS, + }, + .ops = &max98504_dai_ops, + }, +}; + +static const struct snd_soc_component_driver max98504_component_driver = { + .probe = max98504_component_probe, + .remove = max98504_component_remove, + .dapm_widgets = max98504_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98504_dapm_widgets), + .dapm_routes = max98504_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max98504_dapm_routes), +}; + +static const struct regmap_config max98504_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98504_MAX_REGISTER, + .reg_defaults = max98504_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max98504_reg_defaults), + .volatile_reg = max98504_volatile_register, + .readable_reg = max98504_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int max98504_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device_node *node = dev->of_node; + struct max98504_priv *max98504; + int i, ret; + + max98504 = devm_kzalloc(dev, sizeof(*max98504), GFP_KERNEL); + if (!max98504) + return -ENOMEM; + + if (node) { + if (!of_property_read_u32(node, "maxim,brownout-threshold", + &max98504->brownout_threshold)) + max98504->brownout_enable = true; + + of_property_read_u32(node, "maxim,brownout-attenuation", + &max98504->brownout_attenuation); + of_property_read_u32(node, "maxim,brownout-attack-hold-ms", + &max98504->brownout_attack_hold); + of_property_read_u32(node, "maxim,brownout-timed-hold-ms", + &max98504->brownout_timed_hold); + of_property_read_u32(node, "maxim,brownout-release-rate-ms", + &max98504->brownout_release_rate); + } + + max98504->regmap = devm_regmap_init_i2c(client, &max98504_regmap); + if (IS_ERR(max98504->regmap)) { + ret = PTR_ERR(max98504->regmap); + dev_err(&client->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + for (i = 0; i < MAX98504_NUM_SUPPLIES; i++) + max98504->supplies[i].supply = max98504_supply_names[i]; + + ret = devm_regulator_bulk_get(dev, MAX98504_NUM_SUPPLIES, + max98504->supplies); + if (ret < 0) + return ret; + + i2c_set_clientdata(client, max98504); + + return devm_snd_soc_register_component(dev, &max98504_component_driver, + max98504_dai, ARRAY_SIZE(max98504_dai)); +} + +#ifdef CONFIG_OF +static const struct of_device_id max98504_of_match[] = { + { .compatible = "maxim,max98504" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max98504_of_match); +#endif + +static const struct i2c_device_id max98504_i2c_id[] = { + { "max98504" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98504_i2c_id); + +static struct i2c_driver max98504_i2c_driver = { + .driver = { + .name = "max98504", + .of_match_table = of_match_ptr(max98504_of_match), + }, + .probe = max98504_i2c_probe, + .id_table = max98504_i2c_id, +}; +module_i2c_driver(max98504_i2c_driver); + +MODULE_DESCRIPTION("ASoC MAX98504 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98504.h b/sound/soc/codecs/max98504.h new file mode 100644 index 000000000000..afbefad2d5ce --- /dev/null +++ b/sound/soc/codecs/max98504.h @@ -0,0 +1,59 @@ +/* + * MAX98504 ALSA SoC Audio driver + * + * Copyright 2011 - 2012 Maxim Integrated Products + * Copyright 2016 Samsung Electronics Co., 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 MAX98504_H_ +#define MAX98504_H_ + +/* + * MAX98504 Register Definitions + */ +#define MAX98504_INTERRUPT_STATUS 0x01 +#define MAX98504_INTERRUPT_FLAGS 0x02 +#define MAX98504_INTERRUPT_ENABLE 0x03 +#define MAX98504_INTERRUPT_FLAG_CLEARS 0x04 +#define MAX98504_GPIO_ENABLE 0x10 +#define MAX98504_GPIO_CONFIG 0x11 +#define MAX98504_WATCHDOG_ENABLE 0x12 +#define MAX98504_WATCHDOG_CONFIG 0x13 +#define MAX98504_WATCHDOG_CLEAR 0x14 +#define MAX98504_CLOCK_MONITOR_ENABLE 0x15 +#define MAX98504_PVDD_BROWNOUT_ENABLE 0x16 +#define MAX98504_PVDD_BROWNOUT_CONFIG_1 0x17 +#define MAX98504_PVDD_BROWNOUT_CONFIG_2 0x18 +#define MAX98504_PVDD_BROWNOUT_CONFIG_3 0x19 +#define MAX98504_PVDD_BROWNOUT_CONFIG_4 0x1a +#define MAX98504_PCM_RX_ENABLE 0x20 +#define MAX98504_PCM_TX_ENABLE 0x21 +#define MAX98504_PCM_TX_HIZ_CONTROL 0x22 +#define MAX98504_PCM_TX_CHANNEL_SOURCES 0x23 +#define MAX98504_PCM_MODE_CONFIG 0x24 +#define MAX98504_PCM_DSP_CONFIG 0x25 +#define MAX98504_PCM_CLOCK_SETUP 0x26 +#define MAX98504_PCM_SAMPLE_RATE_SETUP 0x27 +#define MAX98504_PCM_TO_SPEAKER_MONOMIX 0x28 +#define MAX98504_PDM_TX_ENABLE 0x30 +#define MAX98504_PDM_TX_HIZ_CONTROL 0x31 +#define MAX98504_PDM_TX_CONTROL 0x32 +#define MAX98504_PDM_RX_ENABLE 0x33 +#define MAX98504_SPEAKER_ENABLE 0x34 +#define MAX98504_SPEAKER_SOURCE_SELECT 0x35 +#define MAX98504_MEASUREMENT_ENABLE 0x36 +#define MAX98504_ANALOGUE_INPUT_GAIN 0x37 +#define MAX98504_TEMPERATURE_LIMIT_CONFIG 0x38 +#define MAX98504_GLOBAL_ENABLE 0x40 +#define MAX98504_SOFTWARE_RESET 0x41 +#define MAX98504_REV_ID 0x7fff + +#define MAX98504_MAX_REGISTER 0x7fff + +#define MAX98504_DAI_ID_PCM 1 +#define MAX98504_DAI_ID_PDM 2 + +#endif /* MAX98504_H_ */ diff --git a/sound/soc/codecs/max9860.c b/sound/soc/codecs/max9860.c new file mode 100644 index 000000000000..68074c92a7c0 --- /dev/null +++ b/sound/soc/codecs/max9860.c @@ -0,0 +1,753 @@ +/* + * Driver for the MAX9860 Mono Audio Voice Codec + * + * https://datasheets.maximintegrated.com/en/ds/MAX9860.pdf + * + * The driver does not support sidetone since the DVST register field is + * backwards with the mute near the maximum level instead of the minimum. + * + * Author: Peter Rosin <peda@axentia.s> + * Copyright 2016 Axentia Technologies + * + * 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> + +#include "max9860.h" + +struct max9860_priv { + struct regmap *regmap; + struct regulator *dvddio; + struct notifier_block dvddio_nb; + u8 psclk; + unsigned long pclk_rate; + int fmt; +}; + +static int max9860_dvddio_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct max9860_priv *max9860 = container_of(nb, struct max9860_priv, + dvddio_nb); + if (event & REGULATOR_EVENT_DISABLE) { + regcache_mark_dirty(max9860->regmap); + regcache_cache_only(max9860->regmap, true); + } + + return 0; +} + +static const struct reg_default max9860_reg_defaults[] = { + { MAX9860_PWRMAN, 0x00 }, + { MAX9860_INTEN, 0x00 }, + { MAX9860_SYSCLK, 0x00 }, + { MAX9860_AUDIOCLKHIGH, 0x00 }, + { MAX9860_AUDIOCLKLOW, 0x00 }, + { MAX9860_IFC1A, 0x00 }, + { MAX9860_IFC1B, 0x00 }, + { MAX9860_VOICEFLTR, 0x00 }, + { MAX9860_DACATTN, 0x00 }, + { MAX9860_ADCLEVEL, 0x00 }, + { MAX9860_DACGAIN, 0x00 }, + { MAX9860_MICGAIN, 0x00 }, + { MAX9860_MICADC, 0x00 }, + { MAX9860_NOISEGATE, 0x00 }, +}; + +static bool max9860_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS ... MAX9860_MICGAIN: + case MAX9860_MICADC ... MAX9860_PWRMAN: + case MAX9860_REVISION: + return true; + } + + return false; +} + +static bool max9860_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTEN ... MAX9860_MICGAIN: + case MAX9860_MICADC ... MAX9860_PWRMAN: + return true; + } + + return false; +} + +static bool max9860_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS: + case MAX9860_MICREADBACK: + return true; + } + + return false; +} + +static bool max9860_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX9860_INTRSTATUS: + return true; + } + + return false; +} + +static const struct regmap_config max9860_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = max9860_readable, + .writeable_reg = max9860_writeable, + .volatile_reg = max9860_volatile, + .precious_reg = max9860_precious, + + .max_register = MAX9860_MAX_REGISTER, + .reg_defaults = max9860_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(max9860_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static const DECLARE_TLV_DB_SCALE(dva_tlv, -9100, 100, 1); +static const DECLARE_TLV_DB_SCALE(dvg_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_RANGE(pam_tlv, + 0, MAX9860_PAM_MAX - 1, TLV_DB_SCALE_ITEM(-2000, 2000, 1), + MAX9860_PAM_MAX, MAX9860_PAM_MAX, TLV_DB_SCALE_ITEM(3000, 0, 0)); +static const DECLARE_TLV_DB_SCALE(pgam_tlv, 0, 100, 0); +static const DECLARE_TLV_DB_SCALE(anth_tlv, -7600, 400, 1); +static const DECLARE_TLV_DB_SCALE(agcth_tlv, -1800, 100, 0); + +static const char * const agchld_text[] = { + "AGC Disabled", "50ms", "100ms", "400ms" +}; + +static SOC_ENUM_SINGLE_DECL(agchld_enum, MAX9860_MICADC, + MAX9860_AGCHLD_SHIFT, agchld_text); + +static const char * const agcsrc_text[] = { + "Left ADC", "Left/Right ADC" +}; + +static SOC_ENUM_SINGLE_DECL(agcsrc_enum, MAX9860_MICADC, + MAX9860_AGCSRC_SHIFT, agcsrc_text); + +static const char * const agcatk_text[] = { + "3ms", "12ms", "50ms", "200ms" +}; + +static SOC_ENUM_SINGLE_DECL(agcatk_enum, MAX9860_MICADC, + MAX9860_AGCATK_SHIFT, agcatk_text); + +static const char * const agcrls_text[] = { + "78ms", "156ms", "312ms", "625ms", + "1.25s", "2.5s", "5s", "10s" +}; + +static SOC_ENUM_SINGLE_DECL(agcrls_enum, MAX9860_MICADC, + MAX9860_AGCRLS_SHIFT, agcrls_text); + +static const char * const filter_text[] = { + "Disabled", + "Elliptical HP 217Hz notch (16kHz)", + "Butterworth HP 500Hz (16kHz)", + "Elliptical HP 217Hz notch (8kHz)", + "Butterworth HP 500Hz (8kHz)", + "Butterworth HP 200Hz (48kHz)" +}; + +static SOC_ENUM_SINGLE_DECL(avflt_enum, MAX9860_VOICEFLTR, + MAX9860_AVFLT_SHIFT, filter_text); + +static SOC_ENUM_SINGLE_DECL(dvflt_enum, MAX9860_VOICEFLTR, + MAX9860_DVFLT_SHIFT, filter_text); + +static const struct snd_kcontrol_new max9860_controls[] = { +SOC_SINGLE_TLV("Master Playback Volume", MAX9860_DACATTN, + MAX9860_DVA_SHIFT, MAX9860_DVA_MUTE, 1, dva_tlv), +SOC_SINGLE_TLV("DAC Gain Volume", MAX9860_DACGAIN, + MAX9860_DVG_SHIFT, MAX9860_DVG_MAX, 0, dvg_tlv), +SOC_DOUBLE_TLV("Line Capture Volume", MAX9860_ADCLEVEL, + MAX9860_ADCLL_SHIFT, MAX9860_ADCRL_SHIFT, MAX9860_ADCxL_MIN, 1, + adc_tlv), + +SOC_ENUM("AGC Hold Time", agchld_enum), +SOC_ENUM("AGC/Noise Gate Source", agcsrc_enum), +SOC_ENUM("AGC Attack Time", agcatk_enum), +SOC_ENUM("AGC Release Time", agcrls_enum), + +SOC_SINGLE_TLV("Noise Gate Threshold Volume", MAX9860_NOISEGATE, + MAX9860_ANTH_SHIFT, MAX9860_ANTH_MAX, 0, anth_tlv), +SOC_SINGLE_TLV("AGC Signal Threshold Volume", MAX9860_NOISEGATE, + MAX9860_AGCTH_SHIFT, MAX9860_AGCTH_MIN, 1, agcth_tlv), + +SOC_SINGLE_TLV("Mic PGA Volume", MAX9860_MICGAIN, + MAX9860_PGAM_SHIFT, MAX9860_PGAM_MIN, 1, pgam_tlv), +SOC_SINGLE_TLV("Mic Preamp Volume", MAX9860_MICGAIN, + MAX9860_PAM_SHIFT, MAX9860_PAM_MAX, 0, pam_tlv), + +SOC_ENUM("ADC Filter", avflt_enum), +SOC_ENUM("DAC Filter", dvflt_enum), +}; + +static const struct snd_soc_dapm_widget max9860_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("MICL"), +SND_SOC_DAPM_INPUT("MICR"), + +SND_SOC_DAPM_ADC("ADCL", NULL, MAX9860_PWRMAN, MAX9860_ADCLEN_SHIFT, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, MAX9860_PWRMAN, MAX9860_ADCREN_SHIFT, 0), + +SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_DAC("DAC", NULL, MAX9860_PWRMAN, MAX9860_DACEN_SHIFT, 0), + +SND_SOC_DAPM_OUTPUT("OUT"), + +SND_SOC_DAPM_SUPPLY("Supply", SND_SOC_NOPM, 0, 0, + NULL, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_REGULATOR_SUPPLY("AVDD", 0, 0), +SND_SOC_DAPM_REGULATOR_SUPPLY("DVDD", 0, 0), +SND_SOC_DAPM_CLOCK_SUPPLY("mclk"), +}; + +static const struct snd_soc_dapm_route max9860_dapm_routes[] = { + { "ADCL", NULL, "MICL" }, + { "ADCR", NULL, "MICR" }, + { "AIFOUTL", NULL, "ADCL" }, + { "AIFOUTR", NULL, "ADCR" }, + + { "DAC", NULL, "AIFINL" }, + { "DAC", NULL, "AIFINR" }, + { "OUT", NULL, "DAC" }, + + { "Supply", NULL, "AVDD" }, + { "Supply", NULL, "DVDD" }, + { "Supply", NULL, "mclk" }, + + { "DAC", NULL, "Supply" }, + { "ADCL", NULL, "Supply" }, + { "ADCR", NULL, "Supply" }, +}; + +static int max9860_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct max9860_priv *max9860 = snd_soc_codec_get_drvdata(codec); + u8 master; + u8 ifc1a = 0; + u8 ifc1b = 0; + u8 sysclk = 0; + unsigned long n; + int ret; + + dev_dbg(codec->dev, "hw_params %u Hz, %u channels\n", + params_rate(params), + params_channels(params)); + + if (params_channels(params) == 2) + ifc1b |= MAX9860_ST; + + switch (max9860->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + case SND_SOC_DAIFMT_CBM_CFM: + master = MAX9860_MASTER; + break; + default: + return -EINVAL; + } + ifc1a |= master; + + if (master) { + if (params_width(params) * params_channels(params) > 48) + ifc1b |= MAX9860_BSEL_64X; + else + ifc1b |= MAX9860_BSEL_48X; + } + + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ifc1a |= MAX9860_DDLY; + ifc1b |= MAX9860_ADLY; + break; + case SND_SOC_DAIFMT_LEFT_J: + ifc1a |= MAX9860_WCI; + break; + case SND_SOC_DAIFMT_DSP_A: + if (params_width(params) != 16) { + dev_err(codec->dev, + "DSP_A works for 16 bits per sample only.\n"); + return -EINVAL; + } + ifc1a |= MAX9860_DDLY | MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM; + ifc1b |= MAX9860_ADLY; + break; + case SND_SOC_DAIFMT_DSP_B: + if (params_width(params) != 16) { + dev_err(codec->dev, + "DSP_B works for 16 bits per sample only.\n"); + return -EINVAL; + } + ifc1a |= MAX9860_WCI | MAX9860_HIZ | MAX9860_TDM; + break; + default: + return -EINVAL; + } + + switch (max9860->fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + return -EINVAL; + } + ifc1a ^= MAX9860_WCI; + break; + case SND_SOC_DAIFMT_IB_IF: + switch (max9860->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + return -EINVAL; + } + ifc1a ^= MAX9860_WCI; + /* fall through */ + case SND_SOC_DAIFMT_IB_NF: + ifc1a ^= MAX9860_DBCI; + ifc1b ^= MAX9860_ABCI; + break; + default: + return -EINVAL; + } + + dev_dbg(codec->dev, "IFC1A %02x\n", ifc1a); + ret = regmap_write(max9860->regmap, MAX9860_IFC1A, ifc1a); + if (ret) { + dev_err(codec->dev, "Failed to set IFC1A: %d\n", ret); + return ret; + } + dev_dbg(codec->dev, "IFC1B %02x\n", ifc1b); + ret = regmap_write(max9860->regmap, MAX9860_IFC1B, ifc1b); + if (ret) { + dev_err(codec->dev, "Failed to set IFC1B: %d\n", ret); + return ret; + } + + /* + * Check if Integer Clock Mode is possible, but avoid it in slave mode + * since we then do not know if lrclk is derived from pclk and the + * datasheet mentions that the frequencies have to match exactly in + * order for this to work. + */ + if (params_rate(params) == 8000 || params_rate(params) == 16000) { + if (master) { + switch (max9860->pclk_rate) { + case 12000000: + sysclk = MAX9860_FREQ_12MHZ; + break; + case 13000000: + sysclk = MAX9860_FREQ_13MHZ; + break; + case 19200000: + sysclk = MAX9860_FREQ_19_2MHZ; + break; + default: + /* + * Integer Clock Mode not possible. Leave + * sysclk at zero and fall through to the + * code below for PLL mode. + */ + break; + } + + if (sysclk && params_rate(params) == 16000) + sysclk |= MAX9860_16KHZ; + } + } + + /* + * Largest possible n: + * 65536 * 96 * 48kHz / 10MHz -> 30199 + * Smallest possible n: + * 65536 * 96 * 8kHz / 20MHz -> 2517 + * Both fit nicely in the available 15 bits, no need to apply any mask. + */ + n = DIV_ROUND_CLOSEST_ULL(65536ULL * 96 * params_rate(params), + max9860->pclk_rate); + + if (!sysclk) { + /* PLL mode */ + if (params_rate(params) > 24000) + sysclk |= MAX9860_16KHZ; + + if (!master) + n |= 1; /* trigger rapid pll lock mode */ + } + + sysclk |= max9860->psclk; + dev_dbg(codec->dev, "SYSCLK %02x\n", sysclk); + ret = regmap_write(max9860->regmap, + MAX9860_SYSCLK, sysclk); + if (ret) { + dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret); + return ret; + } + dev_dbg(codec->dev, "N %lu\n", n); + ret = regmap_write(max9860->regmap, + MAX9860_AUDIOCLKHIGH, n >> 8); + if (ret) { + dev_err(codec->dev, "Failed to set NHI: %d\n", ret); + return ret; + } + ret = regmap_write(max9860->regmap, + MAX9860_AUDIOCLKLOW, n & 0xff); + if (ret) { + dev_err(codec->dev, "Failed to set NLO: %d\n", ret); + return ret; + } + + if (!master) { + dev_dbg(codec->dev, "Enable PLL\n"); + ret = regmap_update_bits(max9860->regmap, MAX9860_AUDIOCLKHIGH, + MAX9860_PLL, MAX9860_PLL); + if (ret) { + dev_err(codec->dev, "Failed to enable PLL: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int max9860_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct max9860_priv *max9860 = snd_soc_codec_get_drvdata(codec); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + max9860->fmt = fmt; + return 0; + + default: + return -EINVAL; + } +} + +static const struct snd_soc_dai_ops max9860_dai_ops = { + .hw_params = max9860_hw_params, + .set_fmt = max9860_set_fmt, +}; + +static struct snd_soc_dai_driver max9860_dai = { + .name = "max9860-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &max9860_dai_ops, + .symmetric_rates = 1, +}; + +static int max9860_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct max9860_priv *max9860 = dev_get_drvdata(codec->dev); + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN, + MAX9860_SHDN, MAX9860_SHDN); + if (ret) { + dev_err(codec->dev, "Failed to remove SHDN: %d\n", ret); + return ret; + } + break; + + case SND_SOC_BIAS_OFF: + ret = regmap_update_bits(max9860->regmap, MAX9860_PWRMAN, + MAX9860_SHDN, 0); + if (ret) { + dev_err(codec->dev, "Failed to request SHDN: %d\n", + ret); + return ret; + } + break; + } + + return 0; +} + +static struct snd_soc_codec_driver max9860_codec_driver = { + .set_bias_level = max9860_set_bias_level, + .idle_bias_off = true, + + .controls = max9860_controls, + .num_controls = ARRAY_SIZE(max9860_controls), + .dapm_widgets = max9860_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max9860_dapm_widgets), + .dapm_routes = max9860_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(max9860_dapm_routes), +}; + +#ifdef CONFIG_PM +static int max9860_suspend(struct device *dev) +{ + struct max9860_priv *max9860 = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK, + MAX9860_PSCLK, MAX9860_PSCLK_OFF); + if (ret) { + dev_err(dev, "Failed to disable clock: %d\n", ret); + return ret; + } + + regulator_disable(max9860->dvddio); + + return 0; +} + +static int max9860_resume(struct device *dev) +{ + struct max9860_priv *max9860 = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(max9860->dvddio); + if (ret) { + dev_err(dev, "Failed to enable DVDDIO: %d\n", ret); + return ret; + } + + regcache_cache_only(max9860->regmap, false); + ret = regcache_sync(max9860->regmap); + if (ret) { + dev_err(dev, "Failed to sync cache: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(max9860->regmap, MAX9860_SYSCLK, + MAX9860_PSCLK, max9860->psclk); + if (ret) { + dev_err(dev, "Failed to enable clock: %d\n", ret); + return ret; + } + + return 0; +} +#endif + +static const struct dev_pm_ops max9860_pm_ops = { + SET_RUNTIME_PM_OPS(max9860_suspend, max9860_resume, NULL) +}; + +static int max9860_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device *dev = &i2c->dev; + struct max9860_priv *max9860; + int ret; + struct clk *mclk; + unsigned long mclk_rate; + int i; + int intr; + + max9860 = devm_kzalloc(dev, sizeof(struct max9860_priv), GFP_KERNEL); + if (!max9860) + return -ENOMEM; + + max9860->dvddio = devm_regulator_get(dev, "DVDDIO"); + if (IS_ERR(max9860->dvddio)) { + ret = PTR_ERR(max9860->dvddio); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get DVDDIO supply: %d\n", ret); + return ret; + } + + max9860->dvddio_nb.notifier_call = max9860_dvddio_event; + + ret = regulator_register_notifier(max9860->dvddio, &max9860->dvddio_nb); + if (ret) + dev_err(dev, "Failed to register DVDDIO notifier: %d\n", ret); + + ret = regulator_enable(max9860->dvddio); + if (ret != 0) { + dev_err(dev, "Failed to enable DVDDIO: %d\n", ret); + return ret; + } + + max9860->regmap = devm_regmap_init_i2c(i2c, &max9860_regmap); + if (IS_ERR(max9860->regmap)) { + ret = PTR_ERR(max9860->regmap); + goto err_regulator; + } + + dev_set_drvdata(dev, max9860); + + /* + * mclk has to be in the 10MHz to 60MHz range. + * psclk is used to scale mclk into pclk so that + * pclk is in the 10MHz to 20MHz range. + */ + mclk = clk_get(dev, "mclk"); + + if (IS_ERR(mclk)) { + ret = PTR_ERR(mclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get MCLK: %d\n", ret); + goto err_regulator; + } + + mclk_rate = clk_get_rate(mclk); + clk_put(mclk); + + if (mclk_rate > 60000000 || mclk_rate < 10000000) { + dev_err(dev, "Bad mclk %luHz (needs 10MHz - 60MHz)\n", + mclk_rate); + ret = -EINVAL; + goto err_regulator; + } + if (mclk_rate >= 40000000) + max9860->psclk = 3; + else if (mclk_rate >= 20000000) + max9860->psclk = 2; + else + max9860->psclk = 1; + max9860->pclk_rate = mclk_rate >> (max9860->psclk - 1); + max9860->psclk <<= MAX9860_PSCLK_SHIFT; + dev_dbg(dev, "mclk %lu pclk %lu\n", mclk_rate, max9860->pclk_rate); + + regcache_cache_bypass(max9860->regmap, true); + for (i = 0; i < max9860_regmap.num_reg_defaults; ++i) { + ret = regmap_write(max9860->regmap, + max9860_regmap.reg_defaults[i].reg, + max9860_regmap.reg_defaults[i].def); + if (ret) { + dev_err(dev, "Failed to initialize register %u: %d\n", + max9860_regmap.reg_defaults[i].reg, ret); + goto err_regulator; + } + } + regcache_cache_bypass(max9860->regmap, false); + + ret = regmap_read(max9860->regmap, MAX9860_INTRSTATUS, &intr); + if (ret) { + dev_err(dev, "Failed to clear INTRSTATUS: %d\n", ret); + goto err_regulator; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = snd_soc_register_codec(dev, &max9860_codec_driver, + &max9860_dai, 1); + if (ret) { + dev_err(dev, "Failed to register CODEC: %d\n", ret); + goto err_pm; + } + + return 0; + +err_pm: + pm_runtime_disable(dev); +err_regulator: + regulator_disable(max9860->dvddio); + return ret; +} + +static int max9860_remove(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct max9860_priv *max9860 = dev_get_drvdata(dev); + + snd_soc_unregister_codec(dev); + pm_runtime_disable(dev); + regulator_disable(max9860->dvddio); + return 0; +} + +static const struct i2c_device_id max9860_i2c_id[] = { + { "max9860", }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9860_i2c_id); + +static const struct of_device_id max9860_of_match[] = { + { .compatible = "maxim,max9860", }, + { } +}; +MODULE_DEVICE_TABLE(of, max9860_of_match); + +static struct i2c_driver max9860_i2c_driver = { + .probe = max9860_probe, + .remove = max9860_remove, + .id_table = max9860_i2c_id, + .driver = { + .name = "max9860", + .of_match_table = max9860_of_match, + .pm = &max9860_pm_ops, + }, +}; + +module_i2c_driver(max9860_i2c_driver); + +MODULE_DESCRIPTION("ASoC MAX9860 Mono Audio Voice Codec driver"); +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/max9860.h b/sound/soc/codecs/max9860.h new file mode 100644 index 000000000000..22041bd67a7d --- /dev/null +++ b/sound/soc/codecs/max9860.h @@ -0,0 +1,162 @@ +/* + * Driver for the MAX9860 Mono Audio Voice Codec + * + * Author: Peter Rosin <peda@axentia.s> + * Copyright 2016 Axentia Technologies + * + * 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. + */ + +#ifndef _SND_SOC_MAX9860 +#define _SND_SOC_MAX9860 + +#define MAX9860_INTRSTATUS 0x00 +#define MAX9860_MICREADBACK 0x01 +#define MAX9860_INTEN 0x02 +#define MAX9860_SYSCLK 0x03 +#define MAX9860_AUDIOCLKHIGH 0x04 +#define MAX9860_AUDIOCLKLOW 0x05 +#define MAX9860_IFC1A 0x06 +#define MAX9860_IFC1B 0x07 +#define MAX9860_VOICEFLTR 0x08 +#define MAX9860_DACATTN 0x09 +#define MAX9860_ADCLEVEL 0x0a +#define MAX9860_DACGAIN 0x0b +#define MAX9860_MICGAIN 0x0c +#define MAX9860_RESERVED 0x0d +#define MAX9860_MICADC 0x0e +#define MAX9860_NOISEGATE 0x0f +#define MAX9860_PWRMAN 0x10 +#define MAX9860_REVISION 0xff + +#define MAX9860_MAX_REGISTER 0xff + +/* INTRSTATUS */ +#define MAX9860_CLD 0x80 +#define MAX9860_SLD 0x40 +#define MAX9860_ULK 0x20 + +/* MICREADBACK */ +#define MAX9860_NG 0xe0 +#define MAX9860_AGC 0x1f + +/* INTEN */ +#define MAX9860_ICLD 0x80 +#define MAX9860_ISLD 0x40 +#define MAX9860_IULK 0x20 + +/* SYSCLK */ +#define MAX9860_PSCLK 0x30 +#define MAX9860_PSCLK_OFF 0x00 +#define MAX9860_PSCLK_SHIFT 4 +#define MAX9860_FREQ 0x06 +#define MAX9860_FREQ_NORMAL 0x00 +#define MAX9860_FREQ_12MHZ 0x02 +#define MAX9860_FREQ_13MHZ 0x04 +#define MAX9860_FREQ_19_2MHZ 0x06 +#define MAX9860_16KHZ 0x01 + +/* AUDIOCLKHIGH */ +#define MAX9860_PLL 0x80 +#define MAX9860_NHI 0x7f + +/* AUDIOCLKLOW */ +#define MAX9860_NLO 0xff + +/* IFC1A */ +#define MAX9860_MASTER 0x80 +#define MAX9860_WCI 0x40 +#define MAX9860_DBCI 0x20 +#define MAX9860_DDLY 0x10 +#define MAX9860_HIZ 0x08 +#define MAX9860_TDM 0x04 + +/* IFC1B */ +#define MAX9860_ABCI 0x20 +#define MAX9860_ADLY 0x10 +#define MAX9860_ST 0x08 +#define MAX9860_BSEL 0x07 +#define MAX9860_BSEL_OFF 0x00 +#define MAX9860_BSEL_64X 0x01 +#define MAX9860_BSEL_48X 0x02 +#define MAX9860_BSEL_PCLK_2 0x04 +#define MAX9860_BSEL_PCLK_4 0x05 +#define MAX9860_BSEL_PCLK_8 0x06 +#define MAX9860_BSEL_PCLK_16 0x07 + +/* VOICEFLTR */ +#define MAX9860_AVFLT 0xf0 +#define MAX9860_AVFLT_SHIFT 4 +#define MAX9860_AVFLT_COUNT 6 +#define MAX9860_DVFLT 0x0f +#define MAX9860_DVFLT_SHIFT 0 +#define MAX9860_DVFLT_COUNT 6 + +/* DACATTN */ +#define MAX9860_DVA 0xfe +#define MAX9860_DVA_SHIFT 1 +#define MAX9860_DVA_MUTE 0x5e + +/* ADCLEVEL */ +#define MAX9860_ADCRL 0xf0 +#define MAX9860_ADCRL_SHIFT 4 +#define MAX9860_ADCLL 0x0f +#define MAX9860_ADCLL_SHIFT 0 +#define MAX9860_ADCxL_MIN 15 + +/* DACGAIN */ +#define MAX9860_DVG 0x60 +#define MAX9860_DVG_SHIFT 5 +#define MAX9860_DVG_MAX 3 +#define MAX9860_DVST 0x1f +#define MAX9860_DVST_SHIFT 0 +#define MAX9860_DVST_MIN 31 + +/* MICGAIN */ +#define MAX9860_PAM 0x60 +#define MAX9860_PAM_SHIFT 5 +#define MAX9860_PAM_MAX 3 +#define MAX9860_PGAM 0x1f +#define MAX9860_PGAM_SHIFT 0 +#define MAX9860_PGAM_MIN 20 + +/* MICADC */ +#define MAX9860_AGCSRC 0x80 +#define MAX9860_AGCSRC_SHIFT 7 +#define MAX9860_AGCSRC_COUNT 2 +#define MAX9860_AGCRLS 0x70 +#define MAX9860_AGCRLS_SHIFT 4 +#define MAX9860_AGCRLS_COUNT 8 +#define MAX9860_AGCATK 0x0c +#define MAX9860_AGCATK_SHIFT 2 +#define MAX9860_AGCATK_COUNT 4 +#define MAX9860_AGCHLD 0x03 +#define MAX9860_AGCHLD_OFF 0x00 +#define MAX9860_AGCHLD_SHIFT 0 +#define MAX9860_AGCHLD_COUNT 4 + +/* NOISEGATE */ +#define MAX9860_ANTH 0xf0 +#define MAX9860_ANTH_SHIFT 4 +#define MAX9860_ANTH_MAX 15 +#define MAX9860_AGCTH 0x0f +#define MAX9860_AGCTH_SHIFT 0 +#define MAX9860_AGCTH_MIN 15 + +/* PWRMAN */ +#define MAX9860_SHDN 0x80 +#define MAX9860_DACEN 0x08 +#define MAX9860_DACEN_SHIFT 3 +#define MAX9860_ADCLEN 0x02 +#define MAX9860_ADCLEN_SHIFT 1 +#define MAX9860_ADCREN 0x01 +#define MAX9860_ADCREN_SHIFT 0 + +#endif /* _SND_SOC_MAX9860 */ diff --git a/sound/soc/codecs/max9867.c b/sound/soc/codecs/max9867.c index 2a22fddeb6af..2a22fddeb6af 100755..100644 --- a/sound/soc/codecs/max9867.c +++ b/sound/soc/codecs/max9867.c diff --git a/sound/soc/codecs/max9867.h b/sound/soc/codecs/max9867.h index 65590b4ad62a..65590b4ad62a 100755..100644 --- a/sound/soc/codecs/max9867.h +++ b/sound/soc/codecs/max9867.h diff --git a/sound/soc/codecs/max9877.h b/sound/soc/codecs/max9877.h index 6da72290ac58..368343f29dd0 100644 --- a/sound/soc/codecs/max9877.h +++ b/sound/soc/codecs/max9877.h @@ -32,6 +32,4 @@ #define MAX9877_BYPASS (1 << 6) #define MAX9877_SHDN (1 << 7) -extern int max9877_add_controls(struct snd_soc_codec *codec); - #endif diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 683769f0f246..5c9707ac4bbf 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -18,6 +18,7 @@ #include <linux/clk.h> #include <linux/acpi.h> #include <linux/math64.h> +#include <linux/semaphore.h> #include <sound/initval.h> #include <sound/tlv.h> @@ -30,10 +31,22 @@ #include "nau8825.h" + +#define NUVOTON_CODEC_DAI "nau8825-hifi" + #define NAU_FREF_MAX 13500000 -#define NAU_FVCO_MAX 100000000 +#define NAU_FVCO_MAX 124000000 #define NAU_FVCO_MIN 90000000 +/* cross talk suppression detection */ +#define LOG10_MAGIC 646456993 +#define GAIN_AUGMENT 22500 +#define SIDETONE_BASE 207000 + + +static int nau8825_configure_sysclk(struct nau8825 *nau8825, + int clk_id, unsigned int freq); + struct nau8825_fll { int mclk_src; int ratio; @@ -156,6 +169,661 @@ static const struct reg_default nau8825_reg_defaults[] = { { NAU8825_REG_CHARGE_PUMP, 0x0 }, }; +/* register backup table when cross talk detection */ +static struct reg_default nau8825_xtalk_baktab[] = { + { NAU8825_REG_ADC_DGAIN_CTRL, 0 }, + { NAU8825_REG_HSVOL_CTRL, 0 }, + { NAU8825_REG_DACL_CTRL, 0 }, + { NAU8825_REG_DACR_CTRL, 0 }, +}; + +static const unsigned short logtable[256] = { + 0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7, + 0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508, + 0x1664, 0x17bf, 0x1919, 0x1a71, 0x1bc8, 0x1d1e, 0x1e73, 0x1fc6, + 0x2119, 0x226a, 0x23ba, 0x2508, 0x2656, 0x27a2, 0x28ed, 0x2a37, + 0x2b80, 0x2cc8, 0x2e0f, 0x2f54, 0x3098, 0x31dc, 0x331e, 0x345f, + 0x359f, 0x36de, 0x381b, 0x3958, 0x3a94, 0x3bce, 0x3d08, 0x3e41, + 0x3f78, 0x40af, 0x41e4, 0x4319, 0x444c, 0x457f, 0x46b0, 0x47e1, + 0x4910, 0x4a3f, 0x4b6c, 0x4c99, 0x4dc5, 0x4eef, 0x5019, 0x5142, + 0x526a, 0x5391, 0x54b7, 0x55dc, 0x5700, 0x5824, 0x5946, 0x5a68, + 0x5b89, 0x5ca8, 0x5dc7, 0x5ee5, 0x6003, 0x611f, 0x623a, 0x6355, + 0x646f, 0x6588, 0x66a0, 0x67b7, 0x68ce, 0x69e4, 0x6af8, 0x6c0c, + 0x6d20, 0x6e32, 0x6f44, 0x7055, 0x7165, 0x7274, 0x7383, 0x7490, + 0x759d, 0x76aa, 0x77b5, 0x78c0, 0x79ca, 0x7ad3, 0x7bdb, 0x7ce3, + 0x7dea, 0x7ef0, 0x7ff6, 0x80fb, 0x81ff, 0x8302, 0x8405, 0x8507, + 0x8608, 0x8709, 0x8809, 0x8908, 0x8a06, 0x8b04, 0x8c01, 0x8cfe, + 0x8dfa, 0x8ef5, 0x8fef, 0x90e9, 0x91e2, 0x92db, 0x93d2, 0x94ca, + 0x95c0, 0x96b6, 0x97ab, 0x98a0, 0x9994, 0x9a87, 0x9b7a, 0x9c6c, + 0x9d5e, 0x9e4f, 0x9f3f, 0xa02e, 0xa11e, 0xa20c, 0xa2fa, 0xa3e7, + 0xa4d4, 0xa5c0, 0xa6ab, 0xa796, 0xa881, 0xa96a, 0xaa53, 0xab3c, + 0xac24, 0xad0c, 0xadf2, 0xaed9, 0xafbe, 0xb0a4, 0xb188, 0xb26c, + 0xb350, 0xb433, 0xb515, 0xb5f7, 0xb6d9, 0xb7ba, 0xb89a, 0xb97a, + 0xba59, 0xbb38, 0xbc16, 0xbcf4, 0xbdd1, 0xbead, 0xbf8a, 0xc065, + 0xc140, 0xc21b, 0xc2f5, 0xc3cf, 0xc4a8, 0xc580, 0xc658, 0xc730, + 0xc807, 0xc8de, 0xc9b4, 0xca8a, 0xcb5f, 0xcc34, 0xcd08, 0xcddc, + 0xceaf, 0xcf82, 0xd054, 0xd126, 0xd1f7, 0xd2c8, 0xd399, 0xd469, + 0xd538, 0xd607, 0xd6d6, 0xd7a4, 0xd872, 0xd93f, 0xda0c, 0xdad9, + 0xdba5, 0xdc70, 0xdd3b, 0xde06, 0xded0, 0xdf9a, 0xe063, 0xe12c, + 0xe1f5, 0xe2bd, 0xe385, 0xe44c, 0xe513, 0xe5d9, 0xe69f, 0xe765, + 0xe82a, 0xe8ef, 0xe9b3, 0xea77, 0xeb3b, 0xebfe, 0xecc1, 0xed83, + 0xee45, 0xef06, 0xefc8, 0xf088, 0xf149, 0xf209, 0xf2c8, 0xf387, + 0xf446, 0xf505, 0xf5c3, 0xf680, 0xf73e, 0xf7fb, 0xf8b7, 0xf973, + 0xfa2f, 0xfaea, 0xfba5, 0xfc60, 0xfd1a, 0xfdd4, 0xfe8e, 0xff47 +}; + +static struct snd_soc_dai *nau8825_get_codec_dai(struct nau8825 *nau8825) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(nau8825->dapm); + struct snd_soc_component *component = &codec->component; + struct snd_soc_dai *codec_dai, *_dai; + + list_for_each_entry_safe(codec_dai, _dai, &component->dai_list, list) { + if (!strncmp(codec_dai->name, NUVOTON_CODEC_DAI, + strlen(NUVOTON_CODEC_DAI))) + return codec_dai; + } + return NULL; +} + +static bool nau8825_dai_is_active(struct nau8825 *nau8825) +{ + struct snd_soc_dai *codec_dai = nau8825_get_codec_dai(nau8825); + + if (codec_dai) { + if (codec_dai->playback_active || codec_dai->capture_active) + return true; + } + return false; +} + +/** + * nau8825_sema_acquire - acquire the semaphore of nau88l25 + * @nau8825: component to register the codec private data with + * @timeout: how long in jiffies to wait before failure or zero to wait + * until release + * + * Attempts to acquire the semaphore with number of jiffies. If no more + * tasks are allowed to acquire the semaphore, calling this function will + * put the task to sleep. If the semaphore is not released within the + * specified number of jiffies, this function returns. + * Acquires the semaphore without jiffies. If no more tasks are allowed + * to acquire the semaphore, calling this function will put the task to + * sleep until the semaphore is released. + * It returns if the semaphore was acquired. + */ +static void nau8825_sema_acquire(struct nau8825 *nau8825, long timeout) +{ + int ret; + + if (timeout) + ret = down_timeout(&nau8825->xtalk_sem, timeout); + else + ret = down_interruptible(&nau8825->xtalk_sem); + + if (ret < 0) + dev_warn(nau8825->dev, "Acquire semaphone fail\n"); +} + +/** + * nau8825_sema_release - release the semaphore of nau88l25 + * @nau8825: component to register the codec private data with + * + * Release the semaphore which may be called from any context and + * even by tasks which have never called down(). + */ +static inline void nau8825_sema_release(struct nau8825 *nau8825) +{ + up(&nau8825->xtalk_sem); +} + +/** + * nau8825_sema_reset - reset the semaphore for nau88l25 + * @nau8825: component to register the codec private data with + * + * Reset the counter of the semaphore. Call this function to restart + * a new round task management. + */ +static inline void nau8825_sema_reset(struct nau8825 *nau8825) +{ + nau8825->xtalk_sem.count = 1; +} + +/** + * Ramp up the headphone volume change gradually to target level. + * + * @nau8825: component to register the codec private data with + * @vol_from: the volume to start up + * @vol_to: the target volume + * @step: the volume span to move on + * + * The headphone volume is from 0dB to minimum -54dB and -1dB per step. + * If the volume changes sharp, there is a pop noise heard in headphone. We + * provide the function to ramp up the volume up or down by delaying 10ms + * per step. + */ +static void nau8825_hpvol_ramp(struct nau8825 *nau8825, + unsigned int vol_from, unsigned int vol_to, unsigned int step) +{ + unsigned int value, volume, ramp_up, from, to; + + if (vol_from == vol_to || step == 0) { + return; + } else if (vol_from < vol_to) { + ramp_up = true; + from = vol_from; + to = vol_to; + } else { + ramp_up = false; + from = vol_to; + to = vol_from; + } + /* only handle volume from 0dB to minimum -54dB */ + if (to > NAU8825_HP_VOL_MIN) + to = NAU8825_HP_VOL_MIN; + + for (volume = from; volume < to; volume += step) { + if (ramp_up) + value = volume; + else + value = to - volume + from; + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL, + NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK, + (value << NAU8825_HPL_VOL_SFT) | value); + usleep_range(10000, 10500); + } + if (ramp_up) + value = to; + else + value = from; + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL, + NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK, + (value << NAU8825_HPL_VOL_SFT) | value); +} + +/** + * Computes log10 of a value; the result is round off to 3 decimal. This func- + * tion takes reference to dvb-math. The source code locates as the following. + * Linux/drivers/media/dvb-core/dvb_math.c + * + * return log10(value) * 1000 + */ +static u32 nau8825_intlog10_dec3(u32 value) +{ + u32 msb, logentry, significand, interpolation, log10val; + u64 log2val; + + /* first detect the msb (count begins at 0) */ + msb = fls(value) - 1; + /** + * now we use a logtable after the following method: + * + * log2(2^x * y) * 2^24 = x * 2^24 + log2(y) * 2^24 + * where x = msb and therefore 1 <= y < 2 + * first y is determined by shifting the value left + * so that msb is bit 31 + * 0x00231f56 -> 0x8C7D5800 + * the result is y * 2^31 -> "significand" + * then the highest 9 bits are used for a table lookup + * the highest bit is discarded because it's always set + * the highest nine bits in our example are 100011000 + * so we would use the entry 0x18 + */ + significand = value << (31 - msb); + logentry = (significand >> 23) & 0xff; + /** + * last step we do is interpolation because of the + * limitations of the log table the error is that part of + * the significand which isn't used for lookup then we + * compute the ratio between the error and the next table entry + * and interpolate it between the log table entry used and the + * next one the biggest error possible is 0x7fffff + * (in our example it's 0x7D5800) + * needed value for next table entry is 0x800000 + * so the interpolation is + * (error / 0x800000) * (logtable_next - logtable_current) + * in the implementation the division is moved to the end for + * better accuracy there is also an overflow correction if + * logtable_next is 256 + */ + interpolation = ((significand & 0x7fffff) * + ((logtable[(logentry + 1) & 0xff] - + logtable[logentry]) & 0xffff)) >> 15; + + log2val = ((msb << 24) + (logtable[logentry] << 8) + interpolation); + /** + * log10(x) = log2(x) * log10(2) + */ + log10val = (log2val * LOG10_MAGIC) >> 31; + /** + * the result is round off to 3 decimal + */ + return log10val / ((1 << 24) / 1000); +} + +/** + * computes cross talk suppression sidetone gain. + * + * @sig_org: orignal signal level + * @sig_cros: cross talk signal level + * + * The orignal and cross talk signal vlues need to be characterized. + * Once these values have been characterized, this sidetone value + * can be converted to decibel with the equation below. + * sidetone = 20 * log (original signal level / crosstalk signal level) + * + * return cross talk sidetone gain + */ +static u32 nau8825_xtalk_sidetone(u32 sig_org, u32 sig_cros) +{ + u32 gain, sidetone; + + if (unlikely(sig_org == 0) || unlikely(sig_cros == 0)) { + WARN_ON(1); + return 0; + } + + sig_org = nau8825_intlog10_dec3(sig_org); + sig_cros = nau8825_intlog10_dec3(sig_cros); + if (sig_org >= sig_cros) + gain = (sig_org - sig_cros) * 20 + GAIN_AUGMENT; + else + gain = (sig_cros - sig_org) * 20 + GAIN_AUGMENT; + sidetone = SIDETONE_BASE - gain * 2; + sidetone /= 1000; + + return sidetone; +} + +static int nau8825_xtalk_baktab_index_by_reg(unsigned int reg) +{ + int index; + + for (index = 0; index < ARRAY_SIZE(nau8825_xtalk_baktab); index++) + if (nau8825_xtalk_baktab[index].reg == reg) + return index; + return -EINVAL; +} + +static void nau8825_xtalk_backup(struct nau8825 *nau8825) +{ + int i; + + /* Backup some register values to backup table */ + for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) + regmap_read(nau8825->regmap, nau8825_xtalk_baktab[i].reg, + &nau8825_xtalk_baktab[i].def); +} + +static void nau8825_xtalk_restore(struct nau8825 *nau8825) +{ + int i, volume; + + /* Restore register values from backup table; When the driver restores + * the headphone volumem, it needs recover to original level gradually + * with 3dB per step for less pop noise. + */ + for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) { + if (nau8825_xtalk_baktab[i].reg == NAU8825_REG_HSVOL_CTRL) { + /* Ramping up the volume change to reduce pop noise */ + volume = nau8825_xtalk_baktab[i].def & + NAU8825_HPR_VOL_MASK; + nau8825_hpvol_ramp(nau8825, 0, volume, 3); + continue; + } + regmap_write(nau8825->regmap, nau8825_xtalk_baktab[i].reg, + nau8825_xtalk_baktab[i].def); + } +} + +static void nau8825_xtalk_prepare_dac(struct nau8825 *nau8825) +{ + /* Enable power of DAC path */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL | + NAU8825_ENABLE_ADC | NAU8825_ENABLE_ADC_CLK | + NAU8825_ENABLE_DAC_CLK, NAU8825_ENABLE_DACR | + NAU8825_ENABLE_DACL | NAU8825_ENABLE_ADC | + NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK); + /* Prevent startup click by letting charge pump to ramp up and + * change bump enable + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN); + /* Enable clock sync of DAC and DAC clock */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN | + NAU8825_RDAC_FS_BCLK_ENB, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN); + /* Power up output driver with 2 stage */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L); + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L); + /* HP outputs not shouted to ground */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, 0); + /* Enable HP boost driver */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_HP_BOOST_DIS, NAU8825_HP_BOOST_DIS); + /* Enable class G compare path to supply 1.8V or 0.9V. */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLASSG_CTRL, + NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN, + NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN); +} + +static void nau8825_xtalk_prepare_adc(struct nau8825 *nau8825) +{ + /* Power up left ADC and raise 5dB than Vmid for Vref */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB); +} + +static void nau8825_xtalk_clock(struct nau8825 *nau8825) +{ + /* Recover FLL default value */ + regmap_write(nau8825->regmap, NAU8825_REG_FLL1, 0x0); + regmap_write(nau8825->regmap, NAU8825_REG_FLL2, 0x3126); + regmap_write(nau8825->regmap, NAU8825_REG_FLL3, 0x0008); + regmap_write(nau8825->regmap, NAU8825_REG_FLL4, 0x0010); + regmap_write(nau8825->regmap, NAU8825_REG_FLL5, 0x0); + regmap_write(nau8825->regmap, NAU8825_REG_FLL6, 0x6000); + /* Enable internal VCO clock for detection signal generated */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, + NAU8825_DCO_EN); + /* Given specific clock frequency of internal clock to + * generate signal. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_MCLK_SRC_MASK, 0xf); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1, + NAU8825_FLL_RATIO_MASK, 0x10); +} + +static void nau8825_xtalk_prepare(struct nau8825 *nau8825) +{ + int volume, index; + + /* Backup those registers changed by cross talk detection */ + nau8825_xtalk_backup(nau8825); + /* Config IIS as master to output signal by codec */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK | + NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_MASTER | + (0x2 << NAU8825_I2S_DRV_SFT) | 0x1); + /* Ramp up headphone volume to 0dB to get better performance and + * avoid pop noise in headphone. + */ + index = nau8825_xtalk_baktab_index_by_reg(NAU8825_REG_HSVOL_CTRL); + if (index != -EINVAL) { + volume = nau8825_xtalk_baktab[index].def & + NAU8825_HPR_VOL_MASK; + nau8825_hpvol_ramp(nau8825, volume, 0, 3); + } + nau8825_xtalk_clock(nau8825); + nau8825_xtalk_prepare_dac(nau8825); + nau8825_xtalk_prepare_adc(nau8825); + /* Config channel path and digital gain */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_MASK | NAU8825_DACL_CH_VOL_MASK, + NAU8825_DACL_CH_SEL_L | 0xab); + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL, + NAU8825_DACR_CH_SEL_MASK | NAU8825_DACR_CH_VOL_MASK, + NAU8825_DACR_CH_SEL_R | 0xab); + /* Config cross talk parameters and generate the 23Hz sine wave with + * 1/16 full scale of signal level for impedance measurement. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, + NAU8825_IMM_THD_MASK | NAU8825_IMM_GEN_VOL_MASK | + NAU8825_IMM_CYC_MASK | NAU8825_IMM_DAC_SRC_MASK, + (0x9 << NAU8825_IMM_THD_SFT) | NAU8825_IMM_GEN_VOL_1_16th | + NAU8825_IMM_CYC_8192 | NAU8825_IMM_DAC_SRC_SIN); + /* RMS intrruption enable */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_INTERRUPT_MASK, NAU8825_IRQ_RMS_EN, 0); + /* Power up left and right DAC */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); +} + +static void nau8825_xtalk_clean_dac(struct nau8825 *nau8825) +{ + /* Disable HP boost driver */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_HP_BOOST_DIS, 0); + /* HP outputs shouted to ground */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, + NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); + /* Power down left and right DAC */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + /* Enable the TESTDAC and disable L/R HP impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP | + NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN); + /* Power down output driver with 2 stage */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, 0); + regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL, + NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L | + NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, 0); + /* Disable clock sync of DAC and DAC clock */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC, + NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN, 0); + /* Disable charge pump ramp up function and change bump */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, 0); + /* Disable power of DAC path */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL | + NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK, 0); + if (!nau8825->irq) + regmap_update_bits(nau8825->regmap, + NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0); +} + +static void nau8825_xtalk_clean_adc(struct nau8825 *nau8825) +{ + /* Power down left ADC and restore voltage to Vmid */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2, + NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, 0); +} + +static void nau8825_xtalk_clean(struct nau8825 *nau8825) +{ + /* Enable internal VCO needed for interruptions */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); + nau8825_xtalk_clean_dac(nau8825); + nau8825_xtalk_clean_adc(nau8825); + /* Clear cross talk parameters and disable */ + regmap_write(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, 0); + /* RMS intrruption disable */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_RMS_EN, NAU8825_IRQ_RMS_EN); + /* Recover default value for IIS */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK | NAU8825_I2S_DRV_MASK | + NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_SLAVE); + /* Restore value of specific register for cross talk */ + nau8825_xtalk_restore(nau8825); +} + +static void nau8825_xtalk_imm_start(struct nau8825 *nau8825, int vol) +{ + /* Apply ADC volume for better cross talk performance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ADC_DGAIN_CTRL, + NAU8825_ADC_DIG_VOL_MASK, vol); + /* Disables JKTIP(HPL) DAC channel for right to left measurement. + * Do it before sending signal in order to erase pop noise. + */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_TESTDACR_EN | NAU8825_BIAS_TESTDACL_EN, + NAU8825_BIAS_TESTDACL_EN); + switch (nau8825->xtalk_state) { + case NAU8825_XTALK_HPR_R2L: + /* Enable right headphone impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP, + NAU8825_BIAS_HPR_IMP); + break; + case NAU8825_XTALK_HPL_R2L: + /* Enable left headphone impedance */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, + NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP, + NAU8825_BIAS_HPL_IMP); + break; + default: + break; + } + msleep(100); + /* Impedance measurement mode enable */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, + NAU8825_IMM_EN, NAU8825_IMM_EN); +} + +static void nau8825_xtalk_imm_stop(struct nau8825 *nau8825) +{ + /* Impedance measurement mode disable */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_IMM_MODE_CTRL, NAU8825_IMM_EN, 0); +} + +/* The cross talk measurement function can reduce cross talk across the + * JKTIP(HPL) and JKR1(HPR) outputs which measures the cross talk signal + * level to determine what cross talk reduction gain is. This system works by + * sending a 23Hz -24dBV sine wave into the headset output DAC and through + * the PGA. The output of the PGA is then connected to an internal current + * sense which measures the attenuated 23Hz signal and passing the output to + * an ADC which converts the measurement to a binary code. With two separated + * measurement, one for JKR1(HPR) and the other JKTIP(HPL), measurement data + * can be separated read in IMM_RMS_L for HSR and HSL after each measurement. + * Thus, the measurement function has four states to complete whole sequence. + * 1. Prepare state : Prepare the resource for detection and transfer to HPR + * IMM stat to make JKR1(HPR) impedance measure. + * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer + * to HPL IMM state to make JKTIP(HPL) impedance measure. + * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and + * transfer to IMM state to determine suppression sidetone gain. + * 4. IMM state : Computes cross talk suppression sidetone gain with orignal + * and cross talk signal level. Apply this gain and then restore codec + * configuration. Then transfer to Done state for ending. + */ +static void nau8825_xtalk_measure(struct nau8825 *nau8825) +{ + u32 sidetone; + + switch (nau8825->xtalk_state) { + case NAU8825_XTALK_PREPARE: + /* In prepare state, set up clock, intrruption, DAC path, ADC + * path and cross talk detection parameters for preparation. + */ + nau8825_xtalk_prepare(nau8825); + msleep(280); + /* Trigger right headphone impedance detection */ + nau8825->xtalk_state = NAU8825_XTALK_HPR_R2L; + nau8825_xtalk_imm_start(nau8825, 0x00d2); + break; + case NAU8825_XTALK_HPR_R2L: + /* In right headphone IMM state, read out right headphone + * impedance measure result, and then start up left side. + */ + regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L, + &nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]); + dev_dbg(nau8825->dev, "HPR_R2L imm: %x\n", + nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]); + /* Disable then re-enable IMM mode to update */ + nau8825_xtalk_imm_stop(nau8825); + /* Trigger left headphone impedance detection */ + nau8825->xtalk_state = NAU8825_XTALK_HPL_R2L; + nau8825_xtalk_imm_start(nau8825, 0x00ff); + break; + case NAU8825_XTALK_HPL_R2L: + /* In left headphone IMM state, read out left headphone + * impedance measure result, and delay some time to wait + * detection sine wave output finish. Then, we can calculate + * the cross talk suppresstion side tone according to the L/R + * headphone imedance. + */ + regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L, + &nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + dev_dbg(nau8825->dev, "HPL_R2L imm: %x\n", + nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + nau8825_xtalk_imm_stop(nau8825); + msleep(150); + nau8825->xtalk_state = NAU8825_XTALK_IMM; + break; + case NAU8825_XTALK_IMM: + /* In impedance measure state, the orignal and cross talk + * signal level vlues are ready. The side tone gain is deter- + * mined with these signal level. After all, restore codec + * configuration. + */ + sidetone = nau8825_xtalk_sidetone( + nau8825->imp_rms[NAU8825_XTALK_HPR_R2L], + nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]); + dev_dbg(nau8825->dev, "cross talk sidetone: %x\n", sidetone); + regmap_write(nau8825->regmap, NAU8825_REG_DAC_DGAIN_CTRL, + (sidetone << 8) | sidetone); + nau8825_xtalk_clean(nau8825); + nau8825->xtalk_state = NAU8825_XTALK_DONE; + break; + default: + break; + } +} + +static void nau8825_xtalk_work(struct work_struct *work) +{ + struct nau8825 *nau8825 = container_of( + work, struct nau8825, xtalk_work); + + nau8825_xtalk_measure(nau8825); + /* To determine the cross talk side tone gain when reach + * the impedance measure state. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_IMM) + nau8825_xtalk_measure(nau8825); + + /* Delay jack report until cross talk detection process + * completed. It can avoid application to do playback + * preparation before cross talk detection is still working. + * Meanwhile, the protection of the cross talk detection + * is released. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_DONE) { + snd_soc_jack_report(nau8825->jack, nau8825->xtalk_event, + nau8825->xtalk_event_mask); + nau8825_sema_release(nau8825); + nau8825->xtalk_protect = false; + } +} + +static void nau8825_xtalk_cancel(struct nau8825 *nau8825) +{ + /* If the xtalk_protect is true, that means the process is still + * on going. The driver forces to cancel the cross talk task and + * restores the configuration to original status. + */ + if (nau8825->xtalk_protect) { + cancel_work_sync(&nau8825->xtalk_work); + nau8825_xtalk_clean(nau8825); + } + /* Reset parameters for cross talk suppression function */ + nau8825_sema_reset(nau8825); + nau8825->xtalk_state = NAU8825_XTALK_DONE; + nau8825->xtalk_protect = false; +} + static bool nau8825_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -217,12 +885,36 @@ static bool nau8825_volatile_reg(struct device *dev, unsigned int reg) case NAU8825_REG_SARDOUT_RAM_STATUS: case NAU8825_REG_CHARGE_PUMP_INPUT_READ: case NAU8825_REG_GENERAL_STATUS: + case NAU8825_REG_BIQ_CTRL ... NAU8825_REG_BIQ_COF10: return true; default: return false; } } +static int nau8825_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC); + break; + case SND_SOC_DAPM_POST_PMD: + if (!nau8825->irq) + regmap_update_bits(nau8825->regmap, + NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + static int nau8825_pump_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -270,6 +962,54 @@ static int nau8825_output_dac_event(struct snd_soc_dapm_widget *w, return 0; } +static int nau8825_biq_coeff_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + + if (!component->regmap) + return -EINVAL; + + regmap_raw_read(component->regmap, NAU8825_REG_BIQ_COF1, + ucontrol->value.bytes.data, params->max); + return 0; +} + +static int nau8825_biq_coeff_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_bytes_ext *params = (void *)kcontrol->private_value; + void *data; + + if (!component->regmap) + return -EINVAL; + + data = kmemdup(ucontrol->value.bytes.data, + params->max, GFP_KERNEL | GFP_DMA); + if (!data) + return -ENOMEM; + + regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL, + NAU8825_BIQ_WRT_EN, 0); + regmap_raw_write(component->regmap, NAU8825_REG_BIQ_COF1, + data, params->max); + regmap_update_bits(component->regmap, NAU8825_REG_BIQ_CTRL, + NAU8825_BIQ_WRT_EN, NAU8825_BIQ_WRT_EN); + + kfree(data); + return 0; +} + +static const char * const nau8825_biq_path[] = { + "ADC", "DAC" +}; + +static const struct soc_enum nau8825_biq_path_enum = + SOC_ENUM_SINGLE(NAU8825_REG_BIQ_CTRL, NAU8825_BIQ_PATH_SFT, + ARRAY_SIZE(nau8825_biq_path), nau8825_biq_path); + static const char * const nau8825_adc_decimation[] = { "32", "64", "128", "256" }; @@ -306,6 +1046,10 @@ static const struct snd_kcontrol_new nau8825_controls[] = { SOC_ENUM("ADC Decimation Rate", nau8825_adc_decimation_enum), SOC_ENUM("DAC Oversampling Rate", nau8825_dac_oversampl_enum), + /* programmable biquad filter */ + SOC_ENUM("BIQ Path Select", nau8825_biq_path_enum), + SND_SOC_BYTES_EXT("BIQ Coefficients", 20, + nau8825_biq_coeff_get, nau8825_biq_coeff_put), }; /* DAC Mux 0x33[9] and 0x34[9] */ @@ -338,7 +1082,9 @@ static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = { SND_SOC_DAPM_PGA("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0, NULL, 0), - SND_SOC_DAPM_ADC("ADC", NULL, NAU8825_REG_ENA_CTRL, 8, 0), + SND_SOC_DAPM_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0, + nau8825_adc_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("ADC Clock", NAU8825_REG_ENA_CTRL, 7, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("ADC Power", NAU8825_REG_ANALOG_ADC_2, 6, 0, NULL, 0), @@ -592,9 +1338,6 @@ int nau8825_enable_jack_detect(struct snd_soc_codec *codec, NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); - regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, - NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0); - return 0; } EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect); @@ -602,24 +1345,21 @@ EXPORT_SYMBOL_GPL(nau8825_enable_jack_detect); static bool nau8825_is_jack_inserted(struct regmap *regmap) { - int status; + bool active_high, is_high; + int status, jkdet; + regmap_read(regmap, NAU8825_REG_JACK_DET_CTRL, &jkdet); + active_high = jkdet & NAU8825_JACK_POLARITY; regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status); - return !(status & NAU8825_GPIO2JD1); + is_high = status & NAU8825_GPIO2JD1; + /* return jack connection status according to jack insertion logic + * active high or active low. + */ + return active_high == is_high; } static void nau8825_restart_jack_detection(struct regmap *regmap) { - /* Chip needs one FSCLK cycle in order to generate interrupts, - * as we cannot guarantee one will be provided by the system. Turning - * master mode on then off enables us to generate that FSCLK cycle - * with a minimum of contention on the clock bus. - */ - regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, - NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER); - regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, - NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE); - /* this will restart the entire jack detection process including MIC/GND * switching and create interrupts. We have to go from 0 to 1 and back * to 0 to restart. @@ -630,11 +1370,30 @@ static void nau8825_restart_jack_detection(struct regmap *regmap) NAU8825_JACK_DET_RESTART, 0); } +static void nau8825_int_status_clear_all(struct regmap *regmap) +{ + int active_irq, clear_irq, i; + + /* Reset the intrruption status from rightmost bit if the corres- + * ponding irq event occurs. + */ + regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq); + for (i = 0; i < NAU8825_REG_DATA_LEN; i++) { + clear_irq = (0x1 << i); + if (active_irq & clear_irq) + regmap_write(regmap, + NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq); + } +} + static void nau8825_eject_jack(struct nau8825 *nau8825) { struct snd_soc_dapm_context *dapm = nau8825->dapm; struct regmap *regmap = nau8825->regmap; + /* Force to cancel the cross talk detection process */ + nau8825_xtalk_cancel(nau8825); + snd_soc_dapm_disable_pin(dapm, "SAR"); snd_soc_dapm_disable_pin(dapm, "MICBIAS"); /* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */ @@ -644,6 +1403,69 @@ static void nau8825_eject_jack(struct nau8825 *nau8825) regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, 0xf, 0xf); snd_soc_dapm_sync(dapm); + + /* Clear all interruption status */ + nau8825_int_status_clear_all(regmap); + + /* Enable the insertion interruption, disable the ejection inter- + * ruption, and then bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_EJECT_DIS | NAU8825_IRQ_INSERT_DIS, + NAU8825_IRQ_EJECT_DIS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN | + NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_INSERT_EN, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_EJECT_EN | + NAU8825_IRQ_HEADSET_COMPLETE_EN); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS); + + /* Disable ADC needed for interruptions at audo mode */ + regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, 0); + + /* Close clock for jack type detection at manual mode */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0); +} + +/* Enable audo mode interruptions with internal clock. */ +static void nau8825_setup_auto_irq(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + /* Enable headset jack type detection complete interruption and + * jack ejection interruption. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0); + + /* Enable internal VCO needed for interruptions */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); + + /* Enable ADC needed for interruptions */ + regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, NAU8825_ENABLE_ADC); + + /* Chip needs one FSCLK cycle in order to generate interruptions, + * as we cannot guarantee one will be provided by the system. Turning + * master mode on then off enables us to generate that FSCLK cycle + * with a minimum of contention on the clock bus. + */ + regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER); + regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2, + NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE); + + /* Not bypass de-bounce circuit */ + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, 0); + + /* Unmask all interruptions */ + regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0); + + /* Restart the jack detection process at auto mode */ + nau8825_restart_jack_detection(regmap); } static int nau8825_button_decode(int value) @@ -676,6 +1498,11 @@ static int nau8825_jack_insert(struct nau8825 *nau8825) regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg); mic_detected = (jack_status_reg >> 10) & 3; + /* The JKSLV and JKR2 all detected in high impedance headset */ + if (mic_detected == 0x3) + nau8825->high_imped = true; + else + nau8825->high_imped = false; switch (mic_detected) { case 0: @@ -773,6 +1600,33 @@ static irqreturn_t nau8825_interrupt(int irq, void *data) } else if (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) { if (nau8825_is_jack_inserted(regmap)) { event |= nau8825_jack_insert(nau8825); + if (!nau8825->high_imped) { + /* Apply the cross talk suppression in the + * headset without high impedance. + */ + if (!nau8825->xtalk_protect) { + /* Raise protection for cross talk de- + * tection if no protection before. + * The driver has to cancel the pro- + * cess and restore changes if process + * is ongoing when ejection. + */ + nau8825->xtalk_protect = true; + nau8825_sema_acquire(nau8825, 0); + } + /* Startup cross talk detection process */ + nau8825->xtalk_state = NAU8825_XTALK_PREPARE; + schedule_work(&nau8825->xtalk_work); + } else { + /* The cross talk suppression shouldn't apply + * in the headset with high impedance. Thus, + * relieve the protection raised before. + */ + if (nau8825->xtalk_protect) { + nau8825_sema_release(nau8825); + nau8825->xtalk_protect = false; + } + } } else { dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n"); nau8825_eject_jack(nau8825); @@ -780,6 +1634,37 @@ static irqreturn_t nau8825_interrupt(int irq, void *data) event_mask |= SND_JACK_HEADSET; clear_irq = NAU8825_HEADSET_COMPLETION_IRQ; + /* Record the interruption report event for driver to report + * the event later. The jack report will delay until cross + * talk detection process is done. + */ + if (nau8825->xtalk_state == NAU8825_XTALK_PREPARE) { + nau8825->xtalk_event = event; + nau8825->xtalk_event_mask = event_mask; + } + } else if (active_irq & NAU8825_IMPEDANCE_MEAS_IRQ) { + schedule_work(&nau8825->xtalk_work); + clear_irq = NAU8825_IMPEDANCE_MEAS_IRQ; + } else if ((active_irq & NAU8825_JACK_INSERTION_IRQ_MASK) == + NAU8825_JACK_INSERTION_DETECTED) { + /* One more step to check GPIO status directly. Thus, the + * driver can confirm the real insertion interruption because + * the intrruption at manual mode has bypassed debounce + * circuit which can get rid of unstable status. + */ + if (nau8825_is_jack_inserted(regmap)) { + /* Turn off insertion interruption at manual mode */ + regmap_update_bits(regmap, + NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_INSERT_DIS, + NAU8825_IRQ_INSERT_DIS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_INSERT_EN, NAU8825_IRQ_INSERT_EN); + /* Enable interruption for jack type detection at audo + * mode which can detect microphone and jack type. + */ + nau8825_setup_auto_irq(nau8825); + } } if (!clear_irq) @@ -787,7 +1672,12 @@ static irqreturn_t nau8825_interrupt(int irq, void *data) /* clears the rightmost interruption */ regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq); - if (event_mask) + /* Delay jack report until cross talk detection is done. It can avoid + * application to do playback preparation when cross talk detection + * process is still working. Otherwise, the resource like clock and + * power will be issued by them at the same time and conflict happens. + */ + if (event_mask && nau8825->xtalk_state == NAU8825_XTALK_DONE) snd_soc_jack_report(nau8825->jack, event, event_mask); return IRQ_HANDLED; @@ -921,11 +1811,16 @@ static void nau8825_init_regs(struct nau8825 *nau8825) NAU8825_RDAC_CLK_DELAY_MASK | NAU8825_RDAC_VREF_MASK, (0x2 << NAU8825_RDAC_CLK_DELAY_SFT) | (0x3 << NAU8825_RDAC_VREF_SFT)); + /* Config L/R channel */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL, + NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_L); + regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL, + NAU8825_DACL_CH_SEL_MASK, NAU8825_DACL_CH_SEL_R); } static const struct regmap_config nau8825_regmap_config = { - .val_bits = 16, - .reg_bits = 16, + .val_bits = NAU8825_REG_DATA_LEN, + .reg_bits = NAU8825_REG_ADDR_LEN, .max_register = NAU8825_REG_MAX, .readable_reg = nau8825_readable_reg, @@ -944,18 +1839,15 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec) nau8825->dapm = dapm; - /* The interrupt clock is gated by x1[10:8], - * one of them needs to be enabled all the time for - * interrupts to happen. - */ - snd_soc_dapm_force_enable_pin(dapm, "DDACR"); - snd_soc_dapm_sync(dapm); + return 0; +} - /* Unmask interruptions. Handler uses dapm object so we can enable - * interruptions only after dapm is fully initialized. - */ - regmap_write(nau8825->regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0); - nau8825_restart_jack_detection(nau8825->regmap); +static int nau8825_codec_remove(struct snd_soc_codec *codec) +{ + struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); + + /* Cancel and reset cross tak suppresstion detection funciton */ + nau8825_xtalk_cancel(nau8825); return 0; } @@ -973,8 +1865,8 @@ static int nau8825_codec_probe(struct snd_soc_codec *codec) static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs, struct nau8825_fll *fll_param) { - u64 fvco; - unsigned int fref, i; + u64 fvco, fvco_max; + unsigned int fref, i, fvco_sel; /* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing * freq_in by 1, 2, 4, or 8 using FLL pre-scalar. @@ -999,18 +1891,23 @@ static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs, fll_param->ratio = fll_ratio[i].val; /* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. - * FDCO must be within the 90MHz - 100MHz or the FFL cannot be + * FDCO must be within the 90MHz - 124MHz or the FFL cannot be * guaranteed across the full range of operation. * FDCO = freq_out * 2 * mclk_src_scaling */ + fvco_max = 0; + fvco_sel = ARRAY_SIZE(mclk_src_scaling); for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) { fvco = 256 * fs * 2 * mclk_src_scaling[i].param; - if (NAU_FVCO_MIN < fvco && fvco < NAU_FVCO_MAX) - break; + if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX && + fvco_max < fvco) { + fvco_max = fvco; + fvco_sel = i; + } } - if (i == ARRAY_SIZE(mclk_src_scaling)) + if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) return -EINVAL; - fll_param->mclk_src = mclk_src_scaling[i].val; + fll_param->mclk_src = mclk_src_scaling[fvco_sel].val; /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional * input based on FDCO, FREF and FLL ratio. @@ -1025,7 +1922,8 @@ static void nau8825_fll_apply(struct nau8825 *nau8825, struct nau8825_fll *fll_param) { regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER, - NAU8825_CLK_MCLK_SRC_MASK, fll_param->mclk_src); + NAU8825_CLK_SRC_MASK | NAU8825_CLK_MCLK_SRC_MASK, + NAU8825_CLK_SRC_MCLK | fll_param->mclk_src); regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1, NAU8825_FLL_RATIO_MASK, fll_param->ratio); /* FLL 16-bit fractional input */ @@ -1038,10 +1936,25 @@ static void nau8825_fll_apply(struct nau8825 *nau8825, NAU8825_FLL_REF_DIV_MASK, fll_param->clk_ref_div); /* select divided VCO input */ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, - NAU8825_FLL_FILTER_SW_MASK, 0x0000); - /* FLL sigma delta modulator enable */ - regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, - NAU8825_SDM_EN_MASK, NAU8825_SDM_EN); + NAU8825_FLL_CLK_SW_MASK, NAU8825_FLL_CLK_SW_REF); + /* Disable free-running mode */ + regmap_update_bits(nau8825->regmap, + NAU8825_REG_FLL6, NAU8825_DCO_EN, 0); + if (fll_param->fll_frac) { + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_MASK, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_FILTER); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, + NAU8825_SDM_EN, NAU8825_SDM_EN); + } else { + regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL5, + NAU8825_FLL_PDB_DAC_EN | NAU8825_FLL_LOOP_FTR_EN | + NAU8825_FLL_FTR_SW_MASK, NAU8825_FLL_FTR_SW_ACCU); + regmap_update_bits(nau8825->regmap, + NAU8825_REG_FLL6, NAU8825_SDM_EN, 0); + } } /* freq_out must be 256*Fs in order to achieve the best performance */ @@ -1069,6 +1982,45 @@ static int nau8825_set_pll(struct snd_soc_codec *codec, int pll_id, int source, return 0; } +static int nau8825_mclk_prepare(struct nau8825 *nau8825, unsigned int freq) +{ + int ret = 0; + + nau8825->mclk = devm_clk_get(nau8825->dev, "mclk"); + if (IS_ERR(nau8825->mclk)) { + dev_info(nau8825->dev, "No 'mclk' clock found, assume MCLK is managed externally"); + return 0; + } + + if (!nau8825->mclk_freq) { + ret = clk_prepare_enable(nau8825->mclk); + if (ret) { + dev_err(nau8825->dev, "Unable to prepare codec mclk\n"); + return ret; + } + } + + if (nau8825->mclk_freq != freq) { + freq = clk_round_rate(nau8825->mclk, freq); + ret = clk_set_rate(nau8825->mclk, freq); + if (ret) { + dev_err(nau8825->dev, "Unable to set mclk rate\n"); + return ret; + } + nau8825->mclk_freq = freq; + } + + return 0; +} + +static void nau8825_configure_mclk_as_sysclk(struct regmap *regmap) +{ + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK); + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_DCO_EN, 0); +} + static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id, unsigned int freq) { @@ -1076,40 +2028,106 @@ static int nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id, int ret; switch (clk_id) { + case NAU8825_CLK_DIS: + /* Clock provided externally and disable internal VCO clock */ + nau8825_configure_mclk_as_sysclk(regmap); + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; + } + + break; case NAU8825_CLK_MCLK: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + nau8825_configure_mclk_as_sysclk(regmap); + /* MCLK not changed by clock tree */ regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, - NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK); - regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, 0); + NAU8825_CLK_MCLK_SRC_MASK, 0); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); - /* We selected MCLK source but the clock itself managed externally */ - if (!nau8825->mclk) - break; + ret = nau8825_mclk_prepare(nau8825, freq); + if (ret) + return ret; - if (!nau8825->mclk_freq) { - ret = clk_prepare_enable(nau8825->mclk); - if (ret) { - dev_err(nau8825->dev, "Unable to prepare codec mclk\n"); - return ret; - } + break; + case NAU8825_CLK_INTERNAL: + if (nau8825_is_jack_inserted(nau8825->regmap)) { + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_DCO_EN, NAU8825_DCO_EN); + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + /* Decrease the VCO frequency for power saving */ + regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, + NAU8825_CLK_MCLK_SRC_MASK, 0xf); + regmap_update_bits(regmap, NAU8825_REG_FLL1, + NAU8825_FLL_RATIO_MASK, 0x10); + regmap_update_bits(regmap, NAU8825_REG_FLL6, + NAU8825_SDM_EN, NAU8825_SDM_EN); + } else { + /* The clock turns off intentionally for power saving + * when no headset connected. + */ + nau8825_configure_mclk_as_sysclk(regmap); + dev_warn(nau8825->dev, "Disable clock for power saving when no headset connected\n"); + } + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; } - if (nau8825->mclk_freq != freq) { - nau8825->mclk_freq = freq; + break; + case NAU8825_CLK_FLL_MCLK: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_MCLK); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); - freq = clk_round_rate(nau8825->mclk, freq); - ret = clk_set_rate(nau8825->mclk, freq); - if (ret) { - dev_err(nau8825->dev, "Unable to set mclk rate\n"); - return ret; - } + ret = nau8825_mclk_prepare(nau8825, freq); + if (ret) + return ret; + + break; + case NAU8825_CLK_FLL_BLK: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_BLK); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); + + if (nau8825->mclk_freq) { + clk_disable_unprepare(nau8825->mclk); + nau8825->mclk_freq = 0; } break; - case NAU8825_CLK_INTERNAL: - regmap_update_bits(regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN, - NAU8825_DCO_EN); - regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, - NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); + case NAU8825_CLK_FLL_FS: + /* Acquire the semaphone to synchronize the playback and + * interrupt handler. In order to avoid the playback inter- + * fered by cross talk process, the driver make the playback + * preparation halted until cross talk process finish. + */ + nau8825_sema_acquire(nau8825, 2 * HZ); + regmap_update_bits(regmap, NAU8825_REG_FLL3, + NAU8825_FLL_CLK_SRC_MASK, NAU8825_FLL_CLK_SRC_FS); + /* Release the semaphone. */ + nau8825_sema_release(nau8825); if (nau8825->mclk_freq) { clk_disable_unprepare(nau8825->mclk); @@ -1135,6 +2153,31 @@ static int nau8825_set_sysclk(struct snd_soc_codec *codec, int clk_id, return nau8825_configure_sysclk(nau8825, clk_id, freq); } +static int nau8825_resume_setup(struct nau8825 *nau8825) +{ + struct regmap *regmap = nau8825->regmap; + + /* Close clock when jack type detection at manual mode */ + nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0); + + /* Clear all interruption status */ + nau8825_int_status_clear_all(regmap); + + /* Enable both insertion and ejection interruptions, and then + * bypass de-bounce circuit. + */ + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN | + NAU8825_IRQ_EJECT_EN | NAU8825_IRQ_INSERT_EN, + NAU8825_IRQ_OUTPUT_EN | NAU8825_IRQ_HEADSET_COMPLETE_EN); + regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL, + NAU8825_JACK_DET_DB_BYPASS, NAU8825_JACK_DET_DB_BYPASS); + regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, + NAU8825_IRQ_INSERT_DIS | NAU8825_IRQ_EJECT_DIS, 0); + + return 0; +} + static int nau8825_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -1157,10 +2200,22 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec, return ret; } } + /* Setup codec configuration after resume */ + nau8825_resume_setup(nau8825); } break; case SND_SOC_BIAS_OFF: + /* Cancel and reset cross talk detection funciton */ + nau8825_xtalk_cancel(nau8825); + /* Turn off all interruptions before system shutdown. Keep the + * interruption quiet before resume setup completes. + */ + regmap_write(nau8825->regmap, + NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff); + /* Disable ADC needed for interruptions at audo mode */ + regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL, + NAU8825_ENABLE_ADC, 0); if (nau8825->mclk_freq) clk_disable_unprepare(nau8825->mclk); break; @@ -1168,57 +2223,46 @@ static int nau8825_set_bias_level(struct snd_soc_codec *codec, return 0; } -#ifdef CONFIG_PM -static int nau8825_suspend(struct snd_soc_codec *codec) +static int __maybe_unused nau8825_suspend(struct snd_soc_codec *codec) { struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); disable_irq(nau8825->irq); + snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF); regcache_cache_only(nau8825->regmap, true); regcache_mark_dirty(nau8825->regmap); return 0; } -static int nau8825_resume(struct snd_soc_codec *codec) +static int __maybe_unused nau8825_resume(struct snd_soc_codec *codec) { struct nau8825 *nau8825 = snd_soc_codec_get_drvdata(codec); - /* The chip may lose power and reset in S3. regcache_sync restores - * register values including configurations for sysclk, irq, and - * jack/button detection. - */ regcache_cache_only(nau8825->regmap, false); regcache_sync(nau8825->regmap); - - /* Check the jack plug status directly. If the headset is unplugged - * during S3 when the chip has no power, there will be no jack - * detection irq even after the nau8825_restart_jack_detection below, - * because the chip just thinks no headset has ever been plugged in. - */ - if (!nau8825_is_jack_inserted(nau8825->regmap)) { - nau8825_eject_jack(nau8825); - snd_soc_jack_report(nau8825->jack, 0, SND_JACK_HEADSET); + if (nau8825_is_jack_inserted(nau8825->regmap)) { + /* If the jack is inserted, we need to check whether the play- + * back is active before suspend. If active, the driver has to + * raise the protection for cross talk function to avoid the + * playback recovers before cross talk process finish. Other- + * wise, the playback will be interfered by cross talk func- + * tion. It is better to apply hardware related parameters + * before starting playback or record. + */ + if (nau8825_dai_is_active(nau8825)) { + nau8825->xtalk_protect = true; + nau8825_sema_acquire(nau8825, 0); + } } - enable_irq(nau8825->irq); - /* Run jack detection to check the type (OMTP or CTIA) of the headset - * if there is one. This handles the case where a different type of - * headset is plugged in during S3. This triggers an IRQ iff a headset - * is already plugged in. - */ - nau8825_restart_jack_detection(nau8825->regmap); - return 0; } -#else -#define nau8825_suspend NULL -#define nau8825_resume NULL -#endif static struct snd_soc_codec_driver nau8825_codec_driver = { .probe = nau8825_codec_probe, + .remove = nau8825_codec_remove, .set_sysclk = nau8825_set_sysclk, .set_pll = nau8825_set_pll, .set_bias_level = nau8825_set_bias_level, @@ -1318,22 +2362,8 @@ static int nau8825_read_device_properties(struct device *dev, static int nau8825_setup_irq(struct nau8825 *nau8825) { - struct regmap *regmap = nau8825->regmap; int ret; - /* IRQ Output Enable */ - regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK, - NAU8825_IRQ_OUTPUT_EN, NAU8825_IRQ_OUTPUT_EN); - - /* Enable internal VCO needed for interruptions */ - nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); - - /* Enable DDACR needed for interrupts - * It is the same as force_enable_pin("DDACR") we do later - */ - regmap_update_bits(regmap, NAU8825_REG_ENA_CTRL, - NAU8825_ENABLE_DACR, NAU8825_ENABLE_DACR); - ret = devm_request_threaded_irq(nau8825->dev, nau8825->irq, NULL, nau8825_interrupt, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "nau8825", nau8825); @@ -1370,6 +2400,13 @@ static int nau8825_i2c_probe(struct i2c_client *i2c, return PTR_ERR(nau8825->regmap); nau8825->dev = dev; nau8825->irq = i2c->irq; + /* Initiate parameters, semaphone and work queue which are needed in + * cross talk suppression measurment function. + */ + nau8825->xtalk_state = NAU8825_XTALK_DONE; + nau8825->xtalk_protect = false; + sema_init(&nau8825->xtalk_sem, 1); + INIT_WORK(&nau8825->xtalk_work, nau8825_xtalk_work); nau8825_print_device_properties(nau8825); @@ -1405,6 +2442,7 @@ static const struct i2c_device_id nau8825_i2c_ids[] = { { "nau8825", 0 }, { } }; +MODULE_DEVICE_TABLE(i2c, nau8825_i2c_ids); #ifdef CONFIG_OF static const struct of_device_id nau8825_of_ids[] = { diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h index 8ceb5f385478..1c63e2abafa9 100644 --- a/sound/soc/codecs/nau8825.h +++ b/sound/soc/codecs/nau8825.h @@ -93,12 +93,21 @@ #define NAU8825_REG_CHARGE_PUMP_INPUT_READ 0x81 #define NAU8825_REG_GENERAL_STATUS 0x82 #define NAU8825_REG_MAX NAU8825_REG_GENERAL_STATUS +/* 16-bit control register address, and 16-bits control register data */ +#define NAU8825_REG_ADDR_LEN 16 +#define NAU8825_REG_DATA_LEN 16 /* ENA_CTRL (0x1) */ #define NAU8825_ENABLE_DACR_SFT 10 #define NAU8825_ENABLE_DACR (1 << NAU8825_ENABLE_DACR_SFT) #define NAU8825_ENABLE_DACL_SFT 9 +#define NAU8825_ENABLE_DACL (1 << NAU8825_ENABLE_DACL_SFT) #define NAU8825_ENABLE_ADC_SFT 8 +#define NAU8825_ENABLE_ADC (1 << NAU8825_ENABLE_ADC_SFT) +#define NAU8825_ENABLE_ADC_CLK_SFT 7 +#define NAU8825_ENABLE_ADC_CLK (1 << NAU8825_ENABLE_ADC_CLK_SFT) +#define NAU8825_ENABLE_DAC_CLK_SFT 6 +#define NAU8825_ENABLE_DAC_CLK (1 << NAU8825_ENABLE_DAC_CLK_SFT) #define NAU8825_ENABLE_SAR_SFT 1 /* CLK_DIVIDER (0x3) */ @@ -113,20 +122,28 @@ /* FLL3 (0x06) */ #define NAU8825_FLL_INTEGER_MASK (0x3ff << 0) +#define NAU8825_FLL_CLK_SRC_SFT 10 +#define NAU8825_FLL_CLK_SRC_MASK (0x3 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_MCLK (0 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_BLK (0x2 << NAU8825_FLL_CLK_SRC_SFT) +#define NAU8825_FLL_CLK_SRC_FS (0x3 << NAU8825_FLL_CLK_SRC_SFT) /* FLL4 (0x07) */ #define NAU8825_FLL_REF_DIV_MASK (0x3 << 10) /* FLL5 (0x08) */ -#define NAU8825_FLL_FILTER_SW_MASK (0x1 << 14) +#define NAU8825_FLL_PDB_DAC_EN (0x1 << 15) +#define NAU8825_FLL_LOOP_FTR_EN (0x1 << 14) +#define NAU8825_FLL_CLK_SW_MASK (0x1 << 13) +#define NAU8825_FLL_CLK_SW_N2 (0x1 << 13) +#define NAU8825_FLL_CLK_SW_REF (0x0 << 13) +#define NAU8825_FLL_FTR_SW_MASK (0x1 << 12) +#define NAU8825_FLL_FTR_SW_ACCU (0x1 << 12) +#define NAU8825_FLL_FTR_SW_FILTER (0x0 << 12) /* FLL6 (0x9) */ -#define NAU8825_DCO_EN_MASK (0x1 << 15) #define NAU8825_DCO_EN (0x1 << 15) -#define NAU8825_DCO_DIS (0x0 << 15) -#define NAU8825_SDM_EN_MASK (0x1 << 14) #define NAU8825_SDM_EN (0x1 << 14) -#define NAU8825_SDM_DIS (0x0 << 14) /* HSD_CTRL (0xc) */ #define NAU8825_HSD_AUTO_MODE (1 << 6) @@ -136,6 +153,7 @@ /* JACK_DET_CTRL (0xd) */ #define NAU8825_JACK_DET_RESTART (1 << 9) +#define NAU8825_JACK_DET_DB_BYPASS (1 << 8) #define NAU8825_JACK_INSERT_DEBOUNCE_SFT 5 #define NAU8825_JACK_INSERT_DEBOUNCE_MASK (0x7 << NAU8825_JACK_INSERT_DEBOUNCE_SFT) #define NAU8825_JACK_EJECT_DEBOUNCE_SFT 2 @@ -145,9 +163,11 @@ /* INTERRUPT_MASK (0xf) */ #define NAU8825_IRQ_OUTPUT_EN (1 << 11) #define NAU8825_IRQ_HEADSET_COMPLETE_EN (1 << 10) +#define NAU8825_IRQ_RMS_EN (1 << 8) #define NAU8825_IRQ_KEY_RELEASE_EN (1 << 7) #define NAU8825_IRQ_KEY_SHORT_PRESS_EN (1 << 5) #define NAU8825_IRQ_EJECT_EN (1 << 2) +#define NAU8825_IRQ_INSERT_EN (1 << 0) /* IRQ_STATUS (0x10) */ #define NAU8825_HEADSET_COMPLETION_IRQ (1 << 10) @@ -168,6 +188,7 @@ #define NAU8825_IRQ_KEY_RELEASE_DIS (1 << 7) #define NAU8825_IRQ_KEY_SHORT_PRESS_DIS (1 << 5) #define NAU8825_IRQ_EJECT_DIS (1 << 2) +#define NAU8825_IRQ_INSERT_DIS (1 << 0) /* SAR_CTRL (0x13) */ #define NAU8825_SAR_ADC_EN_SFT 12 @@ -217,10 +238,21 @@ /* I2S_PCM_CTRL2 (0x1d) */ #define NAU8825_I2S_TRISTATE (1 << 15) /* 0 - normal mode, 1 - Hi-Z output */ +#define NAU8825_I2S_DRV_SFT 12 +#define NAU8825_I2S_DRV_MASK (0x3 << NAU8825_I2S_DRV_SFT) #define NAU8825_I2S_MS_SFT 3 #define NAU8825_I2S_MS_MASK (1 << NAU8825_I2S_MS_SFT) #define NAU8825_I2S_MS_MASTER (1 << NAU8825_I2S_MS_SFT) #define NAU8825_I2S_MS_SLAVE (0 << NAU8825_I2S_MS_SFT) +#define NAU8825_I2S_BLK_DIV_MASK 0x7 + +/* BIQ_CTRL (0x20) */ +#define NAU8825_BIQ_WRT_SFT 4 +#define NAU8825_BIQ_WRT_EN (1 << NAU8825_BIQ_WRT_SFT) +#define NAU8825_BIQ_PATH_SFT 0 +#define NAU8825_BIQ_PATH_MASK (1 << NAU8825_BIQ_PATH_SFT) +#define NAU8825_BIQ_PATH_ADC (0 << NAU8825_BIQ_PATH_SFT) +#define NAU8825_BIQ_PATH_DAC (1 << NAU8825_BIQ_PATH_SFT) /* ADC_RATE (0x2b) */ #define NAU8825_ADC_SYNC_DOWN_SFT 0 @@ -239,22 +271,72 @@ #define NAU8825_DAC_OVERSAMPLE_128 2 #define NAU8825_DAC_OVERSAMPLE_32 4 +/* ADC_DGAIN_CTRL (0x30) */ +#define NAU8825_ADC_DIG_VOL_MASK 0xff + /* MUTE_CTRL (0x31) */ #define NAU8825_DAC_ZERO_CROSSING_EN (1 << 9) #define NAU8825_DAC_SOFT_MUTE (1 << 9) /* HSVOL_CTRL (0x32) */ #define NAU8825_HP_MUTE (1 << 15) +#define NAU8825_HP_MUTE_AUTO (1 << 14) +#define NAU8825_HPL_MUTE (1 << 13) +#define NAU8825_HPR_MUTE (1 << 12) +#define NAU8825_HPL_VOL_SFT 6 +#define NAU8825_HPL_VOL_MASK (0x3f << NAU8825_HPL_VOL_SFT) +#define NAU8825_HPR_VOL_SFT 0 +#define NAU8825_HPR_VOL_MASK (0x3f << NAU8825_HPR_VOL_SFT) +#define NAU8825_HP_VOL_MIN 0x36 /* DACL_CTRL (0x33) */ #define NAU8825_DACL_CH_SEL_SFT 9 +#define NAU8825_DACL_CH_SEL_MASK (0x1 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_SEL_L (0x0 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_SEL_R (0x1 << NAU8825_DACL_CH_SEL_SFT) +#define NAU8825_DACL_CH_VOL_MASK 0xff /* DACR_CTRL (0x34) */ #define NAU8825_DACR_CH_SEL_SFT 9 +#define NAU8825_DACR_CH_SEL_MASK (0x1 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_SEL_L (0x0 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_SEL_R (0x1 << NAU8825_DACR_CH_SEL_SFT) +#define NAU8825_DACR_CH_VOL_MASK 0xff + +/* IMM_MODE_CTRL (0x4C) */ +#define NAU8825_IMM_THD_SFT 8 +#define NAU8825_IMM_THD_MASK (0x3f << NAU8825_IMM_THD_SFT) +#define NAU8825_IMM_GEN_VOL_SFT 6 +#define NAU8825_IMM_GEN_VOL_MASK (0x3 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_2nd (0x0 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_4th (0x1 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_8th (0x2 << NAU8825_IMM_GEN_VOL_SFT) +#define NAU8825_IMM_GEN_VOL_1_16th (0x3 << NAU8825_IMM_GEN_VOL_SFT) + +#define NAU8825_IMM_CYC_SFT 4 +#define NAU8825_IMM_CYC_MASK (0x3 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_1024 (0x0 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_2048 (0x1 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_4096 (0x2 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_CYC_8192 (0x3 << NAU8825_IMM_CYC_SFT) +#define NAU8825_IMM_EN (1 << 3) +#define NAU8825_IMM_DAC_SRC_MASK 0x7 +#define NAU8825_IMM_DAC_SRC_BIQ 0x0 +#define NAU8825_IMM_DAC_SRC_DRC 0x1 +#define NAU8825_IMM_DAC_SRC_MIX 0x2 +#define NAU8825_IMM_DAC_SRC_SIN 0x3 /* CLASSG_CTRL (0x50) */ #define NAU8825_CLASSG_TIMER_SFT 8 #define NAU8825_CLASSG_TIMER_MASK (0x3f << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_1ms (0x1 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_2ms (0x2 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_8ms (0x4 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_16ms (0x8 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_32ms (0x10 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_TIMER_64ms (0x20 << NAU8825_CLASSG_TIMER_SFT) +#define NAU8825_CLASSG_LDAC_EN (0x1 << 2) +#define NAU8825_CLASSG_RDAC_EN (0x1 << 1) #define NAU8825_CLASSG_EN (1 << 0) /* I2C_DEVICE_ID (0x58) */ @@ -263,7 +345,12 @@ #define NAU8825_SOFTWARE_ID_NAU8825 0x0 /* BIAS_ADJ (0x66) */ -#define NAU8825_BIAS_TESTDAC_EN (0x3 << 8) +#define NAU8825_BIAS_HPR_IMP (1 << 15) +#define NAU8825_BIAS_HPL_IMP (1 << 14) +#define NAU8825_BIAS_TESTDAC_SFT 8 +#define NAU8825_BIAS_TESTDAC_EN (0x3 << NAU8825_BIAS_TESTDAC_SFT) +#define NAU8825_BIAS_TESTDACR_EN (0x2 << NAU8825_BIAS_TESTDAC_SFT) +#define NAU8825_BIAS_TESTDACL_EN (0x1 << NAU8825_BIAS_TESTDAC_SFT) #define NAU8825_BIAS_VMID (1 << 6) #define NAU8825_BIAS_VMID_SEL_SFT 4 #define NAU8825_BIAS_VMID_SEL_MASK (3 << NAU8825_BIAS_VMID_SEL_SFT) @@ -282,6 +369,11 @@ #define NAU8825_POWERUP_ADCL (1 << 6) /* RDAC (0x73) */ +#define NAU8825_RDAC_FS_BCLK_ENB (1 << 15) +#define NAU8825_RDAC_EN_SFT 12 +#define NAU8825_RDAC_EN (0x3 << NAU8825_RDAC_EN_SFT) +#define NAU8825_RDAC_CLK_EN_SFT 8 +#define NAU8825_RDAC_CLK_EN (0x3 << NAU8825_RDAC_CLK_EN_SFT) #define NAU8825_RDAC_CLK_DELAY_SFT 4 #define NAU8825_RDAC_CLK_DELAY_MASK (0x7 << NAU8825_RDAC_CLK_DELAY_SFT) #define NAU8825_RDAC_VREF_SFT 2 @@ -318,8 +410,21 @@ /* System Clock Source */ enum { - NAU8825_CLK_MCLK = 0, + NAU8825_CLK_DIS = 0, + NAU8825_CLK_MCLK, NAU8825_CLK_INTERNAL, + NAU8825_CLK_FLL_MCLK, + NAU8825_CLK_FLL_BLK, + NAU8825_CLK_FLL_FS, +}; + +/* Cross talk detection state */ +enum { + NAU8825_XTALK_PREPARE = 0, + NAU8825_XTALK_HPR_R2L, + NAU8825_XTALK_HPL_R2L, + NAU8825_XTALK_IMM, + NAU8825_XTALK_DONE, }; struct nau8825 { @@ -328,6 +433,8 @@ struct nau8825 { struct snd_soc_dapm_context *dapm; struct snd_soc_jack *jack; struct clk *mclk; + struct work_struct xtalk_work; + struct semaphore xtalk_sem; int irq; int mclk_freq; /* 0 - mclk is disabled */ int button_pressed; @@ -346,6 +453,12 @@ struct nau8825 { int key_debounce; int jack_insert_debounce; int jack_eject_debounce; + int high_imped; + int xtalk_state; + int xtalk_event; + int xtalk_event_mask; + bool xtalk_protect; + int imp_rms[NAU8825_XTALK_IMM]; }; int nau8825_enable_jack_detect(struct snd_soc_codec *codec, diff --git a/sound/soc/codecs/pcm1681.c b/sound/soc/codecs/pcm1681.c index 58325234285c..33e1fc2d1598 100644 --- a/sound/soc/codecs/pcm1681.c +++ b/sound/soc/codecs/pcm1681.c @@ -73,7 +73,7 @@ static bool pcm1681_accessible_reg(struct device *dev, unsigned int reg) return !((reg == 0x00) || (reg == 0x0f)); } -static bool pcm1681_writeable_reg(struct device *dev, unsigned register reg) +static bool pcm1681_writeable_reg(struct device *dev, unsigned int reg) { return pcm1681_accessible_reg(dev, reg) && (reg != PCM1681_ZERO_DETECT_STATUS); diff --git a/sound/soc/codecs/pcm179x.c b/sound/soc/codecs/pcm179x.c index 06a66579ca6d..88fbdd184aa0 100644 --- a/sound/soc/codecs/pcm179x.c +++ b/sound/soc/codecs/pcm179x.c @@ -59,7 +59,7 @@ static bool pcm179x_accessible_reg(struct device *dev, unsigned int reg) return reg >= 0x10 && reg <= 0x17; } -static bool pcm179x_writeable_reg(struct device *dev, unsigned register reg) +static bool pcm179x_writeable_reg(struct device *dev, unsigned int reg) { bool accessible; diff --git a/sound/soc/codecs/pcm5102a.c b/sound/soc/codecs/pcm5102a.c index ed515677409b..8ba322a00363 100644 --- a/sound/soc/codecs/pcm5102a.c +++ b/sound/soc/codecs/pcm5102a.c @@ -57,7 +57,6 @@ static struct platform_driver pcm5102a_codec_driver = { .remove = pcm5102a_remove, .driver = { .name = "pcm5102a-codec", - .owner = THIS_MODULE, .of_match_table = pcm5102a_of_match, }, }; diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c index 1bd31644a782..74c0e4eb3788 100644 --- a/sound/soc/codecs/rt286.c +++ b/sound/soc/codecs/rt286.c @@ -1100,6 +1100,13 @@ static const struct dmi_system_id force_combo_jack_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Skylake Client platform") } }, + { + .ident = "Intel Kabylake RVP", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Kabylake Client platform") + } + }, + { } }; diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c new file mode 100644 index 000000000000..77ff8ebe6dfb --- /dev/null +++ b/sound/soc/codecs/rt5514-spi.c @@ -0,0 +1,447 @@ +/* + * rt5514-spi.c -- RT5514 SPI driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Oder Chiou <oder_chiou@realtek.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. + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/spi/spi.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_qos.h> +#include <linux/sysfs.h> +#include <linux/clk.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 "rt5514-spi.h" + +static struct spi_device *rt5514_spi; + +struct rt5514_dsp { + struct device *dev; + struct delayed_work copy_work; + struct mutex dma_lock; + struct snd_pcm_substream *substream; + unsigned int buf_base, buf_limit, buf_rp; + size_t buf_size; + size_t dma_offset; + size_t dsp_offset; +}; + +static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = 0x20000 / 8, + .periods_min = 8, + .periods_max = 8, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 0x20000, +}; + +static struct snd_soc_dai_driver rt5514_spi_dai = { + .name = "rt5514-dsp-cpu-dai", + .id = 0, + .capture = { + .stream_name = "DSP Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; + +static void rt5514_spi_copy_work(struct work_struct *work) +{ + struct rt5514_dsp *rt5514_dsp = + container_of(work, struct rt5514_dsp, copy_work.work); + struct snd_pcm_runtime *runtime; + size_t period_bytes, truncated_bytes = 0; + + mutex_lock(&rt5514_dsp->dma_lock); + if (!rt5514_dsp->substream) { + dev_err(rt5514_dsp->dev, "No pcm substream\n"); + goto done; + } + + runtime = rt5514_dsp->substream->runtime; + period_bytes = snd_pcm_lib_period_bytes(rt5514_dsp->substream); + + if (rt5514_dsp->buf_size - rt5514_dsp->dsp_offset < period_bytes) + period_bytes = rt5514_dsp->buf_size - rt5514_dsp->dsp_offset; + + if (rt5514_dsp->buf_rp + period_bytes <= rt5514_dsp->buf_limit) { + rt5514_spi_burst_read(rt5514_dsp->buf_rp, + runtime->dma_area + rt5514_dsp->dma_offset, + period_bytes); + + if (rt5514_dsp->buf_rp + period_bytes == rt5514_dsp->buf_limit) + rt5514_dsp->buf_rp = rt5514_dsp->buf_base; + else + rt5514_dsp->buf_rp += period_bytes; + } else { + truncated_bytes = rt5514_dsp->buf_limit - rt5514_dsp->buf_rp; + rt5514_spi_burst_read(rt5514_dsp->buf_rp, + runtime->dma_area + rt5514_dsp->dma_offset, + truncated_bytes); + + rt5514_spi_burst_read(rt5514_dsp->buf_base, + runtime->dma_area + rt5514_dsp->dma_offset + + truncated_bytes, period_bytes - truncated_bytes); + + rt5514_dsp->buf_rp = rt5514_dsp->buf_base + + period_bytes - truncated_bytes; + } + + rt5514_dsp->dma_offset += period_bytes; + if (rt5514_dsp->dma_offset >= runtime->dma_bytes) + rt5514_dsp->dma_offset = 0; + + rt5514_dsp->dsp_offset += period_bytes; + + snd_pcm_period_elapsed(rt5514_dsp->substream); + + if (rt5514_dsp->dsp_offset < rt5514_dsp->buf_size) + schedule_delayed_work(&rt5514_dsp->copy_work, 5); +done: + mutex_unlock(&rt5514_dsp->dma_lock); +} + +/* PCM for streaming audio from the DSP buffer */ +static int rt5514_spi_pcm_open(struct snd_pcm_substream *substream) +{ + snd_soc_set_runtime_hwparams(substream, &rt5514_spi_pcm_hardware); + + return 0; +} + +static int rt5514_spi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + int ret; + + mutex_lock(&rt5514_dsp->dma_lock); + ret = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + rt5514_dsp->substream = substream; + mutex_unlock(&rt5514_dsp->dma_lock); + + return ret; +} + +static int rt5514_spi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + + mutex_lock(&rt5514_dsp->dma_lock); + rt5514_dsp->substream = NULL; + mutex_unlock(&rt5514_dsp->dma_lock); + + cancel_delayed_work_sync(&rt5514_dsp->copy_work); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int rt5514_spi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + u8 buf[8]; + + rt5514_dsp->dma_offset = 0; + rt5514_dsp->dsp_offset = 0; + + /** + * The address area x1800XXXX is the register address, and it cannot + * support spi burst read perfectly. So we use the spi burst read + * individually to make sure the data correctly. + */ + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_BASE, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_base = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_LIMIT, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_limit = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_RP, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_rp = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + rt5514_spi_burst_read(RT5514_BUFFER_VOICE_SIZE, (u8 *)&buf, + sizeof(buf)); + rt5514_dsp->buf_size = buf[0] | buf[1] << 8 | buf[2] << 16 | + buf[3] << 24; + + return 0; +} + +static int rt5514_spi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (rt5514_dsp->buf_base && rt5514_dsp->buf_limit && + rt5514_dsp->buf_rp && rt5514_dsp->buf_size) + schedule_delayed_work(&rt5514_dsp->copy_work, 0); + } + + return 0; +} + +static snd_pcm_uframes_t rt5514_spi_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rt5514_dsp *rt5514_dsp = + snd_soc_platform_get_drvdata(rtd->platform); + + return bytes_to_frames(runtime, rt5514_dsp->dma_offset); +} + +static struct snd_pcm_ops rt5514_spi_pcm_ops = { + .open = rt5514_spi_pcm_open, + .hw_params = rt5514_spi_hw_params, + .hw_free = rt5514_spi_hw_free, + .trigger = rt5514_spi_trigger, + .prepare = rt5514_spi_prepare, + .pointer = rt5514_spi_pcm_pointer, + .mmap = snd_pcm_lib_mmap_vmalloc, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static int rt5514_spi_pcm_probe(struct snd_soc_platform *platform) +{ + struct rt5514_dsp *rt5514_dsp; + + rt5514_dsp = devm_kzalloc(platform->dev, sizeof(*rt5514_dsp), + GFP_KERNEL); + + rt5514_dsp->dev = &rt5514_spi->dev; + mutex_init(&rt5514_dsp->dma_lock); + INIT_DELAYED_WORK(&rt5514_dsp->copy_work, rt5514_spi_copy_work); + snd_soc_platform_set_drvdata(platform, rt5514_dsp); + + return 0; +} + +static struct snd_soc_platform_driver rt5514_spi_platform = { + .probe = rt5514_spi_pcm_probe, + .ops = &rt5514_spi_pcm_ops, +}; + +static const struct snd_soc_component_driver rt5514_spi_dai_component = { + .name = "rt5514-spi-dai", +}; + +/** + * rt5514_spi_burst_read - Read data from SPI by rt5514 address. + * @addr: Start address. + * @rxbuf: Data Buffer for reading. + * @len: Data length, it must be a multiple of 8. + * + * + * Returns true for success. + */ +int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len) +{ + u8 spi_cmd = RT5514_SPI_CMD_BURST_READ; + int status; + u8 write_buf[8]; + unsigned int i, end, offset = 0; + + struct spi_message message; + struct spi_transfer x[3]; + + while (offset < len) { + if (offset + RT5514_SPI_BUF_LEN <= len) + end = RT5514_SPI_BUF_LEN; + else + end = len % RT5514_SPI_BUF_LEN; + + write_buf[0] = spi_cmd; + write_buf[1] = ((addr + offset) & 0xff000000) >> 24; + write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + write_buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + spi_message_init(&message); + memset(x, 0, sizeof(x)); + + x[0].len = 5; + x[0].tx_buf = write_buf; + spi_message_add_tail(&x[0], &message); + + x[1].len = 4; + x[1].tx_buf = write_buf; + spi_message_add_tail(&x[1], &message); + + x[2].len = end; + x[2].rx_buf = rxbuf + offset; + spi_message_add_tail(&x[2], &message); + + status = spi_sync(rt5514_spi, &message); + + if (status) + return false; + + offset += RT5514_SPI_BUF_LEN; + } + + for (i = 0; i < len; i += 8) { + write_buf[0] = rxbuf[i + 0]; + write_buf[1] = rxbuf[i + 1]; + write_buf[2] = rxbuf[i + 2]; + write_buf[3] = rxbuf[i + 3]; + write_buf[4] = rxbuf[i + 4]; + write_buf[5] = rxbuf[i + 5]; + write_buf[6] = rxbuf[i + 6]; + write_buf[7] = rxbuf[i + 7]; + + rxbuf[i + 0] = write_buf[7]; + rxbuf[i + 1] = write_buf[6]; + rxbuf[i + 2] = write_buf[5]; + rxbuf[i + 3] = write_buf[4]; + rxbuf[i + 4] = write_buf[3]; + rxbuf[i + 5] = write_buf[2]; + rxbuf[i + 6] = write_buf[1]; + rxbuf[i + 7] = write_buf[0]; + } + + return true; +} + +/** + * rt5514_spi_burst_write - Write data to SPI by rt5514 address. + * @addr: Start address. + * @txbuf: Data Buffer for writng. + * @len: Data length, it must be a multiple of 8. + * + * + * Returns true for success. + */ +int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len) +{ + u8 spi_cmd = RT5514_SPI_CMD_BURST_WRITE; + u8 *write_buf; + unsigned int i, end, offset = 0; + + write_buf = kmalloc(RT5514_SPI_BUF_LEN + 6, GFP_KERNEL); + + if (write_buf == NULL) + return -ENOMEM; + + while (offset < len) { + if (offset + RT5514_SPI_BUF_LEN <= len) + end = RT5514_SPI_BUF_LEN; + else + end = len % RT5514_SPI_BUF_LEN; + + write_buf[0] = spi_cmd; + write_buf[1] = ((addr + offset) & 0xff000000) >> 24; + write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16; + write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8; + write_buf[4] = ((addr + offset) & 0x000000ff) >> 0; + + for (i = 0; i < end; i += 8) { + write_buf[i + 12] = txbuf[offset + i + 0]; + write_buf[i + 11] = txbuf[offset + i + 1]; + write_buf[i + 10] = txbuf[offset + i + 2]; + write_buf[i + 9] = txbuf[offset + i + 3]; + write_buf[i + 8] = txbuf[offset + i + 4]; + write_buf[i + 7] = txbuf[offset + i + 5]; + write_buf[i + 6] = txbuf[offset + i + 6]; + write_buf[i + 5] = txbuf[offset + i + 7]; + } + + write_buf[end + 5] = spi_cmd; + + spi_write(rt5514_spi, write_buf, end + 6); + + offset += RT5514_SPI_BUF_LEN; + } + + kfree(write_buf); + + return 0; +} +EXPORT_SYMBOL_GPL(rt5514_spi_burst_write); + +static int rt5514_spi_probe(struct spi_device *spi) +{ + int ret; + + rt5514_spi = spi; + + ret = devm_snd_soc_register_platform(&spi->dev, &rt5514_spi_platform); + if (ret < 0) { + dev_err(&spi->dev, "Failed to register platform.\n"); + return ret; + } + + ret = devm_snd_soc_register_component(&spi->dev, + &rt5514_spi_dai_component, + &rt5514_spi_dai, 1); + if (ret < 0) { + dev_err(&spi->dev, "Failed to register component.\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id rt5514_of_match[] = { + { .compatible = "realtek,rt5514", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt5514_of_match); + +static struct spi_driver rt5514_spi_driver = { + .driver = { + .name = "rt5514", + .of_match_table = of_match_ptr(rt5514_of_match), + }, + .probe = rt5514_spi_probe, +}; +module_spi_driver(rt5514_spi_driver); + +MODULE_DESCRIPTION("RT5514 SPI driver"); +MODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt5514-spi.h b/sound/soc/codecs/rt5514-spi.h new file mode 100644 index 000000000000..f69b1cdf2f9b --- /dev/null +++ b/sound/soc/codecs/rt5514-spi.h @@ -0,0 +1,38 @@ +/* + * rt5514-spi.h -- RT5514 driver + * + * Copyright 2015 Realtek Semiconductor Corp. + * Author: Oder Chiou <oder_chiou@realtek.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. + */ + +#ifndef __RT5514_SPI_H__ +#define __RT5514_SPI_H__ + +/** + * RT5514_SPI_BUF_LEN is the buffer size of SPI master controller. +*/ +#define RT5514_SPI_BUF_LEN 240 + +#define RT5514_BUFFER_VOICE_BASE 0x18001034 +#define RT5514_BUFFER_VOICE_LIMIT 0x18001038 +#define RT5514_BUFFER_VOICE_RP 0x1800103c +#define RT5514_BUFFER_VOICE_SIZE 0x18001040 + +/* SPI Command */ +enum { + RT5514_SPI_CMD_16_READ = 0, + RT5514_SPI_CMD_16_WRITE, + RT5514_SPI_CMD_32_READ, + RT5514_SPI_CMD_32_WRITE, + RT5514_SPI_CMD_BURST_READ, + RT5514_SPI_CMD_BURST_WRITE, +}; + +int rt5514_spi_burst_read(unsigned int addr, u8 *rxbuf, size_t len); +int rt5514_spi_burst_write(u32 addr, const u8 *txbuf, size_t len); + +#endif /* __RT5514_SPI_H__ */ diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 879bf60f4965..7162f05101d9 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -30,6 +30,9 @@ #include "rl6231.h" #include "rt5514.h" +#if defined(CONFIG_SND_SOC_RT5514_SPI) +#include "rt5514-spi.h" +#endif static const struct reg_sequence rt5514_i2c_patch[] = { {0x1800101c, 0x00000000}, @@ -110,6 +113,35 @@ static const struct reg_default rt5514_reg[] = { {RT5514_VENDOR_ID2, 0x10ec5514}, }; +static void rt5514_enable_dsp_prepare(struct rt5514_priv *rt5514) +{ + /* Reset */ + regmap_write(rt5514->i2c_regmap, 0x18002000, 0x000010ec); + /* LDO_I_limit */ + regmap_write(rt5514->i2c_regmap, 0x18002200, 0x00028604); + /* I2C bypass enable */ + regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000001); + /* mini-core reset */ + regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x0005514b); + regmap_write(rt5514->i2c_regmap, 0x18002f00, 0x00055149); + /* I2C bypass disable */ + regmap_write(rt5514->i2c_regmap, 0xfafafafa, 0x00000000); + /* PIN config */ + regmap_write(rt5514->i2c_regmap, 0x18002070, 0x00000040); + /* PLL3(QN)=RCOSC*(10+2) */ + regmap_write(rt5514->i2c_regmap, 0x18002240, 0x0000000a); + /* PLL3 source=RCOSC, fsi=rt_clk */ + regmap_write(rt5514->i2c_regmap, 0x18002100, 0x0000000b); + /* Power on RCOSC, pll3 */ + regmap_write(rt5514->i2c_regmap, 0x18002004, 0x00808b81); + /* DSP clk source = pll3, ENABLE DSP clk */ + regmap_write(rt5514->i2c_regmap, 0x18002f08, 0x00000005); + /* Enable DSP clk auto switch */ + regmap_write(rt5514->i2c_regmap, 0x18001114, 0x00000001); + /* Reduce DSP power */ + regmap_write(rt5514->i2c_regmap, 0x18001118, 0x00000001); +} + static bool rt5514_volatile_register(struct device *dev, unsigned int reg) { switch (reg) { @@ -248,6 +280,74 @@ static const DECLARE_TLV_DB_RANGE(bst_tlv, static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -17625, 375, 0); +static int rt5514_dsp_voice_wake_up_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = rt5514->dsp_enabled; + + return 0; +} + +static int rt5514_dsp_voice_wake_up_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component); + struct snd_soc_codec *codec = rt5514->codec; + const struct firmware *fw = NULL; + + if (ucontrol->value.integer.value[0] == rt5514->dsp_enabled) + return 0; + + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { + rt5514->dsp_enabled = ucontrol->value.integer.value[0]; + + if (rt5514->dsp_enabled) { + rt5514_enable_dsp_prepare(rt5514); + + request_firmware(&fw, RT5514_FIRMWARE1, codec->dev); + if (fw) { +#if defined(CONFIG_SND_SOC_RT5514_SPI) + rt5514_spi_burst_write(0x4ff60000, fw->data, + ((fw->size/8)+1)*8); +#else + dev_err(codec->dev, "There is no SPI driver for" + " loading the firmware\n"); +#endif + release_firmware(fw); + fw = NULL; + } + + request_firmware(&fw, RT5514_FIRMWARE2, codec->dev); + if (fw) { +#if defined(CONFIG_SND_SOC_RT5514_SPI) + rt5514_spi_burst_write(0x4ffc0000, fw->data, + ((fw->size/8)+1)*8); +#else + dev_err(codec->dev, "There is no SPI driver for" + " loading the firmware\n"); +#endif + release_firmware(fw); + fw = NULL; + } + + /* DSP run */ + regmap_write(rt5514->i2c_regmap, 0x18002f00, + 0x00055148); + } else { + regmap_multi_reg_write(rt5514->i2c_regmap, + rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch)); + regcache_mark_dirty(rt5514->regmap); + regcache_sync(rt5514->regmap); + } + } + + return 0; +} + static const struct snd_kcontrol_new rt5514_snd_controls[] = { SOC_DOUBLE_TLV("MIC Boost Volume", RT5514_ANA_CTRL_MICBST, RT5514_SEL_BSTL_SFT, RT5514_SEL_BSTR_SFT, 8, 0, bst_tlv), @@ -257,6 +357,8 @@ static const struct snd_kcontrol_new rt5514_snd_controls[] = { SOC_DOUBLE_R_TLV("ADC2 Capture Volume", RT5514_DOWNFILTER1_CTRL1, RT5514_DOWNFILTER1_CTRL2, RT5514_AD_GAIN_SFT, 127, 0, adc_vol_tlv), + SOC_SINGLE_EXT("DSP Voice Wake Up", SND_SOC_NOPM, 0, 1, 0, + rt5514_dsp_voice_wake_up_get, rt5514_dsp_voice_wake_up_put), }; /* ADC Mixer*/ @@ -365,6 +467,35 @@ static int rt5514_is_sys_clk_from_pll(struct snd_soc_dapm_widget *source, return 0; } +static int rt5514_pre_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /** + * If the DSP is enabled in start of recording, the DSP + * should be disabled, and sync back to normal recording + * settings to make sure recording properly. + */ + if (rt5514->dsp_enabled) { + rt5514->dsp_enabled = 0; + regmap_multi_reg_write(rt5514->i2c_regmap, + rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch)); + regcache_mark_dirty(rt5514->regmap); + regcache_sync(rt5514->regmap); + } + break; + + default: + return 0; + } + + return 0; +} + static const struct snd_soc_dapm_widget rt5514_dapm_widgets[] = { /* Input Lines */ SND_SOC_DAPM_INPUT("DMIC1L"), @@ -472,6 +603,8 @@ static const struct snd_soc_dapm_widget rt5514_dapm_widgets[] = { /* Audio Interface */ SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_PRE("DAPM Pre", rt5514_pre_event), }; static const struct snd_soc_dapm_route rt5514_dapm_routes[] = { @@ -799,10 +932,41 @@ static int rt5514_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; } +static int rt5514_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec); + int ret; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (IS_ERR(rt5514->mclk)) + break; + + if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_ON) { + clk_disable_unprepare(rt5514->mclk); + } else { + ret = clk_prepare_enable(rt5514->mclk); + if (ret) + return ret; + } + break; + + default: + break; + } + + return 0; +} + static int rt5514_probe(struct snd_soc_codec *codec) { struct rt5514_priv *rt5514 = snd_soc_codec_get_drvdata(codec); + rt5514->mclk = devm_clk_get(codec->dev, "mclk"); + if (PTR_ERR(rt5514->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + rt5514->codec = codec; return 0; @@ -858,6 +1022,7 @@ struct snd_soc_dai_driver rt5514_dai[] = { static struct snd_soc_codec_driver soc_codec_dev_rt5514 = { .probe = rt5514_probe, .idle_bias_off = true, + .set_bias_level = rt5514_set_bias_level, .controls = rt5514_snd_controls, .num_controls = ARRAY_SIZE(rt5514_snd_controls), .dapm_widgets = rt5514_dapm_widgets, @@ -871,7 +1036,6 @@ static const struct regmap_config rt5514_i2c_regmap = { .reg_bits = 32, .val_bits = 32, - .max_register = RT5514_DSP_MAPPING | RT5514_VENDOR_ID2, .readable_reg = rt5514_i2c_readable_register, .cache_type = REGCACHE_NONE, @@ -944,7 +1108,7 @@ static int rt5514_i2c_probe(struct i2c_client *i2c, return -ENODEV; } - ret = regmap_register_patch(rt5514->i2c_regmap, rt5514_i2c_patch, + ret = regmap_multi_reg_write(rt5514->i2c_regmap, rt5514_i2c_patch, ARRAY_SIZE(rt5514_i2c_patch)); if (ret != 0) dev_warn(&i2c->dev, "Failed to apply i2c_regmap patch: %d\n", diff --git a/sound/soc/codecs/rt5514.h b/sound/soc/codecs/rt5514.h index 6ad8a612f659..68883c68e999 100644 --- a/sound/soc/codecs/rt5514.h +++ b/sound/soc/codecs/rt5514.h @@ -12,6 +12,8 @@ #ifndef __RT5514_H__ #define __RT5514_H__ +#include <linux/clk.h> + #define RT5514_DEVICE_ID 0x10ec5514 #define RT5514_RESET 0x2000 @@ -225,6 +227,9 @@ #define RT5514_PLL_INP_MAX 40000000 #define RT5514_PLL_INP_MIN 256000 +#define RT5514_FIRMWARE1 "rt5514_dsp_fw1.bin" +#define RT5514_FIRMWARE2 "rt5514_dsp_fw2.bin" + /* System Clock Source */ enum { RT5514_SCLK_S_MCLK, @@ -240,6 +245,7 @@ enum { struct rt5514_priv { struct snd_soc_codec *codec; struct regmap *i2c_regmap, *regmap; + struct clk *mclk; int sysclk; int sysclk_src; int lrck; @@ -247,6 +253,7 @@ struct rt5514_priv { int pll_src; int pll_in; int pll_out; + int dsp_enabled; }; #endif /* __RT5514_H__ */ diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index d70847c9eeb0..490bfe661346 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -63,6 +63,7 @@ static const struct reg_sequence init_list[] = { {RT5645_PR_BASE + 0x20, 0x611f}, {RT5645_PR_BASE + 0x21, 0x4040}, {RT5645_PR_BASE + 0x23, 0x0004}, + {RT5645_ASRC_4, 0x0120}, }; static const struct reg_sequence rt5650_init_list[] = { @@ -157,7 +158,7 @@ static const struct reg_default rt5645_reg[] = { { 0x83, 0x0000 }, { 0x84, 0x0000 }, { 0x85, 0x0000 }, - { 0x8a, 0x0000 }, + { 0x8a, 0x0120 }, { 0x8e, 0x0004 }, { 0x8f, 0x1100 }, { 0x90, 0x0646 }, @@ -314,7 +315,7 @@ static const struct reg_default rt5650_reg[] = { { 0x83, 0x0000 }, { 0x84, 0x0000 }, { 0x85, 0x0000 }, - { 0x8a, 0x0000 }, + { 0x8a, 0x0120 }, { 0x8e, 0x0004 }, { 0x8f, 0x1100 }, { 0x90, 0x0646 }, @@ -440,6 +441,7 @@ static bool rt5645_volatile_register(struct device *dev, unsigned int reg) switch (reg) { case RT5645_RESET: + case RT5645_PRIV_INDEX: case RT5645_PRIV_DATA: case RT5645_IN1_CTRL1: case RT5645_IN1_CTRL2: @@ -740,6 +742,14 @@ static int rt5645_spk_put_volsw(struct snd_kcontrol *kcontrol, return ret; } +static const char * const rt5645_dac1_vol_ctrl_mode_text[] = { + "immediately", "zero crossing", "soft ramp" +}; + +static SOC_ENUM_SINGLE_DECL( + rt5645_dac1_vol_ctrl_mode, RT5645_PR_BASE, + RT5645_DA1_ZDET_SFT, rt5645_dac1_vol_ctrl_mode_text); + static const struct snd_kcontrol_new rt5645_snd_controls[] = { /* Speaker Output Volume */ SOC_DOUBLE("Speaker Channel Switch", RT5645_SPK_VOL, @@ -806,6 +816,9 @@ static const struct snd_kcontrol_new rt5645_snd_controls[] = { SOC_SINGLE("I2S2 Func Switch", RT5645_GPIO_CTRL1, RT5645_I2S2_SEL_SFT, 1, 1), RT5645_HWEQ("Speaker HWEQ"), + + /* Digital Soft Volume Control */ + SOC_ENUM("DAC1 Digital Volume Control Func", rt5645_dac1_vol_ctrl_mode), }; /** @@ -3531,6 +3544,7 @@ MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id); static const struct acpi_device_id rt5645_acpi_match[] = { { "10EC5645", 0 }, { "10EC5650", 0 }, + { "10EC5640", 0 }, {}, }; MODULE_DEVICE_TABLE(acpi, rt5645_acpi_match); @@ -3561,6 +3575,12 @@ static const struct dmi_system_id dmi_platform_intel_braswell[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Setzer"), }, }, + { + .ident = "Microsoft Surface 3", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, { } }; diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h index 205e0715c99a..cfc5f97549eb 100644 --- a/sound/soc/codecs/rt5645.h +++ b/sound/soc/codecs/rt5645.h @@ -2018,6 +2018,9 @@ /* Codec Private Register definition */ +/* DAC ADC Digital Volume (0x00) */ +#define RT5645_DA1_ZDET_SFT 6 + /* 3D Speaker Control (0x63) */ #define RT5645_3D_SPK_MASK (0x1 << 15) #define RT5645_3D_SPK_SFT 15 diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index 0af5ddbef1da..8ef467f64f03 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -55,6 +55,7 @@ static const struct reg_sequence init_list[] = { { RT5670_PR_BASE + 0x14, 0x9a8a }, { RT5670_PR_BASE + 0x38, 0x3ba1 }, { RT5670_PR_BASE + 0x3d, 0x3640 }, + { 0x8a, 0x0123 }, }; static const struct reg_default rt5670_reg[] = { @@ -131,7 +132,7 @@ static const struct reg_default rt5670_reg[] = { { 0x87, 0x0000 }, { 0x88, 0x0000 }, { 0x89, 0x0000 }, - { 0x8a, 0x0000 }, + { 0x8a, 0x0123 }, { 0x8b, 0x0000 }, { 0x8c, 0x0003 }, { 0x8d, 0x0000 }, diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 08b40460663c..527b759c1562 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -38,7 +38,6 @@ /* default value of sgtl5000 registers */ static const struct reg_default sgtl5000_reg_defaults[] = { { SGTL5000_CHIP_DIG_POWER, 0x0000 }, - { SGTL5000_CHIP_CLK_CTRL, 0x0008 }, { SGTL5000_CHIP_I2S_CTRL, 0x0010 }, { SGTL5000_CHIP_SSS_CTRL, 0x0010 }, { SGTL5000_CHIP_ADCDAC_CTRL, 0x020c }, @@ -47,12 +46,10 @@ static const struct reg_default sgtl5000_reg_defaults[] = { { SGTL5000_CHIP_ANA_ADC_CTRL, 0x0000 }, { SGTL5000_CHIP_ANA_HP_CTRL, 0x1818 }, { SGTL5000_CHIP_ANA_CTRL, 0x0111 }, - { SGTL5000_CHIP_LINREG_CTRL, 0x0000 }, { SGTL5000_CHIP_REF_CTRL, 0x0000 }, { SGTL5000_CHIP_MIC_CTRL, 0x0000 }, { SGTL5000_CHIP_LINE_OUT_CTRL, 0x0000 }, { SGTL5000_CHIP_LINE_OUT_VOL, 0x0404 }, - { SGTL5000_CHIP_ANA_POWER, 0x7060 }, { SGTL5000_CHIP_PLL_CTRL, 0x5000 }, { SGTL5000_CHIP_CLK_TOP_CTRL, 0x0000 }, { SGTL5000_CHIP_ANA_STATUS, 0x0000 }, @@ -92,35 +89,8 @@ static const char *supply_names[SGTL5000_SUPPLY_NUM] = { "VDDD" }; -#define LDO_CONSUMER_NAME "VDDD_LDO" #define LDO_VOLTAGE 1200000 - -static struct regulator_consumer_supply ldo_consumer[] = { - REGULATOR_SUPPLY(LDO_CONSUMER_NAME, NULL), -}; - -static struct regulator_init_data ldo_init_data = { - .constraints = { - .min_uV = 1200000, - .max_uV = 1200000, - .valid_modes_mask = REGULATOR_MODE_NORMAL, - .valid_ops_mask = REGULATOR_CHANGE_STATUS, - }, - .num_consumer_supplies = 1, - .consumer_supplies = &ldo_consumer[0], -}; - -/* - * sgtl5000 internal ldo regulator, - * enabled when VDDD not provided - */ -struct ldo_regulator { - struct regulator_desc desc; - struct regulator_dev *dev; - int voltage; - void *codec_data; - bool enabled; -}; +#define LINREG_VDDD ((1600 - LDO_VOLTAGE / 1000) / 50) enum sgtl5000_micbias_resistor { SGTL5000_MICBIAS_OFF = 0, @@ -135,7 +105,7 @@ struct sgtl5000_priv { int master; /* i2s master or not */ int fmt; /* i2s data format */ struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM]; - struct ldo_regulator *ldo; + int num_supplies; struct regmap *regmap; struct clk *mclk; int revision; @@ -415,6 +385,9 @@ static const DECLARE_TLV_DB_RANGE(mic_gain_tlv, /* tlv for hp volume, -51.5db to 12.0db, step .5db */ static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0); +/* tlv for lineout volume, 31 steps of .5db each */ +static const DECLARE_TLV_DB_SCALE(lineout_volume, -1550, 50, 0); + static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { /* SOC_DOUBLE_S8_TLV with invert */ { @@ -443,6 +416,13 @@ static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL, 0, 3, 0, mic_gain_tlv), + + SOC_DOUBLE_TLV("Lineout Playback Volume", + SGTL5000_CHIP_LINE_OUT_VOL, + SGTL5000_LINE_OUT_VOL_LEFT_SHIFT, + SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT, + 0x1f, 1, + lineout_volume), }; /* mute the codec used by alsa core */ @@ -778,155 +758,6 @@ static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -#ifdef CONFIG_REGULATOR -static int ldo_regulator_is_enabled(struct regulator_dev *dev) -{ - struct ldo_regulator *ldo = rdev_get_drvdata(dev); - - return ldo->enabled; -} - -static int ldo_regulator_enable(struct regulator_dev *dev) -{ - struct ldo_regulator *ldo = rdev_get_drvdata(dev); - struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data; - int reg; - - if (ldo_regulator_is_enabled(dev)) - return 0; - - /* set regulator value firstly */ - reg = (1600 - ldo->voltage / 1000) / 50; - reg = clamp(reg, 0x0, 0xf); - - /* amend the voltage value, unit: uV */ - ldo->voltage = (1600 - reg * 50) * 1000; - - /* set voltage to register */ - snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, - SGTL5000_LINREG_VDDD_MASK, reg); - - snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, - SGTL5000_LINEREG_D_POWERUP, - SGTL5000_LINEREG_D_POWERUP); - - /* when internal ldo is enabled, simple digital power can be disabled */ - snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, - SGTL5000_LINREG_SIMPLE_POWERUP, - 0); - - ldo->enabled = 1; - return 0; -} - -static int ldo_regulator_disable(struct regulator_dev *dev) -{ - struct ldo_regulator *ldo = rdev_get_drvdata(dev); - struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data; - - snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, - SGTL5000_LINEREG_D_POWERUP, - 0); - - /* clear voltage info */ - snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, - SGTL5000_LINREG_VDDD_MASK, 0); - - ldo->enabled = 0; - - return 0; -} - -static int ldo_regulator_get_voltage(struct regulator_dev *dev) -{ - struct ldo_regulator *ldo = rdev_get_drvdata(dev); - - return ldo->voltage; -} - -static struct regulator_ops ldo_regulator_ops = { - .is_enabled = ldo_regulator_is_enabled, - .enable = ldo_regulator_enable, - .disable = ldo_regulator_disable, - .get_voltage = ldo_regulator_get_voltage, -}; - -static int ldo_regulator_register(struct snd_soc_codec *codec, - struct regulator_init_data *init_data, - int voltage) -{ - struct ldo_regulator *ldo; - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - struct regulator_config config = { }; - - ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL); - - if (!ldo) - return -ENOMEM; - - ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL); - if (!ldo->desc.name) { - kfree(ldo); - dev_err(codec->dev, "failed to allocate decs name memory\n"); - return -ENOMEM; - } - - ldo->desc.type = REGULATOR_VOLTAGE; - ldo->desc.owner = THIS_MODULE; - ldo->desc.ops = &ldo_regulator_ops; - ldo->desc.n_voltages = 1; - - ldo->codec_data = codec; - ldo->voltage = voltage; - - config.dev = codec->dev; - config.driver_data = ldo; - config.init_data = init_data; - - ldo->dev = regulator_register(&ldo->desc, &config); - if (IS_ERR(ldo->dev)) { - int ret = PTR_ERR(ldo->dev); - - dev_err(codec->dev, "failed to register regulator\n"); - kfree(ldo->desc.name); - kfree(ldo); - - return ret; - } - sgtl5000->ldo = ldo; - - return 0; -} - -static int ldo_regulator_remove(struct snd_soc_codec *codec) -{ - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - struct ldo_regulator *ldo = sgtl5000->ldo; - - if (!ldo) - return 0; - - regulator_unregister(ldo->dev); - kfree(ldo->desc.name); - kfree(ldo); - - return 0; -} -#else -static int ldo_regulator_register(struct snd_soc_codec *codec, - struct regulator_init_data *init_data, - int voltage) -{ - dev_err(codec->dev, "this setup needs regulator support in the kernel\n"); - return -EINVAL; -} - -static int ldo_regulator_remove(struct snd_soc_codec *codec) -{ - return 0; -} -#endif - /* * set dac bias * common state changes: @@ -940,42 +771,17 @@ static int ldo_regulator_remove(struct snd_soc_codec *codec) static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - int ret; - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - switch (level) { case SND_SOC_BIAS_ON: case SND_SOC_BIAS_PREPARE: - break; case SND_SOC_BIAS_STANDBY: - if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) { - ret = regulator_bulk_enable( - ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - if (ret) - return ret; - udelay(10); - - regcache_cache_only(sgtl5000->regmap, false); - - ret = regcache_sync(sgtl5000->regmap); - if (ret != 0) { - dev_err(codec->dev, - "Failed to restore cache: %d\n", ret); - - regcache_cache_only(sgtl5000->regmap, true); - regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - - return ret; - } - } - + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_REFTOP_POWERUP, + SGTL5000_REFTOP_POWERUP); break; case SND_SOC_BIAS_OFF: - regcache_cache_only(sgtl5000->regmap, true); - regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_REFTOP_POWERUP, 0); break; } @@ -1113,7 +919,6 @@ static const u8 vol_quot_table[] = { * and should be set according to: * 1. vddd provided by external or not * 2. vdda and vddio voltage value. > 3.1v or not - * 3. chip revision >=0x11 or not. If >=0x11, not use external vddd. */ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) { @@ -1131,7 +936,9 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer); vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer); - vddd = regulator_get_voltage(sgtl5000->supplies[VDDD].consumer); + vddd = (sgtl5000->num_supplies > VDDD) + ? regulator_get_voltage(sgtl5000->supplies[VDDD].consumer) + : LDO_VOLTAGE; vdda = vdda / 1000; vddio = vddio / 1000; @@ -1178,25 +985,6 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); - /* set voltage to register */ - snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL, - SGTL5000_LINREG_VDDD_MASK, 0x8); - - /* - * if vddd linear reg has been enabled, - * simple digital supply should be clear to get - * proper VDDD voltage. - */ - if (ana_pwr & SGTL5000_LINEREG_D_POWERUP) - snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, - SGTL5000_LINREG_SIMPLE_POWERUP, - 0); - else - snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, - SGTL5000_LINREG_SIMPLE_POWERUP | - SGTL5000_STARTUP_POWERUP, - 0); - /* * set ADC/DAC VAG to vdda / 2, * should stay in range (0.8v, 1.575v) @@ -1256,78 +1044,43 @@ static int sgtl5000_set_power_regs(struct snd_soc_codec *codec) return 0; } -static int sgtl5000_replace_vddd_with_ldo(struct snd_soc_codec *codec) -{ - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - int ret; - - /* set internal ldo to 1.2v */ - ret = ldo_regulator_register(codec, &ldo_init_data, LDO_VOLTAGE); - if (ret) { - dev_err(codec->dev, - "Failed to register vddd internal supplies: %d\n", ret); - return ret; - } - - sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME; - - dev_info(codec->dev, "Using internal LDO instead of VDDD\n"); - return 0; -} - -static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) +static int sgtl5000_enable_regulators(struct i2c_client *client) { int ret; int i; int external_vddd = 0; - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); struct regulator *vddd; + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++) sgtl5000->supplies[i].supply = supply_names[i]; - /* External VDDD only works before revision 0x11 */ - if (sgtl5000->revision < 0x11) { - vddd = regulator_get_optional(codec->dev, "VDDD"); - if (IS_ERR(vddd)) { - /* See if it's just not registered yet */ - if (PTR_ERR(vddd) == -EPROBE_DEFER) - return -EPROBE_DEFER; - } else { - external_vddd = 1; - regulator_put(vddd); - } - } - - if (!external_vddd) { - ret = sgtl5000_replace_vddd_with_ldo(codec); - if (ret) - return ret; + vddd = regulator_get_optional(&client->dev, "VDDD"); + if (IS_ERR(vddd)) { + /* See if it's just not registered yet */ + if (PTR_ERR(vddd) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } else { + external_vddd = 1; + regulator_put(vddd); } - ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies), + sgtl5000->num_supplies = ARRAY_SIZE(sgtl5000->supplies) + - 1 + external_vddd; + ret = regulator_bulk_get(&client->dev, sgtl5000->num_supplies, sgtl5000->supplies); if (ret) - goto err_ldo_remove; - - ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - if (ret) - goto err_regulator_free; - - /* wait for all power rails bring up */ - udelay(10); + return ret; - return 0; + ret = regulator_bulk_enable(sgtl5000->num_supplies, + sgtl5000->supplies); + if (!ret) + usleep_range(10, 20); + else + regulator_bulk_free(sgtl5000->num_supplies, + sgtl5000->supplies); -err_regulator_free: - regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); -err_ldo_remove: - if (!external_vddd) - ldo_regulator_remove(codec); return ret; - } static int sgtl5000_probe(struct snd_soc_codec *codec) @@ -1335,10 +1088,6 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) int ret; struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - ret = sgtl5000_enable_regulators(codec); - if (ret) - return ret; - /* power up sgtl5000 */ ret = sgtl5000_set_power_regs(codec); if (ret) @@ -1389,25 +1138,11 @@ static int sgtl5000_probe(struct snd_soc_codec *codec) return 0; err: - regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - ldo_regulator_remove(codec); - return ret; } static int sgtl5000_remove(struct snd_soc_codec *codec) { - struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); - - regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies), - sgtl5000->supplies); - ldo_regulator_remove(codec); - return 0; } @@ -1448,8 +1183,9 @@ static const struct regmap_config sgtl5000_regmap = { * and avoid problems like, not being able to probe after an audio playback * followed by a system reset or a 'reboot' command in Linux */ -static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000) +static void sgtl5000_fill_defaults(struct i2c_client *client) { + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); int i, ret, val, index; for (i = 0; i < ARRAY_SIZE(sgtl5000_reg_defaults); i++) { @@ -1457,10 +1193,10 @@ static int sgtl5000_fill_defaults(struct sgtl5000_priv *sgtl5000) index = sgtl5000_reg_defaults[i].reg; ret = regmap_write(sgtl5000->regmap, index, val); if (ret) - return ret; + dev_err(&client->dev, + "%s: error %d setting reg 0x%02x to 0x%04x\n", + __func__, ret, index, val); } - - return 0; } static int sgtl5000_i2c_probe(struct i2c_client *client, @@ -1470,16 +1206,23 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, int ret, reg, rev; struct device_node *np = client->dev.of_node; u32 value; + u16 ana_pwr; sgtl5000 = devm_kzalloc(&client->dev, sizeof(*sgtl5000), GFP_KERNEL); if (!sgtl5000) return -ENOMEM; + i2c_set_clientdata(client, sgtl5000); + + ret = sgtl5000_enable_regulators(client); + if (ret) + return ret; + sgtl5000->regmap = devm_regmap_init_i2c(client, &sgtl5000_regmap); if (IS_ERR(sgtl5000->regmap)) { ret = PTR_ERR(sgtl5000->regmap); dev_err(&client->dev, "Failed to allocate regmap: %d\n", ret); - return ret; + goto disable_regs; } sgtl5000->mclk = devm_clk_get(&client->dev, NULL); @@ -1488,21 +1231,25 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, dev_err(&client->dev, "Failed to get mclock: %d\n", ret); /* Defer the probe to see if the clk will be provided later */ if (ret == -ENOENT) - return -EPROBE_DEFER; - return ret; + ret = -EPROBE_DEFER; + goto disable_regs; } ret = clk_prepare_enable(sgtl5000->mclk); - if (ret) - return ret; + if (ret) { + dev_err(&client->dev, "Error enabling clock %d\n", ret); + goto disable_regs; + } /* Need 8 clocks before I2C accesses */ udelay(1); /* read chip information */ ret = regmap_read(sgtl5000->regmap, SGTL5000_CHIP_ID, ®); - if (ret) + if (ret) { + dev_err(&client->dev, "Error reading chip id %d\n", ret); goto disable_clk; + } if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) != SGTL5000_PARTID_PART_ID) { @@ -1516,6 +1263,44 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, dev_info(&client->dev, "sgtl5000 revision 0x%x\n", rev); sgtl5000->revision = rev; + /* reconfigure the clocks in case we're using the PLL */ + ret = regmap_write(sgtl5000->regmap, + SGTL5000_CHIP_CLK_CTRL, + SGTL5000_CHIP_CLK_CTRL_DEFAULT); + if (ret) + dev_err(&client->dev, + "Error %d initializing CHIP_CLK_CTRL\n", ret); + + /* Follow section 2.2.1.1 of AN3663 */ + ana_pwr = SGTL5000_ANA_POWER_DEFAULT; + if (sgtl5000->num_supplies <= VDDD) { + /* internal VDDD at 1.2V */ + ret = regmap_update_bits(sgtl5000->regmap, + SGTL5000_CHIP_LINREG_CTRL, + SGTL5000_LINREG_VDDD_MASK, + LINREG_VDDD); + if (ret) + dev_err(&client->dev, + "Error %d setting LINREG_VDDD\n", ret); + + ana_pwr |= SGTL5000_LINEREG_D_POWERUP; + dev_info(&client->dev, + "Using internal LDO instead of VDDD: check ER1\n"); + } else { + /* using external LDO for VDDD + * Clear startup powerup and simple powerup + * bits to save power + */ + ana_pwr &= ~(SGTL5000_STARTUP_POWERUP + | SGTL5000_LINREG_SIMPLE_POWERUP); + dev_dbg(&client->dev, "Using external VDDD\n"); + } + ret = regmap_write(sgtl5000->regmap, SGTL5000_CHIP_ANA_POWER, ana_pwr); + if (ret) + dev_err(&client->dev, + "Error %d setting CHIP_ANA_POWER to %04x\n", + ret, ana_pwr); + if (np) { if (!of_property_read_u32(np, "micbias-resistor-k-ohms", &value)) { @@ -1557,12 +1342,8 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, } } - i2c_set_clientdata(client, sgtl5000); - /* Ensure sgtl5000 will start with sane register values */ - ret = sgtl5000_fill_defaults(sgtl5000); - if (ret) - goto disable_clk; + sgtl5000_fill_defaults(client); ret = snd_soc_register_codec(&client->dev, &sgtl5000_driver, &sgtl5000_dai, 1); @@ -1573,6 +1354,11 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, disable_clk: clk_disable_unprepare(sgtl5000->mclk); + +disable_regs: + regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies); + regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies); + return ret; } @@ -1582,6 +1368,9 @@ static int sgtl5000_i2c_remove(struct i2c_client *client) snd_soc_unregister_codec(&client->dev); clk_disable_unprepare(sgtl5000->mclk); + regulator_bulk_disable(sgtl5000->num_supplies, sgtl5000->supplies); + regulator_bulk_free(sgtl5000->num_supplies, sgtl5000->supplies); + return 0; } diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h index 1c317de26176..22f3442af982 100644 --- a/sound/soc/codecs/sgtl5000.h +++ b/sound/soc/codecs/sgtl5000.h @@ -92,6 +92,7 @@ /* * SGTL5000_CHIP_CLK_CTRL */ +#define SGTL5000_CHIP_CLK_CTRL_DEFAULT 0x0008 #define SGTL5000_RATE_MODE_MASK 0x0030 #define SGTL5000_RATE_MODE_SHIFT 4 #define SGTL5000_RATE_MODE_WIDTH 2 @@ -325,6 +326,7 @@ /* * SGTL5000_CHIP_ANA_POWER */ +#define SGTL5000_ANA_POWER_DEFAULT 0x7060 #define SGTL5000_DAC_STEREO 0x4000 #define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 #define SGTL5000_STARTUP_POWERUP 0x1000 diff --git a/sound/soc/codecs/tas571x.c b/sound/soc/codecs/tas571x.c index b8d19b77bde9..d8baca3f8413 100644 --- a/sound/soc/codecs/tas571x.c +++ b/sound/soc/codecs/tas571x.c @@ -28,6 +28,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/tlv.h> +#include <asm/unaligned.h> #include "tas571x.h" @@ -63,6 +64,10 @@ static int tas571x_register_size(struct tas571x_private *priv, unsigned int reg) case TAS571X_INPUT_MUX_REG: case TAS571X_CH4_SRC_SELECT_REG: case TAS571X_PWM_MUX_REG: + case TAS5717_CH1_RIGHT_CH_MIX_REG: + case TAS5717_CH1_LEFT_CH_MIX_REG: + case TAS5717_CH2_LEFT_CH_MIX_REG: + case TAS5717_CH2_RIGHT_CH_MIX_REG: return 4; default: return 1; @@ -135,6 +140,129 @@ static int tas571x_reg_read(void *context, unsigned int reg, return 0; } +/* + * register write for 8- and 20-byte registers + */ +static int tas571x_reg_write_multiword(struct i2c_client *client, + unsigned int reg, const long values[], size_t len) +{ + size_t i; + uint8_t *buf, *p; + int ret; + size_t send_size = 1 + len * sizeof(uint32_t); + + buf = kzalloc(send_size, GFP_KERNEL | GFP_DMA); + if (!buf) + return -ENOMEM; + buf[0] = reg; + + for (i = 0, p = buf + 1; i < len; i++, p += sizeof(uint32_t)) + put_unaligned_be32(values[i], p); + + ret = i2c_master_send(client, buf, send_size); + + kfree(buf); + + if (ret == send_size) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +/* + * register read for 8- and 20-byte registers + */ +static int tas571x_reg_read_multiword(struct i2c_client *client, + unsigned int reg, long values[], size_t len) +{ + unsigned int i; + uint8_t send_buf; + uint8_t *recv_buf, *p; + struct i2c_msg msgs[2]; + unsigned int recv_size = len * sizeof(uint32_t); + int ret; + + recv_buf = kzalloc(recv_size, GFP_KERNEL | GFP_DMA); + if (!recv_buf) + return -ENOMEM; + + send_buf = reg; + + msgs[0].addr = client->addr; + msgs[0].len = sizeof(send_buf); + msgs[0].buf = &send_buf; + msgs[0].flags = 0; + + msgs[1].addr = client->addr; + msgs[1].len = recv_size; + msgs[1].buf = recv_buf; + msgs[1].flags = I2C_M_RD; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + goto err_ret; + else if (ret != ARRAY_SIZE(msgs)) { + ret = -EIO; + goto err_ret; + } + + for (i = 0, p = recv_buf; i < len; i++, p += sizeof(uint32_t)) + values[i] = get_unaligned_be32(p); + +err_ret: + kfree(recv_buf); + return ret; +} + +/* + * Integer array controls for setting biquad, mixer, DRC coefficients. + * According to the datasheet each coefficient is effectively 26bits, + * i.e. stored as 32bits, where bits [31:26] are ignored. + * TI's TAS57xx Graphical Development Environment tool however produces + * coefficients with more than 26 bits. For this reason we allow values + * in the full 32-bits reange. + * The coefficients are ordered as given in the TAS571x data sheet: + * b0, b1, b2, a1, a2 + */ + +static int tas571x_coefficient_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int numcoef = kcontrol->private_value >> 16; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = numcoef; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xffffffff; + return 0; +} + +static int tas571x_coefficient_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct i2c_client *i2c = to_i2c_client(codec->dev); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + + return tas571x_reg_read_multiword(i2c, index, + ucontrol->value.integer.value, numcoef); +} + +static int tas571x_coefficient_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol); + struct i2c_client *i2c = to_i2c_client(codec->dev); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + + return tas571x_reg_write_multiword(i2c, index, + ucontrol->value.integer.value, numcoef); +} + static int tas571x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format) { struct tas571x_private *priv = snd_soc_codec_get_drvdata(dai->codec); @@ -241,6 +369,15 @@ static const struct snd_soc_dai_ops tas571x_dai_ops = { .digital_mute = tas571x_mute, }; + +#define BIQUAD_COEFS(xname, reg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = tas571x_coefficient_info, \ + .get = tas571x_coefficient_get,\ + .put = tas571x_coefficient_put, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .private_value = reg | (5 << 16) } + static const char *const tas5711_supply_names[] = { "AVDD", "DVDD", @@ -264,6 +401,16 @@ static const struct snd_kcontrol_new tas5711_controls[] = { TAS571X_SOFT_MUTE_REG, TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, 1, 1), + + SOC_DOUBLE_R_RANGE("CH1 Mixer Volume", + TAS5717_CH1_LEFT_CH_MIX_REG, + TAS5717_CH1_RIGHT_CH_MIX_REG, + 16, 0, 0x80, 0), + + SOC_DOUBLE_R_RANGE("CH2 Mixer Volume", + TAS5717_CH2_LEFT_CH_MIX_REG, + TAS5717_CH2_RIGHT_CH_MIX_REG, + 16, 0, 0x80, 0), }; static const struct regmap_range tas571x_readonly_regs_range[] = { @@ -340,6 +487,43 @@ static const struct snd_kcontrol_new tas5717_controls[] = { TAS571X_SOFT_MUTE_REG, TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT, 1, 1), + + /* + * The biquads are named according to the register names. + * Please note that TI's TAS57xx Graphical Development Environment + * tool names them different. + */ + BIQUAD_COEFS("CH1 - Biquad 0", TAS5717_CH1_BQ0_REG), + BIQUAD_COEFS("CH1 - Biquad 1", TAS5717_CH1_BQ1_REG), + BIQUAD_COEFS("CH1 - Biquad 2", TAS5717_CH1_BQ2_REG), + BIQUAD_COEFS("CH1 - Biquad 3", TAS5717_CH1_BQ3_REG), + BIQUAD_COEFS("CH1 - Biquad 4", TAS5717_CH1_BQ4_REG), + BIQUAD_COEFS("CH1 - Biquad 5", TAS5717_CH1_BQ5_REG), + BIQUAD_COEFS("CH1 - Biquad 6", TAS5717_CH1_BQ6_REG), + BIQUAD_COEFS("CH1 - Biquad 7", TAS5717_CH1_BQ7_REG), + BIQUAD_COEFS("CH1 - Biquad 8", TAS5717_CH1_BQ8_REG), + BIQUAD_COEFS("CH1 - Biquad 9", TAS5717_CH1_BQ9_REG), + BIQUAD_COEFS("CH1 - Biquad 10", TAS5717_CH1_BQ10_REG), + BIQUAD_COEFS("CH1 - Biquad 11", TAS5717_CH1_BQ11_REG), + + BIQUAD_COEFS("CH2 - Biquad 0", TAS5717_CH2_BQ0_REG), + BIQUAD_COEFS("CH2 - Biquad 1", TAS5717_CH2_BQ1_REG), + BIQUAD_COEFS("CH2 - Biquad 2", TAS5717_CH2_BQ2_REG), + BIQUAD_COEFS("CH2 - Biquad 3", TAS5717_CH2_BQ3_REG), + BIQUAD_COEFS("CH2 - Biquad 4", TAS5717_CH2_BQ4_REG), + BIQUAD_COEFS("CH2 - Biquad 5", TAS5717_CH2_BQ5_REG), + BIQUAD_COEFS("CH2 - Biquad 6", TAS5717_CH2_BQ6_REG), + BIQUAD_COEFS("CH2 - Biquad 7", TAS5717_CH2_BQ7_REG), + BIQUAD_COEFS("CH2 - Biquad 8", TAS5717_CH2_BQ8_REG), + BIQUAD_COEFS("CH2 - Biquad 9", TAS5717_CH2_BQ9_REG), + BIQUAD_COEFS("CH2 - Biquad 10", TAS5717_CH2_BQ10_REG), + BIQUAD_COEFS("CH2 - Biquad 11", TAS5717_CH2_BQ11_REG), + + BIQUAD_COEFS("CH3 - Biquad 0", TAS5717_CH3_BQ0_REG), + BIQUAD_COEFS("CH3 - Biquad 1", TAS5717_CH3_BQ1_REG), + + BIQUAD_COEFS("CH4 - Biquad 0", TAS5717_CH4_BQ0_REG), + BIQUAD_COEFS("CH4 - Biquad 1", TAS5717_CH4_BQ1_REG), }; static const struct reg_default tas5717_reg_defaults[] = { @@ -350,6 +534,10 @@ static const struct reg_default tas5717_reg_defaults[] = { { 0x08, 0x00c0 }, { 0x09, 0x00c0 }, { 0x1b, 0x82 }, + { TAS5717_CH1_RIGHT_CH_MIX_REG, 0x0 }, + { TAS5717_CH1_LEFT_CH_MIX_REG, 0x800000}, + { TAS5717_CH2_LEFT_CH_MIX_REG, 0x0 }, + { TAS5717_CH2_RIGHT_CH_MIX_REG, 0x800000}, }; static const struct regmap_config tas5717_regmap_config = { diff --git a/sound/soc/codecs/tas571x.h b/sound/soc/codecs/tas571x.h index cf800c364f0f..c45677bc26ad 100644 --- a/sound/soc/codecs/tas571x.h +++ b/sound/soc/codecs/tas571x.h @@ -52,4 +52,44 @@ #define TAS571X_CH4_SRC_SELECT_REG 0x21 #define TAS571X_PWM_MUX_REG 0x25 +/* 20-byte biquad registers */ +#define TAS5717_CH1_BQ0_REG 0x26 +#define TAS5717_CH1_BQ1_REG 0x27 +#define TAS5717_CH1_BQ2_REG 0x28 +#define TAS5717_CH1_BQ3_REG 0x29 +#define TAS5717_CH1_BQ4_REG 0x2a +#define TAS5717_CH1_BQ5_REG 0x2b +#define TAS5717_CH1_BQ6_REG 0x2c +#define TAS5717_CH1_BQ7_REG 0x2d +#define TAS5717_CH1_BQ8_REG 0x2e +#define TAS5717_CH1_BQ9_REG 0x2f + +#define TAS5717_CH2_BQ0_REG 0x30 +#define TAS5717_CH2_BQ1_REG 0x31 +#define TAS5717_CH2_BQ2_REG 0x32 +#define TAS5717_CH2_BQ3_REG 0x33 +#define TAS5717_CH2_BQ4_REG 0x34 +#define TAS5717_CH2_BQ5_REG 0x35 +#define TAS5717_CH2_BQ6_REG 0x36 +#define TAS5717_CH2_BQ7_REG 0x37 +#define TAS5717_CH2_BQ8_REG 0x38 +#define TAS5717_CH2_BQ9_REG 0x39 + +#define TAS5717_CH1_BQ10_REG 0x58 +#define TAS5717_CH1_BQ11_REG 0x59 + +#define TAS5717_CH4_BQ0_REG 0x5a +#define TAS5717_CH4_BQ1_REG 0x5b + +#define TAS5717_CH2_BQ10_REG 0x5c +#define TAS5717_CH2_BQ11_REG 0x5d + +#define TAS5717_CH3_BQ0_REG 0x5e +#define TAS5717_CH3_BQ1_REG 0x5f + +#define TAS5717_CH1_RIGHT_CH_MIX_REG 0x72 +#define TAS5717_CH1_LEFT_CH_MIX_REG 0x73 +#define TAS5717_CH2_LEFT_CH_MIX_REG 0x76 +#define TAS5717_CH2_RIGHT_CH_MIX_REG 0x77 + #endif /* _TAS571X_H */ diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h index fe16c34607bb..ac9b146526eb 100644 --- a/sound/soc/codecs/tlv320aic31xx.h +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -38,141 +38,143 @@ struct aic31xx_pdata { int micbias_vg; }; +#define AIC31XX_REG(page, reg) ((page * 128) + reg) + /* Page Control Register */ -#define AIC31XX_PAGECTL 0x00 +#define AIC31XX_PAGECTL AIC31XX_REG(0, 0) /* Page 0 Registers */ /* Software reset register */ -#define AIC31XX_RESET 0x01 +#define AIC31XX_RESET AIC31XX_REG(0, 1) /* OT FLAG register */ -#define AIC31XX_OT_FLAG 0x03 +#define AIC31XX_OT_FLAG AIC31XX_REG(0, 3) /* Clock clock Gen muxing, Multiplexers*/ -#define AIC31XX_CLKMUX 0x04 +#define AIC31XX_CLKMUX AIC31XX_REG(0, 4) /* PLL P and R-VAL register */ -#define AIC31XX_PLLPR 0x05 +#define AIC31XX_PLLPR AIC31XX_REG(0, 5) /* PLL J-VAL register */ -#define AIC31XX_PLLJ 0x06 +#define AIC31XX_PLLJ AIC31XX_REG(0, 6) /* PLL D-VAL MSB register */ -#define AIC31XX_PLLDMSB 0x07 +#define AIC31XX_PLLDMSB AIC31XX_REG(0, 7) /* PLL D-VAL LSB register */ -#define AIC31XX_PLLDLSB 0x08 +#define AIC31XX_PLLDLSB AIC31XX_REG(0, 8) /* DAC NDAC_VAL register*/ -#define AIC31XX_NDAC 0x0B +#define AIC31XX_NDAC AIC31XX_REG(0, 11) /* DAC MDAC_VAL register */ -#define AIC31XX_MDAC 0x0C +#define AIC31XX_MDAC AIC31XX_REG(0, 12) /* DAC OSR setting register 1, MSB value */ -#define AIC31XX_DOSRMSB 0x0D +#define AIC31XX_DOSRMSB AIC31XX_REG(0, 13) /* DAC OSR setting register 2, LSB value */ -#define AIC31XX_DOSRLSB 0x0E -#define AIC31XX_MINI_DSP_INPOL 0x10 +#define AIC31XX_DOSRLSB AIC31XX_REG(0, 14) +#define AIC31XX_MINI_DSP_INPOL AIC31XX_REG(0, 16) /* Clock setting register 8, PLL */ -#define AIC31XX_NADC 0x12 +#define AIC31XX_NADC AIC31XX_REG(0, 18) /* Clock setting register 9, PLL */ -#define AIC31XX_MADC 0x13 +#define AIC31XX_MADC AIC31XX_REG(0, 19) /* ADC Oversampling (AOSR) Register */ -#define AIC31XX_AOSR 0x14 +#define AIC31XX_AOSR AIC31XX_REG(0, 20) /* Clock setting register 9, Multiplexers */ -#define AIC31XX_CLKOUTMUX 0x19 +#define AIC31XX_CLKOUTMUX AIC31XX_REG(0, 25) /* Clock setting register 10, CLOCKOUT M divider value */ -#define AIC31XX_CLKOUTMVAL 0x1A +#define AIC31XX_CLKOUTMVAL AIC31XX_REG(0, 26) /* Audio Interface Setting Register 1 */ -#define AIC31XX_IFACE1 0x1B +#define AIC31XX_IFACE1 AIC31XX_REG(0, 27) /* Audio Data Slot Offset Programming */ -#define AIC31XX_DATA_OFFSET 0x1C +#define AIC31XX_DATA_OFFSET AIC31XX_REG(0, 28) /* Audio Interface Setting Register 2 */ -#define AIC31XX_IFACE2 0x1D +#define AIC31XX_IFACE2 AIC31XX_REG(0, 29) /* Clock setting register 11, BCLK N Divider */ -#define AIC31XX_BCLKN 0x1E +#define AIC31XX_BCLKN AIC31XX_REG(0, 30) /* Audio Interface Setting Register 3, Secondary Audio Interface */ -#define AIC31XX_IFACESEC1 0x1F +#define AIC31XX_IFACESEC1 AIC31XX_REG(0, 31) /* Audio Interface Setting Register 4 */ -#define AIC31XX_IFACESEC2 0x20 +#define AIC31XX_IFACESEC2 AIC31XX_REG(0, 32) /* Audio Interface Setting Register 5 */ -#define AIC31XX_IFACESEC3 0x21 +#define AIC31XX_IFACESEC3 AIC31XX_REG(0, 33) /* I2C Bus Condition */ -#define AIC31XX_I2C 0x22 +#define AIC31XX_I2C AIC31XX_REG(0, 34) /* ADC FLAG */ -#define AIC31XX_ADCFLAG 0x24 +#define AIC31XX_ADCFLAG AIC31XX_REG(0, 36) /* DAC Flag Registers */ -#define AIC31XX_DACFLAG1 0x25 -#define AIC31XX_DACFLAG2 0x26 +#define AIC31XX_DACFLAG1 AIC31XX_REG(0, 37) +#define AIC31XX_DACFLAG2 AIC31XX_REG(0, 38) /* Sticky Interrupt flag (overflow) */ -#define AIC31XX_OFFLAG 0x27 +#define AIC31XX_OFFLAG AIC31XX_REG(0, 39) /* Sticy DAC Interrupt flags */ -#define AIC31XX_INTRDACFLAG 0x2C +#define AIC31XX_INTRDACFLAG AIC31XX_REG(0, 44) /* Sticy ADC Interrupt flags */ -#define AIC31XX_INTRADCFLAG 0x2D +#define AIC31XX_INTRADCFLAG AIC31XX_REG(0, 45) /* DAC Interrupt flags 2 */ -#define AIC31XX_INTRDACFLAG2 0x2E +#define AIC31XX_INTRDACFLAG2 AIC31XX_REG(0, 46) /* ADC Interrupt flags 2 */ -#define AIC31XX_INTRADCFLAG2 0x2F +#define AIC31XX_INTRADCFLAG2 AIC31XX_REG(0, 47) /* INT1 interrupt control */ -#define AIC31XX_INT1CTRL 0x30 +#define AIC31XX_INT1CTRL AIC31XX_REG(0, 48) /* INT2 interrupt control */ -#define AIC31XX_INT2CTRL 0x31 +#define AIC31XX_INT2CTRL AIC31XX_REG(0, 49) /* GPIO1 control */ -#define AIC31XX_GPIO1 0x33 +#define AIC31XX_GPIO1 AIC31XX_REG(0, 50) -#define AIC31XX_DACPRB 0x3C +#define AIC31XX_DACPRB AIC31XX_REG(0, 60) /* ADC Instruction Set Register */ -#define AIC31XX_ADCPRB 0x3D +#define AIC31XX_ADCPRB AIC31XX_REG(0, 61) /* DAC channel setup register */ -#define AIC31XX_DACSETUP 0x3F +#define AIC31XX_DACSETUP AIC31XX_REG(0, 63) /* DAC Mute and volume control register */ -#define AIC31XX_DACMUTE 0x40 +#define AIC31XX_DACMUTE AIC31XX_REG(0, 64) /* Left DAC channel digital volume control */ -#define AIC31XX_LDACVOL 0x41 +#define AIC31XX_LDACVOL AIC31XX_REG(0, 65) /* Right DAC channel digital volume control */ -#define AIC31XX_RDACVOL 0x42 +#define AIC31XX_RDACVOL AIC31XX_REG(0, 66) /* Headset detection */ -#define AIC31XX_HSDETECT 0x43 +#define AIC31XX_HSDETECT AIC31XX_REG(0, 67) /* ADC Digital Mic */ -#define AIC31XX_ADCSETUP 0x51 +#define AIC31XX_ADCSETUP AIC31XX_REG(0, 81) /* ADC Digital Volume Control Fine Adjust */ -#define AIC31XX_ADCFGA 0x52 +#define AIC31XX_ADCFGA AIC31XX_REG(0, 82) /* ADC Digital Volume Control Coarse Adjust */ -#define AIC31XX_ADCVOL 0x53 +#define AIC31XX_ADCVOL AIC31XX_REG(0, 83) /* Page 1 Registers */ /* Headphone drivers */ -#define AIC31XX_HPDRIVER 0x9F +#define AIC31XX_HPDRIVER AIC31XX_REG(1, 31) /* Class-D Speakear Amplifier */ -#define AIC31XX_SPKAMP 0xA0 +#define AIC31XX_SPKAMP AIC31XX_REG(1, 32) /* HP Output Drivers POP Removal Settings */ -#define AIC31XX_HPPOP 0xA1 +#define AIC31XX_HPPOP AIC31XX_REG(1, 33) /* Output Driver PGA Ramp-Down Period Control */ -#define AIC31XX_SPPGARAMP 0xA2 +#define AIC31XX_SPPGARAMP AIC31XX_REG(1, 34) /* DAC_L and DAC_R Output Mixer Routing */ -#define AIC31XX_DACMIXERROUTE 0xA3 +#define AIC31XX_DACMIXERROUTE AIC31XX_REG(1, 35) /* Left Analog Vol to HPL */ -#define AIC31XX_LANALOGHPL 0xA4 +#define AIC31XX_LANALOGHPL AIC31XX_REG(1, 36) /* Right Analog Vol to HPR */ -#define AIC31XX_RANALOGHPR 0xA5 +#define AIC31XX_RANALOGHPR AIC31XX_REG(1, 37) /* Left Analog Vol to SPL */ -#define AIC31XX_LANALOGSPL 0xA6 +#define AIC31XX_LANALOGSPL AIC31XX_REG(1, 38) /* Right Analog Vol to SPR */ -#define AIC31XX_RANALOGSPR 0xA7 +#define AIC31XX_RANALOGSPR AIC31XX_REG(1, 39) /* HPL Driver */ -#define AIC31XX_HPLGAIN 0xA8 +#define AIC31XX_HPLGAIN AIC31XX_REG(1, 40) /* HPR Driver */ -#define AIC31XX_HPRGAIN 0xA9 +#define AIC31XX_HPRGAIN AIC31XX_REG(1, 41) /* SPL Driver */ -#define AIC31XX_SPLGAIN 0xAA +#define AIC31XX_SPLGAIN AIC31XX_REG(1, 42) /* SPR Driver */ -#define AIC31XX_SPRGAIN 0xAB +#define AIC31XX_SPRGAIN AIC31XX_REG(1, 43) /* HP Driver Control */ -#define AIC31XX_HPCONTROL 0xAC +#define AIC31XX_HPCONTROL AIC31XX_REG(1, 44) /* MIC Bias Control */ -#define AIC31XX_MICBIAS 0xAE +#define AIC31XX_MICBIAS AIC31XX_REG(1, 46) /* MIC PGA*/ -#define AIC31XX_MICPGA 0xAF +#define AIC31XX_MICPGA AIC31XX_REG(1, 47) /* Delta-Sigma Mono ADC Channel Fine-Gain Input Selection for P-Terminal */ -#define AIC31XX_MICPGAPI 0xB0 +#define AIC31XX_MICPGAPI AIC31XX_REG(1, 48) /* ADC Input Selection for M-Terminal */ -#define AIC31XX_MICPGAMI 0xB1 +#define AIC31XX_MICPGAMI AIC31XX_REG(1, 49) /* Input CM Settings */ -#define AIC31XX_MICPGACM 0xB2 +#define AIC31XX_MICPGACM AIC31XX_REG(1, 50) /* Bits, masks and shifts */ diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 11d85c5c787a..f1ea052a822e 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -32,6 +32,7 @@ #include <sound/tlv.h> #include <linux/of.h> #include <linux/of_gpio.h> +#include <linux/regmap.h> #include "tpa6130a2.h" @@ -40,219 +41,72 @@ enum tpa_model { TPA6140A2, }; -static struct i2c_client *tpa6130a2_client; - /* This struct is used to save the context */ struct tpa6130a2_data { - struct mutex mutex; - unsigned char regs[TPA6130A2_CACHEREGNUM]; + struct device *dev; + struct regmap *regmap; struct regulator *supply; int power_gpio; - u8 power_state:1; enum tpa_model id; }; -static int tpa6130a2_i2c_read(int reg) -{ - struct tpa6130a2_data *data; - int val; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - /* If powered off, return the cached value */ - if (data->power_state) { - val = i2c_smbus_read_byte_data(tpa6130a2_client, reg); - if (val < 0) - dev_err(&tpa6130a2_client->dev, "Read failed\n"); - else - data->regs[reg] = val; - } else { - val = data->regs[reg]; - } - - return val; -} - -static int tpa6130a2_i2c_write(int reg, u8 value) -{ - struct tpa6130a2_data *data; - int val = 0; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - if (data->power_state) { - val = i2c_smbus_write_byte_data(tpa6130a2_client, reg, value); - if (val < 0) { - dev_err(&tpa6130a2_client->dev, "Write failed\n"); - return val; - } - } - - /* Either powered on or off, we save the context */ - data->regs[reg] = value; - - return val; -} - -static u8 tpa6130a2_read(int reg) -{ - struct tpa6130a2_data *data; - - if (WARN_ON(!tpa6130a2_client)) - return 0; - data = i2c_get_clientdata(tpa6130a2_client); - - return data->regs[reg]; -} - -static int tpa6130a2_initialize(void) -{ - struct tpa6130a2_data *data; - int i, ret = 0; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - for (i = 1; i < TPA6130A2_REG_VERSION; i++) { - ret = tpa6130a2_i2c_write(i, data->regs[i]); - if (ret < 0) - break; - } - - return ret; -} - -static int tpa6130a2_power(u8 power) +static int tpa6130a2_power(struct tpa6130a2_data *data, bool enable) { - struct tpa6130a2_data *data; - u8 val; - int ret = 0; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - mutex_lock(&data->mutex); - if (power == data->power_state) - goto exit; + int ret; - if (power) { + if (enable) { ret = regulator_enable(data->supply); if (ret != 0) { - dev_err(&tpa6130a2_client->dev, + dev_err(data->dev, "Failed to enable supply: %d\n", ret); - goto exit; + return ret; } /* Power on */ if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 1); - - data->power_state = 1; - ret = tpa6130a2_initialize(); - if (ret < 0) { - dev_err(&tpa6130a2_client->dev, - "Failed to initialize chip\n"); - if (data->power_gpio >= 0) - gpio_set_value(data->power_gpio, 0); - regulator_disable(data->supply); - data->power_state = 0; - goto exit; - } } else { - /* set SWS */ - val = tpa6130a2_read(TPA6130A2_REG_CONTROL); - val |= TPA6130A2_SWS; - tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); - /* Power off */ if (data->power_gpio >= 0) gpio_set_value(data->power_gpio, 0); ret = regulator_disable(data->supply); if (ret != 0) { - dev_err(&tpa6130a2_client->dev, + dev_err(data->dev, "Failed to disable supply: %d\n", ret); - goto exit; + return ret; } - data->power_state = 0; + /* device regs does not match the cache state anymore */ + regcache_mark_dirty(data->regmap); } -exit: - mutex_unlock(&data->mutex); return ret; } -static int tpa6130a2_get_volsw(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int tpa6130a2_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kctrl, int event) { - struct soc_mixer_control *mc = - (struct soc_mixer_control *)kcontrol->private_value; - struct tpa6130a2_data *data; - 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 (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - mutex_lock(&data->mutex); - - ucontrol->value.integer.value[0] = - (tpa6130a2_read(reg) >> shift) & mask; - - if (invert) - ucontrol->value.integer.value[0] = - max - ucontrol->value.integer.value[0]; - - mutex_unlock(&data->mutex); - return 0; -} + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct tpa6130a2_data *data = snd_soc_component_get_drvdata(c); + int ret; -static int tpa6130a2_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 tpa6130a2_data *data; - 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; - unsigned int val = (ucontrol->value.integer.value[0] & mask); - unsigned int val_reg; - - if (WARN_ON(!tpa6130a2_client)) - return -EINVAL; - data = i2c_get_clientdata(tpa6130a2_client); - - if (invert) - val = max - val; - - mutex_lock(&data->mutex); - - val_reg = tpa6130a2_read(reg); - if (((val_reg >> shift) & mask) == val) { - mutex_unlock(&data->mutex); - return 0; + /* before widget power up */ + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* Turn on the chip */ + tpa6130a2_power(data, true); + /* Sync the registers */ + ret = regcache_sync(data->regmap); + if (ret < 0) { + dev_err(c->dev, "Failed to initialize chip\n"); + tpa6130a2_power(data, false); + return ret; + } + /* after widget power down */ + } else { + tpa6130a2_power(data, false); } - val_reg &= ~(mask << shift); - val_reg |= val << shift; - tpa6130a2_i2c_write(reg, val_reg); - - mutex_unlock(&data->mutex); - - return 1; + return 0; } /* @@ -273,9 +127,8 @@ static const DECLARE_TLV_DB_RANGE(tpa6130_tlv, ); static const struct snd_kcontrol_new tpa6130a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", + SOC_SINGLE_TLV("Headphone Playback Volume", TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, - tpa6130a2_get_volsw, tpa6130a2_put_volsw, tpa6130_tlv), }; @@ -286,85 +139,79 @@ static const DECLARE_TLV_DB_RANGE(tpa6140_tlv, ); static const struct snd_kcontrol_new tpa6140a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", + SOC_SINGLE_TLV("Headphone Playback Volume", TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, - tpa6130a2_get_volsw, tpa6130a2_put_volsw, tpa6140_tlv), }; -/* - * Enable or disable channel (left or right) - * The bit number for mute and amplifier are the same per channel: - * bit 6: Right channel - * bit 7: Left channel - * in both registers. - */ -static void tpa6130a2_channel_enable(u8 channel, int enable) +static int tpa6130a2_component_probe(struct snd_soc_component *component) { - u8 val; + struct tpa6130a2_data *data = snd_soc_component_get_drvdata(component); - if (enable) { - /* Enable channel */ - /* Enable amplifier */ - val = tpa6130a2_read(TPA6130A2_REG_CONTROL); - val |= channel; - val &= ~TPA6130A2_SWS; - tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); - - /* Unmute channel */ - val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE); - val &= ~channel; - tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val); - } else { - /* Disable channel */ - /* Mute channel */ - val = tpa6130a2_read(TPA6130A2_REG_VOL_MUTE); - val |= channel; - tpa6130a2_i2c_write(TPA6130A2_REG_VOL_MUTE, val); - - /* Disable amplifier */ - val = tpa6130a2_read(TPA6130A2_REG_CONTROL); - val &= ~channel; - tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); - } + if (data->id == TPA6140A2) + return snd_soc_add_component_controls(component, + tpa6140a2_controls, ARRAY_SIZE(tpa6140a2_controls)); + else + return snd_soc_add_component_controls(component, + tpa6130a2_controls, ARRAY_SIZE(tpa6130a2_controls)); } -int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable) -{ - int ret = 0; - if (enable) { - ret = tpa6130a2_power(1); - if (ret < 0) - return ret; - tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L, - 1); - } else { - tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L, - 0); - ret = tpa6130a2_power(0); - } +static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("LEFTIN"), + SND_SOC_DAPM_INPUT("RIGHTIN"), + SND_SOC_DAPM_OUTPUT("HPLEFT"), + SND_SOC_DAPM_OUTPUT("HPRIGHT"), + + SND_SOC_DAPM_PGA("Left Mute", TPA6130A2_REG_VOL_MUTE, + TPA6130A2_HP_EN_L_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Mute", TPA6130A2_REG_VOL_MUTE, + TPA6130A2_HP_EN_R_SHIFT, 1, NULL, 0), + SND_SOC_DAPM_PGA("Left PGA", TPA6130A2_REG_CONTROL, + TPA6130A2_HP_EN_L_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", TPA6130A2_REG_CONTROL, + TPA6130A2_HP_EN_R_SHIFT, 0, NULL, 0), + + SND_SOC_DAPM_SUPPLY("Power", TPA6130A2_REG_CONTROL, + TPA6130A2_SWS_SHIFT, 1, tpa6130a2_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; - return ret; -} -EXPORT_SYMBOL_GPL(tpa6130a2_stereo_enable); +static const struct snd_soc_dapm_route tpa6130a2_dapm_routes[] = { + { "Left PGA", NULL, "LEFTIN" }, + { "Right PGA", NULL, "RIGHTIN" }, -int tpa6130a2_add_controls(struct snd_soc_codec *codec) -{ - struct tpa6130a2_data *data; + { "Left Mute", NULL, "Left PGA" }, + { "Right Mute", NULL, "Right PGA" }, - if (tpa6130a2_client == NULL) - return -ENODEV; + { "HPLEFT", NULL, "Left Mute" }, + { "HPRIGHT", NULL, "Right Mute" }, - data = i2c_get_clientdata(tpa6130a2_client); + { "Left PGA", NULL, "Power" }, + { "Right PGA", NULL, "Power" }, +}; - if (data->id == TPA6140A2) - return snd_soc_add_codec_controls(codec, tpa6140a2_controls, - ARRAY_SIZE(tpa6140a2_controls)); - else - return snd_soc_add_codec_controls(codec, tpa6130a2_controls, - ARRAY_SIZE(tpa6130a2_controls)); -} -EXPORT_SYMBOL_GPL(tpa6130a2_add_controls); +struct snd_soc_component_driver tpa6130a2_component_driver = { + .name = "tpa6130a2", + .probe = tpa6130a2_component_probe, + .dapm_widgets = tpa6130a2_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tpa6130a2_dapm_widgets), + .dapm_routes = tpa6130a2_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tpa6130a2_dapm_routes), +}; + +static const struct reg_default tpa6130a2_reg_defaults[] = { + { TPA6130A2_REG_CONTROL, TPA6130A2_SWS }, + { TPA6130A2_REG_VOL_MUTE, TPA6130A2_MUTE_R | TPA6130A2_MUTE_L }, +}; + +static const struct regmap_config tpa6130a2_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPA6130A2_REG_VERSION, + .reg_defaults = tpa6130a2_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tpa6130a2_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; static int tpa6130a2_probe(struct i2c_client *client, const struct i2c_device_id *id) @@ -374,6 +221,7 @@ static int tpa6130a2_probe(struct i2c_client *client, struct tpa6130a2_platform_data *pdata = client->dev.platform_data; struct device_node *np = client->dev.of_node; const char *regulator; + unsigned int version; int ret; dev = &client->dev; @@ -382,6 +230,12 @@ static int tpa6130a2_probe(struct i2c_client *client, if (!data) return -ENOMEM; + data->dev = dev; + + data->regmap = devm_regmap_init_i2c(client, &tpa6130a2_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + if (pdata) { data->power_gpio = pdata->power_gpio; } else if (np) { @@ -392,26 +246,17 @@ static int tpa6130a2_probe(struct i2c_client *client, return -ENODEV; } - tpa6130a2_client = client; - - i2c_set_clientdata(tpa6130a2_client, data); + i2c_set_clientdata(client, data); data->id = id->driver_data; - mutex_init(&data->mutex); - - /* Set default register values */ - data->regs[TPA6130A2_REG_CONTROL] = TPA6130A2_SWS; - data->regs[TPA6130A2_REG_VOL_MUTE] = TPA6130A2_MUTE_R | - TPA6130A2_MUTE_L; - if (data->power_gpio >= 0) { ret = devm_gpio_request(dev, data->power_gpio, "tpa6130a2 enable"); if (ret < 0) { dev_err(dev, "Failed to request power GPIO (%d)\n", data->power_gpio); - goto err_gpio; + return ret; } gpio_direction_output(data->power_gpio, 0); } @@ -432,39 +277,27 @@ static int tpa6130a2_probe(struct i2c_client *client, if (IS_ERR(data->supply)) { ret = PTR_ERR(data->supply); dev_err(dev, "Failed to request supply: %d\n", ret); - goto err_gpio; + return ret; } - ret = tpa6130a2_power(1); + ret = tpa6130a2_power(data, true); if (ret != 0) - goto err_gpio; + return ret; /* Read version */ - ret = tpa6130a2_i2c_read(TPA6130A2_REG_VERSION) & - TPA6130A2_VERSION_MASK; - if ((ret != 1) && (ret != 2)) - dev_warn(dev, "UNTESTED version detected (%d)\n", ret); + regmap_read(data->regmap, TPA6130A2_REG_VERSION, &version); + version &= TPA6130A2_VERSION_MASK; + if ((version != 1) && (version != 2)) + dev_warn(dev, "UNTESTED version detected (%d)\n", version); /* Disable the chip */ - ret = tpa6130a2_power(0); + ret = tpa6130a2_power(data, false); if (ret != 0) - goto err_gpio; - - return 0; - -err_gpio: - tpa6130a2_client = NULL; + return ret; - return ret; -} - -static int tpa6130a2_remove(struct i2c_client *client) -{ - tpa6130a2_power(0); - tpa6130a2_client = NULL; - - return 0; + return devm_snd_soc_register_component(&client->dev, + &tpa6130a2_component_driver, NULL, 0); } static const struct i2c_device_id tpa6130a2_id[] = { @@ -489,7 +322,6 @@ static struct i2c_driver tpa6130a2_i2c_driver = { .of_match_table = of_match_ptr(tpa6130a2_of_match), }, .probe = tpa6130a2_probe, - .remove = tpa6130a2_remove, .id_table = tpa6130a2_id, }; diff --git a/sound/soc/codecs/tpa6130a2.h b/sound/soc/codecs/tpa6130a2.h index 417444020ba6..f19cad5d4172 100644 --- a/sound/soc/codecs/tpa6130a2.h +++ b/sound/soc/codecs/tpa6130a2.h @@ -30,19 +30,20 @@ #define TPA6130A2_REG_OUT_IMPEDANCE 0x03 #define TPA6130A2_REG_VERSION 0x04 -#define TPA6130A2_CACHEREGNUM (TPA6130A2_REG_VERSION + 1) - /* Register bits */ /* TPA6130A2_REG_CONTROL (0x01) */ -#define TPA6130A2_SWS (0x01 << 0) +#define TPA6130A2_SWS_SHIFT 0 +#define TPA6130A2_SWS (0x01 << TPA6130A2_SWS_SHIFT) #define TPA6130A2_TERMAL (0x01 << 1) #define TPA6130A2_MODE(x) (x << 4) #define TPA6130A2_MODE_STEREO (0x00) #define TPA6130A2_MODE_DUAL_MONO (0x01) #define TPA6130A2_MODE_BRIDGE (0x02) #define TPA6130A2_MODE_MASK (0x03) -#define TPA6130A2_HP_EN_R (0x01 << 6) -#define TPA6130A2_HP_EN_L (0x01 << 7) +#define TPA6130A2_HP_EN_R_SHIFT 6 +#define TPA6130A2_HP_EN_R (0x01 << TPA6130A2_HP_EN_R_SHIFT) +#define TPA6130A2_HP_EN_L_SHIFT 7 +#define TPA6130A2_HP_EN_L (0x01 << TPA6130A2_HP_EN_L_SHIFT) /* TPA6130A2_REG_VOL_MUTE (0x02) */ #define TPA6130A2_VOLUME(x) ((x & 0x3f) << 0) @@ -56,7 +57,4 @@ /* TPA6130A2_REG_VERSION (0x04) */ #define TPA6130A2_VERSION_MASK (0x0f) -extern int tpa6130a2_add_controls(struct snd_soc_codec *codec); -extern int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable); - #endif /* __TPA6130A2_H__ */ diff --git a/sound/soc/codecs/wm5102.c b/sound/soc/codecs/wm5102.c index e7fe6b7b95b7..846deed6af41 100644 --- a/sound/soc/codecs/wm5102.c +++ b/sound/soc/codecs/wm5102.c @@ -1713,6 +1713,7 @@ static const struct snd_soc_dapm_route wm5102_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, }; diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c index d54f1b46c9ec..156547026a40 100644 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@ -1104,6 +1104,11 @@ SND_SOC_DAPM_INPUT("IN4R"), SND_SOC_DAPM_OUTPUT("DRC1 Signal Activity"), SND_SOC_DAPM_OUTPUT("DRC2 Signal Activity"), +SND_SOC_DAPM_OUTPUT("DSP Voice Trigger"), + +SND_SOC_DAPM_SWITCH("DSP3 Voice Trigger", SND_SOC_NOPM, 2, 0, + &arizona_voice_trigger_switch[2]), + SND_SOC_DAPM_PGA_E("IN1L PGA", ARIZONA_INPUT_ENABLES, ARIZONA_IN1L_ENA_SHIFT, 0, NULL, 0, wm5110_in_ev, SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD | @@ -1998,10 +2003,16 @@ static const struct snd_soc_dapm_route wm5110_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, + { "DRC2 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, { "DRC2 Signal Activity", NULL, "DRC2L" }, { "DRC2 Signal Activity", NULL, "DRC2R" }, + + { "DSP Voice Trigger", NULL, "SYSCLK" }, + { "DSP Voice Trigger", NULL, "DSP3 Voice Trigger" }, + { "DSP3 Voice Trigger", "Switch", "DSP3" }, }; static int wm5110_set_fll(struct snd_soc_codec *codec, int fll_id, int source, @@ -2223,6 +2234,7 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data) { struct wm5110_priv *priv = data; struct arizona *arizona = priv->core.arizona; + struct arizona_voice_trigger_info info; int serviced = 0; int i, ret; @@ -2230,6 +2242,12 @@ static irqreturn_t wm5110_adsp2_irq(int irq, void *data) ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]); if (ret != -ENODEV) serviced++; + if (ret == WM_ADSP_COMPR_VOICE_TRIGGER) { + info.core = i; + arizona_call_notifiers(arizona, + ARIZONA_NOTIFY_VOICE_TRIGGER, + &info); + } } if (!serviced) { @@ -2252,6 +2270,7 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec) arizona_init_spk(codec); arizona_init_gpio(codec); arizona_init_mono(codec); + arizona_init_notifiers(codec); ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, "ADSP2 Compressed IRQ", wm5110_adsp2_irq, diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 4bcf5f8ece50..d18261a44256 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -358,6 +358,9 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream, case 24: iface |= 0x0008; break; + case 32: + iface |= 0x000c; + break; } wm8731_set_deemph(codec); @@ -541,7 +544,7 @@ static int wm8731_startup(struct snd_pcm_substream *substream, #define WM8731_RATES SNDRV_PCM_RATE_8000_96000 #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ - SNDRV_PCM_FMTBIT_S24_LE) + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops wm8731_dai_ops = { .startup = wm8731_startup, diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 6f1024f48b19..cdcc91282e8a 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -32,7 +32,6 @@ */ #include <linux/module.h> -#include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/delay.h> @@ -486,7 +485,7 @@ SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0), SND_SOC_DAPM_OUTPUT("MONO1"), SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls), SND_SOC_DAPM_OUTPUT("MONO2"), -SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Out3 Left + Right", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls), SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("OUT3"), diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c index 6ac76fe116b0..7347abff4b2c 100644 --- a/sound/soc/codecs/wm8985.c +++ b/sound/soc/codecs/wm8985.c @@ -1,10 +1,13 @@ /* - * wm8985.c -- WM8985 ALSA SoC Audio driver + * wm8985.c -- WM8985 / WM8758 ALSA SoC Audio driver * * Copyright 2010 Wolfson Microelectronics plc - * * Author: Dimitris Papastamos <dp@opensource.wolfsonmicro.com> * + * WM8758 support: + * Copyright: 2016 Barix AG + * Author: Petr Kulhavy <petr@barix.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. @@ -40,6 +43,11 @@ static const char *wm8985_supply_names[WM8985_NUM_SUPPLIES] = { "AVDD2" }; +enum wm8985_type { + WM8985, + WM8758, +}; + static const struct reg_default wm8985_reg_defaults[] = { { 1, 0x0000 }, /* R1 - Power management 1 */ { 2, 0x0000 }, /* R2 - Power management 2 */ @@ -181,6 +189,7 @@ static const int volume_update_regs[] = { struct wm8985_priv { struct regmap *regmap; struct regulator_bulk_data supplies[WM8985_NUM_SUPPLIES]; + enum wm8985_type dev_type; unsigned int sysclk; unsigned int bclk; }; @@ -289,7 +298,7 @@ static const char *depth_3d_text[] = { }; static SOC_ENUM_SINGLE_DECL(depth_3d, WM8985_3D_CONTROL, 0, depth_3d_text); -static const struct snd_kcontrol_new wm8985_snd_controls[] = { +static const struct snd_kcontrol_new wm8985_common_snd_controls[] = { SOC_SINGLE("Digital Loopback Switch", WM8985_COMPANDING_CONTROL, 0, 1, 0), @@ -355,10 +364,6 @@ static const struct snd_kcontrol_new wm8985_snd_controls[] = { SOC_ENUM("High Pass Filter Mode", filter_mode), SOC_SINGLE("High Pass Filter Cutoff", WM8985_ADC_CONTROL, 4, 7, 0), - SOC_DOUBLE_R_TLV("Aux Bypass Volume", - WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 6, 7, 0, - aux_tlv), - SOC_DOUBLE_R_TLV("Input PGA Bypass Volume", WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 2, 7, 0, bypass_tlv), @@ -379,20 +384,30 @@ static const struct snd_kcontrol_new wm8985_snd_controls[] = { SOC_SINGLE_TLV("EQ5 Volume", WM8985_EQ5_HIGH_SHELF, 0, 24, 1, eq_tlv), SOC_ENUM("3D Depth", depth_3d), +}; + +static const struct snd_kcontrol_new wm8985_specific_snd_controls[] = { + SOC_DOUBLE_R_TLV("Aux Bypass Volume", + WM8985_LEFT_MIXER_CTRL, WM8985_RIGHT_MIXER_CTRL, 6, 7, 0, + aux_tlv), SOC_ENUM("Speaker Mode", speaker_mode) }; static const struct snd_kcontrol_new left_out_mixer[] = { SOC_DAPM_SINGLE("Line Switch", WM8985_LEFT_MIXER_CTRL, 1, 1, 0), - SOC_DAPM_SINGLE("Aux Switch", WM8985_LEFT_MIXER_CTRL, 5, 1, 0), SOC_DAPM_SINGLE("PCM Switch", WM8985_LEFT_MIXER_CTRL, 0, 1, 0), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE("Aux Switch", WM8985_LEFT_MIXER_CTRL, 5, 1, 0), }; static const struct snd_kcontrol_new right_out_mixer[] = { SOC_DAPM_SINGLE("Line Switch", WM8985_RIGHT_MIXER_CTRL, 1, 1, 0), - SOC_DAPM_SINGLE("Aux Switch", WM8985_RIGHT_MIXER_CTRL, 5, 1, 0), SOC_DAPM_SINGLE("PCM Switch", WM8985_RIGHT_MIXER_CTRL, 0, 1, 0), + + /* --- WM8985 only --- */ + SOC_DAPM_SINGLE("Aux Switch", WM8985_RIGHT_MIXER_CTRL, 5, 1, 0), }; static const struct snd_kcontrol_new left_input_mixer[] = { @@ -410,6 +425,8 @@ static const struct snd_kcontrol_new right_input_mixer[] = { static const struct snd_kcontrol_new left_boost_mixer[] = { SOC_DAPM_SINGLE_TLV("L2 Volume", WM8985_LEFT_ADC_BOOST_CTRL, 4, 7, 0, boost_tlv), + + /* --- WM8985 only --- */ SOC_DAPM_SINGLE_TLV("AUXL Volume", WM8985_LEFT_ADC_BOOST_CTRL, 0, 7, 0, boost_tlv) }; @@ -417,11 +434,13 @@ static const struct snd_kcontrol_new left_boost_mixer[] = { static const struct snd_kcontrol_new right_boost_mixer[] = { SOC_DAPM_SINGLE_TLV("R2 Volume", WM8985_RIGHT_ADC_BOOST_CTRL, 4, 7, 0, boost_tlv), + + /* --- WM8985 only --- */ SOC_DAPM_SINGLE_TLV("AUXR Volume", WM8985_RIGHT_ADC_BOOST_CTRL, 0, 7, 0, boost_tlv) }; -static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { +static const struct snd_soc_dapm_widget wm8985_common_dapm_widgets[] = { SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8985_POWER_MANAGEMENT_3, 0, 0), SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8985_POWER_MANAGEMENT_3, @@ -431,21 +450,11 @@ static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8985_POWER_MANAGEMENT_2, 1, 0), - SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, - 2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)), - SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, - 3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)), - SND_SOC_DAPM_MIXER("Left Input Mixer", WM8985_POWER_MANAGEMENT_2, 2, 0, left_input_mixer, ARRAY_SIZE(left_input_mixer)), SND_SOC_DAPM_MIXER("Right Input Mixer", WM8985_POWER_MANAGEMENT_2, 3, 0, right_input_mixer, ARRAY_SIZE(right_input_mixer)), - SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, - 4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)), - SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, - 5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)), - SND_SOC_DAPM_PGA("Left Capture PGA", WM8985_LEFT_INP_PGA_GAIN_CTRL, 6, 1, NULL, 0), SND_SOC_DAPM_PGA("Right Capture PGA", WM8985_RIGHT_INP_PGA_GAIN_CTRL, @@ -468,8 +477,6 @@ static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LIP"), SND_SOC_DAPM_INPUT("RIN"), SND_SOC_DAPM_INPUT("RIP"), - SND_SOC_DAPM_INPUT("AUXL"), - SND_SOC_DAPM_INPUT("AUXR"), SND_SOC_DAPM_INPUT("L2"), SND_SOC_DAPM_INPUT("R2"), SND_SOC_DAPM_OUTPUT("HPL"), @@ -478,13 +485,42 @@ static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("SPKR") }; -static const struct snd_soc_dapm_route wm8985_dapm_routes[] = { +static const struct snd_soc_dapm_widget wm8985_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, + 2, 0, left_out_mixer, ARRAY_SIZE(left_out_mixer)), + SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, + 3, 0, right_out_mixer, ARRAY_SIZE(right_out_mixer)), + + SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 4, 0, left_boost_mixer, ARRAY_SIZE(left_boost_mixer)), + SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 5, 0, right_boost_mixer, ARRAY_SIZE(right_boost_mixer)), + + SND_SOC_DAPM_INPUT("AUXL"), + SND_SOC_DAPM_INPUT("AUXR"), +}; + +static const struct snd_soc_dapm_widget wm8758_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Output Mixer", WM8985_POWER_MANAGEMENT_3, + 2, 0, left_out_mixer, + ARRAY_SIZE(left_out_mixer) - 1), + SND_SOC_DAPM_MIXER("Right Output Mixer", WM8985_POWER_MANAGEMENT_3, + 3, 0, right_out_mixer, + ARRAY_SIZE(right_out_mixer) - 1), + + SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 4, 0, left_boost_mixer, + ARRAY_SIZE(left_boost_mixer) - 1), + SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8985_POWER_MANAGEMENT_2, + 5, 0, right_boost_mixer, + ARRAY_SIZE(right_boost_mixer) - 1), +}; + +static const struct snd_soc_dapm_route wm8985_common_dapm_routes[] = { { "Right Output Mixer", "PCM Switch", "Right DAC" }, - { "Right Output Mixer", "Aux Switch", "AUXR" }, { "Right Output Mixer", "Line Switch", "Right Boost Mixer" }, { "Left Output Mixer", "PCM Switch", "Left DAC" }, - { "Left Output Mixer", "Aux Switch", "AUXL" }, { "Left Output Mixer", "Line Switch", "Left Boost Mixer" }, { "Right Headphone Out", NULL, "Right Output Mixer" }, @@ -501,13 +537,11 @@ static const struct snd_soc_dapm_route wm8985_dapm_routes[] = { { "Right ADC", NULL, "Right Boost Mixer" }, - { "Right Boost Mixer", "AUXR Volume", "AUXR" }, { "Right Boost Mixer", NULL, "Right Capture PGA" }, { "Right Boost Mixer", "R2 Volume", "R2" }, { "Left ADC", NULL, "Left Boost Mixer" }, - { "Left Boost Mixer", "AUXL Volume", "AUXL" }, { "Left Boost Mixer", NULL, "Left Capture PGA" }, { "Left Boost Mixer", "L2 Volume", "L2" }, @@ -522,6 +556,38 @@ static const struct snd_soc_dapm_route wm8985_dapm_routes[] = { { "Left Input Mixer", "MicN Switch", "LIN" }, { "Left Input Mixer", "MicP Switch", "LIP" }, }; +static const struct snd_soc_dapm_route wm8985_aux_dapm_routes[] = { + { "Right Output Mixer", "Aux Switch", "AUXR" }, + { "Left Output Mixer", "Aux Switch", "AUXL" }, + + { "Right Boost Mixer", "AUXR Volume", "AUXR" }, + { "Left Boost Mixer", "AUXL Volume", "AUXL" }, +}; + +static int wm8985_add_widgets(struct snd_soc_codec *codec) +{ + struct wm8985_priv *wm8985 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); + + switch (wm8985->dev_type) { + case WM8758: + snd_soc_dapm_new_controls(dapm, wm8758_dapm_widgets, + ARRAY_SIZE(wm8758_dapm_widgets)); + break; + + case WM8985: + snd_soc_add_codec_controls(codec, wm8985_specific_snd_controls, + ARRAY_SIZE(wm8985_specific_snd_controls)); + + snd_soc_dapm_new_controls(dapm, wm8985_dapm_widgets, + ARRAY_SIZE(wm8985_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, wm8985_aux_dapm_routes, + ARRAY_SIZE(wm8985_aux_dapm_routes)); + break; + } + + return 0; +} static int eqmode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -999,6 +1065,8 @@ static int wm8985_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8985_BIAS_CTRL, WM8985_BIASCUT, WM8985_BIASCUT); + wm8985_add_widgets(codec); + return 0; err_reg_enable: @@ -1042,12 +1110,12 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8985 = { .set_bias_level = wm8985_set_bias_level, .suspend_bias_off = true, - .controls = wm8985_snd_controls, - .num_controls = ARRAY_SIZE(wm8985_snd_controls), - .dapm_widgets = wm8985_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(wm8985_dapm_widgets), - .dapm_routes = wm8985_dapm_routes, - .num_dapm_routes = ARRAY_SIZE(wm8985_dapm_routes), + .controls = wm8985_common_snd_controls, + .num_controls = ARRAY_SIZE(wm8985_common_snd_controls), + .dapm_widgets = wm8985_common_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wm8985_common_dapm_widgets), + .dapm_routes = wm8985_common_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(wm8985_common_dapm_routes), }; static const struct regmap_config wm8985_regmap = { @@ -1074,6 +1142,8 @@ static int wm8985_spi_probe(struct spi_device *spi) spi_set_drvdata(spi, wm8985); + wm8985->dev_type = WM8985; + wm8985->regmap = devm_regmap_init_spi(spi, &wm8985_regmap); if (IS_ERR(wm8985->regmap)) { ret = PTR_ERR(wm8985->regmap); @@ -1115,6 +1185,8 @@ static int wm8985_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, wm8985); + wm8985->dev_type = id->driver_data; + wm8985->regmap = devm_regmap_init_i2c(i2c, &wm8985_regmap); if (IS_ERR(wm8985->regmap)) { ret = PTR_ERR(wm8985->regmap); @@ -1135,7 +1207,8 @@ static int wm8985_i2c_remove(struct i2c_client *i2c) } static const struct i2c_device_id wm8985_i2c_id[] = { - { "wm8985", 0 }, + { "wm8985", WM8985 }, + { "wm8758", WM8758 }, { } }; MODULE_DEVICE_TABLE(i2c, wm8985_i2c_id); @@ -1183,6 +1256,6 @@ static void __exit wm8985_exit(void) } module_exit(wm8985_exit); -MODULE_DESCRIPTION("ASoC WM8985 driver"); +MODULE_DESCRIPTION("ASoC WM8985 / WM8758 driver"); MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8985.h b/sound/soc/codecs/wm8985.h index 2e71ff507638..41b1048e3c97 100644 --- a/sound/soc/codecs/wm8985.h +++ b/sound/soc/codecs/wm8985.h @@ -290,6 +290,9 @@ #define WM8985_GPIO1GPD_MASK 0x0040 /* GPIO1GPD */ #define WM8985_GPIO1GPD_SHIFT 6 /* GPIO1GPD */ #define WM8985_GPIO1GPD_WIDTH 1 /* GPIO1GPD */ +#define WM8758_OPCLKDIV_MASK 0x0030 /* OPCLKDIV - [1:0] */ +#define WM8758_OPCLKDIV_SHIFT 4 /* OPCLKDIV - [1:0] */ +#define WM8758_OPCLKDIV_WIDTH 2 /* OPCLKDIV - [1:0] */ #define WM8985_GPIO1POL 0x0008 /* GPIO1POL */ #define WM8985_GPIO1POL_MASK 0x0008 /* GPIO1POL */ #define WM8985_GPIO1POL_SHIFT 3 /* GPIO1POL */ @@ -301,6 +304,12 @@ /* * R9 (0x09) - Jack Detect Control 1 */ +#define WM8758_JD_VMID1_MASK 0x0100 /* JD_VMID1 */ +#define WM8758_JD_VMID1_SHIFT 8 /* JD_VMID1 */ +#define WM8758_JD_VMID1_WIDTH 1 /* JD_VMID1 */ +#define WM8758_JD_VMID0_MASK 0x0080 /* JD_VMID0 */ +#define WM8758_JD_VMID0_SHIFT 7 /* JD_VMID0 */ +#define WM8758_JD_VMID0_WIDTH 1 /* JD_VMID0 */ #define WM8985_JD_EN 0x0040 /* JD_EN */ #define WM8985_JD_EN_MASK 0x0040 /* JD_EN */ #define WM8985_JD_EN_SHIFT 6 /* JD_EN */ @@ -649,6 +658,12 @@ #define WM8985_OUT4_2LNR_MASK 0x0020 /* OUT4_2LNR */ #define WM8985_OUT4_2LNR_SHIFT 5 /* OUT4_2LNR */ #define WM8985_OUT4_2LNR_WIDTH 1 /* OUT4_2LNR */ +#define WM8758_VMIDTOG_MASK 0x0010 /* VMIDTOG */ +#define WM8758_VMIDTOG_SHIFT 4 /* VMIDTOG */ +#define WM8758_VMIDTOG_WIDTH 1 /* VMIDTOG */ +#define WM8758_OUT2DEL_MASK 0x0008 /* OUT2DEL */ +#define WM8758_OUT2DEL_SHIFT 3 /* OUT2DEL */ +#define WM8758_OUT2DEL_WIDTH 1 /* OUT2DEL */ #define WM8985_POBCTRL 0x0004 /* POBCTRL */ #define WM8985_POBCTRL_MASK 0x0004 /* POBCTRL */ #define WM8985_POBCTRL_SHIFT 2 /* POBCTRL */ @@ -684,6 +699,9 @@ #define WM8985_BEEPVOL_MASK 0x000E /* BEEPVOL - [3:1] */ #define WM8985_BEEPVOL_SHIFT 1 /* BEEPVOL - [3:1] */ #define WM8985_BEEPVOL_WIDTH 3 /* BEEPVOL - [3:1] */ +#define WM8758_DELEN2_MASK 0x0004 /* DELEN2 */ +#define WM8758_DELEN2_SHIFT 2 /* DELEN2 */ +#define WM8758_DELEN2_WIDTH 1 /* DELEN2 */ #define WM8985_BEEPEN 0x0001 /* BEEPEN */ #define WM8985_BEEPEN_MASK 0x0001 /* BEEPEN */ #define WM8985_BEEPEN_SHIFT 0 /* BEEPEN */ @@ -790,6 +808,14 @@ /* * R49 (0x31) - Output ctrl */ +#define WM8758_HP_COM 0x0100 /* HP_COM */ +#define WM8758_HP_COM_MASK 0x0100 /* HP_COM */ +#define WM8758_HP_COM_SHIFT 8 /* HP_COM */ +#define WM8758_HP_COM_WIDTH 1 /* HP_COM */ +#define WM8758_LINE_COM 0x0080 /* LINE_COM */ +#define WM8758_LINE_COM_MASK 0x0080 /* LINE_COM */ +#define WM8758_LINE_COM_SHIFT 7 /* LINE_COM */ +#define WM8758_LINE_COM_WIDTH 1 /* LINE_COM */ #define WM8985_DACL2RMIX 0x0040 /* DACL2RMIX */ #define WM8985_DACL2RMIX_MASK 0x0040 /* DACL2RMIX */ #define WM8985_DACL2RMIX_SHIFT 6 /* DACL2RMIX */ @@ -806,6 +832,14 @@ #define WM8985_OUT3BOOST_MASK 0x0008 /* OUT3BOOST */ #define WM8985_OUT3BOOST_SHIFT 3 /* OUT3BOOST */ #define WM8985_OUT3BOOST_WIDTH 1 /* OUT3BOOST */ +#define WM8758_OUT4ENDEL 0x0010 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_MASK 0x0010 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_SHIFT 4 /* OUT4ENDEL */ +#define WM8758_OUT4ENDEL_WIDTH 1 /* OUT4ENDEL */ +#define WM8758_OUT3ENDEL 0x0008 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_MASK 0x0008 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_SHIFT 3 /* OUT3ENDEL */ +#define WM8758_OUT3ENDEL_WIDTH 1 /* OUT3ENDEL */ #define WM8985_TSOPCTRL 0x0004 /* TSOPCTRL */ #define WM8985_TSOPCTRL_MASK 0x0004 /* TSOPCTRL */ #define WM8985_TSOPCTRL_SHIFT 2 /* TSOPCTRL */ @@ -1021,6 +1055,10 @@ #define WM8985_HALFIPBIAS_MASK 0x0080 /* HALFIPBIAS */ #define WM8985_HALFIPBIAS_SHIFT 7 /* HALFIPBIAS */ #define WM8985_HALFIPBIAS_WIDTH 1 /* HALFIPBIAS */ +#define WM8758_HALFIPBIAS 0x0040 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_MASK 0x0040 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_SHIFT 6 /* HALFI_IPGA */ +#define WM8758_HALFI_IPGA_WIDTH 1 /* HALFI_IPGA */ #define WM8985_VBBIASTST_MASK 0x0060 /* VBBIASTST - [6:5] */ #define WM8985_VBBIASTST_SHIFT 5 /* VBBIASTST - [6:5] */ #define WM8985_VBBIASTST_WIDTH 2 /* VBBIASTST - [6:5] */ diff --git a/sound/soc/codecs/wm8998.c b/sound/soc/codecs/wm8998.c index 449f66636205..3a5c896a2d13 100644 --- a/sound/soc/codecs/wm8998.c +++ b/sound/soc/codecs/wm8998.c @@ -1166,6 +1166,7 @@ static const struct snd_soc_dapm_route wm8998_dapm_routes[] = { { "MICSUPP", NULL, "SYSCLK" }, + { "DRC1 Signal Activity", NULL, "SYSCLK" }, { "DRC1 Signal Activity", NULL, "DRC1L" }, { "DRC1 Signal Activity", NULL, "DRC1R" }, }; diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index a07bd7c2c587..21fbe7d07063 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -394,6 +394,7 @@ static const struct { int compr_direction; int num_caps; const struct wm_adsp_fw_caps *caps; + bool voice_trigger; } wm_adsp_fw[WM_ADSP_NUM_FW] = { [WM_ADSP_FW_MBC_VSS] = { .file = "mbc-vss" }, [WM_ADSP_FW_HIFI] = { .file = "hifi" }, @@ -406,6 +407,7 @@ static const struct { .compr_direction = SND_COMPRESS_CAPTURE, .num_caps = ARRAY_SIZE(ctrl_caps), .caps = ctrl_caps, + .voice_trigger = true, }, [WM_ADSP_FW_ASR] = { .file = "asr" }, [WM_ADSP_FW_TRACE] = { @@ -2366,13 +2368,15 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w, dsp->running = false; regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, - ADSP2_SYS_ENA | ADSP2_CORE_ENA | - ADSP2_START, 0); + ADSP2_CORE_ENA | ADSP2_START, 0); /* Make sure DMAs are quiesced */ + regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0); regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0); - regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 0); + + regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL, + ADSP2_SYS_ENA, 0); list_for_each_entry(ctl, &dsp->ctl_list, list) ctl->enabled = 0; @@ -2998,6 +3002,9 @@ int wm_adsp_compr_handle_irq(struct wm_adsp *dsp) goto out; } + if (wm_adsp_fw[dsp->fw].voice_trigger && buf->irq_count == 2) + ret = WM_ADSP_COMPR_VOICE_TRIGGER; + out_notify: if (compr && compr->stream) snd_compr_fragment_elapsed(compr->stream); @@ -3037,12 +3044,8 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, buf = compr->buf; - if (!compr->buf) { - ret = -ENXIO; - goto out; - } - - if (compr->buf->error) { + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(stream, SNDRV_PCM_STATE_XRUN); ret = -EIO; goto out; } @@ -3060,8 +3063,12 @@ int wm_adsp_compr_pointer(struct snd_compr_stream *stream, */ if (buf->avail < wm_adsp_compr_frag_words(compr)) { ret = wm_adsp_buffer_get_error(buf); - if (ret < 0) + if (ret < 0) { + if (compr->buf->error) + snd_compr_stop_error(stream, + SNDRV_PCM_STATE_XRUN); goto out; + } ret = wm_adsp_buffer_reenable_irq(buf); if (ret < 0) { @@ -3156,11 +3163,10 @@ static int wm_adsp_compr_read(struct wm_adsp_compr *compr, adsp_dbg(dsp, "Requested read of %zu bytes\n", count); - if (!compr->buf) - return -ENXIO; - - if (compr->buf->error) + if (!compr->buf || compr->buf->error) { + snd_compr_stop_error(compr->stream, SNDRV_PCM_STATE_XRUN); return -EIO; + } count /= WM_ADSP_DATA_WORD_SIZE; diff --git a/sound/soc/codecs/wm_adsp.h b/sound/soc/codecs/wm_adsp.h index feb61e2c4bb4..be3b5bcb7f17 100644 --- a/sound/soc/codecs/wm_adsp.h +++ b/sound/soc/codecs/wm_adsp.h @@ -19,6 +19,10 @@ #include "wmfw.h" +/* Return values for wm_adsp_compr_handle_irq */ +#define WM_ADSP_COMPR_OK 0 +#define WM_ADSP_COMPR_VOICE_TRIGGER 1 + struct wm_adsp_region { int type; unsigned int base; diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 237dc67002ef..05c2d33aa74d 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -1599,7 +1599,14 @@ static struct davinci_mcasp_pdata *davinci_mcasp_set_pdata_from_of( pdata = pdev->dev.platform_data; return pdata; } else if (match) { - pdata = (struct davinci_mcasp_pdata*) match->data; + pdata = devm_kmemdup(&pdev->dev, match->data, sizeof(*pdata), + GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, + "Failed to allocate memory for pdata\n"); + ret = -ENOMEM; + return pdata; + } } else { /* control shouldn't reach here. something is wrong */ ret = -EINVAL; diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig index d50e08517dce..c297efe43861 100644 --- a/sound/soc/dwc/Kconfig +++ b/sound/soc/dwc/Kconfig @@ -7,4 +7,13 @@ config SND_DESIGNWARE_I2S Synopsys desigwnware I2S device. The device supports upto maximum of 8 channels each for play and record. +config SND_DESIGNWARE_PCM + tristate "PCM PIO extension for I2S driver" + depends on SND_DESIGNWARE_I2S + help + Say Y, M or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + + This functionality is specially suited for I2S devices that don't have + DMA support. diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile index 319371f690f4..38f1ca31c5fa 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -1,3 +1,5 @@ # SYNOPSYS Platform Support obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o - +ifdef CONFIG_SND_DESIGNWARE_PCM +obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o +endif diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index 0db69b7e9617..dc97f4349e66 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -24,90 +24,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/dmaengine_pcm.h> - -/* common register for all channel */ -#define IER 0x000 -#define IRER 0x004 -#define ITER 0x008 -#define CER 0x00C -#define CCR 0x010 -#define RXFFR 0x014 -#define TXFFR 0x018 - -/* I2STxRxRegisters for all channels */ -#define LRBR_LTHR(x) (0x40 * x + 0x020) -#define RRBR_RTHR(x) (0x40 * x + 0x024) -#define RER(x) (0x40 * x + 0x028) -#define TER(x) (0x40 * x + 0x02C) -#define RCR(x) (0x40 * x + 0x030) -#define TCR(x) (0x40 * x + 0x034) -#define ISR(x) (0x40 * x + 0x038) -#define IMR(x) (0x40 * x + 0x03C) -#define ROR(x) (0x40 * x + 0x040) -#define TOR(x) (0x40 * x + 0x044) -#define RFCR(x) (0x40 * x + 0x048) -#define TFCR(x) (0x40 * x + 0x04C) -#define RFF(x) (0x40 * x + 0x050) -#define TFF(x) (0x40 * x + 0x054) - -/* I2SCOMPRegisters */ -#define I2S_COMP_PARAM_2 0x01F0 -#define I2S_COMP_PARAM_1 0x01F4 -#define I2S_COMP_VERSION 0x01F8 -#define I2S_COMP_TYPE 0x01FC - -/* - * Component parameter register fields - define the I2S block's - * configuration. - */ -#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25) -#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22) -#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19) -#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16) -#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9) -#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7) -#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6) -#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5) -#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4) -#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2) -#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0) - -#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10) -#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7) -#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3) -#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0) - -/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ -#define COMP_MAX_WORDSIZE (1 << 3) -#define COMP_MAX_DATA_WIDTH (1 << 2) - -#define MAX_CHANNEL_NUM 8 -#define MIN_CHANNEL_NUM 2 - -union dw_i2s_snd_dma_data { - struct i2s_dma_data pd; - struct snd_dmaengine_dai_dma_data dt; -}; - -struct dw_i2s_dev { - void __iomem *i2s_base; - struct clk *clk; - int active; - unsigned int capability; - unsigned int quirks; - unsigned int i2s_reg_comp1; - unsigned int i2s_reg_comp2; - struct device *dev; - u32 ccr; - u32 xfer_resolution; - u32 fifo_th; - - /* data related to DMA transfers b/w i2s and DMAC */ - union dw_i2s_snd_dma_data play_dma_data; - union dw_i2s_snd_dma_data capture_dma_data; - struct i2s_clk_config_data config; - int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); -}; +#include "local.h" static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val) { @@ -145,51 +62,115 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream) } } -static void i2s_start(struct dw_i2s_dev *dev, - struct snd_pcm_substream *substream) +static inline void i2s_disable_irqs(struct dw_i2s_dev *dev, u32 stream, + int chan_nr) +{ + u32 i, irq; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); + } + } else { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); + } + } +} + +static inline void i2s_enable_irqs(struct dw_i2s_dev *dev, u32 stream, + int chan_nr) { - struct i2s_clk_config_data *config = &dev->config; u32 i, irq; - i2s_write_reg(dev->i2s_base, IER, 1); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - for (i = 0; i < (config->chan_nr / 2); i++) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { irq = i2s_read_reg(dev->i2s_base, IMR(i)); i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30); } - i2s_write_reg(dev->i2s_base, ITER, 1); } else { - for (i = 0; i < (config->chan_nr / 2); i++) { + for (i = 0; i < (chan_nr / 2); i++) { irq = i2s_read_reg(dev->i2s_base, IMR(i)); i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03); } - i2s_write_reg(dev->i2s_base, IRER, 1); + } +} + +static irqreturn_t i2s_irq_handler(int irq, void *dev_id) +{ + struct dw_i2s_dev *dev = dev_id; + bool irq_valid = false; + u32 isr[4]; + int i; + + for (i = 0; i < 4; i++) + isr[i] = i2s_read_reg(dev->i2s_base, ISR(i)); + + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK); + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE); + + for (i = 0; i < 4; i++) { + /* + * Check if TX fifo is empty. If empty fill FIFO with samples + * NOTE: Only two channels supported + */ + if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) { + dw_pcm_push_tx(dev); + irq_valid = true; + } + + /* Data available. Record mode not supported in PIO mode */ + if (isr[i] & ISR_RXDA) + irq_valid = true; + + /* Error Handling: TX */ + if (isr[i] & ISR_TXFO) { + dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i); + irq_valid = true; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_RXFO) { + dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i); + irq_valid = true; + } } + if (irq_valid) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static void i2s_start(struct dw_i2s_dev *dev, + struct snd_pcm_substream *substream) +{ + struct i2s_clk_config_data *config = &dev->config; + + i2s_write_reg(dev->i2s_base, IER, 1); + i2s_enable_irqs(dev, substream->stream, config->chan_nr); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 1); + else + i2s_write_reg(dev->i2s_base, IRER, 1); + i2s_write_reg(dev->i2s_base, CER, 1); } static void i2s_stop(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { - u32 i = 0, irq; i2s_clear_irqs(dev, substream->stream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) i2s_write_reg(dev->i2s_base, ITER, 0); - - for (i = 0; i < 4; i++) { - irq = i2s_read_reg(dev->i2s_base, IMR(i)); - i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); - } - } else { + else i2s_write_reg(dev->i2s_base, IRER, 0); - for (i = 0; i < 4; i++) { - irq = i2s_read_reg(dev->i2s_base, IMR(i)); - i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); - } - } + i2s_disable_irqs(dev, substream->stream, 8); if (!dev->active) { i2s_write_reg(dev->i2s_base, CER, 0); @@ -223,7 +204,7 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) { - u32 ch_reg, irq; + u32 ch_reg; struct i2s_clk_config_data *config = &dev->config; @@ -235,16 +216,12 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) dev->xfer_resolution); i2s_write_reg(dev->i2s_base, TFCR(ch_reg), dev->fifo_th - 1); - irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); - i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30); i2s_write_reg(dev->i2s_base, TER(ch_reg), 1); } else { i2s_write_reg(dev->i2s_base, RCR(ch_reg), dev->xfer_resolution); i2s_write_reg(dev->i2s_base, RFCR(ch_reg), dev->fifo_th - 1); - irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); - i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03); i2s_write_reg(dev->i2s_base, RER(ch_reg), 1); } @@ -278,7 +255,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, break; default: - dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt"); + dev_err(dev->dev, "designware-i2s: unsupported PCM fmt"); return -EINVAL; } @@ -626,7 +603,7 @@ static int dw_i2s_probe(struct platform_device *pdev) const struct i2s_platform_data *pdata = pdev->dev.platform_data; struct dw_i2s_dev *dev; struct resource *res; - int ret; + int ret, irq; struct snd_soc_dai_driver *dw_i2s_dai; const char *clk_id; @@ -651,6 +628,16 @@ static int dw_i2s_probe(struct platform_device *pdev) dev->dev = &pdev->dev; + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + } + dev->i2s_reg_comp1 = I2S_COMP_PARAM_1; dev->i2s_reg_comp2 = I2S_COMP_PARAM_2; if (pdata) { @@ -697,12 +684,24 @@ static int dw_i2s_probe(struct platform_device *pdev) if (!pdata) { ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) { + if (ret == -EPROBE_DEFER) { + dev_err(&pdev->dev, + "failed to register PCM, deferring probe\n"); + return ret; + } else if (ret) { dev_err(&pdev->dev, - "Could not register PCM: %d\n", ret); - goto err_clk_disable; + "Could not register DMA PCM: %d\n" + "falling back to PIO mode\n", ret); + ret = dw_pcm_register(pdev); + if (ret) { + dev_err(&pdev->dev, + "Could not register PIO PCM: %d\n", + ret); + goto err_clk_disable; + } } } + pm_runtime_enable(&pdev->dev); return 0; diff --git a/sound/soc/dwc/designware_pcm.c b/sound/soc/dwc/designware_pcm.c new file mode 100644 index 000000000000..4a83a22fa3cb --- /dev/null +++ b/sound/soc/dwc/designware_pcm.c @@ -0,0 +1,225 @@ +/* + * ALSA SoC Synopsys PIO PCM for I2S driver + * + * sound/soc/dwc/designware_pcm.c + * + * Copyright (C) 2016 Synopsys + * Jose Abreu <joabreu@synopsys.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/io.h> +#include <linux/rcupdate.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "local.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +#define dw_pcm_tx_fn(sample_bits) \ +static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \ + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \ + bool *period_elapsed) \ +{ \ + const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ + unsigned int period_pos = tx_ptr % runtime->period_size; \ + int i; \ +\ + for (i = 0; i < dev->fifo_th; i++) { \ + iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \ + iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \ + period_pos++; \ + if (++tx_ptr >= runtime->buffer_size) \ + tx_ptr = 0; \ + } \ + *period_elapsed = period_pos >= runtime->period_size; \ + return tx_ptr; \ +} + +dw_pcm_tx_fn(16); +dw_pcm_tx_fn(32); + +#undef dw_pcm_tx_fn + +static const struct snd_pcm_hardware dw_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 16, +}; + +void dw_pcm_push_tx(struct dw_i2s_dev *dev) +{ + struct snd_pcm_substream *tx_substream; + bool tx_active, period_elapsed; + + rcu_read_lock(); + tx_substream = rcu_dereference(dev->tx_substream); + tx_active = tx_substream && snd_pcm_running(tx_substream); + if (tx_active) { + unsigned int tx_ptr = READ_ONCE(dev->tx_ptr); + unsigned int new_tx_ptr = dev->tx_fn(dev, tx_substream->runtime, + tx_ptr, &period_elapsed); + cmpxchg(&dev->tx_ptr, tx_ptr, new_tx_ptr); + + if (period_elapsed) + snd_pcm_period_elapsed(tx_substream); + } + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(dw_pcm_push_tx); + +static int dw_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai); + + snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + +static int dw_pcm_close(struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int dw_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + int ret; + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dev->tx_fn = dw_pcm_tx_16; + break; + case SNDRV_PCM_FORMAT_S32_LE: + dev->tx_fn = dw_pcm_tx_32; + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) { + dev_err(dev->dev, "only playback is available\n"); + return -EINVAL; + } + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) + return ret; + else + return 0; +} + +static int dw_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + rcu_assign_pointer(dev->tx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dw_i2s_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos = READ_ONCE(dev->tx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int dw_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + size_t size = dw_pcm_hardware.buffer_bytes_max; + + return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), size, size); +} + +static void dw_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static const struct snd_pcm_ops dw_pcm_ops = { + .open = dw_pcm_open, + .close = dw_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dw_pcm_hw_params, + .hw_free = dw_pcm_hw_free, + .trigger = dw_pcm_trigger, + .pointer = dw_pcm_pointer, +}; + +static const struct snd_soc_platform_driver dw_pcm_platform = { + .pcm_new = dw_pcm_new, + .pcm_free = dw_pcm_free, + .ops = &dw_pcm_ops, +}; + +int dw_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform); +} +EXPORT_SYMBOL_GPL(dw_pcm_register); diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h new file mode 100644 index 000000000000..68afd7577343 --- /dev/null +++ b/sound/soc/dwc/local.h @@ -0,0 +1,128 @@ +/* + * Copyright (ST) 2012 Rajeev Kumar (rajeevkumar.linux@gmail.com) + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __DESIGNWARE_LOCAL_H +#define __DESIGNWARE_LOCAL_H + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/types.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/designware_i2s.h> + +/* common register for all channel */ +#define IER 0x000 +#define IRER 0x004 +#define ITER 0x008 +#define CER 0x00C +#define CCR 0x010 +#define RXFFR 0x014 +#define TXFFR 0x018 + +/* Interrupt status register fields */ +#define ISR_TXFO BIT(5) +#define ISR_TXFE BIT(4) +#define ISR_RXFO BIT(1) +#define ISR_RXDA BIT(0) + +/* I2STxRxRegisters for all channels */ +#define LRBR_LTHR(x) (0x40 * x + 0x020) +#define RRBR_RTHR(x) (0x40 * x + 0x024) +#define RER(x) (0x40 * x + 0x028) +#define TER(x) (0x40 * x + 0x02C) +#define RCR(x) (0x40 * x + 0x030) +#define TCR(x) (0x40 * x + 0x034) +#define ISR(x) (0x40 * x + 0x038) +#define IMR(x) (0x40 * x + 0x03C) +#define ROR(x) (0x40 * x + 0x040) +#define TOR(x) (0x40 * x + 0x044) +#define RFCR(x) (0x40 * x + 0x048) +#define TFCR(x) (0x40 * x + 0x04C) +#define RFF(x) (0x40 * x + 0x050) +#define TFF(x) (0x40 * x + 0x054) + +/* I2SCOMPRegisters */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +/* + * Component parameter register fields - define the I2S block's + * configuration. + */ +#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25) +#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22) +#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19) +#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16) +#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9) +#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7) +#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6) +#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5) +#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4) +#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2) +#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0) + +#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10) +#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7) +#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3) +#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0) + +/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ +#define COMP_MAX_WORDSIZE (1 << 3) +#define COMP_MAX_DATA_WIDTH (1 << 2) + +#define MAX_CHANNEL_NUM 8 +#define MIN_CHANNEL_NUM 2 + +union dw_i2s_snd_dma_data { + struct i2s_dma_data pd; + struct snd_dmaengine_dai_dma_data dt; +}; + +struct dw_i2s_dev { + void __iomem *i2s_base; + struct clk *clk; + int active; + unsigned int capability; + unsigned int quirks; + unsigned int i2s_reg_comp1; + unsigned int i2s_reg_comp2; + struct device *dev; + u32 ccr; + u32 xfer_resolution; + u32 fifo_th; + + /* data related to DMA transfers b/w i2s and DMAC */ + union dw_i2s_snd_dma_data play_dma_data; + union dw_i2s_snd_dma_data capture_dma_data; + struct i2s_clk_config_data config; + int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); + + /* data related to PIO transfers (TX) */ + bool use_pio; + struct snd_pcm_substream __rcu *tx_substream; + unsigned int (*tx_fn)(struct dw_i2s_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed); + unsigned int tx_ptr; +}; + +#if IS_ENABLED(CONFIG_SND_DESIGNWARE_PCM) +void dw_pcm_push_tx(struct dw_i2s_dev *dev); +int dw_pcm_register(struct platform_device *pdev); +#else +void dw_pcm_push_tx(struct dw_i2s_dev *dev) { } +int dw_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 35aabf9dc503..19bdcac71775 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -4,6 +4,7 @@ comment "Common SoC Audio options for Freescale CPUs:" config SND_SOC_FSL_ASRC tristate "Asynchronous Sample Rate Converter (ASRC) module support" + depends on HAS_DMA select REGMAP_MMIO select SND_SOC_GENERIC_DMAENGINE_PCM help diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index 151849f79863..beec7934a265 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -172,7 +172,7 @@ static void spdif_irq_uqrx_full(struct fsl_spdif_priv *spdif_priv, char name) if (*pos >= size * 2) { *pos = 0; } else if (unlikely((*pos % size) + 3 > size)) { - dev_err(&pdev->dev, "User bit receivce buffer overflow\n"); + dev_err(&pdev->dev, "User bit receive buffer overflow\n"); return; } diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index 610f61251640..c01c5dd68601 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -1,4 +1,8 @@ +config SND_SIMPLE_CARD_UTILS + tristate + config SND_SIMPLE_CARD tristate "ASoC Simple sound card support" + select SND_SIMPLE_CARD_UTILS help This option enables generic simple sound card support diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index 9c3b246792bf..45602ca8536e 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -1,3 +1,5 @@ +obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) := simple-card-utils.o + snd-soc-simple-card-objs := simple-card.o obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c new file mode 100644 index 000000000000..d89a9a1b2471 --- /dev/null +++ b/sound/soc/generic/simple-card-utils.c @@ -0,0 +1,97 @@ +/* + * simple-card-core.c + * + * Copyright (c) 2016 Kuninori Morimoto <kuninori.morimoto.gx@renesas.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. + */ +#include <linux/of.h> +#include <sound/simple_card_utils.h> + +int asoc_simple_card_parse_daifmt(struct device *dev, + struct device_node *node, + struct device_node *codec, + char *prefix, + unsigned int *retfmt) +{ + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + int prefix_len = prefix ? strlen(prefix) : 0; + unsigned int daifmt; + + daifmt = snd_soc_of_parse_daifmt(node, prefix, + &bitclkmaster, &framemaster); + daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + + if (prefix_len && !bitclkmaster && !framemaster) { + /* + * No dai-link level and master setting was not found from + * sound node level, revert back to legacy DT parsing and + * take the settings from codec node. + */ + dev_dbg(dev, "Revert to legacy daifmt parsing\n"); + + daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | + (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); + } else { + if (codec == bitclkmaster) + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; + else + daifmt |= (codec == framemaster) ? + SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; + } + + of_node_put(bitclkmaster); + of_node_put(framemaster); + + *retfmt = daifmt; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_daifmt); + +int asoc_simple_card_set_dailink_name(struct device *dev, + struct snd_soc_dai_link *dai_link, + const char *fmt, ...) +{ + va_list ap; + char *name = NULL; + int ret = -ENOMEM; + + va_start(ap, fmt); + name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap); + va_end(ap); + + if (name) { + ret = 0; + + dai_link->name = name; + dai_link->stream_name = name; + } + + return ret; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_set_dailink_name); + +int asoc_simple_card_parse_card_name(struct snd_soc_card *card, + char *prefix) +{ + char prop[128]; + int ret; + + snprintf(prop, sizeof(prop), "%sname", prefix); + + /* Parse the card name from DT */ + ret = snd_soc_of_parse_card_name(card, prop); + if (ret < 0) + return ret; + + if (!card->name && card->dai_link) + card->name = card->dai_link->name; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_simple_card_parse_card_name); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 466492b7d4f5..43295f024982 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -21,6 +21,12 @@ #include <sound/soc-dai.h> #include <sound/soc.h> +struct asoc_simple_jack { + struct snd_soc_jack jack; + struct snd_soc_jack_pin pin; + struct snd_soc_jack_gpio gpio; +}; + struct simple_card_data { struct snd_soc_card snd_card; struct simple_dai_props { @@ -29,10 +35,8 @@ struct simple_card_data { unsigned int mclk_fs; } *dai_props; unsigned int mclk_fs; - int gpio_hp_det; - int gpio_hp_det_invert; - int gpio_mic_det; - int gpio_mic_det_invert; + struct asoc_simple_jack hp_jack; + struct asoc_simple_jack mic_jack; struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ }; @@ -40,6 +44,69 @@ struct simple_card_data { #define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + i) #define simple_priv_to_props(priv, i) ((priv)->dai_props + i) +#define PREFIX "simple-audio-card," + +#define asoc_simple_card_init_hp(card, sjack, prefix)\ + asoc_simple_card_init_jack(card, sjack, 1, prefix) +#define asoc_simple_card_init_mic(card, sjack, prefix)\ + asoc_simple_card_init_jack(card, sjack, 0, prefix) +static int asoc_simple_card_init_jack(struct snd_soc_card *card, + struct asoc_simple_jack *sjack, + int is_hp, char *prefix) +{ + struct device *dev = card->dev; + enum of_gpio_flags flags; + char prop[128]; + char *pin_name; + char *gpio_name; + int mask; + int det; + + sjack->gpio.gpio = -ENOENT; + + if (is_hp) { + snprintf(prop, sizeof(prop), "%shp-det-gpio", prefix); + pin_name = "Headphones"; + gpio_name = "Headphone detection"; + mask = SND_JACK_HEADPHONE; + } else { + snprintf(prop, sizeof(prop), "%smic-det-gpio", prefix); + pin_name = "Mic Jack"; + gpio_name = "Mic detection"; + mask = SND_JACK_MICROPHONE; + } + + det = of_get_named_gpio_flags(dev->of_node, prop, 0, &flags); + if (det == -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (gpio_is_valid(det)) { + sjack->pin.pin = pin_name; + sjack->pin.mask = mask; + + sjack->gpio.name = gpio_name; + sjack->gpio.report = mask; + sjack->gpio.gpio = det; + sjack->gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW); + sjack->gpio.debounce_time = 150; + + snd_soc_card_jack_new(card, pin_name, mask, + &sjack->jack, + &sjack->pin, 1); + + snd_soc_jack_add_gpios(&sjack->jack, 1, + &sjack->gpio); + } + + return 0; +} + +static void asoc_simple_card_remove_jack(struct asoc_simple_jack *sjack) +{ + if (gpio_is_valid(sjack->gpio.gpio)) + snd_soc_jack_free_gpios(&sjack->jack, 1, &sjack->gpio); +} + static int asoc_simple_card_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -110,32 +177,6 @@ static struct snd_soc_ops asoc_simple_card_ops = { .hw_params = asoc_simple_card_hw_params, }; -static struct snd_soc_jack simple_card_hp_jack; -static struct snd_soc_jack_pin simple_card_hp_jack_pins[] = { - { - .pin = "Headphones", - .mask = SND_JACK_HEADPHONE, - }, -}; -static struct snd_soc_jack_gpio simple_card_hp_jack_gpio = { - .name = "Headphone detection", - .report = SND_JACK_HEADPHONE, - .debounce_time = 150, -}; - -static struct snd_soc_jack simple_card_mic_jack; -static struct snd_soc_jack_pin simple_card_mic_jack_pins[] = { - { - .pin = "Mic Jack", - .mask = SND_JACK_MICROPHONE, - }, -}; -static struct snd_soc_jack_gpio simple_card_mic_jack_gpio = { - .name = "Mic detection", - .report = SND_JACK_MICROPHONE, - .debounce_time = 150, -}; - static int __asoc_simple_card_dai_init(struct snd_soc_dai *dai, struct asoc_simple_dai *set) { @@ -184,30 +225,14 @@ static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) if (ret < 0) return ret; - if (gpio_is_valid(priv->gpio_hp_det)) { - snd_soc_card_jack_new(rtd->card, "Headphones", - SND_JACK_HEADPHONE, - &simple_card_hp_jack, - simple_card_hp_jack_pins, - ARRAY_SIZE(simple_card_hp_jack_pins)); - - simple_card_hp_jack_gpio.gpio = priv->gpio_hp_det; - simple_card_hp_jack_gpio.invert = priv->gpio_hp_det_invert; - snd_soc_jack_add_gpios(&simple_card_hp_jack, 1, - &simple_card_hp_jack_gpio); - } + ret = asoc_simple_card_init_hp(rtd->card, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; + + ret = asoc_simple_card_init_mic(rtd->card, &priv->hp_jack, PREFIX); + if (ret < 0) + return ret; - if (gpio_is_valid(priv->gpio_mic_det)) { - snd_soc_card_jack_new(rtd->card, "Mic Jack", - SND_JACK_MICROPHONE, - &simple_card_mic_jack, - simple_card_mic_jack_pins, - ARRAY_SIZE(simple_card_mic_jack_pins)); - simple_card_mic_jack_gpio.gpio = priv->gpio_mic_det; - simple_card_mic_jack_gpio.invert = priv->gpio_mic_det_invert; - snd_soc_jack_add_gpios(&simple_card_mic_jack, 1, - &simple_card_mic_jack_gpio); - } return 0; } @@ -223,6 +248,9 @@ asoc_simple_card_sub_parse_of(struct device_node *np, u32 val; int ret; + if (!np) + return 0; + /* * Get node via "sound-dai = <&phandle port>" * it will be used as xxx_of_node on soc_bind_dai_link() @@ -238,9 +266,14 @@ asoc_simple_card_sub_parse_of(struct device_node *np, *args_count = args.args_count; /* Get dai->name */ - ret = snd_soc_of_get_dai_name(np, name); - if (ret < 0) - return ret; + if (name) { + ret = snd_soc_of_get_dai_name(np, name); + if (ret < 0) + return ret; + } + + if (!dai) + return 0; /* Parse TDM slot */ ret = snd_soc_of_parse_tdm_slot(np, &dai->tx_slot_mask, @@ -275,48 +308,6 @@ asoc_simple_card_sub_parse_of(struct device_node *np, return 0; } -static int asoc_simple_card_parse_daifmt(struct device_node *node, - struct simple_card_data *priv, - struct device_node *codec, - char *prefix, int idx) -{ - struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); - struct device *dev = simple_priv_to_dev(priv); - struct device_node *bitclkmaster = NULL; - struct device_node *framemaster = NULL; - unsigned int daifmt; - - daifmt = snd_soc_of_parse_daifmt(node, prefix, - &bitclkmaster, &framemaster); - daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; - - if (strlen(prefix) && !bitclkmaster && !framemaster) { - /* - * No dai-link level and master setting was not found from - * sound node level, revert back to legacy DT parsing and - * take the settings from codec node. - */ - dev_dbg(dev, "Revert to legacy daifmt parsing\n"); - - daifmt = snd_soc_of_parse_daifmt(codec, NULL, NULL, NULL) | - (daifmt & ~SND_SOC_DAIFMT_CLOCK_MASK); - } else { - if (codec == bitclkmaster) - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; - else - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; - } - - dai_link->dai_fmt = daifmt; - - of_node_put(bitclkmaster); - of_node_put(framemaster); - - return 0; -} - static int asoc_simple_card_dai_link_of(struct device_node *node, struct simple_card_data *priv, int idx, @@ -328,7 +319,6 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, struct device_node *cpu = NULL; struct device_node *plat = NULL; struct device_node *codec = NULL; - char *name; char prop[128]; char *prefix = ""; int ret, cpu_args; @@ -336,7 +326,7 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, /* For single DAI link & old style of DT node */ if (is_top_level_node) - prefix = "simple-audio-card,"; + prefix = PREFIX; snprintf(prop, sizeof(prop), "%scpu", prefix); cpu = of_get_child_by_name(node, prop); @@ -353,8 +343,8 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, goto dai_link_of_err; } - ret = asoc_simple_card_parse_daifmt(node, priv, - codec, prefix, idx); + ret = asoc_simple_card_parse_daifmt(dev, node, codec, + prefix, &dai_link->dai_fmt); if (ret < 0) goto dai_link_of_err; @@ -374,35 +364,28 @@ static int asoc_simple_card_dai_link_of(struct device_node *node, if (ret < 0) goto dai_link_of_err; + ret = asoc_simple_card_sub_parse_of(plat, NULL, + &dai_link->platform_of_node, + NULL, NULL); + if (ret < 0) + goto dai_link_of_err; + if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) { ret = -EINVAL; goto dai_link_of_err; } - if (plat) { - struct of_phandle_args args; - - ret = of_parse_phandle_with_args(plat, "sound-dai", - "#sound-dai-cells", 0, &args); - dai_link->platform_of_node = args.np; - } else { - /* Assumes platform == cpu */ + /* Assumes platform == cpu */ + if (!dai_link->platform_of_node) dai_link->platform_of_node = dai_link->cpu_of_node; - } - /* DAI link name is created from CPU/CODEC dai name */ - name = devm_kzalloc(dev, - strlen(dai_link->cpu_dai_name) + - strlen(dai_link->codec_dai_name) + 2, - GFP_KERNEL); - if (!name) { - ret = -ENOMEM; + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "%s-%s", + dai_link->cpu_dai_name, + dai_link->codec_dai_name); + if (ret < 0) goto dai_link_of_err; - } - sprintf(name, "%s-%s", dai_link->cpu_dai_name, - dai_link->codec_dai_name); - dai_link->name = dai_link->stream_name = name; dai_link->ops = &asoc_simple_card_ops; dai_link->init = asoc_simple_card_dai_init; @@ -438,42 +421,35 @@ static int asoc_simple_card_parse_of(struct device_node *node, struct simple_card_data *priv) { struct device *dev = simple_priv_to_dev(priv); - enum of_gpio_flags flags; u32 val; int ret; if (!node) return -EINVAL; - /* Parse the card name from DT */ - snd_soc_of_parse_card_name(&priv->snd_card, "simple-audio-card,name"); - /* The off-codec widgets */ - if (of_property_read_bool(node, "simple-audio-card,widgets")) { + if (of_property_read_bool(node, PREFIX "widgets")) { ret = snd_soc_of_parse_audio_simple_widgets(&priv->snd_card, - "simple-audio-card,widgets"); + PREFIX "widgets"); if (ret) return ret; } /* DAPM routes */ - if (of_property_read_bool(node, "simple-audio-card,routing")) { + if (of_property_read_bool(node, PREFIX "routing")) { ret = snd_soc_of_parse_audio_routing(&priv->snd_card, - "simple-audio-card,routing"); + PREFIX "routing"); if (ret) return ret; } /* Factor to mclk, used in hw_params() */ - ret = of_property_read_u32(node, "simple-audio-card,mclk-fs", &val); + ret = of_property_read_u32(node, PREFIX "mclk-fs", &val); if (ret == 0) priv->mclk_fs = val; - dev_dbg(dev, "New simple-card: %s\n", priv->snd_card.name ? - priv->snd_card.name : ""); - /* Single/Muti DAI link(s) & New style of DT node */ - if (of_get_child_by_name(node, "simple-audio-card,dai-link")) { + if (of_get_child_by_name(node, PREFIX "dai-link")) { struct device_node *np = NULL; int i = 0; @@ -494,20 +470,9 @@ static int asoc_simple_card_parse_of(struct device_node *node, return ret; } - priv->gpio_hp_det = of_get_named_gpio_flags(node, - "simple-audio-card,hp-det-gpio", 0, &flags); - priv->gpio_hp_det_invert = !!(flags & OF_GPIO_ACTIVE_LOW); - if (priv->gpio_hp_det == -EPROBE_DEFER) - return -EPROBE_DEFER; - - priv->gpio_mic_det = of_get_named_gpio_flags(node, - "simple-audio-card,mic-det-gpio", 0, &flags); - priv->gpio_mic_det_invert = !!(flags & OF_GPIO_ACTIVE_LOW); - if (priv->gpio_mic_det == -EPROBE_DEFER) - return -EPROBE_DEFER; - - if (!priv->snd_card.name) - priv->snd_card.name = priv->snd_card.dai_link->name; + ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX); + if (ret) + return ret; return 0; } @@ -536,7 +501,7 @@ static int asoc_simple_card_probe(struct platform_device *pdev) int num_links, ret; /* Get the number of DAI links */ - if (np && of_get_child_by_name(np, "simple-audio-card,dai-link")) + if (np && of_get_child_by_name(np, PREFIX "dai-link")) num_links = of_get_child_count(np); else num_links = 1; @@ -555,9 +520,6 @@ static int asoc_simple_card_probe(struct platform_device *pdev) priv->snd_card.dai_link = dai_link; priv->snd_card.num_links = num_links; - priv->gpio_hp_det = -ENOENT; - priv->gpio_mic_det = -ENOENT; - /* Get room for the other properties */ priv->dai_props = devm_kzalloc(dev, sizeof(*priv->dai_props) * num_links, @@ -624,12 +586,8 @@ static int asoc_simple_card_remove(struct platform_device *pdev) struct snd_soc_card *card = platform_get_drvdata(pdev); struct simple_card_data *priv = snd_soc_card_get_drvdata(card); - if (gpio_is_valid(priv->gpio_hp_det)) - snd_soc_jack_free_gpios(&simple_card_hp_jack, 1, - &simple_card_hp_jack_gpio); - if (gpio_is_valid(priv->gpio_mic_det)) - snd_soc_jack_free_gpios(&simple_card_mic_jack, 1, - &simple_card_mic_jack_gpio); + asoc_simple_card_remove_jack(&priv->hp_jack); + asoc_simple_card_remove_jack(&priv->mic_jack); return asoc_simple_card_unref(card); } diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index 91c15abb625e..a20c3dfbcb5d 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -7,7 +7,7 @@ config SND_MFLD_MACHINE help This adds support for ASoC machine driver for Intel(R) MID Medfield platform used as alsa device in audio substem in Intel(R) MID devices - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SST_MFLD_PLATFORM @@ -25,7 +25,6 @@ config SND_SST_IPC_ACPI tristate select SND_SST_IPC select SND_SOC_INTEL_SST - depends on ACPI config SND_SOC_INTEL_SST tristate @@ -33,6 +32,12 @@ config SND_SOC_INTEL_SST select SND_SOC_INTEL_SST_MATCH if ACPI depends on (X86 || COMPILE_TEST) +# firmware stuff depends DW_DMAC_CORE; since there is no depends-on from +# the reverse selection, each machine driver needs to select +# SND_SOC_INTEL_SST_FIRMWARE carefully depending on DW_DMAC_CORE +config SND_SOC_INTEL_SST_FIRMWARE + tristate + config SND_SOC_INTEL_SST_ACPI tristate @@ -48,16 +53,33 @@ config SND_SOC_INTEL_BAYTRAIL config SND_SOC_INTEL_HASWELL_MACH tristate "ASoC Audio DSP support for Intel Haswell Lynxpoint" depends on X86_INTEL_LPSS && I2C && I2C_DESIGNWARE_PLATFORM - depends on DW_DMAC_CORE=y + depends on DW_DMAC_CORE select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_HASWELL select SND_SOC_RT5640 help This adds support for the Lynxpoint Audio DSP on Intel(R) Haswell Ultrabook platforms. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". +config SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH + tristate "ASoC Audio driver for Broxton with DA7219 and MAX98357A in I2S Mode" + depends on X86 && ACPI && I2C + select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SKYLAKE + select SND_SOC_DA7219 + select SND_SOC_MAX98357A + select SND_SOC_DMIC + select SND_SOC_HDAC_HDMI + select SND_HDA_DSP_LOADER + help + This adds support for ASoC machine driver for Broxton-P platforms + with DA7219 + MAX98357A I2S audio codec. + Say Y if you have such a device. + If unsure select "N". + config SND_SOC_INTEL_BXT_RT298_MACH tristate "ASoC Audio driver for Broxton with RT298 I2S mode" depends on X86 && ACPI && I2C @@ -70,26 +92,28 @@ config SND_SOC_INTEL_BXT_RT298_MACH help This adds support for ASoC machine driver for Broxton platforms with RT286 I2S audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_BYT_RT5640_MACH tristate "ASoC Audio driver for Intel Baytrail with RT5640 codec" depends on X86_INTEL_LPSS && I2C - depends on DW_DMAC_CORE=y && (SND_SST_IPC_ACPI = n) + depends on DW_DMAC_CORE && (SND_SST_IPC_ACPI = n) select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_BAYTRAIL select SND_SOC_RT5640 help This adds audio driver for Intel Baytrail platform based boards with the RT5640 audio codec. This driver is deprecated, use - SND_SOC_INTEL_BYTCR_RT5640_MACH instead for better functionality + SND_SOC_INTEL_BYTCR_RT5640_MACH instead for better functionality. config SND_SOC_INTEL_BYT_MAX98090_MACH tristate "ASoC Audio driver for Intel Baytrail with MAX98090 codec" depends on X86_INTEL_LPSS && I2C - depends on DW_DMAC_CORE=y && (SND_SST_IPC_ACPI = n) + depends on DW_DMAC_CORE && (SND_SST_IPC_ACPI = n) select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_BAYTRAIL select SND_SOC_MAX98090 help @@ -100,19 +124,20 @@ config SND_SOC_INTEL_BROADWELL_MACH tristate "ASoC Audio DSP support for Intel Broadwell Wildcatpoint" depends on X86_INTEL_LPSS && I2C && DW_DMAC && \ I2C_DESIGNWARE_PLATFORM - depends on DW_DMAC_CORE=y + depends on DW_DMAC_CORE select SND_SOC_INTEL_SST + select SND_SOC_INTEL_SST_FIRMWARE select SND_SOC_INTEL_HASWELL select SND_SOC_RT286 help This adds support for the Wilcatpoint Audio DSP on Intel(R) Broadwell Ultrabook platforms. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_BYTCR_RT5640_MACH tristate "ASoC Audio driver for Intel Baytrail and Baytrail-CR with RT5640 codec" - depends on X86 && I2C + depends on X86 && I2C && ACPI select SND_SOC_RT5640 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -120,12 +145,12 @@ config SND_SOC_INTEL_BYTCR_RT5640_MACH help This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR platforms with RT5640 audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_BYTCR_RT5651_MACH tristate "ASoC Audio driver for Intel Baytrail and Baytrail-CR with RT5651 codec" - depends on X86 && I2C + depends on X86 && I2C && ACPI select SND_SOC_RT5651 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -133,12 +158,12 @@ config SND_SOC_INTEL_BYTCR_RT5651_MACH help This adds support for ASoC machine driver for Intel(R) Baytrail and Baytrail-CR platforms with RT5651 audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_CHT_BSW_RT5672_MACH tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5672 codec" - depends on X86_INTEL_LPSS && I2C + depends on X86_INTEL_LPSS && I2C && ACPI select SND_SOC_RT5670 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -146,12 +171,12 @@ config SND_SOC_INTEL_CHT_BSW_RT5672_MACH help This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell platforms with RT5672 audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_CHT_BSW_RT5645_MACH tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with RT5645/5650 codec" - depends on X86_INTEL_LPSS && I2C + depends on X86_INTEL_LPSS && I2C && ACPI select SND_SOC_RT5645 select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI @@ -163,16 +188,16 @@ config SND_SOC_INTEL_CHT_BSW_RT5645_MACH config SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH tristate "ASoC Audio driver for Intel Cherrytrail & Braswell with MAX98090 & TI codec" - depends on X86_INTEL_LPSS && I2C + depends on X86_INTEL_LPSS && I2C && ACPI select SND_SOC_MAX98090 select SND_SOC_TS3A227E select SND_SST_MFLD_PLATFORM select SND_SST_IPC_ACPI select SND_SOC_INTEL_SST_MATCH if ACPI help - This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell - platforms with MAX98090 audio codec it also can support TI jack chip as aux device. - If unsure select "N". + This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell + platforms with MAX98090 audio codec it also can support TI jack chip as aux device. + If unsure select "N". config SND_SOC_INTEL_SKYLAKE tristate @@ -192,7 +217,7 @@ config SND_SOC_INTEL_SKL_RT286_MACH help This adds support for ASoC machine driver for Skylake platforms with RT286 I2S audio codec. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH @@ -207,7 +232,7 @@ config SND_SOC_INTEL_SKL_NAU88L25_SSM4567_MACH help This adds support for ASoC Onboard Codec I2S machine driver. This will create an alsa sound card for NAU88L25 + SSM4567. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH @@ -222,5 +247,5 @@ config SND_SOC_INTEL_SKL_NAU88L25_MAX98357A_MACH help This adds support for ASoC Onboard Codec I2S machine driver. This will create an alsa sound card for NAU88L25 + MAX98357A. - Say Y if you have such a device + Say Y if you have such a device. If unsure select "N". diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index 3bc4b63b2f9d..4d3184971227 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -28,6 +28,7 @@ #include <linux/firmware.h> #include <linux/pm_runtime.h> #include <linux/pm_qos.h> +#include <linux/dmi.h> #include <linux/acpi.h> #include <asm/platform_sst_audio.h> #include <sound/core.h> @@ -237,6 +238,9 @@ static int sst_acpi_probe(struct platform_device *pdev) dev_err(dev, "No matching machine driver found\n"); return -ENODEV; } + if (mach->machine_quirk) + mach = mach->machine_quirk(mach); + pdata = mach->pdata; ret = kstrtouint(id->id, 16, &dev_id); @@ -320,6 +324,44 @@ static int sst_acpi_remove(struct platform_device *pdev) return 0; } +static unsigned long cht_machine_id; + +#define CHT_SURFACE_MACH 1 + +static int cht_surface_quirk_cb(const struct dmi_system_id *id) +{ + cht_machine_id = CHT_SURFACE_MACH; + return 1; +} + + +static const struct dmi_system_id cht_table[] = { + { + .callback = cht_surface_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, +}; + + +static struct sst_acpi_mach cht_surface_mach = { + "10EC5640", "cht-bsw-rt5645", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, + &chv_platform_data }; + +static struct sst_acpi_mach *cht_quirk(void *arg) +{ + struct sst_acpi_mach *mach = arg; + + dmi_check_system(cht_table); + + if (cht_machine_id == CHT_SURFACE_MACH) + return &cht_surface_mach; + else + return mach; +} + static struct sst_acpi_mach sst_acpi_bytcr[] = { {"10EC5640", "bytcr_rt5640", "intel/fw_sst_0f28.bin", "bytcr_rt5640", NULL, &byt_rvp_platform_data }, @@ -343,7 +385,7 @@ static struct sst_acpi_mach sst_acpi_chv[] = { {"193C9890", "cht-bsw-max98090", "intel/fw_sst_22a8.bin", "cht-bsw", NULL, &chv_platform_data }, /* some CHT-T platforms rely on RT5640, use Baytrail machine driver */ - {"10EC5640", "bytcr_rt5640", "intel/fw_sst_22a8.bin", "bytcr_rt5640", NULL, + {"10EC5640", "bytcr_rt5640", "intel/fw_sst_22a8.bin", "bytcr_rt5640", cht_quirk, &chv_platform_data }, {}, diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index a8506774f510..dac03a06bfd8 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -2,6 +2,7 @@ snd-soc-sst-haswell-objs := haswell.o snd-soc-sst-byt-rt5640-mach-objs := byt-rt5640.o snd-soc-sst-byt-max98090-mach-objs := byt-max98090.o snd-soc-sst-broadwell-objs := broadwell.o +snd-soc-sst-bxt-da7219_max98357a-objs := bxt_da7219_max98357a.o snd-soc-sst-bxt-rt298-objs := bxt_rt298.o snd-soc-sst-bytcr-rt5640-objs := bytcr_rt5640.o snd-soc-sst-bytcr-rt5651-objs := bytcr_rt5651.o @@ -15,6 +16,7 @@ snd-soc-skl_nau88l25_ssm4567-objs := skl_nau88l25_ssm4567.o obj-$(CONFIG_SND_SOC_INTEL_HASWELL_MACH) += snd-soc-sst-haswell.o obj-$(CONFIG_SND_SOC_INTEL_BYT_RT5640_MACH) += snd-soc-sst-byt-rt5640-mach.o obj-$(CONFIG_SND_SOC_INTEL_BYT_MAX98090_MACH) += snd-soc-sst-byt-max98090-mach.o +obj-$(CONFIG_SND_SOC_INTEL_BXT_DA7219_MAX98357A_MACH) += snd-soc-sst-bxt-da7219_max98357a.o obj-$(CONFIG_SND_SOC_INTEL_BXT_RT298_MACH) += snd-soc-sst-bxt-rt298.o obj-$(CONFIG_SND_SOC_INTEL_BROADWELL_MACH) += snd-soc-sst-broadwell.o obj-$(CONFIG_SND_SOC_INTEL_BYTCR_RT5640_MACH) += snd-soc-sst-bytcr-rt5640.o diff --git a/sound/soc/intel/boards/bxt_da7219_max98357a.c b/sound/soc/intel/boards/bxt_da7219_max98357a.c new file mode 100644 index 000000000000..3774b117d365 --- /dev/null +++ b/sound/soc/intel/boards/bxt_da7219_max98357a.c @@ -0,0 +1,460 @@ +/* + * Intel Broxton-P I2S Machine Driver + * + * Copyright (C) 2016, Intel Corporation. All rights reserved. + * + * Modified from: + * Intel Skylake I2S Machine 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. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../../codecs/hdac_hdmi.h" +#include "../../codecs/da7219.h" +#include "../../codecs/da7219-aad.h" + +#define BXT_DIALOG_CODEC_DAI "da7219-hifi" +#define BXT_MAXIM_CODEC_DAI "HiFi" +#define DUAL_CHANNEL 2 + +static struct snd_soc_jack broxton_headset; + +enum { + BXT_DPCM_AUDIO_PB = 0, + BXT_DPCM_AUDIO_CP, + BXT_DPCM_AUDIO_REF_CP, + BXT_DPCM_AUDIO_HDMI1_PB, + BXT_DPCM_AUDIO_HDMI2_PB, + BXT_DPCM_AUDIO_HDMI3_PB, +}; + +static const struct snd_kcontrol_new broxton_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget broxton_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Spk", NULL), + SND_SOC_DAPM_MIC("SoC DMIC", NULL), + SND_SOC_DAPM_SPK("HDMI1", NULL), + SND_SOC_DAPM_SPK("HDMI2", NULL), + SND_SOC_DAPM_SPK("HDMI3", NULL), +}; + +static const struct snd_soc_dapm_route broxton_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + /* speaker */ + {"Spk", NULL, "Speaker"}, + + /* other jacks */ + {"MIC", NULL, "Headset Mic"}, + + /* digital mics */ + {"DMic", NULL, "SoC DMIC"}, + + /* CODEC BE connections */ + {"HiFi Playback", NULL, "ssp5 Tx"}, + {"ssp5 Tx", NULL, "codec0_out"}, + + {"Playback", NULL, "ssp1 Tx"}, + {"ssp1 Tx", NULL, "codec1_out"}, + + {"codec0_in", NULL, "ssp1 Rx"}, + {"ssp1 Rx", NULL, "Capture"}, + + {"HDMI1", NULL, "hif5 Output"}, + {"HDMI2", NULL, "hif6 Output"}, + {"HDMI3", NULL, "hif7 Output"}, + + {"hifi3", NULL, "iDisp3 Tx"}, + {"iDisp3 Tx", NULL, "iDisp3_out"}, + {"hifi2", NULL, "iDisp2 Tx"}, + {"iDisp2 Tx", NULL, "iDisp2_out"}, + {"hifi1", NULL, "iDisp1 Tx"}, + {"iDisp1 Tx", NULL, "iDisp1_out"}, + + /* DMIC */ + {"dmic01_hifi", NULL, "DMIC01 Rx"}, + {"DMIC01 Rx", NULL, "DMIC AIF"}, +}; + +static int broxton_ssp_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = DUAL_CHANNEL; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int broxton_da7219_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_soc_codec *codec = rtd->codec; + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, &broxton_headset, + NULL, 0); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + da7219_aad_jack_det(codec, &broxton_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + + return ret; +} + +static int broxton_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->codec_dai; + + return hdac_hdmi_jack_init(dai, BXT_DPCM_AUDIO_HDMI1_PB + dai->id); +} + +static int broxton_da7219_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + +static unsigned int rates[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static unsigned int channels[] = { + DUAL_CHANNEL, +}; + +static struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int bxt_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * On this platform for PCM device we support, + * 48Khz + * stereo + * 16 bit audio + */ + + runtime->hw.channels_max = DUAL_CHANNEL; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops broxton_da7219_fe_ops = { + .startup = bxt_fe_startup, +}; + +static int broxton_da7219_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, + DA7219_CLKSRC_MCLK, 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "can't set codec sysclk configuration\n"); + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_PLL_SRM, 0, DA7219_PLL_FREQ_OUT_98304); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to start PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static int broxton_da7219_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, 0, + DA7219_SYSCLK_MCLK, 0, 0); + if (ret < 0) { + dev_err(codec_dai->dev, "failed to stop PLL: %d\n", ret); + return -EIO; + } + + return ret; +} + +static struct snd_soc_ops broxton_da7219_ops = { + .hw_params = broxton_da7219_hw_params, + .hw_free = broxton_da7219_hw_free, +}; + +/* broxton digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link broxton_dais[] = { + /* Front End DAI links */ + [BXT_DPCM_AUDIO_PB] + { + .name = "Bxt Audio Port", + .stream_name = "Audio", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .init = broxton_da7219_fe_init, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_playback = 1, + .ops = &broxton_da7219_fe_ops, + }, + [BXT_DPCM_AUDIO_CP] + { + .name = "Bxt Audio Capture Port", + .stream_name = "Audio Record", + .cpu_dai_name = "System Pin", + .platform_name = "0000:00:0e.0", + .dynamic = 1, + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .nonatomic = 1, + .trigger = { + SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dpcm_capture = 1, + .ops = &broxton_da7219_fe_ops, + }, + [BXT_DPCM_AUDIO_REF_CP] + { + .name = "Bxt Audio Reference cap", + .stream_name = "Refcap", + .cpu_dai_name = "Reference Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .ignore_suspend = 1, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI1_PB] + { + .name = "Bxt HDMI Port1", + .stream_name = "Hdmi1", + .cpu_dai_name = "HDMI1 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI2_PB] + { + .name = "Bxt HDMI Port2", + .stream_name = "Hdmi2", + .cpu_dai_name = "HDMI2 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + [BXT_DPCM_AUDIO_HDMI3_PB] + { + .name = "Bxt HDMI Port3", + .stream_name = "Hdmi3", + .cpu_dai_name = "HDMI3 Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .dpcm_playback = 1, + .init = NULL, + .nonatomic = 1, + .dynamic = 1, + }, + /* Back End DAI links */ + { + /* SSP5 - Codec */ + .name = "SSP5-Codec", + .id = 0, + .cpu_dai_name = "SSP5 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "MX98357A:00", + .codec_dai_name = BXT_MAXIM_CODEC_DAI, + .dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broxton_ssp_fixup, + .dpcm_playback = 1, + }, + { + /* SSP1 - Codec */ + .name = "SSP1-Codec", + .id = 1, + .cpu_dai_name = "SSP1 Pin", + .platform_name = "0000:00:0e.0", + .no_pcm = 1, + .codec_name = "i2c-DLGS7219:00", + .codec_dai_name = BXT_DIALOG_CODEC_DAI, + .init = broxton_da7219_codec_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ignore_pmdown_time = 1, + .be_hw_params_fixup = broxton_ssp_fixup, + .ops = &broxton_da7219_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + { + .name = "dmic01", + .id = 2, + .cpu_dai_name = "DMIC01 Pin", + .codec_name = "dmic-codec", + .codec_dai_name = "dmic-hifi", + .platform_name = "0000:00:0e.0", + .ignore_suspend = 1, + .dpcm_capture = 1, + .no_pcm = 1, + }, + { + .name = "iDisp1", + .id = 3, + .cpu_dai_name = "iDisp1 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi1", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp2", + .id = 4, + .cpu_dai_name = "iDisp2 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi2", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, + { + .name = "iDisp3", + .id = 5, + .cpu_dai_name = "iDisp3 Pin", + .codec_name = "ehdaudio0D2", + .codec_dai_name = "intel-hdmi-hifi3", + .platform_name = "0000:00:0e.0", + .init = broxton_hdmi_init, + .dpcm_playback = 1, + .no_pcm = 1, + }, +}; + +/* broxton audio machine driver for SPT + da7219 */ +static struct snd_soc_card broxton_audio_card = { + .name = "bxtda7219max", + .owner = THIS_MODULE, + .dai_link = broxton_dais, + .num_links = ARRAY_SIZE(broxton_dais), + .controls = broxton_controls, + .num_controls = ARRAY_SIZE(broxton_controls), + .dapm_widgets = broxton_widgets, + .num_dapm_widgets = ARRAY_SIZE(broxton_widgets), + .dapm_routes = broxton_map, + .num_dapm_routes = ARRAY_SIZE(broxton_map), + .fully_routed = true, +}; + +static int broxton_audio_probe(struct platform_device *pdev) +{ + broxton_audio_card.dev = &pdev->dev; + return devm_snd_soc_register_card(&pdev->dev, &broxton_audio_card); +} + +static struct platform_driver broxton_audio = { + .probe = broxton_audio_probe, + .driver = { + .name = "bxt_da7219_max98357a_i2s", + .pm = &snd_soc_pm_ops, + }, +}; +module_platform_driver(broxton_audio) + +/* Module information */ +MODULE_DESCRIPTION("Audio Machine driver-DA7219 & MAX98357A in I2S mode"); +MODULE_AUTHOR("Sathyanarayana Nujella <sathyanarayana.nujella@intel.com>"); +MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com>"); +MODULE_AUTHOR("Harsha Priya <harshapriya.n@intel.com>"); +MODULE_AUTHOR("Conrad Cooke <conrad.cooke@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:bxt_da7219_max98357a_i2s"); diff --git a/sound/soc/intel/boards/bxt_rt298.c b/sound/soc/intel/boards/bxt_rt298.c index f4787515c0ed..253d7bfbf511 100644 --- a/sound/soc/intel/boards/bxt_rt298.c +++ b/sound/soc/intel/boards/bxt_rt298.c @@ -33,6 +33,7 @@ enum { BXT_DPCM_AUDIO_PB = 0, BXT_DPCM_AUDIO_CP, BXT_DPCM_AUDIO_REF_CP, + BXT_DPCM_AUDIO_DMIC_CP, BXT_DPCM_AUDIO_HDMI1_PB, BXT_DPCM_AUDIO_HDMI2_PB, BXT_DPCM_AUDIO_HDMI3_PB, @@ -88,6 +89,7 @@ static const struct snd_soc_dapm_route broxton_rt298_map[] = { /* CODEC BE connections */ { "AIF1 Playback", NULL, "ssp5 Tx"}, { "ssp5 Tx", NULL, "codec0_out"}, + { "ssp5 Tx", NULL, "codec1_out"}, { "codec0_in", NULL, "ssp5 Rx" }, { "ssp5 Rx", NULL, "AIF1 Capture" }, @@ -104,6 +106,17 @@ static const struct snd_soc_dapm_route broxton_rt298_map[] = { }; +static int broxton_rt298_fe_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm; + struct snd_soc_component *component = rtd->cpu_dai->component; + + dapm = snd_soc_component_get_dapm(component); + snd_soc_dapm_ignore_suspend(dapm, "Reference Capture"); + + return 0; +} + static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; @@ -118,6 +131,9 @@ static int broxton_rt298_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; rt298_mic_detect(codec, &broxton_headset); + + snd_soc_dapm_ignore_suspend(&rtd->card->dapm, "SoC DMIC"); + return 0; } @@ -169,6 +185,89 @@ static struct snd_soc_ops broxton_rt298_ops = { .hw_params = broxton_rt298_hw_params, }; +static unsigned int rates[] = { + 48000, +}; + +static struct snd_pcm_hw_constraint_list constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static int broxton_dmic_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + if (params_channels(params) == 2) + channels->min = channels->max = 2; + else + channels->min = channels->max = 4; + + return 0; +} + +static unsigned int channels_dmic[] = { + 2, 4, +}; + +static struct snd_pcm_hw_constraint_list constraints_dmic_channels = { + .count = ARRAY_SIZE(channels_dmic), + .list = channels_dmic, + .mask = 0, +}; + +static int broxton_dmic_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.channels_max = 4; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_dmic_channels); + + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); +} + +static struct snd_soc_ops broxton_dmic_ops = { + .startup = broxton_dmic_startup, +}; + +static unsigned int channels[] = { + 2, +}; + +static struct snd_pcm_hw_constraint_list constraints_channels = { + .count = ARRAY_SIZE(channels), + .list = channels, + .mask = 0, +}; + +static int bxt_fe_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* + * on this platform for PCM device we support: + * 48Khz + * stereo + */ + + runtime->hw.channels_max = 2; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_channels); + + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); + + return 0; +} + +static const struct snd_soc_ops broxton_rt286_fe_ops = { + .startup = bxt_fe_startup, +}; + /* broxton digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link broxton_rt298_dais[] = { /* Front End DAI links */ @@ -182,8 +281,10 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .dynamic = 1, .codec_name = "snd-soc-dummy", .codec_dai_name = "snd-soc-dummy-dai", + .init = broxton_rt298_fe_init, .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_playback = 1, + .ops = &broxton_rt286_fe_ops, }, [BXT_DPCM_AUDIO_CP] { @@ -197,6 +298,7 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .codec_dai_name = "snd-soc-dummy-dai", .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, .dpcm_capture = 1, + .ops = &broxton_rt286_fe_ops, }, [BXT_DPCM_AUDIO_REF_CP] { @@ -211,6 +313,20 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .nonatomic = 1, .dynamic = 1, }, + [BXT_DPCM_AUDIO_DMIC_CP] + { + .name = "Bxt Audio DMIC cap", + .stream_name = "dmiccap", + .cpu_dai_name = "DMIC Pin", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .platform_name = "0000:00:0e.0", + .init = NULL, + .dpcm_capture = 1, + .nonatomic = 1, + .dynamic = 1, + .ops = &broxton_dmic_ops, + }, [BXT_DPCM_AUDIO_HDMI1_PB] { .name = "Bxt HDMI Port1", @@ -276,6 +392,7 @@ static struct snd_soc_dai_link broxton_rt298_dais[] = { .codec_name = "dmic-codec", .codec_dai_name = "dmic-hifi", .platform_name = "0000:00:0e.0", + .be_hw_params_fixup = broxton_dmic_fixup, .ignore_suspend = 1, .dpcm_capture = 1, .no_pcm = 1, @@ -341,6 +458,7 @@ static struct platform_driver broxton_audio = { .probe = broxton_audio_probe, .driver = { .name = "bxt_alc298s_i2s", + .pm = &snd_soc_pm_ops, }, }; module_platform_driver(broxton_audio) diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c index d7ef292c402d..56056ed7fcfd 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5645.c +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -30,6 +30,7 @@ #include <sound/jack.h> #include "../../codecs/rt5645.h" #include "../atom/sst-atom-controls.h" +#include "../common/sst-acpi.h" #define CHT_PLAT_CLK_3_HZ 19200000 #define CHT_CODEC_DAI "rt5645-aif1" @@ -340,10 +341,13 @@ static struct snd_soc_card snd_soc_card_chtrt5650 = { }; static struct cht_acpi_card snd_soc_cards[] = { + {"10EC5640", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, {"10EC5645", CODEC_TYPE_RT5645, &snd_soc_card_chtrt5645}, {"10EC5650", CODEC_TYPE_RT5650, &snd_soc_card_chtrt5650}, }; +static char cht_rt5640_codec_name[16]; /* i2c-<HID>:00 with HID being 8 chars */ + static int snd_cht_mc_probe(struct platform_device *pdev) { int ret_val = 0; @@ -351,6 +355,9 @@ static int snd_cht_mc_probe(struct platform_device *pdev) struct cht_mc_private *drv; struct snd_soc_card *card = snd_soc_cards[0].soc_card; char codec_name[16]; + struct sst_acpi_mach *mach; + const char *i2c_name = NULL; + int dai_index = 0; drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_ATOMIC); if (!drv) @@ -366,12 +373,23 @@ static int snd_cht_mc_probe(struct platform_device *pdev) } } card->dev = &pdev->dev; + mach = card->dev->platform_data; sprintf(codec_name, "i2c-%s:00", drv->acpi_card->codec_id); /* set correct codec name */ for (i = 0; i < ARRAY_SIZE(cht_dailink); i++) - if (!strcmp(card->dai_link[i].codec_name, "i2c-10EC5645:00")) + if (!strcmp(card->dai_link[i].codec_name, "i2c-10EC5645:00")) { card->dai_link[i].codec_name = kstrdup(codec_name, GFP_KERNEL); + dai_index = i; + } + + /* fixup codec name based on HID */ + i2c_name = sst_acpi_find_name_from_hid(mach->id); + if (i2c_name != NULL) { + snprintf(cht_rt5640_codec_name, sizeof(cht_rt5640_codec_name), + "%s%s", "i2c-", i2c_name); + cht_dailink[dai_index].codec_name = cht_rt5640_codec_name; + } snd_soc_card_set_drvdata(card, drv); ret_val = devm_snd_soc_register_card(&pdev->dev, card); diff --git a/sound/soc/intel/boards/skl_nau88l25_max98357a.c b/sound/soc/intel/boards/skl_nau88l25_max98357a.c index d2808652b974..25db5be7fdfa 100644 --- a/sound/soc/intel/boards/skl_nau88l25_max98357a.c +++ b/sound/soc/intel/boards/skl_nau88l25_max98357a.c @@ -23,12 +23,15 @@ #include <sound/soc.h> #include "../../codecs/nau8825.h" #include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" #define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" #define SKL_MAXIM_CODEC_DAI "HiFi" +#define DMIC_CH(p) p->list[p->count-1] static struct snd_soc_jack skylake_headset; static struct snd_soc_card skylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; struct skl_hdmi_pcm { struct list_head head; @@ -339,7 +342,7 @@ static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - if (params_channels(params) == 2) + if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) channels->min = channels->max = 2; else channels->min = channels->max = 4; @@ -357,13 +360,23 @@ static struct snd_pcm_hw_constraint_list constraints_dmic_channels = { .mask = 0, }; +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + static int skylake_dmic_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - runtime->hw.channels_max = 4; + runtime->hw.channels_max = DMIC_CH(dmic_constraints); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - &constraints_dmic_channels); + dmic_constraints); return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); @@ -382,8 +395,22 @@ static struct snd_pcm_hw_constraint_list constraints_16000 = { .list = rates_16000, }; +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + static int skylake_refcap_startup(struct snd_pcm_substream *substream) { + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_16000); @@ -610,6 +637,7 @@ static struct snd_soc_card skylake_audio_card = { static int skylake_audio_probe(struct platform_device *pdev) { struct skl_nau8825_private *ctx; + struct skl_machine_pdata *pdata; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); if (!ctx) @@ -620,15 +648,27 @@ static int skylake_audio_probe(struct platform_device *pdev) skylake_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&skylake_audio_card, ctx); + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card); } +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_n88l25_m98357a" }, + { .name = "kbl_n88l25_m98357a" }, + { } +}; + static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, .driver = { - .name = "skl_nau88l25_max98357a_i2s", + .name = "skl_n88l25_m98357a", .pm = &snd_soc_pm_ops, }, + .id_table = skl_board_ids, }; module_platform_driver(skylake_audio) @@ -637,4 +677,5 @@ module_platform_driver(skylake_audio) MODULE_DESCRIPTION("Audio Machine driver-NAU88L25 & MAX98357A in I2S mode"); MODULE_AUTHOR("Rohit Ainapure <rohit.m.ainapure@intel.com"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:skl_nau88l25_max98357a_i2s"); +MODULE_ALIAS("platform:skl_n88l25_m98357a"); +MODULE_ALIAS("platform:kbl_n88l25_m98357a"); diff --git a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c index e19aa99c4f72..69c5d5da4e86 100644 --- a/sound/soc/intel/boards/skl_nau88l25_ssm4567.c +++ b/sound/soc/intel/boards/skl_nau88l25_ssm4567.c @@ -27,12 +27,15 @@ #include <sound/pcm_params.h> #include "../../codecs/nau8825.h" #include "../../codecs/hdac_hdmi.h" +#include "../skylake/skl.h" #define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" #define SKL_SSM_CODEC_DAI "ssm4567-hifi" +#define DMIC_CH(p) p->list[p->count-1] static struct snd_soc_jack skylake_headset; static struct snd_soc_card skylake_audio_card; +static const struct snd_pcm_hw_constraint_list *dmic_constraints; struct skl_hdmi_pcm { struct list_head head; @@ -367,7 +370,7 @@ static int skylake_dmic_fixup(struct snd_soc_pcm_runtime *rtd, { struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); - if (params_channels(params) == 2) + if (params_channels(params) == 2 || DMIC_CH(dmic_constraints) == 2) channels->min = channels->max = 2; else channels->min = channels->max = 4; @@ -405,13 +408,23 @@ static struct snd_pcm_hw_constraint_list constraints_dmic_channels = { .mask = 0, }; +static const unsigned int dmic_2ch[] = { + 2, +}; + +static const struct snd_pcm_hw_constraint_list constraints_dmic_2ch = { + .count = ARRAY_SIZE(dmic_2ch), + .list = dmic_2ch, + .mask = 0, +}; + static int skylake_dmic_startup(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - runtime->hw.channels_max = 4; + runtime->hw.channels_max = DMIC_CH(dmic_constraints); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, - &constraints_dmic_channels); + dmic_constraints); return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_rates); @@ -430,8 +443,22 @@ static struct snd_pcm_hw_constraint_list constraints_16000 = { .list = rates_16000, }; +static const unsigned int ch_mono[] = { + 1, +}; + +static const struct snd_pcm_hw_constraint_list constraints_refcap = { + .count = ARRAY_SIZE(ch_mono), + .list = ch_mono, +}; + static int skylake_refcap_startup(struct snd_pcm_substream *substream) { + substream->runtime->hw.channels_max = 1; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + &constraints_refcap); + return snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &constraints_16000); @@ -662,6 +689,7 @@ static struct snd_soc_card skylake_audio_card = { static int skylake_audio_probe(struct platform_device *pdev) { struct skl_nau88125_private *ctx; + struct skl_machine_pdata *pdata; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); if (!ctx) @@ -672,15 +700,27 @@ static int skylake_audio_probe(struct platform_device *pdev) skylake_audio_card.dev = &pdev->dev; snd_soc_card_set_drvdata(&skylake_audio_card, ctx); + pdata = dev_get_drvdata(&pdev->dev); + if (pdata) + dmic_constraints = pdata->dmic_num == 2 ? + &constraints_dmic_2ch : &constraints_dmic_channels; + return devm_snd_soc_register_card(&pdev->dev, &skylake_audio_card); } +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_n88l25_s4567" }, + { .name = "kbl_n88l25_s4567" }, + { } +}; + static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, .driver = { - .name = "skl_nau88l25_ssm4567_i2s", + .name = "skl_n88l25_s4567", .pm = &snd_soc_pm_ops, }, + .id_table = skl_board_ids, }; module_platform_driver(skylake_audio) @@ -693,4 +733,5 @@ MODULE_AUTHOR("Sathya Prakash M R <sathya.prakash.m.r@intel.com>"); MODULE_AUTHOR("Yong Zhi <yong.zhi@intel.com>"); MODULE_DESCRIPTION("Intel Audio Machine driver for SKL with NAU88L25 and SSM4567 in I2S Mode"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:skl_nau88l25_ssm4567_i2s"); +MODULE_ALIAS("platform:skl_n88l25_s4567"); +MODULE_ALIAS("platform:kbl_n88l25_s4567"); diff --git a/sound/soc/intel/boards/skl_rt286.c b/sound/soc/intel/boards/skl_rt286.c index 426b48233fdb..88c61e8cb87f 100644 --- a/sound/soc/intel/boards/skl_rt286.c +++ b/sound/soc/intel/boards/skl_rt286.c @@ -505,12 +505,20 @@ static int skylake_audio_probe(struct platform_device *pdev) return devm_snd_soc_register_card(&pdev->dev, &skylake_rt286); } +static const struct platform_device_id skl_board_ids[] = { + { .name = "skl_alc286s_i2s" }, + { .name = "kbl_alc286s_i2s" }, + { } +}; + static struct platform_driver skylake_audio = { .probe = skylake_audio_probe, .driver = { .name = "skl_alc286s_i2s", .pm = &snd_soc_pm_ops, }, + .id_table = skl_board_ids, + }; module_platform_driver(skylake_audio) @@ -520,3 +528,4 @@ MODULE_AUTHOR("Omair Mohammed Abdullah <omair.m.abdullah@intel.com>"); MODULE_DESCRIPTION("Intel SST Audio for Skylake"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:skl_alc286s_i2s"); +MODULE_ALIAS("platform:kbl_alc286s_i2s"); diff --git a/sound/soc/intel/common/Makefile b/sound/soc/intel/common/Makefile index fbbb25c2ceed..1a35149bcad7 100644 --- a/sound/soc/intel/common/Makefile +++ b/sound/soc/intel/common/Makefile @@ -2,9 +2,9 @@ snd-soc-sst-dsp-objs := sst-dsp.o snd-soc-sst-acpi-objs := sst-acpi.o snd-soc-sst-match-objs := sst-match-acpi.o snd-soc-sst-ipc-objs := sst-ipc.o - -snd-soc-sst-dsp-$(CONFIG_DW_DMAC_CORE) += sst-firmware.o +snd-soc-sst-firmware-objs := sst-firmware.o obj-$(CONFIG_SND_SOC_INTEL_SST) += snd-soc-sst-dsp.o snd-soc-sst-ipc.o obj-$(CONFIG_SND_SOC_INTEL_SST_ACPI) += snd-soc-sst-acpi.o obj-$(CONFIG_SND_SOC_INTEL_SST_MATCH) += snd-soc-sst-match.o +obj-$(CONFIG_SND_SOC_INTEL_SST_FIRMWARE) += snd-soc-sst-firmware.o diff --git a/sound/soc/intel/common/sst-acpi.h b/sound/soc/intel/common/sst-acpi.h index 8398cb227ba9..5d2949324d0e 100644 --- a/sound/soc/intel/common/sst-acpi.h +++ b/sound/soc/intel/common/sst-acpi.h @@ -20,7 +20,7 @@ #if IS_ENABLED(CONFIG_ACPI) const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]); #else -inline const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]) +static inline const char *sst_acpi_find_name_from_hid(const u8 hid[ACPI_ID_LEN]) { return NULL; } @@ -40,6 +40,6 @@ struct sst_acpi_mach { /* board name */ const char *board; - void (*machine_quirk)(void); + struct sst_acpi_mach * (*machine_quirk)(void *arg); void *pdata; }; diff --git a/sound/soc/intel/common/sst-dsp-priv.h b/sound/soc/intel/common/sst-dsp-priv.h index 97dc1ae05e69..d13c84364c3c 100644 --- a/sound/soc/intel/common/sst-dsp-priv.h +++ b/sound/soc/intel/common/sst-dsp-priv.h @@ -383,10 +383,6 @@ struct sst_mem_block *sst_mem_block_register(struct sst_dsp *dsp, u32 offset, u32 index, void *private); void sst_mem_block_unregister_all(struct sst_dsp *dsp); -/* Create/Free DMA resources */ -int sst_dma_new(struct sst_dsp *sst); -void sst_dma_free(struct sst_dma *dma); - u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, enum sst_mem_type type); #endif diff --git a/sound/soc/intel/common/sst-dsp.c b/sound/soc/intel/common/sst-dsp.c index b5bbdf4fe93a..c00ede4ea4d7 100644 --- a/sound/soc/intel/common/sst-dsp.c +++ b/sound/soc/intel/common/sst-dsp.c @@ -285,7 +285,7 @@ int sst_dsp_register_poll(struct sst_dsp *ctx, u32 offset, u32 mask, } reg = sst_dsp_shim_read_unlocked(ctx, offset); - dev_info(ctx->dev, "FW Poll Status: reg=%#x %s %s\n", reg, operation, + dev_dbg(ctx->dev, "FW Poll Status: reg=%#x %s %s\n", reg, operation, (time < timeout) ? "successful" : "timedout"); ret = time < timeout ? 0 : -ETIME; @@ -420,73 +420,6 @@ void sst_dsp_inbox_read(struct sst_dsp *sst, void *message, size_t bytes) } EXPORT_SYMBOL_GPL(sst_dsp_inbox_read); -#ifdef CONFIG_DW_DMAC_CORE -struct sst_dsp *sst_dsp_new(struct device *dev, - struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) -{ - struct sst_dsp *sst; - int err; - - dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); - - sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); - if (sst == NULL) - return NULL; - - spin_lock_init(&sst->spinlock); - mutex_init(&sst->mutex); - sst->dev = dev; - sst->dma_dev = pdata->dma_dev; - sst->thread_context = sst_dev->thread_context; - sst->sst_dev = sst_dev; - sst->id = pdata->id; - sst->irq = pdata->irq; - sst->ops = sst_dev->ops; - sst->pdata = pdata; - INIT_LIST_HEAD(&sst->used_block_list); - INIT_LIST_HEAD(&sst->free_block_list); - INIT_LIST_HEAD(&sst->module_list); - INIT_LIST_HEAD(&sst->fw_list); - INIT_LIST_HEAD(&sst->scratch_block_list); - - /* Initialise SST Audio DSP */ - if (sst->ops->init) { - err = sst->ops->init(sst, pdata); - if (err < 0) - return NULL; - } - - /* Register the ISR */ - err = request_threaded_irq(sst->irq, sst->ops->irq_handler, - sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); - if (err) - goto irq_err; - - err = sst_dma_new(sst); - if (err) - dev_warn(dev, "sst_dma_new failed %d\n", err); - - return sst; - -irq_err: - if (sst->ops->free) - sst->ops->free(sst); - - return NULL; -} -EXPORT_SYMBOL_GPL(sst_dsp_new); - -void sst_dsp_free(struct sst_dsp *sst) -{ - free_irq(sst->irq, sst); - if (sst->ops->free) - sst->ops->free(sst); - - sst_dma_free(sst->dma); -} -EXPORT_SYMBOL_GPL(sst_dsp_free); -#endif - /* Module information */ MODULE_AUTHOR("Liam Girdwood"); MODULE_DESCRIPTION("Intel SST Core"); diff --git a/sound/soc/intel/common/sst-dsp.h b/sound/soc/intel/common/sst-dsp.h index 0b84c719ec48..859f0de00339 100644 --- a/sound/soc/intel/common/sst-dsp.h +++ b/sound/soc/intel/common/sst-dsp.h @@ -216,7 +216,7 @@ struct sst_pdata { void *dsp; }; -#ifdef CONFIG_DW_DMAC_CORE +#if IS_ENABLED(CONFIG_DW_DMAC_CORE) /* Initialization */ struct sst_dsp *sst_dsp_new(struct device *dev, struct sst_dsp_device *sst_dev, struct sst_pdata *pdata); diff --git a/sound/soc/intel/common/sst-firmware.c b/sound/soc/intel/common/sst-firmware.c index 25993527370b..a086c35f91bb 100644 --- a/sound/soc/intel/common/sst-firmware.c +++ b/sound/soc/intel/common/sst-firmware.c @@ -1211,3 +1211,71 @@ u32 sst_dsp_get_offset(struct sst_dsp *dsp, u32 offset, } } EXPORT_SYMBOL_GPL(sst_dsp_get_offset); + +struct sst_dsp *sst_dsp_new(struct device *dev, + struct sst_dsp_device *sst_dev, struct sst_pdata *pdata) +{ + struct sst_dsp *sst; + int err; + + dev_dbg(dev, "initialising audio DSP id 0x%x\n", pdata->id); + + sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL); + if (sst == NULL) + return NULL; + + spin_lock_init(&sst->spinlock); + mutex_init(&sst->mutex); + sst->dev = dev; + sst->dma_dev = pdata->dma_dev; + sst->thread_context = sst_dev->thread_context; + sst->sst_dev = sst_dev; + sst->id = pdata->id; + sst->irq = pdata->irq; + sst->ops = sst_dev->ops; + sst->pdata = pdata; + INIT_LIST_HEAD(&sst->used_block_list); + INIT_LIST_HEAD(&sst->free_block_list); + INIT_LIST_HEAD(&sst->module_list); + INIT_LIST_HEAD(&sst->fw_list); + INIT_LIST_HEAD(&sst->scratch_block_list); + + /* Initialise SST Audio DSP */ + if (sst->ops->init) { + err = sst->ops->init(sst, pdata); + if (err < 0) + return NULL; + } + + /* Register the ISR */ + err = request_threaded_irq(sst->irq, sst->ops->irq_handler, + sst_dev->thread, IRQF_SHARED, "AudioDSP", sst); + if (err) + goto irq_err; + + err = sst_dma_new(sst); + if (err) + dev_warn(dev, "sst_dma_new failed %d\n", err); + + return sst; + +irq_err: + if (sst->ops->free) + sst->ops->free(sst); + + return NULL; +} +EXPORT_SYMBOL_GPL(sst_dsp_new); + +void sst_dsp_free(struct sst_dsp *sst) +{ + free_irq(sst->irq, sst); + if (sst->ops->free) + sst->ops->free(sst); + + sst_dma_free(sst->dma); +} +EXPORT_SYMBOL_GPL(sst_dsp_free); + +MODULE_DESCRIPTION("Intel SST Firmware Loader"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/intel/haswell/sst-haswell-pcm.c b/sound/soc/intel/haswell/sst-haswell-pcm.c index 994256b39b9c..3154525c2b83 100644 --- a/sound/soc/intel/haswell/sst-haswell-pcm.c +++ b/sound/soc/intel/haswell/sst-haswell-pcm.c @@ -819,7 +819,6 @@ static int hsw_pcm_open(struct snd_pcm_substream *substream) mutex_lock(&pcm_data->mutex); pm_runtime_get_sync(pdata->dev); - snd_soc_pcm_set_drvdata(rtd, pcm_data); pcm_data->substream = substream; snd_soc_set_runtime_hwparams(substream, &hsw_pcm_hardware); diff --git a/sound/soc/intel/skylake/Makefile b/sound/soc/intel/skylake/Makefile index c28f5d0e1d99..60fbc9bbe473 100644 --- a/sound/soc/intel/skylake/Makefile +++ b/sound/soc/intel/skylake/Makefile @@ -5,6 +5,6 @@ obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl.o # Skylake IPC Support snd-soc-skl-ipc-objs := skl-sst-ipc.o skl-sst-dsp.o skl-sst-cldma.o \ - skl-sst.o bxt-sst.o + skl-sst.o bxt-sst.o skl-sst-utils.o obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl-ipc.o diff --git a/sound/soc/intel/skylake/bxt-sst.c b/sound/soc/intel/skylake/bxt-sst.c index 8b95e09e23e8..2663781278aa 100644 --- a/sound/soc/intel/skylake/bxt-sst.c +++ b/sound/soc/intel/skylake/bxt-sst.c @@ -37,11 +37,19 @@ #define BXT_ADSP_SRAM1_BASE 0xA0000 +#define BXT_INSTANCE_ID 0 +#define BXT_BASE_FW_MODULE_ID 0 + static unsigned int bxt_get_errorcode(struct sst_dsp *ctx) { return sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE); } +/* + * First boot sequence has some extra steps. Core 0 waits for power + * status on core 1, so power up core 1 also momentarily, keep it in + * reset/stall and then turn it off + */ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, const void *fwdata, u32 fwsize) { @@ -49,7 +57,7 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, u32 reg; stream_tag = ctx->dsp_ops.prepare(ctx->dev, 0x40, fwsize, &ctx->dmab); - if (stream_tag < 0) { + if (stream_tag <= 0) { dev_err(ctx->dev, "Failed to prepare DMA FW loading err: %x\n", stream_tag); return stream_tag; @@ -58,17 +66,27 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, ctx->dsp_ops.stream_tag = stream_tag; memcpy(ctx->dmab.area, fwdata, fwsize); - /* Purge FW request */ + /* Step 1: Power up core 0 and core1 */ + ret = skl_dsp_core_power_up(ctx, SKL_DSP_CORE0_MASK | + SKL_DSP_CORE_MASK(1)); + if (ret < 0) { + dev_err(ctx->dev, "dsp core0/1 power up failed\n"); + goto base_fw_load_failed; + } + + /* Step 2: Purge FW request */ sst_dsp_shim_write(ctx, SKL_ADSP_REG_HIPCI, SKL_ADSP_REG_HIPCI_BUSY | - BXT_IPC_PURGE_FW | (stream_tag - 1)); + (BXT_IPC_PURGE_FW | ((stream_tag - 1) << 9))); - ret = skl_dsp_enable_core(ctx); + /* Step 3: Unset core0 reset state & unstall/run core0 */ + ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "Boot dsp core failed ret: %d\n", ret); + dev_err(ctx->dev, "Start dsp core failed ret: %d\n", ret); ret = -EIO; goto base_fw_load_failed; } + /* Step 4: Wait for DONE Bit */ for (i = BXT_INIT_TIMEOUT; i > 0; --i) { reg = sst_dsp_shim_read(ctx, SKL_ADSP_REG_HIPCIE); @@ -88,10 +106,18 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, SKL_ADSP_REG_HIPCIE_DONE); } - /* enable Interrupt */ + /* Step 5: power down core1 */ + ret = skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + if (ret < 0) { + dev_err(ctx->dev, "dsp core1 power down failed\n"); + goto base_fw_load_failed; + } + + /* Step 6: Enable Interrupt */ skl_ipc_int_enable(ctx); skl_ipc_op_int_enable(ctx); + /* Step 7: Wait for ROM init */ for (i = BXT_INIT_TIMEOUT; i > 0; --i) { if (SKL_FW_INIT == (sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS) & @@ -112,7 +138,8 @@ static int sst_bxt_prepare_fw(struct sst_dsp *ctx, base_fw_load_failed: ctx->dsp_ops.cleanup(ctx->dev, &ctx->dmab, stream_tag); - skl_dsp_disable_core(ctx); + skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); return ret; } @@ -130,23 +157,41 @@ static int sst_transfer_fw_host_dma(struct sst_dsp *ctx) return ret; } +#define BXT_ADSP_FW_BIN_HDR_OFFSET 0x2000 + static int bxt_load_base_firmware(struct sst_dsp *ctx) { - const struct firmware *fw = NULL; + struct firmware stripped_fw; struct skl_sst *skl = ctx->thread_context; int ret; - ret = request_firmware(&fw, ctx->fw_name, ctx->dev); + ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); if (ret < 0) { dev_err(ctx->dev, "Request firmware failed %d\n", ret); goto sst_load_base_firmware_failed; } - ret = sst_bxt_prepare_fw(ctx, fw->data, fw->size); + /* check for extended manifest */ + if (ctx->fw == NULL) + goto sst_load_base_firmware_failed; + + ret = snd_skl_parse_uuids(ctx, BXT_ADSP_FW_BIN_HDR_OFFSET); + if (ret < 0) + goto sst_load_base_firmware_failed; + + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + skl_dsp_strip_extended_manifest(&stripped_fw); + + ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); /* Retry Enabling core and ROM load. Retry seemed to help */ if (ret < 0) { - ret = sst_bxt_prepare_fw(ctx, fw->data, fw->size); + ret = sst_bxt_prepare_fw(ctx, stripped_fw.data, stripped_fw.size); if (ret < 0) { + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), + sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); + dev_err(ctx->dev, "Core En/ROM load fail:%d\n", ret); goto sst_load_base_firmware_failed; } @@ -159,83 +204,135 @@ static int bxt_load_base_firmware(struct sst_dsp *ctx) sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); - skl_dsp_disable_core(ctx); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); } else { dev_dbg(ctx->dev, "Firmware download successful\n"); ret = wait_event_timeout(skl->boot_wait, skl->boot_complete, msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); if (ret == 0) { dev_err(ctx->dev, "DSP boot fail, FW Ready timeout\n"); - skl_dsp_disable_core(ctx); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); ret = -EIO; } else { - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); ret = 0; + skl->fw_loaded = true; } } sst_load_base_firmware_failed: - release_firmware(fw); + release_firmware(ctx->fw); return ret; } -static int bxt_set_dsp_D0(struct sst_dsp *ctx) +static int bxt_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) { struct skl_sst *skl = ctx->thread_context; int ret; + struct skl_ipc_dxstate_info dx; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - skl->boot_complete = false; - - ret = skl_dsp_enable_core(ctx); - if (ret < 0) { - dev_err(ctx->dev, "enable dsp core failed ret: %d\n", ret); + if (skl->fw_loaded == false) { + skl->boot_complete = false; + ret = bxt_load_base_firmware(ctx); + if (ret < 0) + dev_err(ctx->dev, "reload fw failed: %d\n", ret); return ret; } - /* enable interrupt */ - skl_ipc_int_enable(ctx); - skl_ipc_op_int_enable(ctx); + /* If core 0 is being turned on, turn on core 1 as well */ + if (core_id == SKL_DSP_CORE0_ID) + ret = skl_dsp_core_power_up(ctx, core_mask | + SKL_DSP_CORE_MASK(1)); + else + ret = skl_dsp_core_power_up(ctx, core_mask); + + if (ret < 0) + goto err; + + if (core_id == SKL_DSP_CORE0_ID) { + + /* + * Enable interrupt after SPA is set and before + * DSP is unstalled + */ + skl_ipc_int_enable(ctx); + skl_ipc_op_int_enable(ctx); + skl->boot_complete = false; + } - ret = wait_event_timeout(skl->boot_wait, skl->boot_complete, - msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); - if (ret == 0) { - dev_err(ctx->dev, "ipc: error DSP boot timeout\n"); - dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", - sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), - sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); - return -EIO; + ret = skl_dsp_start_core(ctx, core_mask); + if (ret < 0) + goto err; + + if (core_id == SKL_DSP_CORE0_ID) { + ret = wait_event_timeout(skl->boot_wait, + skl->boot_complete, + msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); + + /* If core 1 was turned on for booting core 0, turn it off */ + skl_dsp_core_power_down(ctx, SKL_DSP_CORE_MASK(1)); + if (ret == 0) { + dev_err(ctx->dev, "%s: DSP boot timeout\n", __func__); + dev_err(ctx->dev, "Error code=0x%x: FW status=0x%x\n", + sst_dsp_shim_read(ctx, BXT_ADSP_ERROR_CODE), + sst_dsp_shim_read(ctx, BXT_ADSP_FW_STATUS)); + dev_err(ctx->dev, "Failed to set core0 to D0 state\n"); + ret = -EIO; + goto err; + } + } + + /* Tell FW if additional core in now On */ + + if (core_id != SKL_DSP_CORE0_ID) { + dx.core_mask = core_mask; + dx.dx_mask = core_mask; + + ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, + BXT_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, "IPC set_dx for core %d fail: %d\n", + core_id, ret); + goto err; + } } - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); + skl->cores.state[core_id] = SKL_DSP_RUNNING; return 0; +err: + if (core_id == SKL_DSP_CORE0_ID) + core_mask |= SKL_DSP_CORE_MASK(1); + skl_dsp_disable_core(ctx, core_mask); + + return ret; } -static int bxt_set_dsp_D3(struct sst_dsp *ctx) +static int bxt_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) { + int ret; struct skl_ipc_dxstate_info dx; struct skl_sst *skl = ctx->thread_context; - int ret = 0; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - if (!is_skl_dsp_running(ctx)) - return ret; - - dx.core_mask = SKL_DSP_CORE0_MASK; + dx.core_mask = core_mask; dx.dx_mask = SKL_IPC_D3_MASK; - ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, - SKL_BASE_FW_MODULE_ID, &dx); - if (ret < 0) { - dev_err(ctx->dev, "Failed to set DSP to D3 state: %d\n", ret); - return ret; - } + dev_dbg(ctx->dev, "core mask=%x dx_mask=%x\n", + dx.core_mask, dx.dx_mask); + + ret = skl_ipc_set_dx(&skl->ipc, BXT_INSTANCE_ID, + BXT_BASE_FW_MODULE_ID, &dx); + if (ret < 0) + dev_err(ctx->dev, + "Failed to set DSP to D3:core id = %d;Continue reset\n", + core_id); - ret = skl_dsp_disable_core(ctx); + ret = skl_dsp_disable_core(ctx, core_mask); if (ret < 0) { - dev_err(ctx->dev, "disbale dsp core failed: %d\n", ret); - ret = -EIO; + dev_err(ctx->dev, "Failed to disable core %d", ret); + return ret; } - - skl_dsp_set_state_locked(ctx, SKL_DSP_RESET); + skl->cores.state[core_id] = SKL_DSP_RESET; return 0; } @@ -274,6 +371,7 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, skl->dev = dev; skl_dev.thread_context = skl; + INIT_LIST_HEAD(&skl->uuid_list); skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); if (!skl->dsp) { @@ -296,6 +394,7 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, if (ret) return ret; + skl->cores.count = 2; skl->boot_complete = false; init_waitqueue_head(&skl->boot_wait); @@ -305,6 +404,8 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, return ret; } + skl_dsp_init_core_state(sst); + if (dsp) *dsp = skl; @@ -315,6 +416,7 @@ EXPORT_SYMBOL_GPL(bxt_sst_dsp_init); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) { + skl_freeup_uuid_list(ctx); skl_ipc_free(&ctx->ipc); ctx->dsp->cl_dev.ops.cl_cleanup_controller(ctx->dsp); diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c index 226db84ba20f..44ab595ce21a 100644 --- a/sound/soc/intel/skylake/skl-messages.c +++ b/sound/soc/intel/skylake/skl-messages.c @@ -206,6 +206,12 @@ static const struct skl_dsp_ops dsp_ops[] = { .cleanup = skl_sst_dsp_cleanup }, { + .id = 0x9d71, + .loader_ops = skl_get_loader_ops, + .init = skl_sst_dsp_init, + .cleanup = skl_sst_dsp_cleanup + }, + { .id = 0x5a98, .loader_ops = bxt_get_loader_ops, .init = bxt_sst_dsp_init, @@ -730,7 +736,7 @@ static int skl_set_module_format(struct skl_sst *ctx, dev_dbg(ctx->dev, "Module type=%d config size: %d bytes\n", module_config->id.module_id, param_size); - print_hex_dump(KERN_DEBUG, "Module params:", DUMP_PREFIX_OFFSET, 8, 4, + print_hex_dump_debug("Module params:", DUMP_PREFIX_OFFSET, 8, 4, *param_data, param_size, false); return 0; } @@ -1046,7 +1052,7 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) dev_dbg(ctx->dev, "%s: pipe = %d\n", __func__, pipe->ppl_id); - /* If pipe is not started, do not try to stop the pipe in FW. */ + /* If pipe is started, do stop the pipe in FW. */ if (pipe->state > SKL_PIPE_STARTED) { ret = skl_set_pipe_state(ctx, pipe, PPL_PAUSED); if (ret < 0) { @@ -1055,18 +1061,20 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) } pipe->state = SKL_PIPE_PAUSED; - } else { - /* If pipe was not created in FW, do not try to delete it */ - if (pipe->state < SKL_PIPE_CREATED) - return 0; + } - ret = skl_ipc_delete_pipeline(&ctx->ipc, pipe->ppl_id); - if (ret < 0) - dev_err(ctx->dev, "Failed to delete pipeline\n"); + /* If pipe was not created in FW, do not try to delete it */ + if (pipe->state < SKL_PIPE_CREATED) + return 0; - pipe->state = SKL_PIPE_INVALID; + ret = skl_ipc_delete_pipeline(&ctx->ipc, pipe->ppl_id); + if (ret < 0) { + dev_err(ctx->dev, "Failed to delete pipeline\n"); + return ret; } + pipe->state = SKL_PIPE_INVALID; + return ret; } @@ -1125,7 +1133,30 @@ int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) return ret; } - pipe->state = SKL_PIPE_CREATED; + pipe->state = SKL_PIPE_PAUSED; + + return 0; +} + +/* + * Reset the pipeline by sending set pipe state IPC this will reset the DMA + * from the DSP side + */ +int skl_reset_pipe(struct skl_sst *ctx, struct skl_pipe *pipe) +{ + int ret; + + /* If pipe was not created in FW, do not try to pause or delete */ + if (pipe->state < SKL_PIPE_PAUSED) + return 0; + + ret = skl_set_pipe_state(ctx, pipe, PPL_RESET); + if (ret < 0) { + dev_dbg(ctx->dev, "Failed to reset pipe ret=%d\n", ret); + return ret; + } + + pipe->state = SKL_PIPE_RESET; return 0; } diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c index 7d73648e5f9a..3f8e6f0b7eb5 100644 --- a/sound/soc/intel/skylake/skl-nhlt.c +++ b/sound/soc/intel/skylake/skl-nhlt.c @@ -17,6 +17,7 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * */ +#include <linux/pci.h> #include "skl.h" /* Unique identification for getting NHLT blobs */ @@ -149,6 +150,45 @@ struct nhlt_specific_cfg return NULL; } +int skl_get_dmic_geo(struct skl *skl) +{ + struct nhlt_acpi_table *nhlt = (struct nhlt_acpi_table *)skl->nhlt; + struct nhlt_endpoint *epnt; + struct nhlt_dmic_array_config *cfg; + struct device *dev = &skl->pci->dev; + unsigned int dmic_geo = 0; + u8 j; + + epnt = (struct nhlt_endpoint *)nhlt->desc; + + for (j = 0; j < nhlt->endpoint_count; j++) { + if (epnt->linktype == NHLT_LINK_DMIC) { + cfg = (struct nhlt_dmic_array_config *) + (epnt->config.caps); + switch (cfg->array_type) { + case NHLT_MIC_ARRAY_2CH_SMALL: + case NHLT_MIC_ARRAY_2CH_BIG: + dmic_geo |= MIC_ARRAY_2CH; + break; + + case NHLT_MIC_ARRAY_4CH_1ST_GEOM: + case NHLT_MIC_ARRAY_4CH_L_SHAPED: + case NHLT_MIC_ARRAY_4CH_2ND_GEOM: + dmic_geo |= MIC_ARRAY_4CH; + break; + + default: + dev_warn(dev, "undefined DMIC array_type 0x%0x\n", + cfg->array_type); + + } + } + epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length); + } + + return dmic_geo; +} + static void skl_nhlt_trim_space(struct skl *skl) { char *s = skl->tplg_name; diff --git a/sound/soc/intel/skylake/skl-nhlt.h b/sound/soc/intel/skylake/skl-nhlt.h index 3769f9fefe2b..116534e7b3c5 100644 --- a/sound/soc/intel/skylake/skl-nhlt.h +++ b/sound/soc/intel/skylake/skl-nhlt.h @@ -103,4 +103,26 @@ struct nhlt_resource_desc { u64 length; } __packed; +#define MIC_ARRAY_2CH 2 +#define MIC_ARRAY_4CH 4 + +struct nhlt_tdm_config { + u8 virtual_slot; + u8 config_type; +} __packed; + +struct nhlt_dmic_array_config { + struct nhlt_tdm_config tdm_config; + u8 array_type; +} __packed; + +enum { + NHLT_MIC_ARRAY_2CH_SMALL = 0xa, + NHLT_MIC_ARRAY_2CH_BIG = 0xb, + NHLT_MIC_ARRAY_4CH_1ST_GEOM = 0xc, + NHLT_MIC_ARRAY_4CH_L_SHAPED = 0xd, + NHLT_MIC_ARRAY_4CH_2ND_GEOM = 0xe, + NHLT_MIC_ARRAY_VENDOR_DEFINED = 0xf, +}; + #endif diff --git a/sound/soc/intel/skylake/skl-pcm.c b/sound/soc/intel/skylake/skl-pcm.c index 7c81b31748ff..6e05bf8622f7 100644 --- a/sound/soc/intel/skylake/skl-pcm.c +++ b/sound/soc/intel/skylake/skl-pcm.c @@ -227,16 +227,25 @@ static int skl_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct hdac_ext_stream *stream = get_hdac_ext_stream(substream); + struct skl *skl = get_skl_ctx(dai->dev); unsigned int format_val; int err; + struct skl_module_cfg *mconfig; dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + mconfig = skl_tplg_fe_get_cpr_module(dai, substream->stream); + format_val = skl_get_format(substream, dai); dev_dbg(dai->dev, "stream_tag=%d formatvalue=%d\n", hdac_stream(stream)->stream_tag, format_val); snd_hdac_stream_reset(hdac_stream(stream)); + /* In case of XRUN recovery, reset the FW pipe to clean state */ + if (mconfig && (substream->runtime->status->state == + SNDRV_PCM_STATE_XRUN)) + skl_reset_pipe(skl->skl_sst, mconfig->pipe); + err = snd_hdac_stream_set_params(hdac_stream(stream), format_val); if (err < 0) return err; @@ -521,6 +530,8 @@ static int skl_link_pcm_prepare(struct snd_pcm_substream *substream, struct skl_dma_params *dma_params; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct hdac_ext_link *link; + struct skl *skl = get_skl_ctx(dai->dev); + struct skl_module_cfg *mconfig = NULL; dma_params = (struct skl_dma_params *) snd_soc_dai_get_dma_data(codec_dai, substream); @@ -535,6 +546,12 @@ static int skl_link_pcm_prepare(struct snd_pcm_substream *substream, snd_hdac_ext_link_stream_reset(link_dev); + /* In case of XRUN recovery, reset the FW pipe to clean state */ + mconfig = skl_tplg_be_get_cpr_module(dai, substream->stream); + if (mconfig && (substream->runtime->status->state == + SNDRV_PCM_STATE_XRUN)) + skl_reset_pipe(skl->skl_sst, mconfig->pipe); + snd_hdac_ext_link_stream_setup(link_dev, format_val); snd_hdac_ext_link_set_stream_id(link, hdac_stream(link_dev)->stream_tag); @@ -1009,51 +1026,11 @@ static int skl_platform_pcm_trigger(struct snd_pcm_substream *substream, return 0; } -/* calculate runtime delay from LPIB */ -static int skl_get_delay_from_lpib(struct hdac_ext_bus *ebus, - struct hdac_ext_stream *sstream, - unsigned int pos) -{ - struct hdac_bus *bus = ebus_to_hbus(ebus); - struct hdac_stream *hstream = hdac_stream(sstream); - struct snd_pcm_substream *substream = hstream->substream; - int stream = substream->stream; - unsigned int lpib_pos = snd_hdac_stream_get_pos_lpib(hstream); - int delay; - - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - delay = pos - lpib_pos; - else - delay = lpib_pos - pos; - - if (delay < 0) { - if (delay >= hstream->delay_negative_threshold) - delay = 0; - else - delay += hstream->bufsize; - } - - if (hstream->bufsize == delay) - delay = 0; - - if (delay >= hstream->period_bytes) { - dev_info(bus->dev, - "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n", - delay, hstream->period_bytes); - delay = 0; - } - - return bytes_to_frames(substream->runtime, delay); -} - -static unsigned int skl_get_position(struct hdac_ext_stream *hstream, - int codec_delay) +static snd_pcm_uframes_t skl_platform_pcm_pointer + (struct snd_pcm_substream *substream) { - struct hdac_stream *hstr = hdac_stream(hstream); - struct snd_pcm_substream *substream = hstr->substream; - struct hdac_ext_bus *ebus; + struct hdac_ext_stream *hstream = get_hdac_ext_stream(substream); unsigned int pos; - int delay; /* use the position buffer as default */ pos = snd_hdac_stream_get_pos_posbuf(hdac_stream(hstream)); @@ -1061,23 +1038,7 @@ static unsigned int skl_get_position(struct hdac_ext_stream *hstream, if (pos >= hdac_stream(hstream)->bufsize) pos = 0; - if (substream->runtime) { - ebus = get_bus_ctx(substream); - delay = skl_get_delay_from_lpib(ebus, hstream, pos) - + codec_delay; - substream->runtime->delay += delay; - } - - return pos; -} - -static snd_pcm_uframes_t skl_platform_pcm_pointer - (struct snd_pcm_substream *substream) -{ - struct hdac_ext_stream *hstream = get_hdac_ext_stream(substream); - - return bytes_to_frames(substream->runtime, - skl_get_position(hstream, 0)); + return bytes_to_frames(substream->runtime, pos); } static u64 skl_adjust_codec_delay(struct snd_pcm_substream *substream, @@ -1180,9 +1141,17 @@ static int skl_pcm_new(struct snd_soc_pcm_runtime *rtd) static int skl_platform_soc_probe(struct snd_soc_platform *platform) { struct hdac_ext_bus *ebus = dev_get_drvdata(platform->dev); + struct skl *skl = ebus_to_skl(ebus); + int ret; - if (ebus->ppcap) - return skl_tplg_init(platform, ebus); + if (ebus->ppcap) { + ret = skl_tplg_init(platform, ebus); + if (ret < 0) { + dev_err(platform->dev, "Failed to init topology!\n"); + return ret; + } + skl->platform = platform; + } return 0; } diff --git a/sound/soc/intel/skylake/skl-sst-dsp.c b/sound/soc/intel/skylake/skl-sst-dsp.c index 13c19855ee1a..c3deefab65d6 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.c +++ b/sound/soc/intel/skylake/skl-sst-dsp.c @@ -34,33 +34,84 @@ void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state) mutex_unlock(&ctx->mutex); } -static int skl_dsp_core_set_reset_state(struct sst_dsp *ctx) +/* + * Initialize core power state and usage count. To be called after + * successful first boot. Hence core 0 will be running and other cores + * will be reset + */ +void skl_dsp_init_core_state(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + int i; + + skl->cores.state[SKL_DSP_CORE0_ID] = SKL_DSP_RUNNING; + skl->cores.usage_count[SKL_DSP_CORE0_ID] = 1; + + for (i = SKL_DSP_CORE0_ID + 1; i < SKL_DSP_CORES_MAX; i++) { + skl->cores.state[i] = SKL_DSP_RESET; + skl->cores.usage_count[i] = 0; + } +} + +/* Get the mask for all enabled cores */ +unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx) +{ + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask, en_cores_mask; + u32 val; + + core_mask = SKL_DSP_CORES_MASK(skl->cores.count); + + val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS); + + /* Cores having CPA bit set */ + en_cores_mask = (val & SKL_ADSPCS_CPA_MASK(core_mask)) >> + SKL_ADSPCS_CPA_SHIFT; + + /* And cores having CRST bit cleared */ + en_cores_mask &= (~val & SKL_ADSPCS_CRST_MASK(core_mask)) >> + SKL_ADSPCS_CRST_SHIFT; + + /* And cores having CSTALL bit cleared */ + en_cores_mask &= (~val & SKL_ADSPCS_CSTALL_MASK(core_mask)) >> + SKL_ADSPCS_CSTALL_SHIFT; + en_cores_mask &= core_mask; + + dev_dbg(ctx->dev, "DSP enabled cores mask = %x\n", en_cores_mask); + + return en_cores_mask; +} + +static int +skl_dsp_core_set_reset_state(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, - SKL_ADSP_REG_ADSPCS, SKL_ADSPCS_CRST_MASK, - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)); + SKL_ADSP_REG_ADSPCS, SKL_ADSPCS_CRST_MASK(core_mask), + SKL_ADSPCS_CRST_MASK(core_mask)); /* poll with timeout to check if operation successful */ ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CRST_MASK, - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK), + SKL_ADSPCS_CRST_MASK(core_mask), + SKL_ADSPCS_CRST_MASK(core_mask), SKL_DSP_RESET_TO, "Set reset"); if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) != - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) { - dev_err(ctx->dev, "Set reset state failed\n"); + SKL_ADSPCS_CRST_MASK(core_mask)) != + SKL_ADSPCS_CRST_MASK(core_mask)) { + dev_err(ctx->dev, "Set reset state failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx) +int skl_dsp_core_unset_reset_state( + struct sst_dsp *ctx, unsigned int core_mask) { int ret; @@ -68,152 +119,160 @@ static int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx) /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CRST_MASK, 0); + SKL_ADSPCS_CRST_MASK(core_mask), 0); /* poll with timeout to check if operation successful */ ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CRST_MASK, + SKL_ADSPCS_CRST_MASK(core_mask), 0, SKL_DSP_RESET_TO, "Unset reset"); if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) != 0) { - dev_err(ctx->dev, "Unset reset state failed\n"); + SKL_ADSPCS_CRST_MASK(core_mask)) != 0) { + dev_err(ctx->dev, "Unset reset state failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static bool is_skl_dsp_core_enable(struct sst_dsp *ctx) +static bool +is_skl_dsp_core_enable(struct sst_dsp *ctx, unsigned int core_mask) { int val; bool is_enable; val = sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS); - is_enable = ((val & SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK)) && - (val & SKL_ADSPCS_SPA(SKL_DSP_CORES_MASK)) && - !(val & SKL_ADSPCS_CRST(SKL_DSP_CORES_MASK)) && - !(val & SKL_ADSPCS_CSTALL(SKL_DSP_CORES_MASK))); + is_enable = ((val & SKL_ADSPCS_CPA_MASK(core_mask)) && + (val & SKL_ADSPCS_SPA_MASK(core_mask)) && + !(val & SKL_ADSPCS_CRST_MASK(core_mask)) && + !(val & SKL_ADSPCS_CSTALL_MASK(core_mask))); + + dev_dbg(ctx->dev, "DSP core(s) enabled? %d : core_mask %x\n", + is_enable, core_mask); - dev_dbg(ctx->dev, "DSP core is enabled=%d\n", is_enable); return is_enable; } -static int skl_dsp_reset_core(struct sst_dsp *ctx) +static int skl_dsp_reset_core(struct sst_dsp *ctx, unsigned int core_mask) { /* stall core */ - sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CSTALL(SKL_DSP_CORES_MASK)); + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CSTALL_MASK(core_mask), + SKL_ADSPCS_CSTALL_MASK(core_mask)); /* set reset state */ - return skl_dsp_core_set_reset_state(ctx); + return skl_dsp_core_set_reset_state(ctx, core_mask); } -static int skl_dsp_start_core(struct sst_dsp *ctx) +int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* unset reset state */ - ret = skl_dsp_core_unset_reset_state(ctx); - if (ret < 0) { - dev_dbg(ctx->dev, "dsp unset reset fails\n"); + ret = skl_dsp_core_unset_reset_state(ctx, core_mask); + if (ret < 0) return ret; - } /* run core */ - dev_dbg(ctx->dev, "run core...\n"); - sst_dsp_shim_write_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - ~SKL_ADSPCS_CSTALL(SKL_DSP_CORES_MASK)); - - if (!is_skl_dsp_core_enable(ctx)) { - skl_dsp_reset_core(ctx); - dev_err(ctx->dev, "DSP core enable failed\n"); + dev_dbg(ctx->dev, "unstall/run core: core_mask = %x\n", core_mask); + sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, + SKL_ADSPCS_CSTALL_MASK(core_mask), 0); + + if (!is_skl_dsp_core_enable(ctx, core_mask)) { + skl_dsp_reset_core(ctx, core_mask); + dev_err(ctx->dev, "DSP start core failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static int skl_dsp_core_power_up(struct sst_dsp *ctx) +int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_SPA_MASK, SKL_ADSPCS_SPA(SKL_DSP_CORES_MASK)); + SKL_ADSPCS_SPA_MASK(core_mask), + SKL_ADSPCS_SPA_MASK(core_mask)); /* poll with timeout to check if operation successful */ ret = sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CPA_MASK, - SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK), + SKL_ADSPCS_CPA_MASK(core_mask), + SKL_ADSPCS_CPA_MASK(core_mask), SKL_DSP_PU_TO, "Power up"); if ((sst_dsp_shim_read_unlocked(ctx, SKL_ADSP_REG_ADSPCS) & - SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK)) != - SKL_ADSPCS_CPA(SKL_DSP_CORES_MASK)) { - dev_err(ctx->dev, "DSP core power up failed\n"); + SKL_ADSPCS_CPA_MASK(core_mask)) != + SKL_ADSPCS_CPA_MASK(core_mask)) { + dev_err(ctx->dev, "DSP core power up failed: core_mask %x\n", + core_mask); ret = -EIO; } return ret; } -static int skl_dsp_core_power_down(struct sst_dsp *ctx) +int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask) { /* update bits */ sst_dsp_shim_update_bits_unlocked(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_SPA_MASK, 0); + SKL_ADSPCS_SPA_MASK(core_mask), 0); /* poll with timeout to check if operation successful */ return sst_dsp_register_poll(ctx, SKL_ADSP_REG_ADSPCS, - SKL_ADSPCS_CPA_MASK, + SKL_ADSPCS_CPA_MASK(core_mask), 0, SKL_DSP_PD_TO, "Power down"); } -int skl_dsp_enable_core(struct sst_dsp *ctx) +int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask) { int ret; /* power up */ - ret = skl_dsp_core_power_up(ctx); + ret = skl_dsp_core_power_up(ctx, core_mask); if (ret < 0) { - dev_dbg(ctx->dev, "dsp core power up failed\n"); + dev_err(ctx->dev, "dsp core power up failed: core_mask %x\n", + core_mask); return ret; } - return skl_dsp_start_core(ctx); + return skl_dsp_start_core(ctx, core_mask); } -int skl_dsp_disable_core(struct sst_dsp *ctx) +int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask) { int ret; - ret = skl_dsp_reset_core(ctx); + ret = skl_dsp_reset_core(ctx, core_mask); if (ret < 0) { - dev_err(ctx->dev, "dsp core reset failed\n"); + dev_err(ctx->dev, "dsp core reset failed: core_mask %x\n", + core_mask); return ret; } /* power down core*/ - ret = skl_dsp_core_power_down(ctx); + ret = skl_dsp_core_power_down(ctx, core_mask); if (ret < 0) { - dev_err(ctx->dev, "dsp core power down failed\n"); + dev_err(ctx->dev, "dsp core power down fail mask %x: %d\n", + core_mask, ret); return ret; } - if (is_skl_dsp_core_enable(ctx)) { - dev_err(ctx->dev, "DSP core disable failed\n"); + if (is_skl_dsp_core_enable(ctx, core_mask)) { + dev_err(ctx->dev, "dsp core disable fail mask %x: %d\n", + core_mask, ret); ret = -EIO; } @@ -224,28 +283,25 @@ int skl_dsp_boot(struct sst_dsp *ctx) { int ret; - if (is_skl_dsp_core_enable(ctx)) { - dev_dbg(ctx->dev, "dsp core is already enabled, so reset the dap core\n"); - ret = skl_dsp_reset_core(ctx); + if (is_skl_dsp_core_enable(ctx, SKL_DSP_CORE0_MASK)) { + ret = skl_dsp_reset_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "dsp reset failed\n"); + dev_err(ctx->dev, "dsp core0 reset fail: %d\n", ret); return ret; } - ret = skl_dsp_start_core(ctx); + ret = skl_dsp_start_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "dsp start failed\n"); + dev_err(ctx->dev, "dsp core0 start fail: %d\n", ret); return ret; } } else { - dev_dbg(ctx->dev, "disable and enable to make sure DSP is invalid state\n"); - ret = skl_dsp_disable_core(ctx); - + ret = skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); if (ret < 0) { - dev_err(ctx->dev, "dsp disable core failes\n"); + dev_err(ctx->dev, "dsp core0 disable fail: %d\n", ret); return ret; } - ret = skl_dsp_enable_core(ctx); + ret = skl_dsp_enable_core(ctx, SKL_DSP_CORE0_MASK); } return ret; @@ -281,16 +337,74 @@ irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id) return result; } +/* + * skl_dsp_get_core/skl_dsp_put_core will be called inside DAPM context + * within the dapm mutex. Hence no separate lock is used. + */ +int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *skl = ctx->thread_context; + int ret = 0; + + if (core_id >= skl->cores.count) { + dev_err(ctx->dev, "invalid core id: %d\n", core_id); + return -EINVAL; + } + + if (skl->cores.state[core_id] == SKL_DSP_RESET) { + ret = ctx->fw_ops.set_state_D0(ctx, core_id); + if (ret < 0) { + dev_err(ctx->dev, "unable to get core%d\n", core_id); + return ret; + } + } + + skl->cores.usage_count[core_id]++; + + dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", + core_id, skl->cores.state[core_id], + skl->cores.usage_count[core_id]); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_dsp_get_core); + +int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id) +{ + struct skl_sst *skl = ctx->thread_context; + int ret = 0; + + if (core_id >= skl->cores.count) { + dev_err(ctx->dev, "invalid core id: %d\n", core_id); + return -EINVAL; + } + + if (--skl->cores.usage_count[core_id] == 0) { + ret = ctx->fw_ops.set_state_D3(ctx, core_id); + if (ret < 0) { + dev_err(ctx->dev, "unable to put core %d: %d\n", + core_id, ret); + skl->cores.usage_count[core_id]++; + } + } + + dev_dbg(ctx->dev, "core id %d state %d usage_count %d\n", + core_id, skl->cores.state[core_id], + skl->cores.usage_count[core_id]); + + return ret; +} +EXPORT_SYMBOL_GPL(skl_dsp_put_core); int skl_dsp_wake(struct sst_dsp *ctx) { - return ctx->fw_ops.set_state_D0(ctx); + return skl_dsp_get_core(ctx, SKL_DSP_CORE0_ID); } EXPORT_SYMBOL_GPL(skl_dsp_wake); int skl_dsp_sleep(struct sst_dsp *ctx) { - return ctx->fw_ops.set_state_D3(ctx); + return skl_dsp_put_core(ctx, SKL_DSP_CORE0_ID); } EXPORT_SYMBOL_GPL(skl_dsp_sleep); @@ -337,9 +451,7 @@ void skl_dsp_free(struct sst_dsp *dsp) free_irq(dsp->irq, dsp); skl_ipc_op_int_disable(dsp); - skl_ipc_int_disable(dsp); - - skl_dsp_disable_core(dsp); + skl_dsp_disable_core(dsp, SKL_DSP_CORE0_MASK); } EXPORT_SYMBOL_GPL(skl_dsp_free); diff --git a/sound/soc/intel/skylake/skl-sst-dsp.h b/sound/soc/intel/skylake/skl-sst-dsp.h index deabe7308d3b..0f8629ef79ac 100644 --- a/sound/soc/intel/skylake/skl-sst-dsp.h +++ b/sound/soc/intel/skylake/skl-sst-dsp.h @@ -19,6 +19,7 @@ #include <linux/interrupt.h> #include <sound/memalloc.h> #include "skl-sst-cldma.h" +#include "skl-tplg-interface.h" struct sst_dsp; struct skl_sst; @@ -76,35 +77,53 @@ struct sst_dsp_device; #define SKL_ADSPIC_IPC 1 #define SKL_ADSPIS_IPC 1 +/* Core ID of core0 */ +#define SKL_DSP_CORE0_ID 0 + +/* Mask for a given core index, c = 0.. number of supported cores - 1 */ +#define SKL_DSP_CORE_MASK(c) BIT(c) + +/* + * Core 0 mask = SKL_DSP_CORE_MASK(0); Defined separately + * since Core0 is primary core and it is used often + */ +#define SKL_DSP_CORE0_MASK BIT(0) + +/* + * Mask for a given number of cores + * nc = number of supported cores + */ +#define SKL_DSP_CORES_MASK(nc) GENMASK((nc - 1), 0) + /* ADSPCS - Audio DSP Control & Status */ -#define SKL_DSP_CORES 1 -#define SKL_DSP_CORE0_MASK 1 -#define SKL_DSP_CORES_MASK ((1 << SKL_DSP_CORES) - 1) - -/* Core Reset - asserted high */ -#define SKL_ADSPCS_CRST_SHIFT 0 -#define SKL_ADSPCS_CRST_MASK (SKL_DSP_CORES_MASK << SKL_ADSPCS_CRST_SHIFT) -#define SKL_ADSPCS_CRST(x) ((x << SKL_ADSPCS_CRST_SHIFT) & SKL_ADSPCS_CRST_MASK) - -/* Core run/stall - when set to '1' core is stalled */ -#define SKL_ADSPCS_CSTALL_SHIFT 8 -#define SKL_ADSPCS_CSTALL_MASK (SKL_DSP_CORES_MASK << \ - SKL_ADSPCS_CSTALL_SHIFT) -#define SKL_ADSPCS_CSTALL(x) ((x << SKL_ADSPCS_CSTALL_SHIFT) & \ - SKL_ADSPCS_CSTALL_MASK) - -/* Set Power Active - when set to '1' turn cores on */ -#define SKL_ADSPCS_SPA_SHIFT 16 -#define SKL_ADSPCS_SPA_MASK (SKL_DSP_CORES_MASK << SKL_ADSPCS_SPA_SHIFT) -#define SKL_ADSPCS_SPA(x) ((x << SKL_ADSPCS_SPA_SHIFT) & SKL_ADSPCS_SPA_MASK) - -/* Current Power Active - power status of cores, set by hardware */ -#define SKL_ADSPCS_CPA_SHIFT 24 -#define SKL_ADSPCS_CPA_MASK (SKL_DSP_CORES_MASK << SKL_ADSPCS_CPA_SHIFT) -#define SKL_ADSPCS_CPA(x) ((x << SKL_ADSPCS_CPA_SHIFT) & SKL_ADSPCS_CPA_MASK) - -#define SST_DSP_POWER_D0 0x0 /* full On */ -#define SST_DSP_POWER_D3 0x3 /* Off */ + +/* + * Core Reset - asserted high + * CRST Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CRST_SHIFT 0 +#define SKL_ADSPCS_CRST_MASK(cm) ((cm) << SKL_ADSPCS_CRST_SHIFT) + +/* + * Core run/stall - when set to '1' core is stalled + * CSTALL Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CSTALL_SHIFT 8 +#define SKL_ADSPCS_CSTALL_MASK(cm) ((cm) << SKL_ADSPCS_CSTALL_SHIFT) + +/* + * Set Power Active - when set to '1' turn cores on + * SPA Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_SPA_SHIFT 16 +#define SKL_ADSPCS_SPA_MASK(cm) ((cm) << SKL_ADSPCS_SPA_SHIFT) + +/* + * Current Power Active - power status of cores, set by hardware + * CPA Mask for a given core mask pattern, cm + */ +#define SKL_ADSPCS_CPA_SHIFT 24 +#define SKL_ADSPCS_CPA_MASK(cm) ((cm) << SKL_ADSPCS_CPA_SHIFT) enum skl_dsp_states { SKL_DSP_RUNNING = 1, @@ -115,8 +134,8 @@ struct skl_dsp_fw_ops { int (*load_fw)(struct sst_dsp *ctx); /* FW module parser/loader */ int (*parse_fw)(struct sst_dsp *ctx); - int (*set_state_D0)(struct sst_dsp *ctx); - int (*set_state_D3)(struct sst_dsp *ctx); + int (*set_state_D0)(struct sst_dsp *ctx, unsigned int core_id); + int (*set_state_D3)(struct sst_dsp *ctx, unsigned int core_id); unsigned int (*get_fw_errcode)(struct sst_dsp *ctx); int (*load_mod)(struct sst_dsp *ctx, u16 mod_id, u8 *mod_name); int (*unload_mod)(struct sst_dsp *ctx, u16 mod_id); @@ -157,14 +176,26 @@ int skl_cldma_prepare(struct sst_dsp *ctx); void skl_dsp_set_state_locked(struct sst_dsp *ctx, int state); struct sst_dsp *skl_dsp_ctx_init(struct device *dev, struct sst_dsp_device *sst_dev, int irq); -int skl_dsp_enable_core(struct sst_dsp *ctx); -int skl_dsp_disable_core(struct sst_dsp *ctx); bool is_skl_dsp_running(struct sst_dsp *ctx); + +unsigned int skl_dsp_get_enabled_cores(struct sst_dsp *ctx); +void skl_dsp_init_core_state(struct sst_dsp *ctx); +int skl_dsp_enable_core(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_disable_core(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_power_up(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_power_down(struct sst_dsp *ctx, unsigned int core_mask); +int skl_dsp_core_unset_reset_state(struct sst_dsp *ctx, + unsigned int core_mask); +int skl_dsp_start_core(struct sst_dsp *ctx, unsigned int core_mask); + irqreturn_t skl_dsp_sst_interrupt(int irq, void *dev_id); int skl_dsp_wake(struct sst_dsp *ctx); int skl_dsp_sleep(struct sst_dsp *ctx); void skl_dsp_free(struct sst_dsp *dsp); +int skl_dsp_get_core(struct sst_dsp *ctx, unsigned int core_id); +int skl_dsp_put_core(struct sst_dsp *ctx, unsigned int core_id); + int skl_dsp_boot(struct sst_dsp *ctx); int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, const char *fw_name, struct skl_dsp_loader_ops dsp_ops, @@ -175,4 +206,11 @@ int bxt_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); void bxt_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx); +int snd_skl_get_module_info(struct skl_sst *ctx, u8 *uuid, + struct skl_dfw_module *dfw_config); +int snd_skl_parse_uuids(struct sst_dsp *ctx, unsigned int offset); +void skl_freeup_uuid_list(struct skl_sst *ctx); + +int skl_dsp_strip_extended_manifest(struct firmware *fw); + #endif /*__SKL_SST_DSP_H__*/ diff --git a/sound/soc/intel/skylake/skl-sst-ipc.c b/sound/soc/intel/skylake/skl-sst-ipc.c index 543460293b00..96f2f6889b18 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.c +++ b/sound/soc/intel/skylake/skl-sst-ipc.c @@ -363,7 +363,7 @@ static void skl_ipc_process_reply(struct sst_generic_ipc *ipc, /* first process the header */ switch (reply) { case IPC_GLB_REPLY_SUCCESS: - dev_info(ipc->dev, "ipc FW reply %x: success\n", header.primary); + dev_dbg(ipc->dev, "ipc FW reply %x: success\n", header.primary); /* copy the rx data from the mailbox */ sst_dsp_inbox_read(ipc->dsp, msg->rx_data, msg->rx_size); break; @@ -692,7 +692,7 @@ int skl_ipc_init_instance(struct sst_generic_ipc *ipc, /* param_block_size must be in dwords */ u16 param_block_size = msg->param_data_size / sizeof(u32); - print_hex_dump(KERN_DEBUG, NULL, DUMP_PREFIX_NONE, + print_hex_dump_debug("Param data:", DUMP_PREFIX_NONE, 16, 4, buffer, param_block_size, false); header.primary = IPC_MSG_TARGET(IPC_MOD_MSG); diff --git a/sound/soc/intel/skylake/skl-sst-ipc.h b/sound/soc/intel/skylake/skl-sst-ipc.h index d59d1ba62a43..2e3d4e80ef97 100644 --- a/sound/soc/intel/skylake/skl-sst-ipc.h +++ b/sound/soc/intel/skylake/skl-sst-ipc.h @@ -45,6 +45,14 @@ struct skl_ipc_header { u32 extension; }; +#define SKL_DSP_CORES_MAX 2 + +struct skl_dsp_cores { + unsigned int count; + enum skl_dsp_states state[SKL_DSP_CORES_MAX]; + int usage_count[SKL_DSP_CORES_MAX]; +}; + struct skl_sst { struct device *dev; struct sst_dsp *dsp; @@ -60,6 +68,15 @@ struct skl_sst { void (*enable_miscbdcge)(struct device *dev, bool enable); /*Is CGCTL.MISCBDCGE disabled*/ bool miscbdcg_disabled; + + /* Populate module information */ + struct list_head uuid_list; + + /* Is firmware loaded */ + bool fw_loaded; + + /* multi-core */ + struct skl_dsp_cores cores; }; struct skl_ipc_init_instance_msg { @@ -136,5 +153,6 @@ void skl_ipc_int_disable(struct sst_dsp *dsp); bool skl_ipc_int_status(struct sst_dsp *dsp); void skl_ipc_free(struct sst_generic_ipc *ipc); int skl_ipc_init(struct device *dev, struct skl_sst *skl); +void skl_clear_module_cnt(struct sst_dsp *ctx); #endif /* __SKL_IPC_H */ diff --git a/sound/soc/intel/skylake/skl-sst-utils.c b/sound/soc/intel/skylake/skl-sst-utils.c new file mode 100644 index 000000000000..25fcb796bd86 --- /dev/null +++ b/sound/soc/intel/skylake/skl-sst-utils.c @@ -0,0 +1,256 @@ +/* + * skl-sst-utils.c - SKL sst utils functions + * + * Copyright (C) 2016 Intel Corp + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as 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. + */ + +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/uuid.h> +#include "skl-sst-dsp.h" +#include "../common/sst-dsp.h" +#include "../common/sst-dsp-priv.h" +#include "skl-sst-ipc.h" + + +#define UUID_STR_SIZE 37 +#define DEFAULT_HASH_SHA256_LEN 32 + +/* FW Extended Manifest Header id = $AE1 */ +#define SKL_EXT_MANIFEST_HEADER_MAGIC 0x31454124 + +struct skl_dfw_module_mod { + char name[100]; + struct skl_dfw_module skl_dfw_mod; +}; + +struct UUID { + u8 id[16]; +}; + +union seg_flags { + u32 ul; + struct { + u32 contents : 1; + u32 alloc : 1; + u32 load : 1; + u32 read_only : 1; + u32 code : 1; + u32 data : 1; + u32 _rsvd0 : 2; + u32 type : 4; + u32 _rsvd1 : 4; + u32 length : 16; + } r; +} __packed; + +struct segment_desc { + union seg_flags flags; + u32 v_base_addr; + u32 file_offset; +}; + +struct module_type { + u32 load_type : 4; + u32 auto_start : 1; + u32 domain_ll : 1; + u32 domain_dp : 1; + u32 rsvd : 25; +} __packed; + +struct adsp_module_entry { + u32 struct_id; + u8 name[8]; + struct UUID uuid; + struct module_type type; + u8 hash1[DEFAULT_HASH_SHA256_LEN]; + u32 entry_point; + u16 cfg_offset; + u16 cfg_count; + u32 affinity_mask; + u16 instance_max_count; + u16 instance_bss_size; + struct segment_desc segments[3]; +} __packed; + +struct adsp_fw_hdr { + u32 id; + u32 len; + u8 name[8]; + u32 preload_page_count; + u32 fw_image_flags; + u32 feature_mask; + u16 major; + u16 minor; + u16 hotfix; + u16 build; + u32 num_modules; + u32 hw_buf_base; + u32 hw_buf_length; + u32 load_offset; +} __packed; + +struct uuid_module { + uuid_le uuid; + int id; + int is_loadable; + + struct list_head list; +}; + +struct skl_ext_manifest_hdr { + u32 id; + u32 len; + u16 version_major; + u16 version_minor; + u32 entries; +}; + +int snd_skl_get_module_info(struct skl_sst *ctx, u8 *uuid, + struct skl_dfw_module *dfw_config) +{ + struct uuid_module *module; + uuid_le *uuid_mod; + + uuid_mod = (uuid_le *)uuid; + + list_for_each_entry(module, &ctx->uuid_list, list) { + if (uuid_le_cmp(*uuid_mod, module->uuid) == 0) { + dfw_config->module_id = module->id; + dfw_config->is_loadable = module->is_loadable; + + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_skl_get_module_info); + +/* + * Parse the firmware binary to get the UUID, module id + * and loadable flags + */ +int snd_skl_parse_uuids(struct sst_dsp *ctx, unsigned int offset) +{ + struct adsp_fw_hdr *adsp_hdr; + struct adsp_module_entry *mod_entry; + int i, num_entry; + uuid_le *uuid_bin; + const char *buf; + struct skl_sst *skl = ctx->thread_context; + struct uuid_module *module; + struct firmware stripped_fw; + unsigned int safe_file; + + /* Get the FW pointer to derive ADSP header */ + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + + skl_dsp_strip_extended_manifest(&stripped_fw); + + buf = stripped_fw.data; + + /* check if we have enough space in file to move to header */ + safe_file = sizeof(*adsp_hdr) + offset; + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No space for hdr\n"); + return -EINVAL; + } + + adsp_hdr = (struct adsp_fw_hdr *)(buf + offset); + + /* check 1st module entry is in file */ + safe_file += adsp_hdr->len + sizeof(*mod_entry); + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No module entry\n"); + return -EINVAL; + } + + mod_entry = (struct adsp_module_entry *) + (buf + offset + adsp_hdr->len); + + num_entry = adsp_hdr->num_modules; + + /* check all entries are in file */ + safe_file += num_entry * sizeof(*mod_entry); + if (stripped_fw.size <= safe_file) { + dev_err(ctx->dev, "Small fw file size, No modules\n"); + return -EINVAL; + } + + + /* + * Read the UUID(GUID) from FW Manifest. + * + * The 16 byte UUID format is: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX + * Populate the UUID table to store module_id and loadable flags + * for the module. + */ + + for (i = 0; i < num_entry; i++, mod_entry++) { + module = kzalloc(sizeof(*module), GFP_KERNEL); + if (!module) + return -ENOMEM; + + uuid_bin = (uuid_le *)mod_entry->uuid.id; + memcpy(&module->uuid, uuid_bin, sizeof(module->uuid)); + + module->id = i; + module->is_loadable = mod_entry->type.load_type; + + list_add_tail(&module->list, &skl->uuid_list); + + dev_dbg(ctx->dev, + "Adding uuid :%pUL mod id: %d Loadable: %d\n", + &module->uuid, module->id, module->is_loadable); + } + + return 0; +} + +void skl_freeup_uuid_list(struct skl_sst *ctx) +{ + struct uuid_module *uuid, *_uuid; + + list_for_each_entry_safe(uuid, _uuid, &ctx->uuid_list, list) { + list_del(&uuid->list); + kfree(uuid); + } +} + +/* + * some firmware binary contains some extended manifest. This needs + * to be stripped in that case before we load and use that image. + * + * Get the module id for the module by checking + * the table for the UUID for the module + */ +int skl_dsp_strip_extended_manifest(struct firmware *fw) +{ + struct skl_ext_manifest_hdr *hdr; + + /* check if fw file is greater than header we are looking */ + if (fw->size < sizeof(hdr)) { + pr_err("%s: Firmware file small, no hdr\n", __func__); + return -EINVAL; + } + + hdr = (struct skl_ext_manifest_hdr *)fw->data; + + if (hdr->id == SKL_EXT_MANIFEST_HEADER_MAGIC) { + fw->size -= hdr->len; + fw->data += hdr->len; + } + + return 0; +} diff --git a/sound/soc/intel/skylake/skl-sst.c b/sound/soc/intel/skylake/skl-sst.c index 13ec8d53b526..588f899ceb65 100644 --- a/sound/soc/intel/skylake/skl-sst.c +++ b/sound/soc/intel/skylake/skl-sst.c @@ -68,10 +68,13 @@ static int skl_transfer_firmware(struct sst_dsp *ctx, return ret; } +#define SKL_ADSP_FW_BIN_HDR_OFFSET 0x284 + static int skl_load_base_firmware(struct sst_dsp *ctx) { int ret = 0, i; struct skl_sst *skl = ctx->thread_context; + struct firmware stripped_fw; u32 reg; skl->boot_complete = false; @@ -81,11 +84,25 @@ static int skl_load_base_firmware(struct sst_dsp *ctx) ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); if (ret < 0) { dev_err(ctx->dev, "Request firmware failed %d\n", ret); - skl_dsp_disable_core(ctx); return -EIO; } } + ret = snd_skl_parse_uuids(ctx, SKL_ADSP_FW_BIN_HDR_OFFSET); + if (ret < 0) { + dev_err(ctx->dev, + "UUID parsing err: %d\n", ret); + release_firmware(ctx->fw); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); + return ret; + } + + /* check for extended manifest */ + stripped_fw.data = ctx->fw->data; + stripped_fw.size = ctx->fw->size; + + skl_dsp_strip_extended_manifest(&stripped_fw); + ret = skl_dsp_boot(ctx); if (ret < 0) { dev_err(ctx->dev, "Boot dsp core failed ret: %d", ret); @@ -119,7 +136,7 @@ static int skl_load_base_firmware(struct sst_dsp *ctx) goto transfer_firmware_failed; } - ret = skl_transfer_firmware(ctx, ctx->fw->data, ctx->fw->size); + ret = skl_transfer_firmware(ctx, stripped_fw.data, stripped_fw.size); if (ret < 0) { dev_err(ctx->dev, "Transfer firmware failed%d\n", ret); goto transfer_firmware_failed; @@ -133,67 +150,87 @@ static int skl_load_base_firmware(struct sst_dsp *ctx) } dev_dbg(ctx->dev, "Download firmware successful%d\n", ret); - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); + skl->fw_loaded = true; } return 0; transfer_firmware_failed: ctx->cl_dev.ops.cl_cleanup_controller(ctx); skl_load_base_firmware_failed: - skl_dsp_disable_core(ctx); + skl_dsp_disable_core(ctx, SKL_DSP_CORE0_MASK); release_firmware(ctx->fw); ctx->fw = NULL; return ret; } -static int skl_set_dsp_D0(struct sst_dsp *ctx) +static int skl_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id) { int ret; + struct skl_ipc_dxstate_info dx; + struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - ret = skl_load_base_firmware(ctx); - if (ret < 0) { - dev_err(ctx->dev, "unable to load firmware\n"); - return ret; + /* If core0 is being turned on, we need to load the FW */ + if (core_id == SKL_DSP_CORE0_ID) { + ret = skl_load_base_firmware(ctx); + if (ret < 0) { + dev_err(ctx->dev, "unable to load firmware\n"); + return ret; + } } - skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); + /* + * If any core other than core 0 is being moved to D0, enable the + * core and send the set dx IPC for the core. + */ + if (core_id != SKL_DSP_CORE0_ID) { + ret = skl_dsp_enable_core(ctx, core_mask); + if (ret < 0) + return ret; + + dx.core_mask = core_mask; + dx.dx_mask = core_mask; + + ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, + SKL_BASE_FW_MODULE_ID, &dx); + if (ret < 0) { + dev_err(ctx->dev, "Failed to set dsp to D0:core id= %d\n", + core_id); + skl_dsp_disable_core(ctx, core_mask); + } + } + + skl->cores.state[core_id] = SKL_DSP_RUNNING; return ret; } -static int skl_set_dsp_D3(struct sst_dsp *ctx) +static int skl_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id) { int ret; struct skl_ipc_dxstate_info dx; struct skl_sst *skl = ctx->thread_context; + unsigned int core_mask = SKL_DSP_CORE_MASK(core_id); - dev_dbg(ctx->dev, "In %s:\n", __func__); - mutex_lock(&ctx->mutex); - if (!is_skl_dsp_running(ctx)) { - mutex_unlock(&ctx->mutex); - return 0; - } - mutex_unlock(&ctx->mutex); - - dx.core_mask = SKL_DSP_CORE0_MASK; + dx.core_mask = core_mask; dx.dx_mask = SKL_IPC_D3_MASK; + ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, SKL_BASE_FW_MODULE_ID, &dx); if (ret < 0) - dev_err(ctx->dev, - "D3 request to FW failed, continuing reset: %d", ret); - - /* disable Interrupt */ - ctx->cl_dev.ops.cl_cleanup_controller(ctx); - skl_cldma_int_disable(ctx); - skl_ipc_op_int_disable(ctx); - skl_ipc_int_disable(ctx); - - ret = skl_dsp_disable_core(ctx); - if (ret < 0) { - dev_err(ctx->dev, "disable dsp core failed ret: %d\n", ret); - ret = -EIO; + dev_err(ctx->dev, "set Dx core %d fail: %d\n", core_id, ret); + + if (core_id == SKL_DSP_CORE0_ID) { + /* disable Interrupt */ + ctx->cl_dev.ops.cl_cleanup_controller(ctx); + skl_cldma_int_disable(ctx); + skl_ipc_op_int_disable(ctx); + skl_ipc_int_disable(ctx); } - skl_dsp_set_state_locked(ctx, SKL_DSP_RESET); + ret = skl_dsp_disable_core(ctx, core_mask); + if (ret < 0) + return ret; + + skl->cores.state[core_id] = SKL_DSP_RESET; return ret; } @@ -360,6 +397,19 @@ static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id) return ret; } +void skl_clear_module_cnt(struct sst_dsp *ctx) +{ + struct skl_module_table *module; + + if (list_empty(&ctx->module_list)) + return; + + list_for_each_entry(module, &ctx->module_list, list) { + module->usage_cnt = 0; + } +} +EXPORT_SYMBOL_GPL(skl_clear_module_cnt); + static void skl_clear_module_table(struct sst_dsp *ctx) { struct skl_module_table *module, *tmp; @@ -409,6 +459,7 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, skl->dev = dev; skl_dev.thread_context = skl; + INIT_LIST_HEAD(&skl->uuid_list); skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); if (!skl->dsp) { @@ -432,12 +483,16 @@ int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, if (ret) return ret; + skl->cores.count = 2; + ret = sst->fw_ops.load_fw(sst); if (ret < 0) { dev_err(dev, "Load base fw failed : %d", ret); goto cleanup; } + skl_dsp_init_core_state(sst); + if (dsp) *dsp = skl; @@ -452,6 +507,7 @@ EXPORT_SYMBOL_GPL(skl_sst_dsp_init); void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) { skl_clear_module_table(ctx->dsp); + skl_freeup_uuid_list(ctx); skl_ipc_free(&ctx->ipc); ctx->dsp->ops->free(ctx->dsp); if (ctx->boot_complete) { diff --git a/sound/soc/intel/skylake/skl-topology.c b/sound/soc/intel/skylake/skl-topology.c index 3e036b0349b9..cc0150fc2601 100644 --- a/sound/soc/intel/skylake/skl-topology.c +++ b/sound/soc/intel/skylake/skl-topology.c @@ -379,43 +379,6 @@ static void skl_tplg_update_module_params(struct snd_soc_dapm_widget *w, } /* - * A pipe can have multiple modules, each of them will be a DAPM widget as - * well. While managing a pipeline we need to get the list of all the - * widgets in a pipelines, so this helper - skl_tplg_get_pipe_widget() helps - * to get the SKL type widgets in that pipeline - */ -static int skl_tplg_alloc_pipe_widget(struct device *dev, - struct snd_soc_dapm_widget *w, struct skl_pipe *pipe) -{ - struct skl_module_cfg *src_module = NULL; - struct snd_soc_dapm_path *p = NULL; - struct skl_pipe_module *p_module = NULL; - - p_module = devm_kzalloc(dev, sizeof(*p_module), GFP_KERNEL); - if (!p_module) - return -ENOMEM; - - p_module->w = w; - list_add_tail(&p_module->node, &pipe->w_list); - - snd_soc_dapm_widget_for_each_sink_path(w, p) { - if ((p->sink->priv == NULL) - && (!is_skl_dsp_widget_type(w))) - continue; - - if ((p->sink->priv != NULL) && p->connect - && is_skl_dsp_widget_type(p->sink)) { - - src_module = p->sink->priv; - if (pipe->ppl_id == src_module->pipe->ppl_id) - skl_tplg_alloc_pipe_widget(dev, - p->sink, pipe); - } - } - return 0; -} - -/* * some modules can have multiple params set from user control and * need to be set after module is initialized. If set_param flag is * set module params will be done after module is initialised. @@ -448,7 +411,7 @@ static int skl_tplg_set_module_params(struct snd_soc_dapm_widget *w, if (bc->set_params == SKL_PARAM_SET) { ret = skl_set_module_params(ctx, - (u32 *)bc->params, bc->max, + (u32 *)bc->params, bc->size, bc->param_id, mconfig); if (ret < 0) return ret; @@ -483,7 +446,7 @@ static int skl_tplg_set_module_init_data(struct snd_soc_dapm_widget *w) continue; mconfig->formats_config.caps = (u32 *)&bc->params; - mconfig->formats_config.caps_size = bc->max; + mconfig->formats_config.caps_size = bc->size; break; } @@ -514,8 +477,6 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) if (!skl_is_pipe_mcps_avail(skl, mconfig)) return -ENOMEM; - skl_tplg_alloc_pipe_mcps(skl, mconfig); - if (mconfig->is_loadable && ctx->dsp->fw_ops.load_mod) { ret = ctx->dsp->fw_ops.load_mod(ctx->dsp, mconfig->id.module_id, mconfig->guid); @@ -539,6 +500,7 @@ skl_tplg_init_pipe_modules(struct skl *skl, struct skl_pipe *pipe) if (ret < 0) return ret; + skl_tplg_alloc_pipe_mcps(skl, mconfig); ret = skl_tplg_set_module_params(w, ctx); if (ret < 0) return ret; @@ -591,9 +553,6 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, if (!skl_is_pipe_mem_avail(skl, mconfig)) return -ENOMEM; - skl_tplg_alloc_pipe_mem(skl, mconfig); - skl_tplg_alloc_pipe_mcps(skl, mconfig); - /* * Create a list of modules for pipe. * This list contains modules from source to sink @@ -602,19 +561,8 @@ static int skl_tplg_mixer_dapm_pre_pmu_event(struct snd_soc_dapm_widget *w, if (ret < 0) return ret; - /* - * we create a w_list of all widgets in that pipe. This list is not - * freed on PMD event as widgets within a pipe are static. This - * saves us cycles to get widgets in pipe every time. - * - * So if we have already initialized all the widgets of a pipeline - * we skip, so check for list_empty and create the list if empty - */ - if (list_empty(&s_pipe->w_list)) { - ret = skl_tplg_alloc_pipe_widget(ctx->dev, w, s_pipe); - if (ret < 0) - return ret; - } + skl_tplg_alloc_pipe_mem(skl, mconfig); + skl_tplg_alloc_pipe_mcps(skl, mconfig); /* Init all pipe modules from source to sink */ ret = skl_tplg_init_pipe_modules(skl, s_pipe); @@ -949,13 +897,17 @@ static int skl_tplg_mixer_dapm_post_pmd_event(struct snd_soc_dapm_widget *w, struct skl_pipe *s_pipe = mconfig->pipe; int ret = 0; + if (s_pipe->state == SKL_PIPE_INVALID) + return -EINVAL; + skl_tplg_free_pipe_mcps(skl, mconfig); skl_tplg_free_pipe_mem(skl, mconfig); list_for_each_entry(w_module, &s_pipe->w_list, node) { dst_module = w_module->w->priv; - skl_tplg_free_pipe_mcps(skl, dst_module); + if (mconfig->m_state >= SKL_MODULE_INIT_DONE) + skl_tplg_free_pipe_mcps(skl, dst_module); if (src_module == NULL) { src_module = dst_module; continue; @@ -1102,7 +1054,7 @@ static int skl_tplg_tlv_control_get(struct snd_kcontrol *kcontrol, if (w->power) skl_get_module_params(skl->skl_sst, (u32 *)bc->params, - bc->max, bc->param_id, mconfig); + bc->size, bc->param_id, mconfig); /* decrement size for TLV header */ size -= 2 * sizeof(u32); @@ -1136,6 +1088,10 @@ static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, struct skl *skl = get_skl_ctx(w->dapm->dev); if (ac->params) { + if (size > ac->max) + return -EINVAL; + + ac->size = size; /* * if the param_is is of type Vendor, firmware expects actual * parameter id and size from the control. @@ -1151,7 +1107,7 @@ static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, if (w->power) return skl_set_module_params(skl->skl_sst, - (u32 *)ac->params, ac->max, + (u32 *)ac->params, ac->size, ac->param_id, mconfig); } @@ -1159,6 +1115,39 @@ static int skl_tplg_tlv_control_set(struct snd_kcontrol *kcontrol, } /* + * Fill the dma id for host and link. In case of passthrough + * pipeline, this will both host and link in the same + * pipeline, so need to copy the link and host based on dev_type + */ +static void skl_tplg_fill_dma_id(struct skl_module_cfg *mcfg, + struct skl_pipe_params *params) +{ + struct skl_pipe *pipe = mcfg->pipe; + + if (pipe->passthru) { + switch (mcfg->dev_type) { + case SKL_DEVICE_HDALINK: + pipe->p_params->link_dma_id = params->link_dma_id; + break; + + case SKL_DEVICE_HDAHOST: + pipe->p_params->host_dma_id = params->host_dma_id; + break; + + default: + break; + } + pipe->p_params->s_fmt = params->s_fmt; + pipe->p_params->ch = params->ch; + pipe->p_params->s_freq = params->s_freq; + pipe->p_params->stream = params->stream; + + } else { + memcpy(pipe->p_params, params, sizeof(*params)); + } +} + +/* * The FE params are passed by hw_params of the DAI. * On hw_params, the params are stored in Gateway module of the FE and we * need to calculate the format in DSP module configuration, that @@ -1168,10 +1157,9 @@ int skl_tplg_update_pipe_params(struct device *dev, struct skl_module_cfg *mconfig, struct skl_pipe_params *params) { - struct skl_pipe *pipe = mconfig->pipe; struct skl_module_fmt *format = NULL; - memcpy(pipe->p_params, params, sizeof(*params)); + skl_tplg_fill_dma_id(mconfig, params); if (params->stream == SNDRV_PCM_STREAM_PLAYBACK) format = &mconfig->in_fmt[0]; @@ -1358,12 +1346,11 @@ static int skl_tplg_be_fill_pipe_params(struct snd_soc_dai *dai, struct skl_module_cfg *mconfig, struct skl_pipe_params *params) { - struct skl_pipe *pipe = mconfig->pipe; struct nhlt_specific_cfg *cfg; struct skl *skl = get_skl_ctx(dai->dev); int link_type = skl_tplg_be_link_type(mconfig->dev_type); - memcpy(pipe->p_params, params, sizeof(*params)); + skl_tplg_fill_dma_id(mconfig, params); if (link_type == NHLT_LINK_HDA) return 0; @@ -1554,6 +1541,55 @@ static void skl_tplg_fill_fmt(struct skl_module_fmt *dst_fmt, } } +static void skl_clear_pin_config(struct snd_soc_platform *platform, + struct snd_soc_dapm_widget *w) +{ + int i; + struct skl_module_cfg *mconfig; + struct skl_pipe *pipe; + + if (!strncmp(w->dapm->component->name, platform->component.name, + strlen(platform->component.name))) { + mconfig = w->priv; + pipe = mconfig->pipe; + for (i = 0; i < mconfig->max_in_queue; i++) { + mconfig->m_in_pin[i].in_use = false; + mconfig->m_in_pin[i].pin_state = SKL_PIN_UNBIND; + } + for (i = 0; i < mconfig->max_out_queue; i++) { + mconfig->m_out_pin[i].in_use = false; + mconfig->m_out_pin[i].pin_state = SKL_PIN_UNBIND; + } + pipe->state = SKL_PIPE_INVALID; + mconfig->m_state = SKL_MODULE_UNINIT; + } +} + +void skl_cleanup_resources(struct skl *skl) +{ + struct skl_sst *ctx = skl->skl_sst; + struct snd_soc_platform *soc_platform = skl->platform; + struct snd_soc_dapm_widget *w; + struct snd_soc_card *card; + + if (soc_platform == NULL) + return; + + card = soc_platform->component.card; + if (!card || !card->instantiated) + return; + + skl->resource.mem = 0; + skl->resource.mcps = 0; + + list_for_each_entry(w, &card->widgets, list) { + if (is_skl_dsp_widget_type(w) && (w->priv != NULL)) + skl_clear_pin_config(soc_platform, w); + } + + skl_clear_module_cnt(ctx->dsp); +} + /* * Topology core widget load callback * @@ -1585,6 +1621,10 @@ static int skl_tplg_widget_load(struct snd_soc_component *cmpnt, w->priv = mconfig; memcpy(&mconfig->guid, &dfw_config->uuid, 16); + ret = snd_skl_get_module_info(skl->skl_sst, mconfig->guid, dfw_config); + if (ret < 0) + return ret; + mconfig->id.module_id = dfw_config->module_id; mconfig->id.instance_id = dfw_config->instance_id; mconfig->mcps = dfw_config->max_mcps; @@ -1683,6 +1723,7 @@ static int skl_init_algo_data(struct device *dev, struct soc_bytes_ext *be, ac->max = dfw_ac->max; ac->param_id = dfw_ac->param_id; ac->set_params = dfw_ac->set_params; + ac->size = dfw_ac->max; if (ac->max) { ac->params = (char *) devm_kzalloc(dev, ac->max, GFP_KERNEL); @@ -1733,6 +1774,60 @@ static struct snd_soc_tplg_ops skl_tplg_ops = { .bytes_ext_ops_count = ARRAY_SIZE(skl_tlv_ops), }; +/* + * A pipe can have multiple modules, each of them will be a DAPM widget as + * well. While managing a pipeline we need to get the list of all the + * widgets in a pipelines, so this helper - skl_tplg_create_pipe_widget_list() + * helps to get the SKL type widgets in that pipeline + */ +static int skl_tplg_create_pipe_widget_list(struct snd_soc_platform *platform) +{ + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mcfg = NULL; + struct skl_pipe_module *p_module = NULL; + struct skl_pipe *pipe; + + list_for_each_entry(w, &platform->component.card->widgets, list) { + if (is_skl_dsp_widget_type(w) && w->priv != NULL) { + mcfg = w->priv; + pipe = mcfg->pipe; + + p_module = devm_kzalloc(platform->dev, + sizeof(*p_module), GFP_KERNEL); + if (!p_module) + return -ENOMEM; + + p_module->w = w; + list_add_tail(&p_module->node, &pipe->w_list); + } + } + + return 0; +} + +static void skl_tplg_set_pipe_type(struct skl *skl, struct skl_pipe *pipe) +{ + struct skl_pipe_module *w_module; + struct snd_soc_dapm_widget *w; + struct skl_module_cfg *mconfig; + bool host_found = false, link_found = false; + + list_for_each_entry(w_module, &pipe->w_list, node) { + w = w_module->w; + mconfig = w->priv; + + if (mconfig->dev_type == SKL_DEVICE_HDAHOST) + host_found = true; + else if (mconfig->dev_type != SKL_DEVICE_NONE) + link_found = true; + } + + if (host_found && link_found) + pipe->passthru = true; + else + pipe->passthru = false; +} + /* This will be read from topology manifest, currently defined here */ #define SKL_MAX_MCPS 30000000 #define SKL_FW_MAX_MEM 1000000 @@ -1746,6 +1841,7 @@ int skl_tplg_init(struct snd_soc_platform *platform, struct hdac_ext_bus *ebus) const struct firmware *fw; struct hdac_bus *bus = ebus_to_hbus(ebus); struct skl *skl = ebus_to_skl(ebus); + struct skl_pipeline *ppl; ret = request_firmware(&fw, skl->tplg_name, bus->dev); if (ret < 0) { @@ -1775,6 +1871,12 @@ int skl_tplg_init(struct snd_soc_platform *platform, struct hdac_ext_bus *ebus) skl->resource.max_mem = SKL_FW_MAX_MEM; skl->tplg = fw; + ret = skl_tplg_create_pipe_widget_list(platform); + if (ret < 0) + return ret; + + list_for_each_entry(ppl, &skl->ppl_list, node) + skl_tplg_set_pipe_type(skl, ppl->pipe); return 0; } diff --git a/sound/soc/intel/skylake/skl-topology.h b/sound/soc/intel/skylake/skl-topology.h index e4b399cd7868..22d3ef83817d 100644 --- a/sound/soc/intel/skylake/skl-topology.h +++ b/sound/soc/intel/skylake/skl-topology.h @@ -244,7 +244,8 @@ enum skl_pipe_state { SKL_PIPE_INVALID = 0, SKL_PIPE_CREATED = 1, SKL_PIPE_PAUSED = 2, - SKL_PIPE_STARTED = 3 + SKL_PIPE_STARTED = 3, + SKL_PIPE_RESET = 4 }; struct skl_pipe_module { @@ -270,6 +271,7 @@ struct skl_pipe { struct skl_pipe_params *p_params; enum skl_pipe_state state; struct list_head w_list; + bool passthru; }; enum skl_module_state { @@ -319,6 +321,7 @@ struct skl_algo_data { u32 param_id; u32 set_params; u32 max; + u32 size; char *params; }; @@ -357,6 +360,8 @@ int skl_delete_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); int skl_stop_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); +int skl_reset_pipe(struct skl_sst *ctx, struct skl_pipe *pipe); + int skl_init_module(struct skl_sst *ctx, struct skl_module_cfg *module_config); int skl_bind_modules(struct skl_sst *ctx, struct skl_module_cfg diff --git a/sound/soc/intel/skylake/skl.c b/sound/soc/intel/skylake/skl.c index 06d8c263c68f..cd59536a761d 100644 --- a/sound/soc/intel/skylake/skl.c +++ b/sound/soc/intel/skylake/skl.c @@ -35,6 +35,8 @@ #include "skl-sst-dsp.h" #include "skl-sst-ipc.h" +static struct skl_machine_pdata skl_dmic_data; + /* * initialize the PCI registers */ @@ -184,6 +186,7 @@ static int _skl_suspend(struct hdac_ext_bus *ebus) { struct skl *skl = ebus_to_skl(ebus); struct hdac_bus *bus = ebus_to_hbus(ebus); + struct pci_dev *pci = to_pci_dev(bus->dev); int ret; snd_hdac_ext_bus_link_power_down_all(ebus); @@ -193,9 +196,12 @@ static int _skl_suspend(struct hdac_ext_bus *ebus) return ret; snd_hdac_bus_stop_chip(bus); + update_pci_dword(pci, AZX_PCIREG_PGCTL, + AZX_PGCTL_LSRMD_MASK, AZX_PGCTL_LSRMD_MASK); skl_enable_miscbdcge(bus->dev, false); snd_hdac_bus_enter_link_reset(bus); skl_enable_miscbdcge(bus->dev, true); + skl_cleanup_resources(skl); return 0; } @@ -242,6 +248,7 @@ static int skl_suspend(struct device *dev) ret = _skl_suspend(ebus); if (ret < 0) return ret; + skl->skl_sst->fw_loaded = false; } if (IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)) { @@ -397,6 +404,10 @@ static int skl_machine_device_register(struct skl *skl, void *driver_data) platform_device_put(pdev); return -EIO; } + + if (mach->pdata) + dev_set_drvdata(&pdev->dev, mach->pdata); + skl->i2s_dev = pdev; return 0; @@ -657,6 +668,8 @@ static int skl_probe(struct pci_dev *pci, skl->pci_id = pci->device; + device_disable_async_suspend(bus->dev); + skl->nhlt = skl_nhlt_init(bus->dev); if (skl->nhlt == NULL) @@ -666,6 +679,8 @@ static int skl_probe(struct pci_dev *pci, pci_set_drvdata(skl->pci, ebus); + skl_dmic_data.dmic_num = skl_get_dmic_geo(skl); + /* check if dsp is there */ if (ebus->ppcap) { err = skl_machine_device_register(skl, @@ -713,7 +728,7 @@ static int skl_probe(struct pci_dev *pci, list_for_each_entry(hlink, &ebus->hlink_list, list) snd_hdac_ext_bus_link_put(ebus, hlink); - /*configure PM */ + /* configure PM */ pm_runtime_put_noidle(bus->dev); pm_runtime_allow(bus->dev); @@ -766,8 +781,7 @@ static void skl_remove(struct pci_dev *pci) struct hdac_ext_bus *ebus = pci_get_drvdata(pci); struct skl *skl = ebus_to_skl(ebus); - if (skl->tplg) - release_firmware(skl->tplg); + release_firmware(skl->tplg); if (pci_dev_run_wake(pci)) pm_runtime_get_noresume(&pci->dev); @@ -786,15 +800,23 @@ static void skl_remove(struct pci_dev *pci) static struct sst_acpi_mach sst_skl_devdata[] = { { "INT343A", "skl_alc286s_i2s", "intel/dsp_fw_release.bin", NULL, NULL, NULL }, - { "INT343B", "skl_nau88l25_ssm4567_i2s", "intel/dsp_fw_release.bin", - NULL, NULL, NULL }, - { "MX98357A", "skl_nau88l25_max98357a_i2s", "intel/dsp_fw_release.bin", - NULL, NULL, NULL }, + { "INT343B", "skl_n88l25_s4567", "intel/dsp_fw_release.bin", + NULL, NULL, &skl_dmic_data }, + { "MX98357A", "skl_n88l25_m98357a", "intel/dsp_fw_release.bin", + NULL, NULL, &skl_dmic_data }, {} }; static struct sst_acpi_mach sst_bxtp_devdata[] = { { "INT343A", "bxt_alc298s_i2s", "intel/dsp_fw_bxtn.bin", NULL, NULL, NULL }, + { "DLGS7219", "bxt_da7219_max98357a_i2s", "intel/dsp_fw_bxtn.bin", NULL, NULL, NULL }, +}; + +static struct sst_acpi_mach sst_kbl_devdata[] = { + { "INT343A", "kbl_alc286s_i2s", "intel/dsp_fw_kbl.bin", NULL, NULL, NULL }, + { "INT343B", "kbl_n88l25_s4567", "intel/dsp_fw_kbl.bin", NULL, NULL, &skl_dmic_data }, + { "MX98357A", "kbl_n88l25_m98357a", "intel/dsp_fw_kbl.bin", NULL, NULL, &skl_dmic_data }, + {} }; /* PCI IDs */ @@ -805,6 +827,9 @@ static const struct pci_device_id skl_ids[] = { /* BXT-P */ { PCI_DEVICE(0x8086, 0x5a98), .driver_data = (unsigned long)&sst_bxtp_devdata}, + /* KBL */ + { PCI_DEVICE(0x8086, 0x9D71), + .driver_data = (unsigned long)&sst_kbl_devdata}, { 0, } }; MODULE_DEVICE_TABLE(pci, skl_ids); diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index 4b4b3876aea9..9064e5b0d676 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -48,6 +48,8 @@ #define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 #define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 +#define AZX_PCIREG_PGCTL 0x44 +#define AZX_PGCTL_LSRMD_MASK (1 << 4) #define AZX_PCIREG_CGCTL 0x48 #define AZX_CGCTL_MISCBDCGE_MASK (1 << 6) @@ -65,6 +67,7 @@ struct skl { unsigned int init_failed:1; /* delayed init failed */ struct platform_device *dmic_dev; struct platform_device *i2s_dev; + struct snd_soc_platform *platform; struct nhlt_acpi_table *nhlt; /* nhlt ptr */ struct skl_sst *skl_sst; /* sst skl ctx */ @@ -90,6 +93,11 @@ struct skl_dma_params { u8 stream_tag; }; +/* to pass dmic data */ +struct skl_machine_pdata { + u32 dmic_num; +}; + struct skl_dsp_ops { int id; struct skl_dsp_loader_ops (*loader_ops)(void); @@ -108,9 +116,11 @@ void skl_nhlt_free(struct nhlt_acpi_table *addr); struct nhlt_specific_cfg *skl_get_ep_blob(struct skl *skl, u32 instance, u8 link_type, u8 s_fmt, u8 no_ch, u32 s_rate, u8 dirn); +int skl_get_dmic_geo(struct skl *skl); int skl_nhlt_update_topology_bin(struct skl *skl); int skl_init_dsp(struct skl *skl); int skl_free_dsp(struct skl *skl); int skl_suspend_dsp(struct skl *skl); int skl_resume_dsp(struct skl *skl); +void skl_cleanup_resources(struct skl *skl); #endif /* __SOUND_SOC_SKL_H */ diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 3abf51c07851..05cf809cf9e1 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -1,15 +1,40 @@ config SND_SOC_MEDIATEK - tristate "ASoC support for Mediatek chip" + tristate + +config SND_SOC_MT2701 + tristate "ASoC support for Mediatek MT2701 chip" + depends on ARCH_MEDIATEK + select SND_SOC_MEDIATEK + help + This adds ASoC driver for Mediatek MT2701 boards + that can be used with other codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT2701_CS42448 + tristate "ASoc Audio driver for MT2701 with CS42448 codec" + depends on SND_SOC_MT2701 + select SND_SOC_CS42XX8_I2C + select SND_SOC_BT_SCO + help + This adds ASoC driver for Mediatek MT2701 boards + with the CS42448 codecs. + Select Y if you have such device. + If unsure select "N". + +config SND_SOC_MT8173 + tristate "ASoC support for Mediatek MT8173 chip" depends on ARCH_MEDIATEK + select SND_SOC_MEDIATEK help - This adds ASoC platform driver support for Mediatek chip + This adds ASoC platform driver support for Mediatek MT8173 chip that can be used with other codecs. Select Y if you have such device. Ex: MT8173 config SND_SOC_MT8173_MAX98090 tristate "ASoC Audio driver for MT8173 with MAX98090 codec" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_MAX98090 help This adds ASoC driver for Mediatek MT8173 boards @@ -19,8 +44,9 @@ config SND_SOC_MT8173_MAX98090 config SND_SOC_MT8173_RT5650 tristate "ASoC Audio driver for MT8173 with RT5650 codec" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_RT5645 + select SND_SOC_HDMI_CODEC help This adds ASoC driver for Mediatek MT8173 boards with the RT5650 audio codec. @@ -29,7 +55,7 @@ config SND_SOC_MT8173_RT5650 config SND_SOC_MT8173_RT5650_RT5514 tristate "ASoC Audio driver for MT8173 with RT5650 RT5514 codecs" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_RT5645 select SND_SOC_RT5514 help @@ -40,7 +66,7 @@ config SND_SOC_MT8173_RT5650_RT5514 config SND_SOC_MT8173_RT5650_RT5676 tristate "ASoC Audio driver for MT8173 with RT5650 RT5676 codecs" - depends on SND_SOC_MEDIATEK && I2C + depends on SND_SOC_MT8173 && I2C select SND_SOC_RT5645 select SND_SOC_RT5677 select SND_SOC_HDMI_CODEC diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile index d486860c0a88..6bcab35dc828 100644 --- a/sound/soc/mediatek/Makefile +++ b/sound/soc/mediatek/Makefile @@ -1,7 +1,3 @@ -# MTK Platform Support -obj-$(CONFIG_SND_SOC_MEDIATEK) += mtk-afe-pcm.o -# Machine support -obj-$(CONFIG_SND_SOC_MT8173_MAX98090) += mt8173-max98090.o -obj-$(CONFIG_SND_SOC_MT8173_RT5650) += mt8173-rt5650.o -obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5514) += mt8173-rt5650-rt5514.o -obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5676) += mt8173-rt5650-rt5676.o +obj-$(CONFIG_SND_SOC_MEDIATEK) += common/ +obj-$(CONFIG_SND_SOC_MT2701) += mt2701/ +obj-$(CONFIG_SND_SOC_MT8173) += mt8173/ diff --git a/sound/soc/mediatek/common/Makefile b/sound/soc/mediatek/common/Makefile new file mode 100644 index 000000000000..a55d33bc7b01 --- /dev/null +++ b/sound/soc/mediatek/common/Makefile @@ -0,0 +1,16 @@ +# +# Copyright (C) 2015 MediaTek 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. +# +# 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. +# + +# platform driver +snd-soc-mtk-common-objs := mtk-afe-platform-driver.o mtk-afe-fe-dai.o +obj-$(CONFIG_SND_SOC_MEDIATEK) += snd-soc-mtk-common.o diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.c b/sound/soc/mediatek/common/mtk-afe-fe-dai.c new file mode 100644 index 000000000000..b788791b0a35 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.c @@ -0,0 +1,379 @@ +/* + * mtk-afe-fe-dais.c -- Mediatek afe fe dai operator + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "mtk-afe-fe-dai.h" +#include "mtk-base-afe.h" + +#define AFE_BASE_END_OFFSET 8 + +int mtk_regmap_update_bits(struct regmap *map, int reg, unsigned int mask, + unsigned int val) +{ + if (reg < 0) + return 0; + return regmap_update_bits(map, reg, mask, val); +} + +int mtk_regmap_write(struct regmap *map, int reg, unsigned int val) +{ + if (reg < 0) + return 0; + return regmap_write(map, reg, val); +} + +int mtk_afe_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct snd_pcm_runtime *runtime = substream->runtime; + int memif_num = rtd->cpu_dai->id; + struct mtk_base_afe_memif *memif = &afe->memif[memif_num]; + const struct snd_pcm_hardware *mtk_afe_hardware = afe->mtk_afe_hardware; + int ret; + + memif->substream = substream; + + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); + /* enable agent */ + mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, + 1 << memif->data->agent_disable_shift, + 0 << memif->data->agent_disable_shift); + + snd_soc_set_runtime_hwparams(substream, mtk_afe_hardware); + + /* + * Capture cannot use ping-pong buffer since hw_ptr at IRQ may be + * smaller than period_size due to AFE's internal buffer. + * This easily leads to overrun when avail_min is period_size. + * One more period can hold the possible unread buffer. + */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + int periods_max = mtk_afe_hardware->periods_max; + + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIODS, + 3, periods_max); + if (ret < 0) { + dev_err(afe->dev, "hw_constraint_minmax failed\n"); + return ret; + } + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); + + /* dynamic allocate irq to memif */ + if (memif->irq_usage < 0) { + int irq_id = mtk_dynamic_irq_acquire(afe); + + if (irq_id != afe->irqs_size) { + /* link */ + memif->irq_usage = irq_id; + } else { + dev_err(afe->dev, "%s() error: no more asys irq\n", + __func__); + ret = -EBUSY; + } + } + return ret; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_startup); + +void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int irq_id; + + irq_id = memif->irq_usage; + + mtk_regmap_update_bits(afe->regmap, memif->data->agent_disable_reg, + 1 << memif->data->agent_disable_shift, + 1 << memif->data->agent_disable_shift); + + if (!memif->const_irq) { + mtk_dynamic_irq_release(afe, irq_id); + memif->irq_usage = -1; + memif->substream = NULL; + } +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_shutdown); + +int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int msb_at_bit33 = 0; + int ret, fs = 0; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + msb_at_bit33 = upper_32_bits(substream->runtime->dma_addr) ? 1 : 0; + memif->phys_buf_addr = lower_32_bits(substream->runtime->dma_addr); + memif->buffer_size = substream->runtime->dma_bytes; + + /* start */ + mtk_regmap_write(afe->regmap, memif->data->reg_ofs_base, + memif->phys_buf_addr); + /* end */ + mtk_regmap_write(afe->regmap, + memif->data->reg_ofs_base + AFE_BASE_END_OFFSET, + memif->phys_buf_addr + memif->buffer_size - 1); + + /* set MSB to 33-bit */ + mtk_regmap_update_bits(afe->regmap, memif->data->msb_reg, + 1 << memif->data->msb_shift, + msb_at_bit33 << memif->data->msb_shift); + + /* set channel */ + if (memif->data->mono_shift >= 0) { + unsigned int mono = (params_channels(params) == 1) ? 1 : 0; + + mtk_regmap_update_bits(afe->regmap, memif->data->mono_reg, + 1 << memif->data->mono_shift, + mono << memif->data->mono_shift); + } + + /* set rate */ + if (memif->data->fs_shift < 0) + return 0; + + fs = afe->memif_fs(substream, params_rate(params)); + + if (fs < 0) + return -EINVAL; + + mtk_regmap_update_bits(afe->regmap, memif->data->fs_reg, + memif->data->fs_maskbit << memif->data->fs_shift, + fs << memif->data->fs_shift); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_params); + +int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return snd_pcm_lib_free_pages(substream); +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_hw_free); + +int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime * const runtime = substream->runtime; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + struct mtk_base_afe_irq *irqs = &afe->irqs[memif->irq_usage]; + const struct mtk_base_irq_data *irq_data = irqs->irq_data; + unsigned int counter = runtime->period_size; + int fs; + + dev_dbg(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (memif->data->enable_shift >= 0) + mtk_regmap_update_bits(afe->regmap, + memif->data->enable_reg, + 1 << memif->data->enable_shift, + 1 << memif->data->enable_shift); + + /* set irq counter */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_cnt_reg, + irq_data->irq_cnt_maskbit + << irq_data->irq_cnt_shift, + counter << irq_data->irq_cnt_shift); + + /* set irq fs */ + fs = afe->irq_fs(substream, runtime->rate); + + if (fs < 0) + return -EINVAL; + + mtk_regmap_update_bits(afe->regmap, irq_data->irq_fs_reg, + irq_data->irq_fs_maskbit + << irq_data->irq_fs_shift, + fs << irq_data->irq_fs_shift); + + /* enable interrupt */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + 1 << irq_data->irq_en_shift, + 1 << irq_data->irq_en_shift); + + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + mtk_regmap_update_bits(afe->regmap, memif->data->enable_reg, + 1 << memif->data->enable_shift, 0); + /* disable interrupt */ + mtk_regmap_update_bits(afe->regmap, irq_data->irq_en_reg, + 1 << irq_data->irq_en_shift, + 0 << irq_data->irq_en_shift); + /* and clear pending IRQ */ + mtk_regmap_write(afe->regmap, irq_data->irq_clr_reg, + 1 << irq_data->irq_clr_shift); + return 0; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_trigger); + +int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int hd_audio = 0; + + /* set hd mode */ + switch (substream->runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + hd_audio = 0; + break; + case SNDRV_PCM_FORMAT_S32_LE: + hd_audio = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hd_audio = 1; + break; + default: + dev_err(afe->dev, "%s() error: unsupported format %d\n", + __func__, substream->runtime->format); + break; + } + + mtk_regmap_update_bits(afe->regmap, memif->data->hd_reg, + 1 << memif->data->hd_shift, + hd_audio << memif->data->hd_shift); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_fe_prepare); + +const struct snd_soc_dai_ops mtk_afe_fe_ops = { + .startup = mtk_afe_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mtk_afe_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, +}; +EXPORT_SYMBOL_GPL(mtk_afe_fe_ops); + +static DEFINE_MUTEX(irqs_lock); +int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe) +{ + int i; + + mutex_lock(&afe->irq_alloc_lock); + for (i = 0; i < afe->irqs_size; ++i) { + if (afe->irqs[i].irq_occupyed == 0) { + afe->irqs[i].irq_occupyed = 1; + mutex_unlock(&afe->irq_alloc_lock); + return i; + } + } + mutex_unlock(&afe->irq_alloc_lock); + return afe->irqs_size; +} +EXPORT_SYMBOL_GPL(mtk_dynamic_irq_acquire); + +int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id) +{ + mutex_lock(&afe->irq_alloc_lock); + if (irq_id >= 0 && irq_id < afe->irqs_size) { + afe->irqs[irq_id].irq_occupyed = 0; + mutex_unlock(&afe->irq_alloc_lock); + return 0; + } + mutex_unlock(&afe->irq_alloc_lock); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtk_dynamic_irq_release); + +int mtk_afe_dai_suspend(struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dai->dev); + struct device *dev = afe->dev; + struct regmap *regmap = afe->regmap; + int i; + + if (pm_runtime_status_suspended(dev) || afe->suspended) + return 0; + + if (!afe->reg_back_up) + afe->reg_back_up = + devm_kcalloc(dev, afe->reg_back_up_list_num, + sizeof(unsigned int), GFP_KERNEL); + + for (i = 0; i < afe->reg_back_up_list_num; i++) + regmap_read(regmap, afe->reg_back_up_list[i], + &afe->reg_back_up[i]); + + afe->suspended = true; + afe->runtime_suspend(dev); + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_dai_suspend); + +int mtk_afe_dai_resume(struct snd_soc_dai *dai) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dai->dev); + struct device *dev = afe->dev; + struct regmap *regmap = afe->regmap; + int i = 0; + + if (pm_runtime_status_suspended(dev) || !afe->suspended) + return 0; + + afe->runtime_resume(dev); + + if (!afe->reg_back_up) + dev_dbg(dev, "%s no reg_backup\n", __func__); + + for (i = 0; i < afe->reg_back_up_list_num; i++) + mtk_regmap_write(regmap, afe->reg_back_up_list[i], + afe->reg_back_up[i]); + + afe->suspended = false; + return 0; +} +EXPORT_SYMBOL_GPL(mtk_afe_dai_resume); + +MODULE_DESCRIPTION("Mediatek simple fe dai operator"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/mediatek/common/mtk-afe-fe-dai.h b/sound/soc/mediatek/common/mtk-afe-fe-dai.h new file mode 100644 index 000000000000..28cb17854da1 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-fe-dai.h @@ -0,0 +1,45 @@ +/* + * mtk-afe-fe-dais.h -- Mediatek afe fe dai operator definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MTK_AFE_FE_DAI_H_ +#define _MTK_AFE_FE_DAI_H_ + +struct snd_soc_dai_ops; +struct mtk_base_afe; +struct mtk_base_afe_memif; + +int mtk_afe_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +void mtk_afe_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai); +int mtk_afe_fe_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); +int mtk_afe_fe_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai); + +extern const struct snd_soc_dai_ops mtk_afe_fe_ops; + +int mtk_dynamic_irq_acquire(struct mtk_base_afe *afe); +int mtk_dynamic_irq_release(struct mtk_base_afe *afe, int irq_id); +int mtk_afe_dai_suspend(struct snd_soc_dai *dai); +int mtk_afe_dai_resume(struct snd_soc_dai *dai); + +#endif diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.c b/sound/soc/mediatek/common/mtk-afe-platform-driver.c new file mode 100644 index 000000000000..82d439c15f4e --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.c @@ -0,0 +1,90 @@ +/* + * mtk-afe-platform-driver.c -- Mediatek afe platform driver + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <sound/soc.h> + +#include "mtk-afe-platform-driver.h" +#include "mtk-base-afe.h" + +static snd_pcm_uframes_t mtk_afe_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + const struct mtk_base_memif_data *memif_data = memif->data; + struct regmap *regmap = afe->regmap; + struct device *dev = afe->dev; + int reg_ofs_base = memif_data->reg_ofs_base; + int reg_ofs_cur = memif_data->reg_ofs_cur; + unsigned int hw_ptr = 0, hw_base = 0; + int ret, pcm_ptr_bytes; + + ret = regmap_read(regmap, reg_ofs_cur, &hw_ptr); + if (ret || hw_ptr == 0) { + dev_err(dev, "%s hw_ptr err\n", __func__); + pcm_ptr_bytes = 0; + goto POINTER_RETURN_FRAMES; + } + + ret = regmap_read(regmap, reg_ofs_base, &hw_base); + if (ret || hw_base == 0) { + dev_err(dev, "%s hw_ptr err\n", __func__); + pcm_ptr_bytes = 0; + goto POINTER_RETURN_FRAMES; + } + + pcm_ptr_bytes = hw_ptr - hw_base; + +POINTER_RETURN_FRAMES: + return bytes_to_frames(substream->runtime, pcm_ptr_bytes); +} + +static const struct snd_pcm_ops mtk_afe_pcm_ops = { + .ioctl = snd_pcm_lib_ioctl, + .pointer = mtk_afe_pcm_pointer, +}; + +static int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + size_t size; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + + size = afe->mtk_afe_hardware->buffer_bytes_max; + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, size, size); +} + +static void mtk_afe_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +const struct snd_soc_platform_driver mtk_afe_pcm_platform = { + .ops = &mtk_afe_pcm_ops, + .pcm_new = mtk_afe_pcm_new, + .pcm_free = mtk_afe_pcm_free, +}; +EXPORT_SYMBOL_GPL(mtk_afe_pcm_platform); + +MODULE_DESCRIPTION("Mediatek simple platform driver"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.h b/sound/soc/mediatek/common/mtk-afe-platform-driver.h new file mode 100644 index 000000000000..a973fc9253b4 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.h @@ -0,0 +1,23 @@ +/* + * mtk-afe-platform-driver.h -- Mediatek afe platform driver definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MTK_AFE_PLATFORM_DRIVER_H_ +#define _MTK_AFE_PLATFORM_DRIVER_H_ + +extern const struct snd_soc_platform_driver mtk_afe_pcm_platform; + +#endif + diff --git a/sound/soc/mediatek/common/mtk-base-afe.h b/sound/soc/mediatek/common/mtk-base-afe.h new file mode 100644 index 000000000000..3a78f6f17195 --- /dev/null +++ b/sound/soc/mediatek/common/mtk-base-afe.h @@ -0,0 +1,104 @@ +/* + * mtk-base-afe.h -- Mediatek base afe structure + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MTK_BASE_AFE_H_ +#define _MTK_BASE_AFE_H_ + +struct mtk_base_memif_data { + int id; + const char *name; + int reg_ofs_base; + int reg_ofs_cur; + int fs_reg; + int fs_shift; + int fs_maskbit; + int mono_reg; + int mono_shift; + int enable_reg; + int enable_shift; + int hd_reg; + int hd_shift; + int msb_reg; + int msb_shift; + int agent_disable_reg; + int agent_disable_shift; +}; + +struct mtk_base_irq_data { + int id; + int irq_cnt_reg; + int irq_cnt_shift; + int irq_cnt_maskbit; + int irq_fs_reg; + int irq_fs_shift; + int irq_fs_maskbit; + int irq_en_reg; + int irq_en_shift; + int irq_clr_reg; + int irq_clr_shift; +}; + +struct device; +struct mtk_base_afe_memif; +struct mtk_base_afe_irq; +struct regmap; +struct snd_pcm_substream; +struct snd_soc_dai; + +struct mtk_base_afe { + void __iomem *base_addr; + struct device *dev; + struct regmap *regmap; + struct mutex irq_alloc_lock; /* dynamic alloc irq lock */ + + unsigned int const *reg_back_up_list; + unsigned int *reg_back_up; + unsigned int reg_back_up_list_num; + + int (*runtime_suspend)(struct device *dev); + int (*runtime_resume)(struct device *dev); + bool suspended; + + struct mtk_base_afe_memif *memif; + int memif_size; + struct mtk_base_afe_irq *irqs; + int irqs_size; + + const struct snd_pcm_hardware *mtk_afe_hardware; + int (*memif_fs)(struct snd_pcm_substream *substream, + unsigned int rate); + int (*irq_fs)(struct snd_pcm_substream *substream, + unsigned int rate); + + void *platform_priv; +}; + +struct mtk_base_afe_memif { + unsigned int phys_buf_addr; + int buffer_size; + struct snd_pcm_substream *substream; + const struct mtk_base_memif_data *data; + int irq_usage; + int const_irq; +}; + +struct mtk_base_afe_irq { + const struct mtk_base_irq_data *irq_data; + int irq_occupyed; +}; + +#endif + diff --git a/sound/soc/mediatek/mt2701/Makefile b/sound/soc/mediatek/mt2701/Makefile new file mode 100644 index 000000000000..31c3d04d4942 --- /dev/null +++ b/sound/soc/mediatek/mt2701/Makefile @@ -0,0 +1,19 @@ +# +# Copyright (C) 2015 MediaTek 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. +# +# 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. +# + +# platform driver +snd-soc-mt2701-afe-objs := mt2701-afe-pcm.o mt2701-afe-clock-ctrl.o +obj-$(CONFIG_SND_SOC_MT2701) += snd-soc-mt2701-afe.o + +# machine driver +obj-$(CONFIG_SND_SOC_MT2701_CS42448) += mt2701-cs42448.o diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c new file mode 100644 index 000000000000..b815ecc6bbf6 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.c @@ -0,0 +1,464 @@ +/* + * mt2701-afe-clock-ctrl.c -- Mediatek 2701 afe clock ctrl + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <sound/soc.h> +#include <linux/regmap.h> +#include <linux/pm_runtime.h> + +#include "mt2701-afe-common.h" +#include "mt2701-afe-clock-ctrl.h" + +static const char *aud_clks[MT2701_CLOCK_NUM] = { + [MT2701_AUD_INFRA_SYS_AUDIO] = "infra_sys_audio_clk", + [MT2701_AUD_AUD_MUX1_SEL] = "top_audio_mux1_sel", + [MT2701_AUD_AUD_MUX2_SEL] = "top_audio_mux2_sel", + [MT2701_AUD_AUD_MUX1_DIV] = "top_audio_mux1_div", + [MT2701_AUD_AUD_MUX2_DIV] = "top_audio_mux2_div", + [MT2701_AUD_AUD_48K_TIMING] = "top_audio_48k_timing", + [MT2701_AUD_AUD_44K_TIMING] = "top_audio_44k_timing", + [MT2701_AUD_AUDPLL_MUX_SEL] = "top_audpll_mux_sel", + [MT2701_AUD_APLL_SEL] = "top_apll_sel", + [MT2701_AUD_AUD1PLL_98M] = "top_aud1_pll_98M", + [MT2701_AUD_AUD2PLL_90M] = "top_aud2_pll_90M", + [MT2701_AUD_HADDS2PLL_98M] = "top_hadds2_pll_98M", + [MT2701_AUD_HADDS2PLL_294M] = "top_hadds2_pll_294M", + [MT2701_AUD_AUDPLL] = "top_audpll", + [MT2701_AUD_AUDPLL_D4] = "top_audpll_d4", + [MT2701_AUD_AUDPLL_D8] = "top_audpll_d8", + [MT2701_AUD_AUDPLL_D16] = "top_audpll_d16", + [MT2701_AUD_AUDPLL_D24] = "top_audpll_d24", + [MT2701_AUD_AUDINTBUS] = "top_audintbus_sel", + [MT2701_AUD_CLK_26M] = "clk_26m", + [MT2701_AUD_SYSPLL1_D4] = "top_syspll1_d4", + [MT2701_AUD_AUD_K1_SRC_SEL] = "top_aud_k1_src_sel", + [MT2701_AUD_AUD_K2_SRC_SEL] = "top_aud_k2_src_sel", + [MT2701_AUD_AUD_K3_SRC_SEL] = "top_aud_k3_src_sel", + [MT2701_AUD_AUD_K4_SRC_SEL] = "top_aud_k4_src_sel", + [MT2701_AUD_AUD_K5_SRC_SEL] = "top_aud_k5_src_sel", + [MT2701_AUD_AUD_K6_SRC_SEL] = "top_aud_k6_src_sel", + [MT2701_AUD_AUD_K1_SRC_DIV] = "top_aud_k1_src_div", + [MT2701_AUD_AUD_K2_SRC_DIV] = "top_aud_k2_src_div", + [MT2701_AUD_AUD_K3_SRC_DIV] = "top_aud_k3_src_div", + [MT2701_AUD_AUD_K4_SRC_DIV] = "top_aud_k4_src_div", + [MT2701_AUD_AUD_K5_SRC_DIV] = "top_aud_k5_src_div", + [MT2701_AUD_AUD_K6_SRC_DIV] = "top_aud_k6_src_div", + [MT2701_AUD_AUD_I2S1_MCLK] = "top_aud_i2s1_mclk", + [MT2701_AUD_AUD_I2S2_MCLK] = "top_aud_i2s2_mclk", + [MT2701_AUD_AUD_I2S3_MCLK] = "top_aud_i2s3_mclk", + [MT2701_AUD_AUD_I2S4_MCLK] = "top_aud_i2s4_mclk", + [MT2701_AUD_AUD_I2S5_MCLK] = "top_aud_i2s5_mclk", + [MT2701_AUD_AUD_I2S6_MCLK] = "top_aud_i2s6_mclk", + [MT2701_AUD_ASM_M_SEL] = "top_asm_m_sel", + [MT2701_AUD_ASM_H_SEL] = "top_asm_h_sel", + [MT2701_AUD_UNIVPLL2_D4] = "top_univpll2_d4", + [MT2701_AUD_UNIVPLL2_D2] = "top_univpll2_d2", + [MT2701_AUD_SYSPLL_D5] = "top_syspll_d5", +}; + +int mt2701_init_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i = 0; + + for (i = 0; i < MT2701_CLOCK_NUM; i++) { + afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); + if (IS_ERR(aud_clks[i])) { + dev_warn(afe->dev, "%s devm_clk_get %s fail\n", + __func__, aud_clks[i]); + return PTR_ERR(aud_clks[i]); + } + } + + return 0; +} + +int mt2701_afe_enable_clock(struct mtk_base_afe *afe) +{ + int ret = 0; + + ret = mt2701_turn_on_a1sys_clock(afe); + if (ret) { + dev_err(afe->dev, "%s turn_on_a1sys_clock fail %d\n", + __func__, ret); + return ret; + } + + ret = mt2701_turn_on_a2sys_clock(afe); + if (ret) { + dev_err(afe->dev, "%s turn_on_a2sys_clock fail %d\n", + __func__, ret); + mt2701_turn_off_a1sys_clock(afe); + return ret; + } + + ret = mt2701_turn_on_afe_clock(afe); + if (ret) { + dev_err(afe->dev, "%s turn_on_afe_clock fail %d\n", + __func__, ret); + mt2701_turn_off_a1sys_clock(afe); + mt2701_turn_off_a2sys_clock(afe); + return ret; + } + + regmap_update_bits(afe->regmap, ASYS_TOP_CON, + AUDIO_TOP_CON0_A1SYS_A2SYS_ON, + AUDIO_TOP_CON0_A1SYS_A2SYS_ON); + regmap_update_bits(afe->regmap, AFE_DAC_CON0, + AFE_DAC_CON0_AFE_ON, + AFE_DAC_CON0_AFE_ON); + regmap_write(afe->regmap, PWR2_TOP_CON, + PWR2_TOP_CON_INIT_VAL); + regmap_write(afe->regmap, PWR1_ASM_CON1, + PWR1_ASM_CON1_INIT_VAL); + regmap_write(afe->regmap, PWR2_ASM_CON1, + PWR2_ASM_CON1_INIT_VAL); + + return 0; +} + +void mt2701_afe_disable_clock(struct mtk_base_afe *afe) +{ + mt2701_turn_off_afe_clock(afe); + mt2701_turn_off_a1sys_clock(afe); + mt2701_turn_off_a2sys_clock(afe); + regmap_update_bits(afe->regmap, ASYS_TOP_CON, + AUDIO_TOP_CON0_A1SYS_A2SYS_ON, 0); + regmap_update_bits(afe->regmap, AFE_DAC_CON0, + AFE_DAC_CON0_AFE_ON, 0); +} + +int mt2701_turn_on_a1sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret = 0; + + /* Set Mux */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_MUX1_SEL], ret); + goto A1SYS_CLK_AUD_MUX1_SEL_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL], + afe_priv->clocks[MT2701_AUD_AUD1PLL_98M]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX1_SEL], + aud_clks[MT2701_AUD_AUD1PLL_98M], ret); + goto A1SYS_CLK_AUD_MUX1_SEL_ERR; + } + + /* Set Divider */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, + aud_clks[MT2701_AUD_AUD_MUX1_DIV], + ret); + goto A1SYS_CLK_AUD_MUX1_DIV_ERR; + } + + ret = clk_set_rate(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV], + MT2701_AUD_AUD_MUX1_DIV_RATE); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%d fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX1_DIV], + MT2701_AUD_AUD_MUX1_DIV_RATE, ret); + goto A1SYS_CLK_AUD_MUX1_DIV_ERR; + } + + /* Enable clock gate */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_48K_TIMING], ret); + goto A1SYS_CLK_AUD_48K_ERR; + } + + /* Enable infra audio */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret); + goto A1SYS_CLK_INFRA_ERR; + } + + return 0; + +A1SYS_CLK_INFRA_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); +A1SYS_CLK_AUD_48K_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]); +A1SYS_CLK_AUD_MUX1_DIV_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]); +A1SYS_CLK_AUD_MUX1_SEL_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); + + return ret; +} + +void mt2701_turn_off_a1sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_48K_TIMING]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_DIV]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); +} + +int mt2701_turn_on_a2sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret = 0; + + /* Set Mux */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_MUX2_SEL], ret); + goto A2SYS_CLK_AUD_MUX2_SEL_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL], + afe_priv->clocks[MT2701_AUD_AUD2PLL_90M]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX2_SEL], + aud_clks[MT2701_AUD_AUD2PLL_90M], ret); + goto A2SYS_CLK_AUD_MUX2_SEL_ERR; + } + + /* Set Divider */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_MUX2_DIV], ret); + goto A2SYS_CLK_AUD_MUX2_DIV_ERR; + } + + ret = clk_set_rate(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV], + MT2701_AUD_AUD_MUX2_DIV_RATE); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%d fail %d\n", __func__, + aud_clks[MT2701_AUD_AUD_MUX2_DIV], + MT2701_AUD_AUD_MUX2_DIV_RATE, ret); + goto A2SYS_CLK_AUD_MUX2_DIV_ERR; + } + + /* Enable clock gate */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUD_44K_TIMING], ret); + goto A2SYS_CLK_AUD_44K_ERR; + } + + /* Enable infra audio */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret); + goto A2SYS_CLK_INFRA_ERR; + } + + return 0; + +A2SYS_CLK_INFRA_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); +A2SYS_CLK_AUD_44K_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]); +A2SYS_CLK_AUD_MUX2_DIV_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]); +A2SYS_CLK_AUD_MUX2_SEL_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); + + return ret; +} + +void mt2701_turn_off_a2sys_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_44K_TIMING]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_DIV]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); +} + +int mt2701_turn_on_afe_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret; + + /* enable INFRA_SYS */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_INFRA_SYS_AUDIO], ret); + goto AFE_AUD_INFRA_ERR; + } + + /* Set MT2701_AUD_AUDINTBUS to MT2701_AUD_SYSPLL1_D4 */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_AUDINTBUS]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_AUDINTBUS], ret); + goto AFE_AUD_AUDINTBUS_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_AUDINTBUS], + afe_priv->clocks[MT2701_AUD_SYSPLL1_D4]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_AUDINTBUS], + aud_clks[MT2701_AUD_SYSPLL1_D4], ret); + goto AFE_AUD_AUDINTBUS_ERR; + } + + /* Set MT2701_AUD_ASM_H_SEL to MT2701_AUD_UNIVPLL2_D2 */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_ASM_H_SEL], ret); + goto AFE_AUD_ASM_H_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_ASM_H_SEL], + afe_priv->clocks[MT2701_AUD_UNIVPLL2_D2]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_ASM_H_SEL], + aud_clks[MT2701_AUD_UNIVPLL2_D2], ret); + goto AFE_AUD_ASM_H_ERR; + } + + /* Set MT2701_AUD_ASM_M_SEL to MT2701_AUD_UNIVPLL2_D4 */ + ret = clk_prepare_enable(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]); + if (ret) { + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[MT2701_AUD_ASM_M_SEL], ret); + goto AFE_AUD_ASM_M_ERR; + } + + ret = clk_set_parent(afe_priv->clocks[MT2701_AUD_ASM_M_SEL], + afe_priv->clocks[MT2701_AUD_UNIVPLL2_D4]); + if (ret) { + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", __func__, + aud_clks[MT2701_AUD_ASM_M_SEL], + aud_clks[MT2701_AUD_UNIVPLL2_D4], ret); + goto AFE_AUD_ASM_M_ERR; + } + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_AFE, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_APLL_CK, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A1SYS, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A2SYS, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_AFE_CONN, 0); + + return 0; + +AFE_AUD_ASM_M_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]); +AFE_AUD_ASM_H_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]); +AFE_AUD_AUDINTBUS_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUDINTBUS]); +AFE_AUD_INFRA_ERR: + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + + return ret; +} + +void mt2701_turn_off_afe_clock(struct mtk_base_afe *afe) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_INFRA_SYS_AUDIO]); + + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_AUDINTBUS]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_H_SEL]); + clk_disable_unprepare(afe_priv->clocks[MT2701_AUD_ASM_M_SEL]); + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_AFE, AUDIO_TOP_CON0_PDN_AFE); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, + AUDIO_TOP_CON0_PDN_APLL_CK, + AUDIO_TOP_CON0_PDN_APLL_CK); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A1SYS, + AUDIO_TOP_CON4_PDN_A1SYS); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_A2SYS, + AUDIO_TOP_CON4_PDN_A2SYS); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_AFE_CONN, + AUDIO_TOP_CON4_PDN_AFE_CONN); +} + +void mt2701_mclk_configuration(struct mtk_base_afe *afe, int id, int domain, + int mclk) +{ + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int ret; + int aud_src_div_id = MT2701_AUD_AUD_K1_SRC_DIV + id; + int aud_src_clk_id = MT2701_AUD_AUD_K1_SRC_SEL + id; + + /* Set MCLK Kx_SRC_SEL(domain) */ + ret = clk_prepare_enable(afe_priv->clocks[aud_src_clk_id]); + if (ret) + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[aud_src_clk_id], ret); + + if (domain == 0) { + ret = clk_set_parent(afe_priv->clocks[aud_src_clk_id], + afe_priv->clocks[MT2701_AUD_AUD_MUX1_SEL]); + if (ret) + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", + __func__, aud_clks[aud_src_clk_id], + aud_clks[MT2701_AUD_AUD_MUX1_SEL], ret); + } else { + ret = clk_set_parent(afe_priv->clocks[aud_src_clk_id], + afe_priv->clocks[MT2701_AUD_AUD_MUX2_SEL]); + if (ret) + dev_err(afe->dev, "%s clk_set_parent %s-%s fail %d\n", + __func__, aud_clks[aud_src_clk_id], + aud_clks[MT2701_AUD_AUD_MUX2_SEL], ret); + } + clk_disable_unprepare(afe_priv->clocks[aud_src_clk_id]); + + /* Set MCLK Kx_SRC_DIV(divider) */ + ret = clk_prepare_enable(afe_priv->clocks[aud_src_div_id]); + if (ret) + dev_err(afe->dev, "%s clk_prepare_enable %s fail %d\n", + __func__, aud_clks[aud_src_div_id], ret); + + ret = clk_set_rate(afe_priv->clocks[aud_src_div_id], mclk); + if (ret) + dev_err(afe->dev, "%s clk_set_rate %s-%d fail %d\n", __func__, + aud_clks[aud_src_div_id], mclk, ret); + clk_disable_unprepare(afe_priv->clocks[aud_src_div_id]); +} + +MODULE_DESCRIPTION("MT2701 afe clock control"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h new file mode 100644 index 000000000000..6497d570cf09 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-clock-ctrl.h @@ -0,0 +1,38 @@ +/* + * mt2701-afe-clock-ctrl.h -- Mediatek 2701 afe clock ctrl definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MT2701_AFE_CLOCK_CTRL_H_ +#define _MT2701_AFE_CLOCK_CTRL_H_ + +struct mtk_base_afe; + +int mt2701_init_clock(struct mtk_base_afe *afe); +int mt2701_afe_enable_clock(struct mtk_base_afe *afe); +void mt2701_afe_disable_clock(struct mtk_base_afe *afe); + +int mt2701_turn_on_a1sys_clock(struct mtk_base_afe *afe); +void mt2701_turn_off_a1sys_clock(struct mtk_base_afe *afe); + +int mt2701_turn_on_a2sys_clock(struct mtk_base_afe *afe); +void mt2701_turn_off_a2sys_clock(struct mtk_base_afe *afe); + +int mt2701_turn_on_afe_clock(struct mtk_base_afe *afe); +void mt2701_turn_off_afe_clock(struct mtk_base_afe *afe); + +void mt2701_mclk_configuration(struct mtk_base_afe *afe, int id, int domain, + int mclk); + +#endif diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-common.h b/sound/soc/mediatek/mt2701/mt2701-afe-common.h new file mode 100644 index 000000000000..c19430e98adf --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-common.h @@ -0,0 +1,172 @@ +/* + * mt2701-afe-common.h -- Mediatek 2701 audio driver definitions + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MT_2701_AFE_COMMON_H_ +#define _MT_2701_AFE_COMMON_H_ +#include <sound/soc.h> +#include <linux/clk.h> +#include <linux/regmap.h> +#include "mt2701-reg.h" +#include "../common/mtk-base-afe.h" + +#define MT2701_STREAM_DIR_NUM (SNDRV_PCM_STREAM_LAST + 1) +#define MT2701_PLL_DOMAIN_0_RATE 98304000 +#define MT2701_PLL_DOMAIN_1_RATE 90316800 +#define MT2701_AUD_AUD_MUX1_DIV_RATE (MT2701_PLL_DOMAIN_0_RATE / 2) +#define MT2701_AUD_AUD_MUX2_DIV_RATE (MT2701_PLL_DOMAIN_1_RATE / 2) + +enum { + MT2701_I2S_1, + MT2701_I2S_2, + MT2701_I2S_3, + MT2701_I2S_4, + MT2701_I2S_NUM, +}; + +enum { + MT2701_MEMIF_DL1, + MT2701_MEMIF_DL2, + MT2701_MEMIF_DL3, + MT2701_MEMIF_DL4, + MT2701_MEMIF_DL5, + MT2701_MEMIF_DL_SINGLE_NUM, + MT2701_MEMIF_DLM = MT2701_MEMIF_DL_SINGLE_NUM, + MT2701_MEMIF_UL1, + MT2701_MEMIF_UL2, + MT2701_MEMIF_UL3, + MT2701_MEMIF_UL4, + MT2701_MEMIF_UL5, + MT2701_MEMIF_DLBT, + MT2701_MEMIF_ULBT, + MT2701_MEMIF_NUM, + MT2701_IO_I2S = MT2701_MEMIF_NUM, + MT2701_IO_2ND_I2S, + MT2701_IO_3RD_I2S, + MT2701_IO_4TH_I2S, + MT2701_IO_5TH_I2S, + MT2701_IO_6TH_I2S, + MT2701_IO_MRG, +}; + +enum { + MT2701_IRQ_ASYS_START, + MT2701_IRQ_ASYS_IRQ1 = MT2701_IRQ_ASYS_START, + MT2701_IRQ_ASYS_IRQ2, + MT2701_IRQ_ASYS_IRQ3, + MT2701_IRQ_ASYS_END, +}; + +/* 2701 clock def */ +enum audio_system_clock_type { + MT2701_AUD_INFRA_SYS_AUDIO, + MT2701_AUD_AUD_MUX1_SEL, + MT2701_AUD_AUD_MUX2_SEL, + MT2701_AUD_AUD_MUX1_DIV, + MT2701_AUD_AUD_MUX2_DIV, + MT2701_AUD_AUD_48K_TIMING, + MT2701_AUD_AUD_44K_TIMING, + MT2701_AUD_AUDPLL_MUX_SEL, + MT2701_AUD_APLL_SEL, + MT2701_AUD_AUD1PLL_98M, + MT2701_AUD_AUD2PLL_90M, + MT2701_AUD_HADDS2PLL_98M, + MT2701_AUD_HADDS2PLL_294M, + MT2701_AUD_AUDPLL, + MT2701_AUD_AUDPLL_D4, + MT2701_AUD_AUDPLL_D8, + MT2701_AUD_AUDPLL_D16, + MT2701_AUD_AUDPLL_D24, + MT2701_AUD_AUDINTBUS, + MT2701_AUD_CLK_26M, + MT2701_AUD_SYSPLL1_D4, + MT2701_AUD_AUD_K1_SRC_SEL, + MT2701_AUD_AUD_K2_SRC_SEL, + MT2701_AUD_AUD_K3_SRC_SEL, + MT2701_AUD_AUD_K4_SRC_SEL, + MT2701_AUD_AUD_K5_SRC_SEL, + MT2701_AUD_AUD_K6_SRC_SEL, + MT2701_AUD_AUD_K1_SRC_DIV, + MT2701_AUD_AUD_K2_SRC_DIV, + MT2701_AUD_AUD_K3_SRC_DIV, + MT2701_AUD_AUD_K4_SRC_DIV, + MT2701_AUD_AUD_K5_SRC_DIV, + MT2701_AUD_AUD_K6_SRC_DIV, + MT2701_AUD_AUD_I2S1_MCLK, + MT2701_AUD_AUD_I2S2_MCLK, + MT2701_AUD_AUD_I2S3_MCLK, + MT2701_AUD_AUD_I2S4_MCLK, + MT2701_AUD_AUD_I2S5_MCLK, + MT2701_AUD_AUD_I2S6_MCLK, + MT2701_AUD_ASM_M_SEL, + MT2701_AUD_ASM_H_SEL, + MT2701_AUD_UNIVPLL2_D4, + MT2701_AUD_UNIVPLL2_D2, + MT2701_AUD_SYSPLL_D5, + MT2701_CLOCK_NUM +}; + +static const unsigned int mt2701_afe_backup_list[] = { + AUDIO_TOP_CON0, + AUDIO_TOP_CON4, + AUDIO_TOP_CON5, + ASYS_TOP_CON, + AFE_CONN0, + AFE_CONN1, + AFE_CONN2, + AFE_CONN3, + AFE_CONN15, + AFE_CONN16, + AFE_CONN17, + AFE_CONN18, + AFE_CONN19, + AFE_CONN20, + AFE_CONN21, + AFE_CONN22, + AFE_DAC_CON0, + AFE_MEMIF_PBUF_SIZE, +}; + +struct snd_pcm_substream; +struct mtk_base_irq_data; + +struct mt2701_i2s_data { + int i2s_ctrl_reg; + int i2s_pwn_shift; + int i2s_asrc_fs_shift; + int i2s_asrc_fs_mask; +}; + +enum mt2701_i2s_dir { + I2S_OUT, + I2S_IN, + I2S_DIR_NUM, +}; + +struct mt2701_i2s_path { + int dai_id; + int mclk_rate; + int on[I2S_DIR_NUM]; + int occupied[I2S_DIR_NUM]; + const struct mt2701_i2s_data *i2s_data[2]; +}; + +struct mt2701_afe_private { + struct clk *clocks[MT2701_CLOCK_NUM]; + struct mt2701_i2s_path i2s_path[MT2701_I2S_NUM]; + bool mrg_enable[MT2701_STREAM_DIR_NUM]; +}; + +#endif diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c new file mode 100644 index 000000000000..34a6123480d3 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -0,0 +1,1656 @@ +/* + * Mediatek ALSA SoC AFE platform driver for 2701 + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.com> + * Ir Lian <ir.lian@mediatek.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 and + * only 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. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> + +#include "mt2701-afe-common.h" + +#include "mt2701-afe-clock-ctrl.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" + +#define AFE_IRQ_STATUS_BITS 0xff + +static const struct snd_pcm_hardware mt2701_afe_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 1024, + .period_bytes_max = 1024 * 256, + .periods_min = 4, + .periods_max = 1024, + .buffer_bytes_max = 1024 * 1024 * 16, + .fifo_size = 0, +}; + +struct mt2701_afe_rate { + unsigned int rate; + unsigned int regvalue; +}; + +static const struct mt2701_afe_rate mt2701_afe_i2s_rates[] = { + { .rate = 8000, .regvalue = 0 }, + { .rate = 12000, .regvalue = 1 }, + { .rate = 16000, .regvalue = 2 }, + { .rate = 24000, .regvalue = 3 }, + { .rate = 32000, .regvalue = 4 }, + { .rate = 48000, .regvalue = 5 }, + { .rate = 96000, .regvalue = 6 }, + { .rate = 192000, .regvalue = 7 }, + { .rate = 384000, .regvalue = 8 }, + { .rate = 7350, .regvalue = 16 }, + { .rate = 11025, .regvalue = 17 }, + { .rate = 14700, .regvalue = 18 }, + { .rate = 22050, .regvalue = 19 }, + { .rate = 29400, .regvalue = 20 }, + { .rate = 44100, .regvalue = 21 }, + { .rate = 88200, .regvalue = 22 }, + { .rate = 176400, .regvalue = 23 }, + { .rate = 352800, .regvalue = 24 }, +}; + +static int mt2701_dai_num_to_i2s(struct mtk_base_afe *afe, int num) +{ + int val = num - MT2701_IO_I2S; + + if (val < 0 || val >= MT2701_I2S_NUM) { + dev_err(afe->dev, "%s, num not available, num %d, val %d\n", + __func__, num, val); + return -EINVAL; + } + return val; +} + +static int mt2701_afe_i2s_fs(unsigned int sample_rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mt2701_afe_i2s_rates); i++) + if (mt2701_afe_i2s_rates[i].rate == sample_rate) + return mt2701_afe_i2s_rates[i].regvalue; + + return -EINVAL; +} + +static int mt2701_afe_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + int clk_num = MT2701_AUD_AUD_I2S1_MCLK + i2s_num; + int ret = 0; + + if (i2s_num < 0) + return i2s_num; + + /* enable mclk */ + ret = clk_prepare_enable(afe_priv->clocks[clk_num]); + if (ret) + dev_err(afe->dev, "Failed to enable mclk for I2S: %d\n", + i2s_num); + + return ret; +} + +static int mt2701_afe_i2s_path_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int dir_invert) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + const struct mt2701_i2s_data *i2s_data; + int stream_dir = substream->stream; + + if (i2s_num < 0) + return i2s_num; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (dir_invert) { + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) + stream_dir = SNDRV_PCM_STREAM_CAPTURE; + else + stream_dir = SNDRV_PCM_STREAM_PLAYBACK; + } + i2s_data = i2s_path->i2s_data[stream_dir]; + + i2s_path->on[stream_dir]--; + if (i2s_path->on[stream_dir] < 0) { + dev_warn(afe->dev, "i2s_path->on: %d, dir: %d\n", + i2s_path->on[stream_dir], stream_dir); + i2s_path->on[stream_dir] = 0; + } + if (i2s_path->on[stream_dir]) + return 0; + + /* disable i2s */ + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_I2S_EN, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + 1 << i2s_data->i2s_pwn_shift, + 1 << i2s_data->i2s_pwn_shift); + return 0; +} + +static void mt2701_afe_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + int clk_num = MT2701_AUD_AUD_I2S1_MCLK + i2s_num; + + if (i2s_num < 0) + return; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (i2s_path->occupied[substream->stream]) + i2s_path->occupied[substream->stream] = 0; + else + goto I2S_UNSTART; + + mt2701_afe_i2s_path_shutdown(substream, dai, 0); + + /* need to disable i2s-out path when disable i2s-in */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + mt2701_afe_i2s_path_shutdown(substream, dai, 1); + +I2S_UNSTART: + /* disable mclk */ + clk_disable_unprepare(afe_priv->clocks[clk_num]); +} + +static int mt2701_i2s_path_prepare_enable(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int dir_invert) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + const struct mt2701_i2s_data *i2s_data; + struct snd_pcm_runtime * const runtime = substream->runtime; + int reg, fs, w_len = 1; /* now we support bck 64bits only */ + int stream_dir = substream->stream; + unsigned int mask = 0, val = 0; + + if (i2s_num < 0) + return i2s_num; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + + if (dir_invert) { + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) + stream_dir = SNDRV_PCM_STREAM_CAPTURE; + else + stream_dir = SNDRV_PCM_STREAM_PLAYBACK; + } + i2s_data = i2s_path->i2s_data[stream_dir]; + + /* no need to enable if already done */ + i2s_path->on[stream_dir]++; + + if (i2s_path->on[stream_dir] != 1) + return 0; + + fs = mt2701_afe_i2s_fs(runtime->rate); + + mask = ASYS_I2S_CON_FS | + ASYS_I2S_CON_I2S_COUPLE_MODE | /* 0 */ + ASYS_I2S_CON_I2S_MODE | + ASYS_I2S_CON_WIDE_MODE; + + val = ASYS_I2S_CON_FS_SET(fs) | + ASYS_I2S_CON_I2S_MODE | + ASYS_I2S_CON_WIDE_MODE_SET(w_len); + + if (stream_dir == SNDRV_PCM_STREAM_CAPTURE) { + mask |= ASYS_I2S_IN_PHASE_FIX; + val |= ASYS_I2S_IN_PHASE_FIX; + } + + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, mask, val); + + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) + reg = ASMO_TIMING_CON1; + else + reg = ASMI_TIMING_CON1; + + regmap_update_bits(afe->regmap, reg, + i2s_data->i2s_asrc_fs_mask + << i2s_data->i2s_asrc_fs_shift, + fs << i2s_data->i2s_asrc_fs_shift); + + /* enable i2s */ + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + 1 << i2s_data->i2s_pwn_shift, + 0 << i2s_data->i2s_pwn_shift); + + /* reset i2s hw status before enable */ + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_RESET, ASYS_I2S_CON_RESET); + udelay(1); + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_RESET, 0); + udelay(1); + regmap_update_bits(afe->regmap, i2s_data->i2s_ctrl_reg, + ASYS_I2S_CON_I2S_EN, ASYS_I2S_CON_I2S_EN); + return 0; +} + +static int mt2701_afe_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int clk_domain; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + struct mt2701_i2s_path *i2s_path; + int mclk_rate; + + if (i2s_num < 0) + return i2s_num; + + i2s_path = &afe_priv->i2s_path[i2s_num]; + mclk_rate = i2s_path->mclk_rate; + + if (i2s_path->occupied[substream->stream]) + return -EBUSY; + i2s_path->occupied[substream->stream] = 1; + + if (MT2701_PLL_DOMAIN_0_RATE % mclk_rate == 0) { + clk_domain = 0; + } else if (MT2701_PLL_DOMAIN_1_RATE % mclk_rate == 0) { + clk_domain = 1; + } else { + dev_err(dai->dev, "%s() bad mclk rate %d\n", + __func__, mclk_rate); + return -EINVAL; + } + mt2701_mclk_configuration(afe, i2s_num, clk_domain, mclk_rate); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mt2701_i2s_path_prepare_enable(substream, dai, 0); + } else { + /* need to enable i2s-out path when enable i2s-in */ + /* prepare for another direction "out" */ + mt2701_i2s_path_prepare_enable(substream, dai, 1); + /* prepare for "in" */ + mt2701_i2s_path_prepare_enable(substream, dai, 0); + } + + return 0; +} + +static int mt2701_afe_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dai->dev); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + int i2s_num = mt2701_dai_num_to_i2s(afe, dai->id); + + if (i2s_num < 0) + return i2s_num; + + /* mclk */ + if (dir == SND_SOC_CLOCK_IN) { + dev_warn(dai->dev, + "%s() warning: mt2701 doesn't support mclk input\n", + __func__); + return -EINVAL; + } + afe_priv->i2s_path[i2s_num].mclk_rate = freq; + return 0; +} + +static int mt2701_btmrg_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_MRGIF, 0); + + afe_priv->mrg_enable[substream->stream] = 1; + return 0; +} + +static int mt2701_btmrg_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int stream_fs; + u32 val, msk; + + stream_fs = params_rate(params); + + if ((stream_fs != 8000) && (stream_fs != 16000)) { + dev_err(afe->dev, "%s() btmgr not supprt this stream_fs %d\n", + __func__, stream_fs); + return -EINVAL; + } + + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_I2S_MODE_MASK, + AFE_MRGIF_CON_I2S_MODE_32K); + + val = AFE_DAIBT_CON0_BT_FUNC_EN | AFE_DAIBT_CON0_BT_FUNC_RDY + | AFE_DAIBT_CON0_MRG_USE; + msk = val; + + if (stream_fs == 16000) + val |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN; + + msk |= AFE_DAIBT_CON0_BT_WIDE_MODE_EN; + + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, msk, val); + + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, + AFE_DAIBT_CON0_DAIBT_EN, + AFE_DAIBT_CON0_DAIBT_EN); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_I2S_EN, + AFE_MRGIF_CON_MRG_I2S_EN); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_EN, + AFE_MRGIF_CON_MRG_EN); + return 0; +} + +static void mt2701_btmrg_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt2701_afe_private *afe_priv = afe->platform_priv; + + /* if the other direction stream is not occupied */ + if (!afe_priv->mrg_enable[!substream->stream]) { + regmap_update_bits(afe->regmap, AFE_DAIBT_CON0, + AFE_DAIBT_CON0_DAIBT_EN, 0); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_EN, 0); + regmap_update_bits(afe->regmap, AFE_MRGIF_CON, + AFE_MRGIF_CON_MRG_I2S_EN, 0); + regmap_update_bits(afe->regmap, AUDIO_TOP_CON4, + AUDIO_TOP_CON4_PDN_MRGIF, + AUDIO_TOP_CON4_PDN_MRGIF); + } + afe_priv->mrg_enable[substream->stream] = 0; +} + +static int mt2701_simple_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int stream_dir = substream->stream; + int memif_num = rtd->cpu_dai->id; + struct mtk_base_afe_memif *memif_tmp; + + /* can't run single DL & DLM at the same time */ + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) { + memif_tmp = &afe->memif[MT2701_MEMIF_DLM]; + if (memif_tmp->substream) { + dev_warn(afe->dev, "%s memif is not available, stream_dir %d, memif_num %d\n", + __func__, stream_dir, memif_num); + return -EBUSY; + } + } + return mtk_afe_fe_startup(substream, dai); +} + +static int mt2701_simple_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int stream_dir = substream->stream; + + /* single DL use PAIR_INTERLEAVE */ + if (stream_dir == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_MASK, + AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE); + } + return mtk_afe_fe_hw_params(substream, params, dai); +} + +static int mt2701_dlm_fe_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif_tmp; + const struct mtk_base_memif_data *memif_data; + int i; + + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_tmp = &afe->memif[i]; + if (memif_tmp->substream) + return -EBUSY; + } + + /* enable agent for all signal DL (due to hw design) */ + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_data = afe->memif[i].data; + regmap_update_bits(afe->regmap, + memif_data->agent_disable_reg, + 1 << memif_data->agent_disable_shift, + 0 << memif_data->agent_disable_shift); + } + + return mtk_afe_fe_startup(substream, dai); +} + +static void mt2701_dlm_fe_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + const struct mtk_base_memif_data *memif_data; + int i; + + for (i = MT2701_MEMIF_DL1; i < MT2701_MEMIF_DL_SINGLE_NUM; ++i) { + memif_data = afe->memif[i].data; + regmap_update_bits(afe->regmap, + memif_data->agent_disable_reg, + 1 << memif_data->agent_disable_shift, + 1 << memif_data->agent_disable_shift); + } + return mtk_afe_fe_shutdown(substream, dai); +} + +static int mt2701_dlm_fe_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + int channels = params_channels(params); + + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_MASK, + AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE); + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK, + AFE_MEMIF_PBUF_SIZE_DLM_32BYTES); + regmap_update_bits(afe->regmap, + AFE_MEMIF_PBUF_SIZE, + AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK, + AFE_MEMIF_PBUF_SIZE_DLM_CH(channels)); + + return mtk_afe_fe_hw_params(substream, params, dai); +} + +static int mt2701_dlm_fe_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif_tmp = &afe->memif[MT2701_MEMIF_DL1]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg, + 1 << memif_tmp->data->enable_shift, + 1 << memif_tmp->data->enable_shift); + mtk_afe_fe_trigger(substream, cmd, dai); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + mtk_afe_fe_trigger(substream, cmd, dai); + regmap_update_bits(afe->regmap, memif_tmp->data->enable_reg, + 1 << memif_tmp->data->enable_shift, 0); + + return 0; + default: + return -EINVAL; + } +} + +static int mt2701_memif_fs(struct snd_pcm_substream *substream, + unsigned int rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int fs; + + if (rtd->cpu_dai->id != MT2701_MEMIF_ULBT) + fs = mt2701_afe_i2s_fs(rate); + else + fs = (rate == 16000 ? 1 : 0); + return fs; +} + +static int mt2701_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) +{ + return mt2701_afe_i2s_fs(rate); +} + +/* FE DAIs */ +static const struct snd_soc_dai_ops mt2701_single_memif_dai_ops = { + .startup = mt2701_simple_fe_startup, + .shutdown = mtk_afe_fe_shutdown, + .hw_params = mt2701_simple_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mtk_afe_fe_trigger, + +}; + +static const struct snd_soc_dai_ops mt2701_dlm_memif_dai_ops = { + .startup = mt2701_dlm_fe_startup, + .shutdown = mt2701_dlm_fe_shutdown, + .hw_params = mt2701_dlm_fe_hw_params, + .hw_free = mtk_afe_fe_hw_free, + .prepare = mtk_afe_fe_prepare, + .trigger = mt2701_dlm_fe_trigger, +}; + +/* I2S BE DAIs */ +static const struct snd_soc_dai_ops mt2701_afe_i2s_ops = { + .startup = mt2701_afe_i2s_startup, + .shutdown = mt2701_afe_i2s_shutdown, + .prepare = mt2701_afe_i2s_prepare, + .set_sysclk = mt2701_afe_i2s_set_sysclk, +}; + +/* MRG BE DAIs */ +static struct snd_soc_dai_ops mt2701_btmrg_ops = { + .startup = mt2701_btmrg_startup, + .shutdown = mt2701_btmrg_shutdown, + .hw_params = mt2701_btmrg_hw_params, +}; + +static struct snd_soc_dai_driver mt2701_afe_pcm_dais[] = { + /* FE DAIs: memory intefaces to CPU */ + { + .name = "PCM_multi", + .id = MT2701_MEMIF_DLM, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DLM", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_dlm_memif_dai_ops, + }, + { + .name = "PCM0", + .id = MT2701_MEMIF_UL1, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "UL1", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM1", + .id = MT2701_MEMIF_UL2, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "UL2", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM_BT_DL", + .id = MT2701_MEMIF_DLBT, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .playback = { + .stream_name = "DLBT", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_single_memif_dai_ops, + }, + { + .name = "PCM_BT_UL", + .id = MT2701_MEMIF_ULBT, + .suspend = mtk_afe_dai_suspend, + .resume = mtk_afe_dai_resume, + .capture = { + .stream_name = "ULBT", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_single_memif_dai_ops, + }, + /* BE DAIs */ + { + .name = "I2S0", + .id = MT2701_IO_I2S, + .playback = { + .stream_name = "I2S0 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .capture = { + .stream_name = "I2S0 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S1", + .id = MT2701_IO_2ND_I2S, + .playback = { + .stream_name = "I2S1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S2", + .id = MT2701_IO_3RD_I2S, + .playback = { + .stream_name = "I2S2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "I2S3", + .id = MT2701_IO_4TH_I2S, + .playback = { + .stream_name = "I2S3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .capture = { + .stream_name = "I2S3 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE + | SNDRV_PCM_FMTBIT_S24_LE + | SNDRV_PCM_FMTBIT_S32_LE) + }, + .ops = &mt2701_afe_i2s_ops, + .symmetric_rates = 1, + }, + { + .name = "MRG BT", + .id = MT2701_IO_MRG, + .playback = { + .stream_name = "BT Playback", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "BT Capture", + .channels_min = 1, + .channels_max = 1, + .rates = (SNDRV_PCM_RATE_8000 + | SNDRV_PCM_RATE_16000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &mt2701_btmrg_ops, + .symmetric_rates = 1, + } +}; + +static const struct snd_kcontrol_new mt2701_afe_o00_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I00 Switch", AFE_CONN0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o01_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I01 Switch", AFE_CONN1, 1, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o02_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I02 Switch", AFE_CONN2, 2, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o03_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 3, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o14_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I26 Switch", AFE_CONN14, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o15_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I12 Switch", AFE_CONN15, 12, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o16_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I13 Switch", AFE_CONN16, 13, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o17_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I14 Switch", AFE_CONN17, 14, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o18_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I15 Switch", AFE_CONN18, 15, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o19_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I16 Switch", AFE_CONN19, 16, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o20_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN20, 17, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o21_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN21, 18, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o22_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I19 Switch", AFE_CONN22, 19, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o23_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I20 Switch", AFE_CONN23, 20, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o24_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I21 Switch", AFE_CONN24, 21, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_o31_mix[] = { + SOC_DAPM_SINGLE_AUTODISABLE("I35 Switch", AFE_CONN41, 9, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_i02_mix[] = { + SOC_DAPM_SINGLE("I2S0 Switch", SND_SOC_NOPM, 0, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s0[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S0 Out Switch", + ASYS_I2SO1_CON, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s1[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S1 Out Switch", + ASYS_I2SO2_CON, 26, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s2[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S2 Out Switch", + PWR2_TOP_CON, 17, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s3[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S3 Out Switch", + PWR2_TOP_CON, 18, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_i2s4[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Multich I2S4 Out Switch", + PWR2_TOP_CON, 19, 1, 0), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc0[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc0 out Switch", AUDIO_TOP_CON4, 14, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc1[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc1 out Switch", AUDIO_TOP_CON4, 15, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc2[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc2 out Switch", PWR2_TOP_CON, 6, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc3[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc3 out Switch", PWR2_TOP_CON, 7, 1, + 1), +}; + +static const struct snd_kcontrol_new mt2701_afe_multi_ch_out_asrc4[] = { + SOC_DAPM_SINGLE_AUTODISABLE("Asrc4 out Switch", PWR2_TOP_CON, 8, 1, + 1), +}; + +static const struct snd_soc_dapm_widget mt2701_afe_pcm_widgets[] = { + /* inter-connections */ + SND_SOC_DAPM_MIXER("I00", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I01", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I02", SND_SOC_NOPM, 0, 0, mt2701_afe_i02_mix, + ARRAY_SIZE(mt2701_afe_i02_mix)), + SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I12", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I13", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I14", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I15", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I16", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I17", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I19", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I26", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("I35", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("O00", SND_SOC_NOPM, 0, 0, mt2701_afe_o00_mix, + ARRAY_SIZE(mt2701_afe_o00_mix)), + SND_SOC_DAPM_MIXER("O01", SND_SOC_NOPM, 0, 0, mt2701_afe_o01_mix, + ARRAY_SIZE(mt2701_afe_o01_mix)), + SND_SOC_DAPM_MIXER("O02", SND_SOC_NOPM, 0, 0, mt2701_afe_o02_mix, + ARRAY_SIZE(mt2701_afe_o02_mix)), + SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0, mt2701_afe_o03_mix, + ARRAY_SIZE(mt2701_afe_o03_mix)), + SND_SOC_DAPM_MIXER("O14", SND_SOC_NOPM, 0, 0, mt2701_afe_o14_mix, + ARRAY_SIZE(mt2701_afe_o14_mix)), + SND_SOC_DAPM_MIXER("O15", SND_SOC_NOPM, 0, 0, mt2701_afe_o15_mix, + ARRAY_SIZE(mt2701_afe_o15_mix)), + SND_SOC_DAPM_MIXER("O16", SND_SOC_NOPM, 0, 0, mt2701_afe_o16_mix, + ARRAY_SIZE(mt2701_afe_o16_mix)), + SND_SOC_DAPM_MIXER("O17", SND_SOC_NOPM, 0, 0, mt2701_afe_o17_mix, + ARRAY_SIZE(mt2701_afe_o17_mix)), + SND_SOC_DAPM_MIXER("O18", SND_SOC_NOPM, 0, 0, mt2701_afe_o18_mix, + ARRAY_SIZE(mt2701_afe_o18_mix)), + SND_SOC_DAPM_MIXER("O19", SND_SOC_NOPM, 0, 0, mt2701_afe_o19_mix, + ARRAY_SIZE(mt2701_afe_o19_mix)), + SND_SOC_DAPM_MIXER("O20", SND_SOC_NOPM, 0, 0, mt2701_afe_o20_mix, + ARRAY_SIZE(mt2701_afe_o20_mix)), + SND_SOC_DAPM_MIXER("O21", SND_SOC_NOPM, 0, 0, mt2701_afe_o21_mix, + ARRAY_SIZE(mt2701_afe_o21_mix)), + SND_SOC_DAPM_MIXER("O22", SND_SOC_NOPM, 0, 0, mt2701_afe_o22_mix, + ARRAY_SIZE(mt2701_afe_o22_mix)), + SND_SOC_DAPM_MIXER("O31", SND_SOC_NOPM, 0, 0, mt2701_afe_o31_mix, + ARRAY_SIZE(mt2701_afe_o31_mix)), + + SND_SOC_DAPM_MIXER("I12I13", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s0, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s0)), + SND_SOC_DAPM_MIXER("I14I15", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s1, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s1)), + SND_SOC_DAPM_MIXER("I16I17", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s2, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s2)), + SND_SOC_DAPM_MIXER("I18I19", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_i2s3, + ARRAY_SIZE(mt2701_afe_multi_ch_out_i2s3)), + + SND_SOC_DAPM_MIXER("ASRC_O0", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc0, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc0)), + SND_SOC_DAPM_MIXER("ASRC_O1", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc1, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc1)), + SND_SOC_DAPM_MIXER("ASRC_O2", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc2, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc2)), + SND_SOC_DAPM_MIXER("ASRC_O3", SND_SOC_NOPM, 0, 0, + mt2701_afe_multi_ch_out_asrc3, + ARRAY_SIZE(mt2701_afe_multi_ch_out_asrc3)), +}; + +static const struct snd_soc_dapm_route mt2701_afe_pcm_routes[] = { + {"I12", NULL, "DL1"}, + {"I13", NULL, "DL1"}, + {"I35", NULL, "DLBT"}, + + {"I2S0 Playback", NULL, "O15"}, + {"I2S0 Playback", NULL, "O16"}, + + {"I2S1 Playback", NULL, "O17"}, + {"I2S1 Playback", NULL, "O18"}, + {"I2S2 Playback", NULL, "O19"}, + {"I2S2 Playback", NULL, "O20"}, + {"I2S3 Playback", NULL, "O21"}, + {"I2S3 Playback", NULL, "O22"}, + {"BT Playback", NULL, "O31"}, + + {"UL1", NULL, "O00"}, + {"UL1", NULL, "O01"}, + {"UL2", NULL, "O02"}, + {"UL2", NULL, "O03"}, + {"ULBT", NULL, "O14"}, + + {"I00", NULL, "I2S0 Capture"}, + {"I01", NULL, "I2S0 Capture"}, + + {"I02", NULL, "I2S1 Capture"}, + {"I03", NULL, "I2S1 Capture"}, + /* I02,03 link to UL2, also need to open I2S0 */ + {"I02", "I2S0 Switch", "I2S0 Capture"}, + + {"I26", NULL, "BT Capture"}, + + {"ASRC_O0", "Asrc0 out Switch", "DLM"}, + {"ASRC_O1", "Asrc1 out Switch", "DLM"}, + {"ASRC_O2", "Asrc2 out Switch", "DLM"}, + {"ASRC_O3", "Asrc3 out Switch", "DLM"}, + + {"I12I13", "Multich I2S0 Out Switch", "ASRC_O0"}, + {"I14I15", "Multich I2S1 Out Switch", "ASRC_O1"}, + {"I16I17", "Multich I2S2 Out Switch", "ASRC_O2"}, + {"I18I19", "Multich I2S3 Out Switch", "ASRC_O3"}, + + { "I12", NULL, "I12I13" }, + { "I13", NULL, "I12I13" }, + { "I14", NULL, "I14I15" }, + { "I15", NULL, "I14I15" }, + { "I16", NULL, "I16I17" }, + { "I17", NULL, "I16I17" }, + { "I18", NULL, "I18I19" }, + { "I19", NULL, "I18I19" }, + + { "O00", "I00 Switch", "I00" }, + { "O01", "I01 Switch", "I01" }, + { "O02", "I02 Switch", "I02" }, + { "O03", "I03 Switch", "I03" }, + { "O14", "I26 Switch", "I26" }, + { "O15", "I12 Switch", "I12" }, + { "O16", "I13 Switch", "I13" }, + { "O17", "I14 Switch", "I14" }, + { "O18", "I15 Switch", "I15" }, + { "O19", "I16 Switch", "I16" }, + { "O20", "I17 Switch", "I17" }, + { "O21", "I18 Switch", "I18" }, + { "O22", "I19 Switch", "I19" }, + { "O31", "I35 Switch", "I35" }, + +}; + +static const struct snd_soc_component_driver mt2701_afe_pcm_dai_component = { + .name = "mt2701-afe-pcm-dai", + .dapm_widgets = mt2701_afe_pcm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_afe_pcm_widgets), + .dapm_routes = mt2701_afe_pcm_routes, + .num_dapm_routes = ARRAY_SIZE(mt2701_afe_pcm_routes), +}; + +static const struct mtk_base_memif_data memif_data[MT2701_MEMIF_NUM] = { + { + .name = "DL1", + .id = MT2701_MEMIF_DL1, + .reg_ofs_base = AFE_DL1_BASE, + .reg_ofs_cur = AFE_DL1_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 16, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 1, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 6, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL2", + .id = MT2701_MEMIF_DL2, + .reg_ofs_base = AFE_DL2_BASE, + .reg_ofs_cur = AFE_DL2_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 5, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 17, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 2, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 2, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 7, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL3", + .id = MT2701_MEMIF_DL3, + .reg_ofs_base = AFE_DL3_BASE, + .reg_ofs_cur = AFE_DL3_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 18, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 3, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 4, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 8, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL4", + .id = MT2701_MEMIF_DL4, + .reg_ofs_base = AFE_DL4_BASE, + .reg_ofs_cur = AFE_DL4_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 15, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 19, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 4, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 6, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 9, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DL5", + .id = MT2701_MEMIF_DL5, + .reg_ofs_base = AFE_DL5_BASE, + .reg_ofs_cur = AFE_DL5_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 20, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 20, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 5, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 8, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 10, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DLM", + .id = MT2701_MEMIF_DLM, + .reg_ofs_base = AFE_DLMCH_BASE, + .reg_ofs_cur = AFE_DLMCH_CUR, + .fs_reg = AFE_DAC_CON1, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = -1, + .mono_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 7, + .hd_reg = AFE_MEMIF_PBUF_SIZE, + .hd_shift = 28, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 12, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL1", + .id = MT2701_MEMIF_UL1, + .reg_ofs_base = AFE_VUL_BASE, + .reg_ofs_cur = AFE_VUL_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 0, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 0, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 10, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 0, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL2", + .id = MT2701_MEMIF_UL2, + .reg_ofs_base = AFE_UL2_BASE, + .reg_ofs_cur = AFE_UL2_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 5, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 2, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 11, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 2, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 1, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL3", + .id = MT2701_MEMIF_UL3, + .reg_ofs_base = AFE_UL3_BASE, + .reg_ofs_cur = AFE_UL3_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 4, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 12, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 0, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 2, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL4", + .id = MT2701_MEMIF_UL4, + .reg_ofs_base = AFE_UL4_BASE, + .reg_ofs_cur = AFE_UL4_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 15, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 6, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 13, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 6, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 3, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "UL5", + .id = MT2701_MEMIF_UL5, + .reg_ofs_base = AFE_UL5_BASE, + .reg_ofs_cur = AFE_UL5_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 20, + .mono_reg = AFE_DAC_CON4, + .mono_shift = 8, + .fs_maskbit = 0x1f, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 14, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 8, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 4, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "DLBT", + .id = MT2701_MEMIF_DLBT, + .reg_ofs_base = AFE_ARB1_BASE, + .reg_ofs_cur = AFE_ARB1_CUR, + .fs_reg = AFE_DAC_CON3, + .fs_shift = 10, + .fs_maskbit = 0x1f, + .mono_reg = AFE_DAC_CON3, + .mono_shift = 22, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 8, + .hd_reg = AFE_MEMIF_HD_CON0, + .hd_shift = 14, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 13, + .msb_reg = -1, + .msb_shift = -1, + }, + { + .name = "ULBT", + .id = MT2701_MEMIF_ULBT, + .reg_ofs_base = AFE_DAI_BASE, + .reg_ofs_cur = AFE_DAI_CUR, + .fs_reg = AFE_DAC_CON2, + .fs_shift = 30, + .fs_maskbit = 0x1, + .mono_reg = -1, + .mono_shift = -1, + .enable_reg = AFE_DAC_CON0, + .enable_shift = 17, + .hd_reg = AFE_MEMIF_HD_CON1, + .hd_shift = 20, + .agent_disable_reg = AUDIO_TOP_CON5, + .agent_disable_shift = 16, + .msb_reg = -1, + .msb_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[MT2701_IRQ_ASYS_END] = { + { + .id = MT2701_IRQ_ASYS_IRQ1, + .irq_cnt_reg = ASYS_IRQ1_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ1_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ1_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 0, + }, + { + .id = MT2701_IRQ_ASYS_IRQ2, + .irq_cnt_reg = ASYS_IRQ2_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ2_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ2_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 1, + }, + { + .id = MT2701_IRQ_ASYS_IRQ3, + .irq_cnt_reg = ASYS_IRQ3_CON, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0xffffff, + .irq_fs_reg = ASYS_IRQ3_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0x1f, + .irq_en_reg = ASYS_IRQ3_CON, + .irq_en_shift = 31, + .irq_clr_reg = ASYS_IRQ_CLR, + .irq_clr_shift = 2, + } +}; + +static const struct mt2701_i2s_data mt2701_i2s_data[MT2701_I2S_NUM][2] = { + { + { + .i2s_ctrl_reg = ASYS_I2SO1_CON, + .i2s_pwn_shift = 6, + .i2s_asrc_fs_shift = 0, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN1_CON, + .i2s_pwn_shift = 0, + .i2s_asrc_fs_shift = 0, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, + { + { + .i2s_ctrl_reg = ASYS_I2SO2_CON, + .i2s_pwn_shift = 7, + .i2s_asrc_fs_shift = 5, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN2_CON, + .i2s_pwn_shift = 1, + .i2s_asrc_fs_shift = 5, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, + { + { + .i2s_ctrl_reg = ASYS_I2SO3_CON, + .i2s_pwn_shift = 8, + .i2s_asrc_fs_shift = 10, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN3_CON, + .i2s_pwn_shift = 2, + .i2s_asrc_fs_shift = 10, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, + { + { + .i2s_ctrl_reg = ASYS_I2SO4_CON, + .i2s_pwn_shift = 9, + .i2s_asrc_fs_shift = 15, + .i2s_asrc_fs_mask = 0x1f, + + }, + { + .i2s_ctrl_reg = ASYS_I2SIN4_CON, + .i2s_pwn_shift = 3, + .i2s_asrc_fs_shift = 15, + .i2s_asrc_fs_mask = 0x1f, + + }, + }, +}; + +static const struct regmap_config mt2701_afe_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = AFE_END_ADDR, + .cache_type = REGCACHE_NONE, +}; + +static irqreturn_t mt2701_asys_isr(int irq_id, void *dev) +{ + int id; + struct mtk_base_afe *afe = dev; + struct mtk_base_afe_memif *memif; + struct mtk_base_afe_irq *irq; + u32 status; + + regmap_read(afe->regmap, ASYS_IRQ_STATUS, &status); + regmap_write(afe->regmap, ASYS_IRQ_CLR, status); + + for (id = 0; id < MT2701_MEMIF_NUM; ++id) { + memif = &afe->memif[id]; + if (memif->irq_usage < 0) + continue; + irq = &afe->irqs[memif->irq_usage]; + if (status & 1 << (irq->irq_data->irq_clr_shift)) + snd_pcm_period_elapsed(memif->substream); + } + return IRQ_HANDLED; +} + +static int mt2701_afe_runtime_suspend(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + + mt2701_afe_disable_clock(afe); + return 0; +} + +static int mt2701_afe_runtime_resume(struct device *dev) +{ + struct mtk_base_afe *afe = dev_get_drvdata(dev); + + return mt2701_afe_enable_clock(afe); +} + +static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) +{ + int ret, i; + unsigned int irq_id; + struct mtk_base_afe *afe; + struct mt2701_afe_private *afe_priv; + struct resource *res; + struct device *dev; + + ret = 0; + afe = devm_kzalloc(&pdev->dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), + GFP_KERNEL); + if (!afe->platform_priv) + return -ENOMEM; + afe_priv = afe->platform_priv; + + afe->dev = &pdev->dev; + dev = afe->dev; + + irq_id = platform_get_irq(pdev, 0); + if (!irq_id) { + dev_err(dev, "%s no irq found\n", dev->of_node->name); + return -ENXIO; + } + ret = devm_request_irq(dev, irq_id, mt2701_asys_isr, + IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); + if (ret) { + dev_err(dev, "could not request_irq for asys-isr\n"); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + afe->base_addr = devm_ioremap_resource(&pdev->dev, res); + + if (IS_ERR(afe->base_addr)) + return PTR_ERR(afe->base_addr); + + afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, + &mt2701_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return PTR_ERR(afe->regmap); + + mutex_init(&afe->irq_alloc_lock); + + /* memif initialize */ + afe->memif_size = MT2701_MEMIF_NUM; + afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif), + GFP_KERNEL); + + if (!afe->memif) + return -ENOMEM; + + for (i = 0; i < afe->memif_size; i++) { + afe->memif[i].data = &memif_data[i]; + afe->memif[i].irq_usage = -1; + } + + /* irq initialize */ + afe->irqs_size = MT2701_IRQ_ASYS_END; + afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs), + GFP_KERNEL); + + if (!afe->irqs) + return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) + afe->irqs[i].irq_data = &irq_data[i]; + + /* I2S initialize */ + for (i = 0; i < MT2701_I2S_NUM; i++) { + afe_priv->i2s_path[i].i2s_data[I2S_OUT] + = &mt2701_i2s_data[i][I2S_OUT]; + afe_priv->i2s_path[i].i2s_data[I2S_IN] + = &mt2701_i2s_data[i][I2S_IN]; + } + + afe->mtk_afe_hardware = &mt2701_afe_hardware; + afe->memif_fs = mt2701_memif_fs; + afe->irq_fs = mt2701_irq_fs; + + afe->reg_back_up_list = mt2701_afe_backup_list; + afe->reg_back_up_list_num = ARRAY_SIZE(mt2701_afe_backup_list); + afe->runtime_resume = mt2701_afe_runtime_resume; + afe->runtime_suspend = mt2701_afe_runtime_suspend; + + /* initial audio related clock */ + ret = mt2701_init_clock(afe); + if (ret) { + dev_err(dev, "init clock error\n"); + return ret; + } + + platform_set_drvdata(pdev, afe); + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) + goto err_pm_disable; + + ret = snd_soc_register_platform(&pdev->dev, &mtk_afe_pcm_platform); + if (ret) { + dev_warn(dev, "err_platform\n"); + goto err_platform; + } + + ret = snd_soc_register_component(&pdev->dev, + &mt2701_afe_pcm_dai_component, + mt2701_afe_pcm_dais, + ARRAY_SIZE(mt2701_afe_pcm_dais)); + if (ret) { + dev_warn(dev, "err_dai_component\n"); + goto err_dai_component; + } + + mt2701_afe_runtime_resume(&pdev->dev); + + return 0; + +err_dai_component: + snd_soc_unregister_component(&pdev->dev); + +err_platform: + snd_soc_unregister_platform(&pdev->dev); + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int mt2701_afe_pcm_dev_remove(struct platform_device *pdev) +{ + struct mtk_base_afe *afe = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + mt2701_afe_runtime_suspend(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + snd_soc_unregister_platform(&pdev->dev); + /* disable afe clock */ + mt2701_afe_disable_clock(afe); + return 0; +} + +static const struct of_device_id mt2701_afe_pcm_dt_match[] = { + { .compatible = "mediatek,mt2701-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mt2701_afe_pcm_dt_match); + +static const struct dev_pm_ops mt2701_afe_pm_ops = { + SET_RUNTIME_PM_OPS(mt2701_afe_runtime_suspend, + mt2701_afe_runtime_resume, NULL) +}; + +static struct platform_driver mt2701_afe_pcm_driver = { + .driver = { + .name = "mt2701-audio", + .of_match_table = mt2701_afe_pcm_dt_match, +#ifdef CONFIG_PM + .pm = &mt2701_afe_pm_ops, +#endif + }, + .probe = mt2701_afe_pcm_dev_probe, + .remove = mt2701_afe_pcm_dev_remove, +}; + +module_platform_driver(mt2701_afe_pcm_driver); + +MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver for 2701"); +MODULE_AUTHOR("Garlic Tseng <garlic.tseng@mediatek.com>"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/mediatek/mt2701/mt2701-cs42448.c b/sound/soc/mediatek/mt2701/mt2701-cs42448.c new file mode 100644 index 000000000000..1e7e8d43fd8a --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-cs42448.c @@ -0,0 +1,412 @@ +/* + * mt2701-cs42448.c -- MT2701 CS42448 ALSA SoC machine driver + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Ir Lian <ir.lian@mediatek.com> + * Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/pinctrl/consumer.h> +#include <linux/of_gpio.h> + +#include "mt2701-afe-common.h" + +struct mt2701_cs42448_private { + int i2s1_in_mux; + int i2s1_in_mux_gpio_sel_1; + int i2s1_in_mux_gpio_sel_2; +}; + +static const char * const i2sin_mux_switch_text[] = { + "ADC_SDOUT2", + "ADC_SDOUT3", + "I2S_IN_1", + "I2S_IN_2", +}; + +static const struct soc_enum i2sin_mux_enum = + SOC_ENUM_SINGLE_EXT(4, i2sin_mux_switch_text); + +static int mt2701_cs42448_i2sin1_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card); + + ucontrol->value.integer.value[0] = priv->i2s1_in_mux; + return 0; +} + +static int mt2701_cs42448_i2sin1_mux_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mt2701_cs42448_private *priv = snd_soc_card_get_drvdata(card); + + if (ucontrol->value.integer.value[0] == priv->i2s1_in_mux) + return 0; + + switch (ucontrol->value.integer.value[0]) { + case 0: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0); + break; + case 1: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 0); + break; + case 2: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 0); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1); + break; + case 3: + gpio_set_value(priv->i2s1_in_mux_gpio_sel_1, 1); + gpio_set_value(priv->i2s1_in_mux_gpio_sel_2, 1); + break; + default: + dev_warn(card->dev, "%s invalid setting\n", __func__); + } + + priv->i2s1_in_mux = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_soc_dapm_widget + mt2701_cs42448_asoc_card_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out Jack", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_LINE("Tuner In", NULL), + SND_SOC_DAPM_LINE("Satellite Tuner In", NULL), + SND_SOC_DAPM_LINE("AUX In", NULL), +}; + +static const struct snd_kcontrol_new mt2701_cs42448_controls[] = { + SOC_DAPM_PIN_SWITCH("Line Out Jack"), + SOC_DAPM_PIN_SWITCH("AMIC"), + SOC_DAPM_PIN_SWITCH("Tuner In"), + SOC_DAPM_PIN_SWITCH("Satellite Tuner In"), + SOC_DAPM_PIN_SWITCH("AUX In"), + SOC_ENUM_EXT("I2SIN1_MUX_Switch", i2sin_mux_enum, + mt2701_cs42448_i2sin1_mux_get, + mt2701_cs42448_i2sin1_mux_set), +}; + +static const unsigned int mt2701_cs42448_sampling_rates[] = {48000}; + +static struct snd_pcm_hw_constraint_list mt2701_cs42448_constraints_rates = { + .count = ARRAY_SIZE(mt2701_cs42448_sampling_rates), + .list = mt2701_cs42448_sampling_rates, + .mask = 0, +}; + +static int mt2701_cs42448_fe_ops_startup(struct snd_pcm_substream *substream) +{ + int err; + + err = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &mt2701_cs42448_constraints_rates); + if (err < 0) { + dev_err(substream->pcm->card->dev, + "%s snd_pcm_hw_constraint_list failed: 0x%x\n", + __func__, err); + return err; + } + return 0; +} + +static struct snd_soc_ops mt2701_cs42448_48k_fe_ops = { + .startup = mt2701_cs42448_fe_ops_startup, +}; + +static int mt2701_cs42448_be_ops_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->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + unsigned int div_mclk_over_bck = rate > 192000 ? 2 : 4; + unsigned int div_bck_over_lrck = 64; + + mclk_rate = rate * div_bck_over_lrck * div_mclk_over_bck; + + /* mt2701 mclk */ + snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, SND_SOC_CLOCK_OUT); + + /* codec mclk */ + snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, SND_SOC_CLOCK_IN); + + return 0; +} + +static struct snd_soc_ops mt2701_cs42448_be_ops = { + .hw_params = mt2701_cs42448_be_ops_hw_params +}; + +enum { + DAI_LINK_FE_MULTI_CH_OUT, + DAI_LINK_FE_PCM0_IN, + DAI_LINK_FE_PCM1_IN, + DAI_LINK_FE_BT_OUT, + DAI_LINK_FE_BT_IN, + DAI_LINK_BE_I2S0, + DAI_LINK_BE_I2S1, + DAI_LINK_BE_I2S2, + DAI_LINK_BE_I2S3, + DAI_LINK_BE_MRG_BT, +}; + +static struct snd_soc_dai_link mt2701_cs42448_dai_links[] = { + /* FE */ + [DAI_LINK_FE_MULTI_CH_OUT] = { + .name = "mt2701-cs42448-multi-ch-out", + .stream_name = "mt2701-cs42448-multi-ch-out", + .cpu_dai_name = "PCM_multi", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_FE_PCM0_IN] = { + .name = "mt2701-cs42448-pcm0", + .stream_name = "mt2701-cs42448-pcm0-data-UL", + .cpu_dai_name = "PCM0", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_FE_PCM1_IN] = { + .name = "mt2701-cs42448-pcm1-data-UL", + .stream_name = "mt2701-cs42448-pcm1-data-UL", + .cpu_dai_name = "PCM1", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .ops = &mt2701_cs42448_48k_fe_ops, + .dynamic = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_FE_BT_OUT] = { + .name = "mt2701-cs42448-pcm-BT-out", + .stream_name = "mt2701-cs42448-pcm-BT", + .cpu_dai_name = "PCM_BT_DL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, + [DAI_LINK_FE_BT_IN] = { + .name = "mt2701-cs42448-pcm-BT-in", + .stream_name = "mt2701-cs42448-pcm-BT", + .cpu_dai_name = "PCM_BT_UL", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, + SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_capture = 1, + }, + /* BE */ + [DAI_LINK_BE_I2S0] = { + .name = "mt2701-cs42448-I2S0", + .cpu_dai_name = "I2S0", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S1] = { + .name = "mt2701-cs42448-I2S1", + .cpu_dai_name = "I2S1", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S2] = { + .name = "mt2701-cs42448-I2S2", + .cpu_dai_name = "I2S2", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_I2S3] = { + .name = "mt2701-cs42448-I2S3", + .cpu_dai_name = "I2S3", + .no_pcm = 1, + .codec_dai_name = "cs42448", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS + | SND_SOC_DAIFMT_GATED, + .ops = &mt2701_cs42448_be_ops, + .dpcm_playback = 1, + .dpcm_capture = 1, + }, + [DAI_LINK_BE_MRG_BT] = { + .name = "mt2701-cs42448-MRG-BT", + .cpu_dai_name = "MRG BT", + .no_pcm = 1, + .codec_dai_name = "bt-sco-pcm-wb", + .dpcm_playback = 1, + .dpcm_capture = 1, + }, +}; + +static struct snd_soc_card mt2701_cs42448_soc_card = { + .name = "mt2701-cs42448", + .owner = THIS_MODULE, + .dai_link = mt2701_cs42448_dai_links, + .num_links = ARRAY_SIZE(mt2701_cs42448_dai_links), + .controls = mt2701_cs42448_controls, + .num_controls = ARRAY_SIZE(mt2701_cs42448_controls), + .dapm_widgets = mt2701_cs42448_asoc_card_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt2701_cs42448_asoc_card_dapm_widgets), +}; + +static int mt2701_cs42448_machine_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt2701_cs42448_soc_card; + int ret; + int i; + struct device_node *platform_node, *codec_node, *codec_node_bt_mrg; + struct mt2701_cs42448_private *priv = + devm_kzalloc(&pdev->dev, sizeof(struct mt2701_cs42448_private), + GFP_KERNEL); + struct device *dev = &pdev->dev; + + if (!priv) + return -ENOMEM; + + platform_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,platform", 0); + if (!platform_node) { + dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_cs42448_dai_links[i].platform_name) + continue; + mt2701_cs42448_dai_links[i].platform_of_node = platform_node; + } + + card->dev = dev; + + codec_node = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec", 0); + if (!codec_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } + for (i = 0; i < card->num_links; i++) { + if (mt2701_cs42448_dai_links[i].codec_name) + continue; + mt2701_cs42448_dai_links[i].codec_of_node = codec_node; + } + + codec_node_bt_mrg = of_parse_phandle(pdev->dev.of_node, + "mediatek,audio-codec-bt-mrg", 0); + if (!codec_node_bt_mrg) { + dev_err(&pdev->dev, + "Property 'audio-codec-bt-mrg' missing or invalid\n"); + return -EINVAL; + } + mt2701_cs42448_dai_links[DAI_LINK_BE_MRG_BT].codec_of_node + = codec_node_bt_mrg; + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + return ret; + } + + priv->i2s1_in_mux_gpio_sel_1 = + of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio1", 0); + if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_1)) { + ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_1, + "i2s1_in_mux_gpio_sel_1"); + if (ret) + dev_warn(&pdev->dev, "%s devm_gpio_request fail %d\n", + __func__, ret); + gpio_direction_output(priv->i2s1_in_mux_gpio_sel_1, 0); + } + + priv->i2s1_in_mux_gpio_sel_2 = + of_get_named_gpio(dev->of_node, "i2s1-in-sel-gpio2", 0); + if (gpio_is_valid(priv->i2s1_in_mux_gpio_sel_2)) { + ret = devm_gpio_request(dev, priv->i2s1_in_mux_gpio_sel_2, + "i2s1_in_mux_gpio_sel_2"); + if (ret) + dev_warn(&pdev->dev, "%s devm_gpio_request fail2 %d\n", + __func__, ret); + gpio_direction_output(priv->i2s1_in_mux_gpio_sel_2, 0); + } + snd_soc_card_set_drvdata(card, priv); + + ret = devm_snd_soc_register_card(&pdev->dev, card); + + if (ret) + dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", + __func__, ret); + return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id mt2701_cs42448_machine_dt_match[] = { + {.compatible = "mediatek,mt2701-cs42448-machine",}, + {} +}; +#endif + +static struct platform_driver mt2701_cs42448_machine = { + .driver = { + .name = "mt2701-cs42448", + #ifdef CONFIG_OF + .of_match_table = mt2701_cs42448_machine_dt_match, + #endif + }, + .probe = mt2701_cs42448_machine_probe, +}; + +module_platform_driver(mt2701_cs42448_machine); + +/* Module information */ +MODULE_DESCRIPTION("MT2701 CS42448 ALSA SoC machine driver"); +MODULE_AUTHOR("Ir Lian <ir.lian@mediatek.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("mt2701 cs42448 soc card"); diff --git a/sound/soc/mediatek/mt2701/mt2701-reg.h b/sound/soc/mediatek/mt2701/mt2701-reg.h new file mode 100644 index 000000000000..bb62b1c55957 --- /dev/null +++ b/sound/soc/mediatek/mt2701/mt2701-reg.h @@ -0,0 +1,186 @@ +/* + * mt2701-reg.h -- Mediatek 2701 audio driver reg definition + * + * Copyright (c) 2016 MediaTek Inc. + * Author: Garlic Tseng <garlic.tseng@mediatek.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 and + * only 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. + */ + +#ifndef _MT2701_REG_H_ +#define _MT2701_REG_H_ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include "mt2701-afe-common.h" + +/***************************************************************************** + * R E G I S T E R D E F I N I T I O N + *****************************************************************************/ +#define AUDIO_TOP_CON0 0x0000 +#define AUDIO_TOP_CON4 0x0010 +#define AUDIO_TOP_CON5 0x0014 +#define AFE_DAIBT_CON0 0x001c +#define AFE_MRGIF_CON 0x003c +#define ASMI_TIMING_CON1 0x0100 +#define ASMO_TIMING_CON1 0x0104 +#define PWR1_ASM_CON1 0x0108 +#define ASYS_TOP_CON 0x0600 +#define ASYS_I2SIN1_CON 0x0604 +#define ASYS_I2SIN2_CON 0x0608 +#define ASYS_I2SIN3_CON 0x060c +#define ASYS_I2SIN4_CON 0x0610 +#define ASYS_I2SIN5_CON 0x0614 +#define ASYS_I2SO1_CON 0x061C +#define ASYS_I2SO2_CON 0x0620 +#define ASYS_I2SO3_CON 0x0624 +#define ASYS_I2SO4_CON 0x0628 +#define ASYS_I2SO5_CON 0x062c +#define PWR2_TOP_CON 0x0634 +#define AFE_CONN0 0x06c0 +#define AFE_CONN1 0x06c4 +#define AFE_CONN2 0x06c8 +#define AFE_CONN3 0x06cc +#define AFE_CONN14 0x06f8 +#define AFE_CONN15 0x06fc +#define AFE_CONN16 0x0700 +#define AFE_CONN17 0x0704 +#define AFE_CONN18 0x0708 +#define AFE_CONN19 0x070c +#define AFE_CONN20 0x0710 +#define AFE_CONN21 0x0714 +#define AFE_CONN22 0x0718 +#define AFE_CONN23 0x071c +#define AFE_CONN24 0x0720 +#define AFE_CONN41 0x0764 +#define ASYS_IRQ1_CON 0x0780 +#define ASYS_IRQ2_CON 0x0784 +#define ASYS_IRQ3_CON 0x0788 +#define ASYS_IRQ_CLR 0x07c0 +#define ASYS_IRQ_STATUS 0x07c4 +#define PWR2_ASM_CON1 0x1070 +#define AFE_DAC_CON0 0x1200 +#define AFE_DAC_CON1 0x1204 +#define AFE_DAC_CON2 0x1208 +#define AFE_DAC_CON3 0x120c +#define AFE_DAC_CON4 0x1210 +#define AFE_MEMIF_HD_CON1 0x121c +#define AFE_MEMIF_PBUF_SIZE 0x1238 +#define AFE_MEMIF_HD_CON0 0x123c +#define AFE_DL1_BASE 0x1240 +#define AFE_DL1_CUR 0x1244 +#define AFE_DL2_BASE 0x1250 +#define AFE_DL2_CUR 0x1254 +#define AFE_DL3_BASE 0x1260 +#define AFE_DL3_CUR 0x1264 +#define AFE_DL4_BASE 0x1270 +#define AFE_DL4_CUR 0x1274 +#define AFE_DL5_BASE 0x1280 +#define AFE_DL5_CUR 0x1284 +#define AFE_DLMCH_BASE 0x12a0 +#define AFE_DLMCH_CUR 0x12a4 +#define AFE_ARB1_BASE 0x12b0 +#define AFE_ARB1_CUR 0x12b4 +#define AFE_VUL_BASE 0x1300 +#define AFE_VUL_CUR 0x130c +#define AFE_UL2_BASE 0x1310 +#define AFE_UL2_END 0x1318 +#define AFE_UL2_CUR 0x131c +#define AFE_UL3_BASE 0x1320 +#define AFE_UL3_END 0x1328 +#define AFE_UL3_CUR 0x132c +#define AFE_UL4_BASE 0x1330 +#define AFE_UL4_END 0x1338 +#define AFE_UL4_CUR 0x133c +#define AFE_UL5_BASE 0x1340 +#define AFE_UL5_END 0x1348 +#define AFE_UL5_CUR 0x134c +#define AFE_DAI_BASE 0x1370 +#define AFE_DAI_CUR 0x137c + +/* AUDIO_TOP_CON0 (0x0000) */ +#define AUDIO_TOP_CON0_A1SYS_A2SYS_ON (0x3 << 0) +#define AUDIO_TOP_CON0_PDN_AFE (0x1 << 2) +#define AUDIO_TOP_CON0_PDN_APLL_CK (0x1 << 23) + +/* AUDIO_TOP_CON4 (0x0010) */ +#define AUDIO_TOP_CON4_I2SO1_PWN (0x1 << 6) +#define AUDIO_TOP_CON4_PDN_A1SYS (0x1 << 21) +#define AUDIO_TOP_CON4_PDN_A2SYS (0x1 << 22) +#define AUDIO_TOP_CON4_PDN_AFE_CONN (0x1 << 23) +#define AUDIO_TOP_CON4_PDN_MRGIF (0x1 << 25) + +/* AFE_DAIBT_CON0 (0x001c) */ +#define AFE_DAIBT_CON0_DAIBT_EN (0x1 << 0) +#define AFE_DAIBT_CON0_BT_FUNC_EN (0x1 << 1) +#define AFE_DAIBT_CON0_BT_FUNC_RDY (0x1 << 3) +#define AFE_DAIBT_CON0_BT_WIDE_MODE_EN (0x1 << 9) +#define AFE_DAIBT_CON0_MRG_USE (0x1 << 12) + +/* PWR1_ASM_CON1 (0x0108) */ +#define PWR1_ASM_CON1_INIT_VAL (0x492) + +/* AFE_MRGIF_CON (0x003c) */ +#define AFE_MRGIF_CON_MRG_EN (0x1 << 0) +#define AFE_MRGIF_CON_MRG_I2S_EN (0x1 << 16) +#define AFE_MRGIF_CON_I2S_MODE_MASK (0xf << 20) +#define AFE_MRGIF_CON_I2S_MODE_32K (0x4 << 20) + +/* ASYS_I2SO1_CON (0x061c) */ +#define ASYS_I2SO1_CON_FS (0x1f << 8) +#define ASYS_I2SO1_CON_FS_SET(x) ((x) << 8) +#define ASYS_I2SO1_CON_MULTI_CH (0x1 << 16) +#define ASYS_I2SO1_CON_SIDEGEN (0x1 << 30) +#define ASYS_I2SO1_CON_I2S_EN (0x1 << 0) +/* 0:EIAJ 1:I2S */ +#define ASYS_I2SO1_CON_I2S_MODE (0x1 << 3) +#define ASYS_I2SO1_CON_WIDE_MODE (0x1 << 1) +#define ASYS_I2SO1_CON_WIDE_MODE_SET(x) ((x) << 1) + +/* PWR2_TOP_CON (0x0634) */ +#define PWR2_TOP_CON_INIT_VAL (0xffe1ffff) + +/* ASYS_IRQ_CLR (0x07c0) */ +#define ASYS_IRQ_CLR_ALL (0xffffffff) + +/* PWR2_ASM_CON1 (0x1070) */ +#define PWR2_ASM_CON1_INIT_VAL (0x492492) + +/* AFE_DAC_CON0 (0x1200) */ +#define AFE_DAC_CON0_AFE_ON (0x1 << 0) + +/* AFE_MEMIF_PBUF_SIZE (0x1238) */ +#define AFE_MEMIF_PBUF_SIZE_DLM_MASK (0x1 << 29) +#define AFE_MEMIF_PBUF_SIZE_PAIR_INTERLEAVE (0x0 << 29) +#define AFE_MEMIF_PBUF_SIZE_FULL_INTERLEAVE (0x1 << 29) +#define DLMCH_BIT_WIDTH_MASK (0x1 << 28) +#define AFE_MEMIF_PBUF_SIZE_DLM_CH_MASK (0xf << 24) +#define AFE_MEMIF_PBUF_SIZE_DLM_CH(x) ((x) << 24) +#define AFE_MEMIF_PBUF_SIZE_DLM_BYTE_MASK (0x3 << 12) +#define AFE_MEMIF_PBUF_SIZE_DLM_32BYTES (0x1 << 12) + +/* I2S in/out register bit control */ +#define ASYS_I2S_CON_FS (0x1f << 8) +#define ASYS_I2S_CON_FS_SET(x) ((x) << 8) +#define ASYS_I2S_CON_RESET (0x1 << 30) +#define ASYS_I2S_CON_I2S_EN (0x1 << 0) +#define ASYS_I2S_CON_I2S_COUPLE_MODE (0x1 << 17) +/* 0:EIAJ 1:I2S */ +#define ASYS_I2S_CON_I2S_MODE (0x1 << 3) +#define ASYS_I2S_CON_WIDE_MODE (0x1 << 1) +#define ASYS_I2S_CON_WIDE_MODE_SET(x) ((x) << 1) +#define ASYS_I2S_IN_PHASE_FIX (0x1 << 31) + +#define AFE_END_ADDR 0x15e0 +#endif diff --git a/sound/soc/mediatek/mt8173/Makefile b/sound/soc/mediatek/mt8173/Makefile new file mode 100644 index 000000000000..0357b27d29f2 --- /dev/null +++ b/sound/soc/mediatek/mt8173/Makefile @@ -0,0 +1,7 @@ +# MTK Platform Support +obj-$(CONFIG_SND_SOC_MT8173) += mt8173-afe-pcm.o +# Machine support +obj-$(CONFIG_SND_SOC_MT8173_MAX98090) += mt8173-max98090.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650) += mt8173-rt5650.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5514) += mt8173-rt5650-rt5514.o +obj-$(CONFIG_SND_SOC_MT8173_RT5650_RT5676) += mt8173-rt5650-rt5676.o diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-common.h b/sound/soc/mediatek/mt8173/mt8173-afe-common.h new file mode 100644 index 000000000000..9a4837cc181a --- /dev/null +++ b/sound/soc/mediatek/mt8173/mt8173-afe-common.h @@ -0,0 +1,73 @@ +/* + * mt8173_afe_common.h -- Mediatek 8173 audio driver common definitions + * + * Copyright (c) 2015 MediaTek Inc. + * Author: Koro Chen <koro.chen@mediatek.com> + * Sascha Hauer <s.hauer@pengutronix.de> + * Hidalgo Huang <hidalgo.huang@mediatek.com> + * Ir Lian <ir.lian@mediatek.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 and + * only 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. + */ + +#ifndef _MT8173_AFE_COMMON_H_ +#define _MT8173_AFE_COMMON_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> + +enum { + MT8173_AFE_MEMIF_DL1, + MT8173_AFE_MEMIF_DL2, + MT8173_AFE_MEMIF_VUL, + MT8173_AFE_MEMIF_DAI, + MT8173_AFE_MEMIF_AWB, + MT8173_AFE_MEMIF_MOD_DAI, + MT8173_AFE_MEMIF_HDMI, + MT8173_AFE_MEMIF_NUM, + MT8173_AFE_IO_MOD_PCM1 = MT8173_AFE_MEMIF_NUM, + MT8173_AFE_IO_MOD_PCM2, + MT8173_AFE_IO_PMIC, + MT8173_AFE_IO_I2S, + MT8173_AFE_IO_2ND_I2S, + MT8173_AFE_IO_HW_GAIN1, + MT8173_AFE_IO_HW_GAIN2, + MT8173_AFE_IO_MRG_O, + MT8173_AFE_IO_MRG_I, + MT8173_AFE_IO_DAIBT, + MT8173_AFE_IO_HDMI, +}; + +enum { + MT8173_AFE_IRQ_DL1, + MT8173_AFE_IRQ_DL2, + MT8173_AFE_IRQ_VUL, + MT8173_AFE_IRQ_DAI, + MT8173_AFE_IRQ_AWB, + MT8173_AFE_IRQ_MOD_DAI, + MT8173_AFE_IRQ_HDMI, + MT8173_AFE_IRQ_NUM, +}; + +enum { + MT8173_CLK_INFRASYS_AUD, + MT8173_CLK_TOP_PDN_AUD, + MT8173_CLK_TOP_PDN_AUD_BUS, + MT8173_CLK_I2S0_M, + MT8173_CLK_I2S1_M, + MT8173_CLK_I2S2_M, + MT8173_CLK_I2S3_M, + MT8173_CLK_I2S3_B, + MT8173_CLK_BCK0, + MT8173_CLK_BCK1, + MT8173_CLK_NUM +}; + +#endif diff --git a/sound/soc/mediatek/mtk-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index 2b5df2ef51a3..8a643a35d3d4 100644 --- a/sound/soc/mediatek/mtk-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -1,5 +1,5 @@ /* - * Mediatek ALSA SoC AFE platform driver + * Mediatek 8173 ALSA SoC AFE platform driver * * Copyright (c) 2015 MediaTek Inc. * Author: Koro Chen <koro.chen@mediatek.com> @@ -24,7 +24,10 @@ #include <linux/dma-mapping.h> #include <linux/pm_runtime.h> #include <sound/soc.h> -#include "mtk-afe-common.h" +#include "mt8173-afe-common.h" +#include "../common/mtk-base-afe.h" +#include "../common/mtk-afe-platform-driver.h" +#include "../common/mtk-afe-fe-dai.h" /***************************************************************************** * R E G I S T E R D E F I N I T I O N @@ -81,7 +84,6 @@ #define AFE_TDM_CON1 0x0548 #define AFE_TDM_CON2 0x054c -#define AFE_BASE_END_OFFSET 8 #define AFE_IRQ_STATUS_BITS 0xff /* AUDIO_TOP_CON0 (0x0000) */ @@ -135,7 +137,7 @@ enum afe_tdm_ch_start { AFE_TDM_CH_ZERO, }; -static const unsigned int mtk_afe_backup_list[] = { +static const unsigned int mt8173_afe_backup_list[] = { AUDIO_TOP_CON0, AFE_CONN1, AFE_CONN2, @@ -152,18 +154,11 @@ static const unsigned int mtk_afe_backup_list[] = { AFE_DAC_CON0, }; -struct mtk_afe { - /* address for ioremap audio hardware register */ - void __iomem *base_addr; - struct device *dev; - struct regmap *regmap; - struct mtk_afe_memif memif[MTK_AFE_MEMIF_NUM]; - struct clk *clocks[MTK_CLK_NUM]; - unsigned int backup_regs[ARRAY_SIZE(mtk_afe_backup_list)]; - bool suspended; +struct mt8173_afe_private { + struct clk *clocks[MT8173_CLK_NUM]; }; -static const struct snd_pcm_hardware mtk_afe_hardware = { +static const struct snd_pcm_hardware mt8173_afe_hardware = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID), .buffer_bytes_max = 256 * 1024, @@ -174,59 +169,12 @@ static const struct snd_pcm_hardware mtk_afe_hardware = { .fifo_size = 0, }; -static snd_pcm_uframes_t mtk_afe_pcm_pointer - (struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); - struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; - unsigned int hw_ptr; - int ret; - - ret = regmap_read(afe->regmap, memif->data->reg_ofs_cur, &hw_ptr); - if (ret || hw_ptr == 0) { - dev_err(afe->dev, "%s hw_ptr err\n", __func__); - hw_ptr = memif->phys_buf_addr; - } - - return bytes_to_frames(substream->runtime, - hw_ptr - memif->phys_buf_addr); -} - -static const struct snd_pcm_ops mtk_afe_pcm_ops = { - .ioctl = snd_pcm_lib_ioctl, - .pointer = mtk_afe_pcm_pointer, -}; - -static int mtk_afe_pcm_new(struct snd_soc_pcm_runtime *rtd) -{ - size_t size; - struct snd_card *card = rtd->card->snd_card; - struct snd_pcm *pcm = rtd->pcm; - - size = mtk_afe_hardware.buffer_bytes_max; - - return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, - card->dev, size, size); -} - -static void mtk_afe_pcm_free(struct snd_pcm *pcm) -{ - snd_pcm_lib_preallocate_free_for_all(pcm); -} - -static const struct snd_soc_platform_driver mtk_afe_pcm_platform = { - .ops = &mtk_afe_pcm_ops, - .pcm_new = mtk_afe_pcm_new, - .pcm_free = mtk_afe_pcm_free, -}; - -struct mtk_afe_rate { +struct mt8173_afe_rate { unsigned int rate; unsigned int regvalue; }; -static const struct mtk_afe_rate mtk_afe_i2s_rates[] = { +static const struct mt8173_afe_rate mt8173_afe_i2s_rates[] = { { .rate = 8000, .regvalue = 0 }, { .rate = 11025, .regvalue = 1 }, { .rate = 12000, .regvalue = 2 }, @@ -242,21 +190,21 @@ static const struct mtk_afe_rate mtk_afe_i2s_rates[] = { { .rate = 192000, .regvalue = 14 }, }; -static int mtk_afe_i2s_fs(unsigned int sample_rate) +static int mt8173_afe_i2s_fs(unsigned int sample_rate) { int i; - for (i = 0; i < ARRAY_SIZE(mtk_afe_i2s_rates); i++) - if (mtk_afe_i2s_rates[i].rate == sample_rate) - return mtk_afe_i2s_rates[i].regvalue; + for (i = 0; i < ARRAY_SIZE(mt8173_afe_i2s_rates); i++) + if (mt8173_afe_i2s_rates[i].rate == sample_rate) + return mt8173_afe_i2s_rates[i].regvalue; return -EINVAL; } -static int mtk_afe_set_i2s(struct mtk_afe *afe, unsigned int rate) +static int mt8173_afe_set_i2s(struct mtk_base_afe *afe, unsigned int rate) { unsigned int val; - int fs = mtk_afe_i2s_fs(rate); + int fs = mt8173_afe_i2s_fs(rate); if (fs < 0) return -EINVAL; @@ -281,7 +229,7 @@ static int mtk_afe_set_i2s(struct mtk_afe *afe, unsigned int rate) return 0; } -static void mtk_afe_set_i2s_enable(struct mtk_afe *afe, bool enable) +static void mt8173_afe_set_i2s_enable(struct mtk_base_afe *afe, bool enable) { unsigned int val; @@ -296,8 +244,8 @@ static void mtk_afe_set_i2s_enable(struct mtk_afe *afe, bool enable) regmap_update_bits(afe->regmap, AFE_I2S_CON1, 0x1, enable); } -static int mtk_afe_dais_enable_clks(struct mtk_afe *afe, - struct clk *m_ck, struct clk *b_ck) +static int mt8173_afe_dais_enable_clks(struct mtk_base_afe *afe, + struct clk *m_ck, struct clk *b_ck) { int ret; @@ -319,9 +267,9 @@ static int mtk_afe_dais_enable_clks(struct mtk_afe *afe, return 0; } -static int mtk_afe_dais_set_clks(struct mtk_afe *afe, - struct clk *m_ck, unsigned int mck_rate, - struct clk *b_ck, unsigned int bck_rate) +static int mt8173_afe_dais_set_clks(struct mtk_base_afe *afe, + struct clk *m_ck, unsigned int mck_rate, + struct clk *b_ck, unsigned int bck_rate) { int ret; @@ -343,8 +291,8 @@ static int mtk_afe_dais_set_clks(struct mtk_afe *afe, return 0; } -static void mtk_afe_dais_disable_clks(struct mtk_afe *afe, - struct clk *m_ck, struct clk *b_ck) +static void mt8173_afe_dais_disable_clks(struct mtk_base_afe *afe, + struct clk *m_ck, struct clk *b_ck) { if (m_ck) clk_disable_unprepare(m_ck); @@ -352,102 +300,101 @@ static void mtk_afe_dais_disable_clks(struct mtk_afe *afe, clk_disable_unprepare(b_ck); } -static int mtk_afe_i2s_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int mt8173_afe_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); if (dai->active) return 0; - mtk_afe_dais_enable_clks(afe, afe->clocks[MTK_CLK_I2S1_M], NULL); - mtk_afe_dais_enable_clks(afe, afe->clocks[MTK_CLK_I2S2_M], NULL); regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M, 0); return 0; } -static void mtk_afe_i2s_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static void mt8173_afe_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); if (dai->active) return; - mtk_afe_set_i2s_enable(afe, false); + mt8173_afe_set_i2s_enable(afe, false); regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M, AUD_TCON0_PDN_22M | AUD_TCON0_PDN_24M); - mtk_afe_dais_disable_clks(afe, afe->clocks[MTK_CLK_I2S1_M], NULL); - mtk_afe_dais_disable_clks(afe, afe->clocks[MTK_CLK_I2S2_M], NULL); } -static int mtk_afe_i2s_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int mt8173_afe_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_pcm_runtime * const runtime = substream->runtime; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; int ret; - mtk_afe_dais_set_clks(afe, - afe->clocks[MTK_CLK_I2S1_M], runtime->rate * 256, - NULL, 0); - mtk_afe_dais_set_clks(afe, - afe->clocks[MTK_CLK_I2S2_M], runtime->rate * 256, - NULL, 0); + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S1_M], + runtime->rate * 256, NULL, 0); + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S2_M], + runtime->rate * 256, NULL, 0); /* config I2S */ - ret = mtk_afe_set_i2s(afe, substream->runtime->rate); + ret = mt8173_afe_set_i2s(afe, substream->runtime->rate); if (ret) return ret; - mtk_afe_set_i2s_enable(afe, true); + mt8173_afe_set_i2s_enable(afe, true); return 0; } -static int mtk_afe_hdmi_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int mt8173_afe_hdmi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; if (dai->active) return 0; - mtk_afe_dais_enable_clks(afe, afe->clocks[MTK_CLK_I2S3_M], - afe->clocks[MTK_CLK_I2S3_B]); + mt8173_afe_dais_enable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + afe_priv->clocks[MT8173_CLK_I2S3_B]); return 0; } -static void mtk_afe_hdmi_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static void mt8173_afe_hdmi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; if (dai->active) return; - mtk_afe_dais_disable_clks(afe, afe->clocks[MTK_CLK_I2S3_M], - afe->clocks[MTK_CLK_I2S3_B]); + mt8173_afe_dais_disable_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + afe_priv->clocks[MT8173_CLK_I2S3_B]); } -static int mtk_afe_hdmi_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int mt8173_afe_hdmi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_pcm_runtime * const runtime = substream->runtime; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mt8173_afe_private *afe_priv = afe->platform_priv; + unsigned int val; - mtk_afe_dais_set_clks(afe, - afe->clocks[MTK_CLK_I2S3_M], runtime->rate * 128, - afe->clocks[MTK_CLK_I2S3_B], - runtime->rate * runtime->channels * 32); + mt8173_afe_dais_set_clks(afe, afe_priv->clocks[MT8173_CLK_I2S3_M], + runtime->rate * 128, + afe_priv->clocks[MT8173_CLK_I2S3_B], + runtime->rate * runtime->channels * 32); val = AFE_TDM_CON1_BCK_INV | AFE_TDM_CON1_LRCK_INV | @@ -498,11 +445,11 @@ static int mtk_afe_hdmi_prepare(struct snd_pcm_substream *substream, return 0; } -static int mtk_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) +static int mt8173_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); dev_info(afe->dev, "%s cmd=%d %s\n", __func__, cmd, dai->name); @@ -514,10 +461,14 @@ static int mtk_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, /* set connections: O30~O37: L/R/LS/RS/C/LFE/CH7/CH8 */ regmap_write(afe->regmap, AFE_HDMI_CONN0, - AFE_HDMI_CONN0_O30_I30 | AFE_HDMI_CONN0_O31_I31 | - AFE_HDMI_CONN0_O32_I34 | AFE_HDMI_CONN0_O33_I35 | - AFE_HDMI_CONN0_O34_I32 | AFE_HDMI_CONN0_O35_I33 | - AFE_HDMI_CONN0_O36_I36 | AFE_HDMI_CONN0_O37_I37); + AFE_HDMI_CONN0_O30_I30 | + AFE_HDMI_CONN0_O31_I31 | + AFE_HDMI_CONN0_O32_I34 | + AFE_HDMI_CONN0_O33_I35 | + AFE_HDMI_CONN0_O34_I32 | + AFE_HDMI_CONN0_O35_I33 | + AFE_HDMI_CONN0_O36_I36 | + AFE_HDMI_CONN0_O37_I37); /* enable Out control */ regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0, 0x1, 0x1); @@ -537,284 +488,65 @@ static int mtk_afe_hdmi_trigger(struct snd_pcm_substream *substream, int cmd, regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF, AUD_TCON0_PDN_HDMI | AUD_TCON0_PDN_SPDF); - return 0; default: return -EINVAL; } } -static int mtk_afe_dais_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); - struct snd_pcm_runtime *runtime = substream->runtime; - struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; - int ret; - - memif->substream = substream; - - snd_soc_set_runtime_hwparams(substream, &mtk_afe_hardware); - - /* - * Capture cannot use ping-pong buffer since hw_ptr at IRQ may be - * smaller than period_size due to AFE's internal buffer. - * This easily leads to overrun when avail_min is period_size. - * One more period can hold the possible unread buffer. - */ - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - ret = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIODS, - 3, - mtk_afe_hardware.periods_max); - if (ret < 0) { - dev_err(afe->dev, "hw_constraint_minmax failed\n"); - return ret; - } - } - ret = snd_pcm_hw_constraint_integer(runtime, - SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) - dev_err(afe->dev, "snd_pcm_hw_constraint_integer failed\n"); - return ret; -} - -static void mtk_afe_dais_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int mt8173_memif_fs(struct snd_pcm_substream *substream, + unsigned int rate) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); - struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; - - memif->substream = NULL; -} - -static int mtk_afe_dais_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); - struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; - int msb_at_bit33 = 0; - int ret; - - dev_dbg(afe->dev, - "%s period = %u, rate= %u, channels=%u\n", - __func__, params_period_size(params), params_rate(params), - params_channels(params)); + struct mtk_base_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); + struct mtk_base_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; + int fs; - ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); - if (ret < 0) - return ret; - - msb_at_bit33 = upper_32_bits(substream->runtime->dma_addr) ? 1 : 0; - memif->phys_buf_addr = lower_32_bits(substream->runtime->dma_addr); - memif->buffer_size = substream->runtime->dma_bytes; - - /* start */ - regmap_write(afe->regmap, - memif->data->reg_ofs_base, memif->phys_buf_addr); - /* end */ - regmap_write(afe->regmap, - memif->data->reg_ofs_base + AFE_BASE_END_OFFSET, - memif->phys_buf_addr + memif->buffer_size - 1); - - /* set MSB to 33-bit */ - regmap_update_bits(afe->regmap, AFE_MEMIF_MSB, - 1 << memif->data->msb_shift, - msb_at_bit33 << memif->data->msb_shift); - - /* set channel */ - if (memif->data->mono_shift >= 0) { - unsigned int mono = (params_channels(params) == 1) ? 1 : 0; - - regmap_update_bits(afe->regmap, AFE_DAC_CON1, - 1 << memif->data->mono_shift, - mono << memif->data->mono_shift); - } - - /* set rate */ - if (memif->data->fs_shift < 0) - return 0; - if (memif->data->id == MTK_AFE_MEMIF_DAI || - memif->data->id == MTK_AFE_MEMIF_MOD_DAI) { - unsigned int val; - - switch (params_rate(params)) { + if (memif->data->id == MT8173_AFE_MEMIF_DAI || + memif->data->id == MT8173_AFE_MEMIF_MOD_DAI) { + switch (rate) { case 8000: - val = 0; + fs = 0; break; case 16000: - val = 1; + fs = 1; break; case 32000: - val = 2; + fs = 2; break; default: return -EINVAL; } - - if (memif->data->id == MTK_AFE_MEMIF_DAI) - regmap_update_bits(afe->regmap, AFE_DAC_CON0, - 0x3 << memif->data->fs_shift, - val << memif->data->fs_shift); - else - regmap_update_bits(afe->regmap, AFE_DAC_CON1, - 0x3 << memif->data->fs_shift, - val << memif->data->fs_shift); - } else { - int fs = mtk_afe_i2s_fs(params_rate(params)); - - if (fs < 0) - return -EINVAL; - - regmap_update_bits(afe->regmap, AFE_DAC_CON1, - 0xf << memif->data->fs_shift, - fs << memif->data->fs_shift); + fs = mt8173_afe_i2s_fs(rate); } - - return 0; + return fs; } -static int mtk_afe_dais_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int mt8173_irq_fs(struct snd_pcm_substream *substream, unsigned int rate) { - return snd_pcm_lib_free_pages(substream); + return mt8173_afe_i2s_fs(rate); } -static int mtk_afe_dais_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime * const runtime = substream->runtime; - struct mtk_afe *afe = snd_soc_platform_get_drvdata(rtd->platform); - struct mtk_afe_memif *memif = &afe->memif[rtd->cpu_dai->id]; - unsigned int counter = runtime->period_size; - - dev_info(afe->dev, "%s %s cmd=%d\n", __func__, memif->data->name, cmd); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - if (memif->data->enable_shift >= 0) - regmap_update_bits(afe->regmap, AFE_DAC_CON0, - 1 << memif->data->enable_shift, - 1 << memif->data->enable_shift); - - /* set irq counter */ - regmap_update_bits(afe->regmap, - memif->data->irq_reg_cnt, - 0x3ffff << memif->data->irq_cnt_shift, - counter << memif->data->irq_cnt_shift); - - /* set irq fs */ - if (memif->data->irq_fs_shift >= 0) { - int fs = mtk_afe_i2s_fs(runtime->rate); - - if (fs < 0) - return -EINVAL; - - regmap_update_bits(afe->regmap, - AFE_IRQ_MCU_CON, - 0xf << memif->data->irq_fs_shift, - fs << memif->data->irq_fs_shift); - } - /* enable interrupt */ - regmap_update_bits(afe->regmap, AFE_IRQ_MCU_CON, - 1 << memif->data->irq_en_shift, - 1 << memif->data->irq_en_shift); - - return 0; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - if (memif->data->enable_shift >= 0) - regmap_update_bits(afe->regmap, AFE_DAC_CON0, - 1 << memif->data->enable_shift, 0); - /* disable interrupt */ - regmap_update_bits(afe->regmap, AFE_IRQ_MCU_CON, - 1 << memif->data->irq_en_shift, - 0 << memif->data->irq_en_shift); - /* and clear pending IRQ */ - regmap_write(afe->regmap, AFE_IRQ_CLR, - 1 << memif->data->irq_clr_shift); - return 0; - default: - return -EINVAL; - } -} - -/* FE DAIs */ -static const struct snd_soc_dai_ops mtk_afe_dai_ops = { - .startup = mtk_afe_dais_startup, - .shutdown = mtk_afe_dais_shutdown, - .hw_params = mtk_afe_dais_hw_params, - .hw_free = mtk_afe_dais_hw_free, - .trigger = mtk_afe_dais_trigger, -}; - /* BE DAIs */ -static const struct snd_soc_dai_ops mtk_afe_i2s_ops = { - .startup = mtk_afe_i2s_startup, - .shutdown = mtk_afe_i2s_shutdown, - .prepare = mtk_afe_i2s_prepare, +static const struct snd_soc_dai_ops mt8173_afe_i2s_ops = { + .startup = mt8173_afe_i2s_startup, + .shutdown = mt8173_afe_i2s_shutdown, + .prepare = mt8173_afe_i2s_prepare, }; -static const struct snd_soc_dai_ops mtk_afe_hdmi_ops = { - .startup = mtk_afe_hdmi_startup, - .shutdown = mtk_afe_hdmi_shutdown, - .prepare = mtk_afe_hdmi_prepare, - .trigger = mtk_afe_hdmi_trigger, - +static const struct snd_soc_dai_ops mt8173_afe_hdmi_ops = { + .startup = mt8173_afe_hdmi_startup, + .shutdown = mt8173_afe_hdmi_shutdown, + .prepare = mt8173_afe_hdmi_prepare, + .trigger = mt8173_afe_hdmi_trigger, }; -static int mtk_afe_runtime_suspend(struct device *dev); -static int mtk_afe_runtime_resume(struct device *dev); - -static int mtk_afe_dai_suspend(struct snd_soc_dai *dai) -{ - struct mtk_afe *afe = snd_soc_dai_get_drvdata(dai); - int i; - - dev_dbg(afe->dev, "%s\n", __func__); - if (pm_runtime_status_suspended(afe->dev) || afe->suspended) - return 0; - - for (i = 0; i < ARRAY_SIZE(mtk_afe_backup_list); i++) - regmap_read(afe->regmap, mtk_afe_backup_list[i], - &afe->backup_regs[i]); - - afe->suspended = true; - mtk_afe_runtime_suspend(afe->dev); - return 0; -} - -static int mtk_afe_dai_resume(struct snd_soc_dai *dai) -{ - struct mtk_afe *afe = snd_soc_dai_get_drvdata(dai); - int i = 0; - - dev_dbg(afe->dev, "%s\n", __func__); - if (pm_runtime_status_suspended(afe->dev) || !afe->suspended) - return 0; - - mtk_afe_runtime_resume(afe->dev); - - for (i = 0; i < ARRAY_SIZE(mtk_afe_backup_list); i++) - regmap_write(afe->regmap, mtk_afe_backup_list[i], - afe->backup_regs[i]); - - afe->suspended = false; - return 0; -} - -static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = { +static struct snd_soc_dai_driver mt8173_afe_pcm_dais[] = { /* FE DAIs: memory intefaces to CPU */ { .name = "DL1", /* downlink 1 */ - .id = MTK_AFE_MEMIF_DL1, + .id = MT8173_AFE_MEMIF_DL1, .suspend = mtk_afe_dai_suspend, .resume = mtk_afe_dai_resume, .playback = { @@ -824,10 +556,10 @@ static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = { .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, - .ops = &mtk_afe_dai_ops, + .ops = &mtk_afe_fe_ops, }, { .name = "VUL", /* voice uplink */ - .id = MTK_AFE_MEMIF_VUL, + .id = MT8173_AFE_MEMIF_VUL, .suspend = mtk_afe_dai_suspend, .resume = mtk_afe_dai_resume, .capture = { @@ -837,11 +569,11 @@ static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = { .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, - .ops = &mtk_afe_dai_ops, + .ops = &mtk_afe_fe_ops, }, { /* BE DAIs */ .name = "I2S", - .id = MTK_AFE_IO_I2S, + .id = MT8173_AFE_IO_I2S, .playback = { .stream_name = "I2S Playback", .channels_min = 1, @@ -856,16 +588,16 @@ static struct snd_soc_dai_driver mtk_afe_pcm_dais[] = { .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, - .ops = &mtk_afe_i2s_ops, + .ops = &mt8173_afe_i2s_ops, .symmetric_rates = 1, }, }; -static struct snd_soc_dai_driver mtk_afe_hdmi_dais[] = { +static struct snd_soc_dai_driver mt8173_afe_hdmi_dais[] = { /* FE DAIs */ { .name = "HDMI", - .id = MTK_AFE_MEMIF_HDMI, + .id = MT8173_AFE_MEMIF_HDMI, .suspend = mtk_afe_dai_suspend, .resume = mtk_afe_dai_resume, .playback = { @@ -878,11 +610,11 @@ static struct snd_soc_dai_driver mtk_afe_hdmi_dais[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, - .ops = &mtk_afe_dai_ops, + .ops = &mtk_afe_fe_ops, }, { /* BE DAIs */ .name = "HDMIO", - .id = MTK_AFE_IO_HDMI, + .id = MT8173_AFE_IO_HDMI, .playback = { .stream_name = "HDMIO Playback", .channels_min = 2, @@ -893,29 +625,29 @@ static struct snd_soc_dai_driver mtk_afe_hdmi_dais[] = { SNDRV_PCM_RATE_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, - .ops = &mtk_afe_hdmi_ops, + .ops = &mt8173_afe_hdmi_ops, }, }; -static const struct snd_kcontrol_new mtk_afe_o03_mix[] = { +static const struct snd_kcontrol_new mt8173_afe_o03_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("I05 Switch", AFE_CONN1, 21, 1, 0), }; -static const struct snd_kcontrol_new mtk_afe_o04_mix[] = { +static const struct snd_kcontrol_new mt8173_afe_o04_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("I06 Switch", AFE_CONN2, 6, 1, 0), }; -static const struct snd_kcontrol_new mtk_afe_o09_mix[] = { +static const struct snd_kcontrol_new mt8173_afe_o09_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("I03 Switch", AFE_CONN3, 0, 1, 0), SOC_DAPM_SINGLE_AUTODISABLE("I17 Switch", AFE_CONN7, 30, 1, 0), }; -static const struct snd_kcontrol_new mtk_afe_o10_mix[] = { +static const struct snd_kcontrol_new mt8173_afe_o10_mix[] = { SOC_DAPM_SINGLE_AUTODISABLE("I04 Switch", AFE_CONN3, 3, 1, 0), SOC_DAPM_SINGLE_AUTODISABLE("I18 Switch", AFE_CONN8, 0, 1, 0), }; -static const struct snd_soc_dapm_widget mtk_afe_pcm_widgets[] = { +static const struct snd_soc_dapm_widget mt8173_afe_pcm_widgets[] = { /* inter-connections */ SND_SOC_DAPM_MIXER("I03", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("I04", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -925,16 +657,16 @@ static const struct snd_soc_dapm_widget mtk_afe_pcm_widgets[] = { SND_SOC_DAPM_MIXER("I18", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("O03", SND_SOC_NOPM, 0, 0, - mtk_afe_o03_mix, ARRAY_SIZE(mtk_afe_o03_mix)), + mt8173_afe_o03_mix, ARRAY_SIZE(mt8173_afe_o03_mix)), SND_SOC_DAPM_MIXER("O04", SND_SOC_NOPM, 0, 0, - mtk_afe_o04_mix, ARRAY_SIZE(mtk_afe_o04_mix)), + mt8173_afe_o04_mix, ARRAY_SIZE(mt8173_afe_o04_mix)), SND_SOC_DAPM_MIXER("O09", SND_SOC_NOPM, 0, 0, - mtk_afe_o09_mix, ARRAY_SIZE(mtk_afe_o09_mix)), + mt8173_afe_o09_mix, ARRAY_SIZE(mt8173_afe_o09_mix)), SND_SOC_DAPM_MIXER("O10", SND_SOC_NOPM, 0, 0, - mtk_afe_o10_mix, ARRAY_SIZE(mtk_afe_o10_mix)), + mt8173_afe_o10_mix, ARRAY_SIZE(mt8173_afe_o10_mix)), }; -static const struct snd_soc_dapm_route mtk_afe_pcm_routes[] = { +static const struct snd_soc_dapm_route mt8173_afe_pcm_routes[] = { {"I05", NULL, "DL1"}, {"I06", NULL, "DL1"}, {"I2S Playback", NULL, "O03"}, @@ -953,140 +685,257 @@ static const struct snd_soc_dapm_route mtk_afe_pcm_routes[] = { { "O10", "I04 Switch", "I04" }, }; -static const struct snd_soc_dapm_route mtk_afe_hdmi_routes[] = { +static const struct snd_soc_dapm_route mt8173_afe_hdmi_routes[] = { {"HDMIO Playback", NULL, "HDMI"}, }; -static const struct snd_soc_component_driver mtk_afe_pcm_dai_component = { - .name = "mtk-afe-pcm-dai", - .dapm_widgets = mtk_afe_pcm_widgets, - .num_dapm_widgets = ARRAY_SIZE(mtk_afe_pcm_widgets), - .dapm_routes = mtk_afe_pcm_routes, - .num_dapm_routes = ARRAY_SIZE(mtk_afe_pcm_routes), +static const struct snd_soc_component_driver mt8173_afe_pcm_dai_component = { + .name = "mt8173-afe-pcm-dai", + .dapm_widgets = mt8173_afe_pcm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt8173_afe_pcm_widgets), + .dapm_routes = mt8173_afe_pcm_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_afe_pcm_routes), }; -static const struct snd_soc_component_driver mtk_afe_hdmi_dai_component = { - .name = "mtk-afe-hdmi-dai", - .dapm_routes = mtk_afe_hdmi_routes, - .num_dapm_routes = ARRAY_SIZE(mtk_afe_hdmi_routes), +static const struct snd_soc_component_driver mt8173_afe_hdmi_dai_component = { + .name = "mt8173-afe-hdmi-dai", + .dapm_routes = mt8173_afe_hdmi_routes, + .num_dapm_routes = ARRAY_SIZE(mt8173_afe_hdmi_routes), }; -static const char *aud_clks[MTK_CLK_NUM] = { - [MTK_CLK_INFRASYS_AUD] = "infra_sys_audio_clk", - [MTK_CLK_TOP_PDN_AUD] = "top_pdn_audio", - [MTK_CLK_TOP_PDN_AUD_BUS] = "top_pdn_aud_intbus", - [MTK_CLK_I2S0_M] = "i2s0_m", - [MTK_CLK_I2S1_M] = "i2s1_m", - [MTK_CLK_I2S2_M] = "i2s2_m", - [MTK_CLK_I2S3_M] = "i2s3_m", - [MTK_CLK_I2S3_B] = "i2s3_b", - [MTK_CLK_BCK0] = "bck0", - [MTK_CLK_BCK1] = "bck1", +static const char *aud_clks[MT8173_CLK_NUM] = { + [MT8173_CLK_INFRASYS_AUD] = "infra_sys_audio_clk", + [MT8173_CLK_TOP_PDN_AUD] = "top_pdn_audio", + [MT8173_CLK_TOP_PDN_AUD_BUS] = "top_pdn_aud_intbus", + [MT8173_CLK_I2S0_M] = "i2s0_m", + [MT8173_CLK_I2S1_M] = "i2s1_m", + [MT8173_CLK_I2S2_M] = "i2s2_m", + [MT8173_CLK_I2S3_M] = "i2s3_m", + [MT8173_CLK_I2S3_B] = "i2s3_b", + [MT8173_CLK_BCK0] = "bck0", + [MT8173_CLK_BCK1] = "bck1", }; -static const struct mtk_afe_memif_data memif_data[MTK_AFE_MEMIF_NUM] = { +static const struct mtk_base_memif_data memif_data[MT8173_AFE_MEMIF_NUM] = { { .name = "DL1", - .id = MTK_AFE_MEMIF_DL1, + .id = MT8173_AFE_MEMIF_DL1, .reg_ofs_base = AFE_DL1_BASE, .reg_ofs_cur = AFE_DL1_CUR, + .fs_reg = AFE_DAC_CON1, .fs_shift = 0, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, .mono_shift = 21, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, .enable_shift = 1, - .irq_reg_cnt = AFE_IRQ_CNT1, - .irq_cnt_shift = 0, - .irq_en_shift = 0, - .irq_fs_shift = 4, - .irq_clr_shift = 0, + .msb_reg = AFE_MEMIF_MSB, .msb_shift = 0, + .agent_disable_reg = -1, + .agent_disable_shift = -1, }, { .name = "DL2", - .id = MTK_AFE_MEMIF_DL2, + .id = MT8173_AFE_MEMIF_DL2, .reg_ofs_base = AFE_DL2_BASE, .reg_ofs_cur = AFE_DL2_CUR, + .fs_reg = AFE_DAC_CON1, .fs_shift = 4, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, .mono_shift = 22, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, .enable_shift = 2, - .irq_reg_cnt = AFE_IRQ_CNT1, - .irq_cnt_shift = 20, - .irq_en_shift = 2, - .irq_fs_shift = 16, - .irq_clr_shift = 2, + .msb_reg = AFE_MEMIF_MSB, .msb_shift = 1, + .agent_disable_reg = -1, + .agent_disable_shift = -1, }, { .name = "VUL", - .id = MTK_AFE_MEMIF_VUL, + .id = MT8173_AFE_MEMIF_VUL, .reg_ofs_base = AFE_VUL_BASE, .reg_ofs_cur = AFE_VUL_CUR, + .fs_reg = AFE_DAC_CON1, .fs_shift = 16, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, .mono_shift = 27, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, .enable_shift = 3, - .irq_reg_cnt = AFE_IRQ_CNT2, - .irq_cnt_shift = 0, - .irq_en_shift = 1, - .irq_fs_shift = 8, - .irq_clr_shift = 1, + .msb_reg = AFE_MEMIF_MSB, .msb_shift = 6, + .agent_disable_reg = -1, + .agent_disable_shift = -1, }, { .name = "DAI", - .id = MTK_AFE_MEMIF_DAI, + .id = MT8173_AFE_MEMIF_DAI, .reg_ofs_base = AFE_DAI_BASE, .reg_ofs_cur = AFE_DAI_CUR, + .fs_reg = AFE_DAC_CON0, .fs_shift = 24, + .fs_maskbit = 0x3, + .mono_reg = -1, .mono_shift = -1, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, .enable_shift = 4, - .irq_reg_cnt = AFE_IRQ_CNT2, - .irq_cnt_shift = 20, - .irq_en_shift = 3, - .irq_fs_shift = 20, - .irq_clr_shift = 3, + .msb_reg = AFE_MEMIF_MSB, .msb_shift = 5, + .agent_disable_reg = -1, + .agent_disable_shift = -1, }, { .name = "AWB", - .id = MTK_AFE_MEMIF_AWB, + .id = MT8173_AFE_MEMIF_AWB, .reg_ofs_base = AFE_AWB_BASE, .reg_ofs_cur = AFE_AWB_CUR, + .fs_reg = AFE_DAC_CON1, .fs_shift = 12, + .fs_maskbit = 0xf, + .mono_reg = AFE_DAC_CON1, .mono_shift = 24, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, .enable_shift = 6, - .irq_reg_cnt = AFE_IRQ_CNT7, - .irq_cnt_shift = 0, - .irq_en_shift = 14, - .irq_fs_shift = 24, - .irq_clr_shift = 6, + .msb_reg = AFE_MEMIF_MSB, .msb_shift = 3, + .agent_disable_reg = -1, + .agent_disable_shift = -1, }, { .name = "MOD_DAI", - .id = MTK_AFE_MEMIF_MOD_DAI, + .id = MT8173_AFE_MEMIF_MOD_DAI, .reg_ofs_base = AFE_MOD_PCM_BASE, .reg_ofs_cur = AFE_MOD_PCM_CUR, + .fs_reg = AFE_DAC_CON1, .fs_shift = 30, + .fs_maskbit = 0x3, + .mono_reg = AFE_DAC_CON1, .mono_shift = 30, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = AFE_DAC_CON0, .enable_shift = 7, - .irq_reg_cnt = AFE_IRQ_CNT2, - .irq_cnt_shift = 20, - .irq_en_shift = 3, - .irq_fs_shift = 20, - .irq_clr_shift = 3, + .msb_reg = AFE_MEMIF_MSB, .msb_shift = 4, + .agent_disable_reg = -1, + .agent_disable_shift = -1, }, { .name = "HDMI", - .id = MTK_AFE_MEMIF_HDMI, + .id = MT8173_AFE_MEMIF_HDMI, .reg_ofs_base = AFE_HDMI_OUT_BASE, .reg_ofs_cur = AFE_HDMI_OUT_CUR, + .fs_reg = -1, .fs_shift = -1, + .fs_maskbit = -1, + .mono_reg = -1, .mono_shift = -1, + .hd_reg = -1, + .hd_shift = -1, + .enable_reg = -1, .enable_shift = -1, - .irq_reg_cnt = AFE_IRQ_CNT5, + .msb_reg = AFE_MEMIF_MSB, + .msb_shift = 8, + .agent_disable_reg = -1, + .agent_disable_shift = -1, + }, +}; + +static const struct mtk_base_irq_data irq_data[MT8173_AFE_IRQ_NUM] = { + { + .id = MT8173_AFE_IRQ_DL1, + .irq_cnt_reg = AFE_IRQ_CNT1, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 0, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 4, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 0, + }, { + .id = MT8173_AFE_IRQ_DL2, + .irq_cnt_reg = AFE_IRQ_CNT1, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 2, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 16, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 2, + + }, { + .id = MT8173_AFE_IRQ_VUL, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 1, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 8, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 1, + }, { + .id = MT8173_AFE_IRQ_DAI, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 3, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 20, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 3, + }, { + .id = MT8173_AFE_IRQ_AWB, + .irq_cnt_reg = AFE_IRQ_CNT7, .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 14, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 24, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 6, + }, { + .id = MT8173_AFE_IRQ_DAI, + .irq_cnt_reg = AFE_IRQ_CNT2, + .irq_cnt_shift = 20, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, + .irq_en_shift = 3, + .irq_fs_reg = AFE_IRQ_MCU_CON, + .irq_fs_shift = 20, + .irq_fs_maskbit = 0xf, + .irq_clr_reg = AFE_IRQ_CLR, + .irq_clr_shift = 3, + }, { + .id = MT8173_AFE_IRQ_HDMI, + .irq_cnt_reg = AFE_IRQ_CNT5, + .irq_cnt_shift = 0, + .irq_cnt_maskbit = 0x3ffff, + .irq_en_reg = AFE_IRQ_MCU_CON, .irq_en_shift = 12, + .irq_fs_reg = -1, .irq_fs_shift = -1, + .irq_fs_maskbit = -1, + .irq_clr_reg = AFE_IRQ_CLR, .irq_clr_shift = 4, - .msb_shift = 8, }, }; -static const struct regmap_config mtk_afe_regmap_config = { +static const struct regmap_config mt8173_afe_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, @@ -1094,9 +943,9 @@ static const struct regmap_config mtk_afe_regmap_config = { .cache_type = REGCACHE_NONE, }; -static irqreturn_t mtk_afe_irq_handler(int irq, void *dev_id) +static irqreturn_t mt8173_afe_irq_handler(int irq, void *dev_id) { - struct mtk_afe *afe = dev_id; + struct mtk_base_afe *afe = dev_id; unsigned int reg_value; int i, ret; @@ -1107,10 +956,16 @@ static irqreturn_t mtk_afe_irq_handler(int irq, void *dev_id) goto err_irq; } - for (i = 0; i < MTK_AFE_MEMIF_NUM; i++) { - struct mtk_afe_memif *memif = &afe->memif[i]; + for (i = 0; i < MT8173_AFE_MEMIF_NUM; i++) { + struct mtk_base_afe_memif *memif = &afe->memif[i]; + struct mtk_base_afe_irq *irq; - if (!(reg_value & (1 << memif->data->irq_clr_shift))) + if (memif->irq_usage < 0) + continue; + + irq = &afe->irqs[memif->irq_usage]; + + if (!(reg_value & (1 << irq->irq_data->irq_clr_shift))) continue; snd_pcm_period_elapsed(memif->substream); @@ -1118,14 +973,16 @@ static irqreturn_t mtk_afe_irq_handler(int irq, void *dev_id) err_irq: /* clear irq */ - regmap_write(afe->regmap, AFE_IRQ_CLR, reg_value & AFE_IRQ_STATUS_BITS); + regmap_write(afe->regmap, AFE_IRQ_CLR, + reg_value & AFE_IRQ_STATUS_BITS); return IRQ_HANDLED; } -static int mtk_afe_runtime_suspend(struct device *dev) +static int mt8173_afe_runtime_suspend(struct device *dev) { - struct mtk_afe *afe = dev_get_drvdata(dev); + struct mtk_base_afe *afe = dev_get_drvdata(dev); + struct mt8173_afe_private *afe_priv = afe->platform_priv; /* disable AFE */ regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0); @@ -1134,38 +991,47 @@ static int mtk_afe_runtime_suspend(struct device *dev) regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_AFE, AUD_TCON0_PDN_AFE); - clk_disable_unprepare(afe->clocks[MTK_CLK_BCK0]); - clk_disable_unprepare(afe->clocks[MTK_CLK_BCK1]); - clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD]); - clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD_BUS]); - clk_disable_unprepare(afe->clocks[MTK_CLK_INFRASYS_AUD]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK1]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); return 0; } -static int mtk_afe_runtime_resume(struct device *dev) +static int mt8173_afe_runtime_resume(struct device *dev) { - struct mtk_afe *afe = dev_get_drvdata(dev); + struct mtk_base_afe *afe = dev_get_drvdata(dev); + struct mt8173_afe_private *afe_priv = afe->platform_priv; int ret; - ret = clk_prepare_enable(afe->clocks[MTK_CLK_INFRASYS_AUD]); + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); if (ret) return ret; - ret = clk_prepare_enable(afe->clocks[MTK_CLK_TOP_PDN_AUD_BUS]); + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); if (ret) goto err_infra; - ret = clk_prepare_enable(afe->clocks[MTK_CLK_TOP_PDN_AUD]); + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); if (ret) goto err_top_aud_bus; - ret = clk_prepare_enable(afe->clocks[MTK_CLK_BCK0]); + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK0]); if (ret) goto err_top_aud; - ret = clk_prepare_enable(afe->clocks[MTK_CLK_BCK1]); + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_BCK1]); if (ret) goto err_bck0; + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S1_M]); + if (ret) + goto err_i2s1_m; + ret = clk_prepare_enable(afe_priv->clocks[MT8173_CLK_I2S2_M]); + if (ret) + goto err_i2s2_m; /* enable AFE clk */ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0, AUD_TCON0_PDN_AFE, 0); @@ -1181,39 +1047,45 @@ static int mtk_afe_runtime_resume(struct device *dev) regmap_update_bits(afe->regmap, AFE_DAC_CON0, 0x1, 0x1); return 0; +err_i2s1_m: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S1_M]); +err_i2s2_m: + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_I2S2_M]); err_bck0: - clk_disable_unprepare(afe->clocks[MTK_CLK_BCK0]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_BCK0]); err_top_aud: - clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD]); err_top_aud_bus: - clk_disable_unprepare(afe->clocks[MTK_CLK_TOP_PDN_AUD_BUS]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_TOP_PDN_AUD_BUS]); err_infra: - clk_disable_unprepare(afe->clocks[MTK_CLK_INFRASYS_AUD]); + clk_disable_unprepare(afe_priv->clocks[MT8173_CLK_INFRASYS_AUD]); return ret; } -static int mtk_afe_init_audio_clk(struct mtk_afe *afe) +static int mt8173_afe_init_audio_clk(struct mtk_base_afe *afe) { size_t i; + struct mt8173_afe_private *afe_priv = afe->platform_priv; for (i = 0; i < ARRAY_SIZE(aud_clks); i++) { - afe->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); - if (IS_ERR(afe->clocks[i])) { + afe_priv->clocks[i] = devm_clk_get(afe->dev, aud_clks[i]); + if (IS_ERR(afe_priv->clocks[i])) { dev_err(afe->dev, "%s devm_clk_get %s fail\n", __func__, aud_clks[i]); - return PTR_ERR(afe->clocks[i]); + return PTR_ERR(afe_priv->clocks[i]); } } - clk_set_rate(afe->clocks[MTK_CLK_BCK0], 22579200); /* 22M */ - clk_set_rate(afe->clocks[MTK_CLK_BCK1], 24576000); /* 24M */ + clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK0], 22579200); /* 22M */ + clk_set_rate(afe_priv->clocks[MT8173_CLK_BCK1], 24576000); /* 24M */ return 0; } -static int mtk_afe_pcm_dev_probe(struct platform_device *pdev) +static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) { int ret, i; unsigned int irq_id; - struct mtk_afe *afe; + struct mtk_base_afe *afe; + struct mt8173_afe_private *afe_priv; struct resource *res; ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(33)); @@ -1224,6 +1096,12 @@ static int mtk_afe_pcm_dev_probe(struct platform_device *pdev) if (!afe) return -ENOMEM; + afe->platform_priv = devm_kzalloc(&pdev->dev, sizeof(*afe_priv), + GFP_KERNEL); + afe_priv = afe->platform_priv; + if (!afe_priv) + return -ENOMEM; + afe->dev = &pdev->dev; irq_id = platform_get_irq(pdev, 0); @@ -1231,7 +1109,7 @@ static int mtk_afe_pcm_dev_probe(struct platform_device *pdev) dev_err(afe->dev, "np %s no irq\n", afe->dev->of_node->name); return -ENXIO; } - ret = devm_request_irq(afe->dev, irq_id, mtk_afe_irq_handler, + ret = devm_request_irq(afe->dev, irq_id, mt8173_afe_irq_handler, 0, "Afe_ISR_Handle", (void *)afe); if (ret) { dev_err(afe->dev, "could not request_irq\n"); @@ -1244,48 +1122,75 @@ static int mtk_afe_pcm_dev_probe(struct platform_device *pdev) return PTR_ERR(afe->base_addr); afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, - &mtk_afe_regmap_config); + &mt8173_afe_regmap_config); if (IS_ERR(afe->regmap)) return PTR_ERR(afe->regmap); /* initial audio related clock */ - ret = mtk_afe_init_audio_clk(afe); + ret = mt8173_afe_init_audio_clk(afe); if (ret) { - dev_err(afe->dev, "mtk_afe_init_audio_clk fail\n"); + dev_err(afe->dev, "mt8173_afe_init_audio_clk fail\n"); return ret; } - for (i = 0; i < MTK_AFE_MEMIF_NUM; i++) + /* memif % irq initialize*/ + afe->memif_size = MT8173_AFE_MEMIF_NUM; + afe->memif = devm_kcalloc(afe->dev, afe->memif_size, + sizeof(*afe->memif), GFP_KERNEL); + if (!afe->memif) + return -ENOMEM; + + afe->irqs_size = MT8173_AFE_IRQ_NUM; + afe->irqs = devm_kcalloc(afe->dev, afe->irqs_size, + sizeof(*afe->irqs), GFP_KERNEL); + if (!afe->irqs) + return -ENOMEM; + + for (i = 0; i < afe->irqs_size; i++) { afe->memif[i].data = &memif_data[i]; + afe->irqs[i].irq_data = &irq_data[i]; + afe->irqs[i].irq_occupyed = true; + afe->memif[i].irq_usage = i; + afe->memif[i].const_irq = 1; + } + + afe->mtk_afe_hardware = &mt8173_afe_hardware; + afe->memif_fs = mt8173_memif_fs; + afe->irq_fs = mt8173_irq_fs; platform_set_drvdata(pdev, afe); pm_runtime_enable(&pdev->dev); if (!pm_runtime_enabled(&pdev->dev)) { - ret = mtk_afe_runtime_resume(&pdev->dev); + ret = mt8173_afe_runtime_resume(&pdev->dev); if (ret) goto err_pm_disable; } + afe->reg_back_up_list = mt8173_afe_backup_list; + afe->reg_back_up_list_num = ARRAY_SIZE(mt8173_afe_backup_list); + afe->runtime_resume = mt8173_afe_runtime_resume; + afe->runtime_suspend = mt8173_afe_runtime_suspend; + ret = snd_soc_register_platform(&pdev->dev, &mtk_afe_pcm_platform); if (ret) goto err_pm_disable; ret = snd_soc_register_component(&pdev->dev, - &mtk_afe_pcm_dai_component, - mtk_afe_pcm_dais, - ARRAY_SIZE(mtk_afe_pcm_dais)); + &mt8173_afe_pcm_dai_component, + mt8173_afe_pcm_dais, + ARRAY_SIZE(mt8173_afe_pcm_dais)); if (ret) goto err_platform; ret = snd_soc_register_component(&pdev->dev, - &mtk_afe_hdmi_dai_component, - mtk_afe_hdmi_dais, - ARRAY_SIZE(mtk_afe_hdmi_dais)); + &mt8173_afe_hdmi_dai_component, + mt8173_afe_hdmi_dais, + ARRAY_SIZE(mt8173_afe_hdmi_dais)); if (ret) goto err_comp; - dev_info(&pdev->dev, "MTK AFE driver initialized.\n"); + dev_info(&pdev->dev, "MT8173 AFE driver initialized.\n"); return 0; err_comp: @@ -1297,38 +1202,38 @@ err_pm_disable: return ret; } -static int mtk_afe_pcm_dev_remove(struct platform_device *pdev) +static int mt8173_afe_pcm_dev_remove(struct platform_device *pdev) { pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) - mtk_afe_runtime_suspend(&pdev->dev); + mt8173_afe_runtime_suspend(&pdev->dev); snd_soc_unregister_component(&pdev->dev); snd_soc_unregister_platform(&pdev->dev); return 0; } -static const struct of_device_id mtk_afe_pcm_dt_match[] = { +static const struct of_device_id mt8173_afe_pcm_dt_match[] = { { .compatible = "mediatek,mt8173-afe-pcm", }, { } }; -MODULE_DEVICE_TABLE(of, mtk_afe_pcm_dt_match); +MODULE_DEVICE_TABLE(of, mt8173_afe_pcm_dt_match); -static const struct dev_pm_ops mtk_afe_pm_ops = { - SET_RUNTIME_PM_OPS(mtk_afe_runtime_suspend, mtk_afe_runtime_resume, - NULL) +static const struct dev_pm_ops mt8173_afe_pm_ops = { + SET_RUNTIME_PM_OPS(mt8173_afe_runtime_suspend, + mt8173_afe_runtime_resume, NULL) }; -static struct platform_driver mtk_afe_pcm_driver = { +static struct platform_driver mt8173_afe_pcm_driver = { .driver = { - .name = "mtk-afe-pcm", - .of_match_table = mtk_afe_pcm_dt_match, - .pm = &mtk_afe_pm_ops, + .name = "mt8173-afe-pcm", + .of_match_table = mt8173_afe_pcm_dt_match, + .pm = &mt8173_afe_pm_ops, }, - .probe = mtk_afe_pcm_dev_probe, - .remove = mtk_afe_pcm_dev_remove, + .probe = mt8173_afe_pcm_dev_probe, + .remove = mt8173_afe_pcm_dev_remove, }; -module_platform_driver(mtk_afe_pcm_driver); +module_platform_driver(mt8173_afe_pcm_driver); MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver"); MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); diff --git a/sound/soc/mediatek/mt8173-max98090.c b/sound/soc/mediatek/mt8173/mt8173-max98090.c index 71a1a35047ba..5524a2c727ec 100644 --- a/sound/soc/mediatek/mt8173-max98090.c +++ b/sound/soc/mediatek/mt8173/mt8173-max98090.c @@ -18,7 +18,7 @@ #include <sound/soc.h> #include <sound/jack.h> #include <linux/gpio.h> -#include "../codecs/max98090.h" +#include "../../codecs/max98090.h" static struct snd_soc_jack mt8173_max98090_jack; diff --git a/sound/soc/mediatek/mt8173-rt5650-rt5514.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c index 58e083642dea..467f7049a288 100644 --- a/sound/soc/mediatek/mt8173-rt5650-rt5514.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5514.c @@ -19,7 +19,7 @@ #include <linux/of_gpio.h> #include <sound/soc.h> #include <sound/jack.h> -#include "../codecs/rt5645.h" +#include "../../codecs/rt5645.h" #define MCLK_FOR_CODECS 12288000 diff --git a/sound/soc/mediatek/mt8173-rt5650-rt5676.c b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c index bb593926c62d..1b8b2a778845 100644 --- a/sound/soc/mediatek/mt8173-rt5650-rt5676.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650-rt5676.c @@ -19,8 +19,8 @@ #include <linux/of_gpio.h> #include <sound/soc.h> #include <sound/jack.h> -#include "../codecs/rt5645.h" -#include "../codecs/rt5677.h" +#include "../../codecs/rt5645.h" +#include "../../codecs/rt5677.h" #define MCLK_FOR_CODECS 12288000 diff --git a/sound/soc/mediatek/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c index a27a6673dbe3..ba65f4157a7e 100644 --- a/sound/soc/mediatek/mt8173-rt5650.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c @@ -19,10 +19,24 @@ #include <linux/of_gpio.h> #include <sound/soc.h> #include <sound/jack.h> -#include "../codecs/rt5645.h" +#include "../../codecs/rt5645.h" #define MCLK_FOR_CODECS 12288000 +enum mt8173_rt5650_mclk { + MT8173_RT5650_MCLK_EXTERNAL = 0, + MT8173_RT5650_MCLK_INTERNAL, +}; + +struct mt8173_rt5650_platform_data { + enum mt8173_rt5650_mclk pll_from; + /* 0 = external oscillator; 1 = internal source from mt8173 */ +}; + +static struct mt8173_rt5650_platform_data mt8173_rt5650_priv = { + .pll_from = MT8173_RT5650_MCLK_EXTERNAL, +}; + static const struct snd_soc_dapm_widget mt8173_rt5650_widgets[] = { SND_SOC_DAPM_SPK("Speaker", NULL), SND_SOC_DAPM_MIC("Int Mic", NULL), @@ -54,13 +68,29 @@ static int mt8173_rt5650_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int mclk_clock; int i, ret; + switch (mt8173_rt5650_priv.pll_from) { + case MT8173_RT5650_MCLK_EXTERNAL: + /* mclk = 12.288M */ + mclk_clock = MCLK_FOR_CODECS; + break; + case MT8173_RT5650_MCLK_INTERNAL: + /* mclk = sampling rate*256 */ + mclk_clock = params_rate(params) * 256; + break; + default: + /* mclk = 12.288M */ + mclk_clock = MCLK_FOR_CODECS; + break; + } + for (i = 0; i < rtd->num_codecs; i++) { struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; - /* pll from mclk 12.288M */ - ret = snd_soc_dai_set_pll(codec_dai, 0, 0, MCLK_FOR_CODECS, + /* pll from mclk */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, mclk_clock, params_rate(params) * 512); if (ret) return ret; @@ -139,7 +169,9 @@ static struct snd_soc_dai_link_component mt8173_rt5650_codecs[] = { enum { DAI_LINK_PLAYBACK, DAI_LINK_CAPTURE, + DAI_LINK_HDMI, DAI_LINK_CODEC_I2S, + DAI_LINK_HDMI_I2S, }; /* Digital audio interface glue - connects codec <---> CPU */ @@ -165,6 +197,16 @@ static struct snd_soc_dai_link mt8173_rt5650_dais[] = { .dynamic = 1, .dpcm_capture = 1, }, + [DAI_LINK_HDMI] = { + .name = "HDMI", + .stream_name = "HDMI PCM", + .cpu_dai_name = "HDMI", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", + .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, + .dynamic = 1, + .dpcm_playback = 1, + }, /* Back End DAI links */ [DAI_LINK_CODEC_I2S] = { .name = "Codec", @@ -180,6 +222,13 @@ static struct snd_soc_dai_link mt8173_rt5650_dais[] = { .dpcm_playback = 1, .dpcm_capture = 1, }, + [DAI_LINK_HDMI_I2S] = { + .name = "HDMI BE", + .cpu_dai_name = "HDMIO", + .no_pcm = 1, + .codec_dai_name = "i2s-hifi", + .dpcm_playback = 1, + }, }; static struct snd_soc_card mt8173_rt5650_card = { @@ -243,6 +292,24 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) mt8173_rt5650_codecs[1].dai_name = codec_capture_dai; } + if (device_property_present(&pdev->dev, "mediatek,mclk")) { + ret = device_property_read_u32(&pdev->dev, + "mediatek,mclk", + &mt8173_rt5650_priv.pll_from); + if (ret) { + dev_err(&pdev->dev, + "%s snd_soc_register_card fail %d\n", + __func__, ret); + } + } + + mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node = + of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); + if (!mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codec_of_node) { + dev_err(&pdev->dev, + "Property 'audio-codec' missing or invalid\n"); + return -EINVAL; + } card->dev = &pdev->dev; platform_set_drvdata(pdev, card); diff --git a/sound/soc/mediatek/mtk-afe-common.h b/sound/soc/mediatek/mtk-afe-common.h deleted file mode 100644 index f341f623e887..000000000000 --- a/sound/soc/mediatek/mtk-afe-common.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * mtk_afe_common.h -- Mediatek audio driver common definitions - * - * Copyright (c) 2015 MediaTek Inc. - * Author: Koro Chen <koro.chen@mediatek.com> - * Sascha Hauer <s.hauer@pengutronix.de> - * Hidalgo Huang <hidalgo.huang@mediatek.com> - * Ir Lian <ir.lian@mediatek.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 and - * only 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. - */ - -#ifndef _MTK_AFE_COMMON_H_ -#define _MTK_AFE_COMMON_H_ - -#include <linux/clk.h> -#include <linux/regmap.h> - -enum { - MTK_AFE_MEMIF_DL1, - MTK_AFE_MEMIF_DL2, - MTK_AFE_MEMIF_VUL, - MTK_AFE_MEMIF_DAI, - MTK_AFE_MEMIF_AWB, - MTK_AFE_MEMIF_MOD_DAI, - MTK_AFE_MEMIF_HDMI, - MTK_AFE_MEMIF_NUM, - MTK_AFE_IO_MOD_PCM1 = MTK_AFE_MEMIF_NUM, - MTK_AFE_IO_MOD_PCM2, - MTK_AFE_IO_PMIC, - MTK_AFE_IO_I2S, - MTK_AFE_IO_2ND_I2S, - MTK_AFE_IO_HW_GAIN1, - MTK_AFE_IO_HW_GAIN2, - MTK_AFE_IO_MRG_O, - MTK_AFE_IO_MRG_I, - MTK_AFE_IO_DAIBT, - MTK_AFE_IO_HDMI, -}; - -enum { - MTK_AFE_IRQ_1, - MTK_AFE_IRQ_2, - MTK_AFE_IRQ_3, - MTK_AFE_IRQ_4, - MTK_AFE_IRQ_5, - MTK_AFE_IRQ_6, - MTK_AFE_IRQ_7, - MTK_AFE_IRQ_8, - MTK_AFE_IRQ_NUM, -}; - -enum { - MTK_CLK_INFRASYS_AUD, - MTK_CLK_TOP_PDN_AUD, - MTK_CLK_TOP_PDN_AUD_BUS, - MTK_CLK_I2S0_M, - MTK_CLK_I2S1_M, - MTK_CLK_I2S2_M, - MTK_CLK_I2S3_M, - MTK_CLK_I2S3_B, - MTK_CLK_BCK0, - MTK_CLK_BCK1, - MTK_CLK_NUM -}; - -struct mtk_afe; -struct snd_pcm_substream; - -struct mtk_afe_memif_data { - int id; - const char *name; - int reg_ofs_base; - int reg_ofs_cur; - int fs_shift; - int mono_shift; - int enable_shift; - int irq_reg_cnt; - int irq_cnt_shift; - int irq_en_shift; - int irq_fs_shift; - int irq_clr_shift; - int msb_shift; -}; - -struct mtk_afe_memif { - unsigned int phys_buf_addr; - int buffer_size; - struct snd_pcm_substream *substream; - const struct mtk_afe_memif_data *data; - const struct mtk_afe_irq_data *irqdata; -}; - -#endif diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 5185a3844da9..f5451c78ede5 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -100,13 +100,14 @@ config SND_OMAP_SOC_OMAP_TWL4030 config SND_OMAP_SOC_OMAP_ABE_TWL6040 tristate "SoC Audio support for OMAP boards using ABE and twl6040 codec" - depends on TWL6040_CORE && SND_OMAP_SOC + depends on TWL6040_CORE && SND_OMAP_SOC && COMMON_CLK depends on ARCH_OMAP4 || (SOC_OMAP5 && MFD_PALMAS) || COMPILE_TEST select SND_OMAP_SOC_DMIC select SND_OMAP_SOC_MCPDM select SND_SOC_TWL6040 select SND_SOC_DMIC select COMMON_CLK_PALMAS if (SOC_OMAP5 && MFD_PALMAS) + select CLK_TWL6040 help Say Y if you want to add support for SoC audio on OMAP boards using ABE and twl6040 codec. This driver currently supports: diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c index b837265ac3e9..e7cdc51fd806 100644 --- a/sound/soc/omap/omap-mcpdm.c +++ b/sound/soc/omap/omap-mcpdm.c @@ -31,6 +31,7 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/irq.h> +#include <linux/clk.h> #include <linux/slab.h> #include <linux/pm_runtime.h> #include <linux/of_device.h> @@ -54,6 +55,7 @@ struct omap_mcpdm { unsigned long phys_base; void __iomem *io_base; int irq; + struct clk *pdmclk; struct mutex mutex; @@ -66,6 +68,9 @@ struct omap_mcpdm { /* McPDM needs to be restarted due to runtime reconfiguration */ bool restart; + /* pm state for suspend/resume handling */ + int pm_active_count; + struct snd_dmaengine_dai_dma_data dma_data[2]; }; @@ -173,6 +178,10 @@ static inline int omap_mcpdm_active(struct omap_mcpdm *mcpdm) */ static void omap_mcpdm_open_streams(struct omap_mcpdm *mcpdm) { + u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); + + omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl | MCPDM_WD_EN); + omap_mcpdm_write(mcpdm, MCPDM_REG_IRQENABLE_SET, MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL | MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL); @@ -258,12 +267,9 @@ static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, mutex_lock(&mcpdm->mutex); - if (!dai->active) { - u32 ctrl = omap_mcpdm_read(mcpdm, MCPDM_REG_CTRL); - - omap_mcpdm_write(mcpdm, MCPDM_REG_CTRL, ctrl | MCPDM_WD_EN); + if (!dai->active) omap_mcpdm_open_streams(mcpdm); - } + mutex_unlock(&mcpdm->mutex); return 0; @@ -384,6 +390,7 @@ static int omap_mcpdm_probe(struct snd_soc_dai *dai) struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); int ret; + clk_prepare_enable(mcpdm->pdmclk); pm_runtime_enable(mcpdm->dev); /* Disable lines while request is ongoing */ @@ -418,8 +425,54 @@ static int omap_mcpdm_remove(struct snd_soc_dai *dai) pm_runtime_disable(mcpdm->dev); + clk_disable_unprepare(mcpdm->pdmclk); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int omap_mcpdm_suspend(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + if (dai->active) { + omap_mcpdm_stop(mcpdm); + omap_mcpdm_close_streams(mcpdm); + } + + mcpdm->pm_active_count = 0; + while (pm_runtime_active(mcpdm->dev)) { + pm_runtime_put_sync(mcpdm->dev); + mcpdm->pm_active_count++; + } + + clk_disable_unprepare(mcpdm->pdmclk); + + return 0; +} + +static int omap_mcpdm_resume(struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + clk_prepare_enable(mcpdm->pdmclk); + + if (mcpdm->pm_active_count) { + while (mcpdm->pm_active_count--) + pm_runtime_get_sync(mcpdm->dev); + + if (dai->active) { + omap_mcpdm_open_streams(mcpdm); + omap_mcpdm_start(mcpdm); + } + } + + return 0; } +#else +#define omap_mcpdm_suspend NULL +#define omap_mcpdm_resume NULL +#endif #define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) #define OMAP_MCPDM_FORMATS SNDRV_PCM_FMTBIT_S32_LE @@ -427,6 +480,8 @@ static int omap_mcpdm_remove(struct snd_soc_dai *dai) static struct snd_soc_dai_driver omap_mcpdm_dai = { .probe = omap_mcpdm_probe, .remove = omap_mcpdm_remove, + .suspend = omap_mcpdm_suspend, + .resume = omap_mcpdm_resume, .probe_order = SND_SOC_COMP_ORDER_LATE, .remove_order = SND_SOC_COMP_ORDER_EARLY, .playback = { @@ -494,6 +549,15 @@ static int asoc_mcpdm_probe(struct platform_device *pdev) mcpdm->dev = &pdev->dev; + mcpdm->pdmclk = devm_clk_get(&pdev->dev, "pdmclk"); + if (IS_ERR(mcpdm->pdmclk)) { + if (PTR_ERR(mcpdm->pdmclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, "Error getting pdmclk (%ld)!\n", + PTR_ERR(mcpdm->pdmclk)); + mcpdm->pdmclk = NULL; + } + ret = devm_snd_soc_register_component(&pdev->dev, &omap_mcpdm_component, &omap_mcpdm_dai, 1); diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c index 54949242bc70..a76845748a10 100644 --- a/sound/soc/omap/rx51.c +++ b/sound/soc/omap/rx51.c @@ -33,7 +33,6 @@ #include <sound/pcm.h> #include <sound/soc.h> #include <linux/platform_data/asoc-ti-mcbsp.h> -#include "../codecs/tpa6130a2.h" #include <asm/mach-types.h> @@ -164,19 +163,6 @@ static int rx51_spk_event(struct snd_soc_dapm_widget *w, return 0; } -static int rx51_hp_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); - - if (SND_SOC_DAPM_EVENT_ON(event)) - tpa6130a2_stereo_enable(codec, 1); - else - tpa6130a2_stereo_enable(codec, 0); - - return 0; -} - static int rx51_get_input(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -235,7 +221,7 @@ static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = { static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event), SND_SOC_DAPM_MIC("DMic", NULL), - SND_SOC_DAPM_HP("Headphone Jack", rx51_hp_event), + SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("HS Mic", NULL), SND_SOC_DAPM_LINE("FM Transmitter", NULL), SND_SOC_DAPM_SPK("Earphone", NULL), @@ -246,11 +232,14 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Ext Spk", NULL, "HPROUT"}, {"Ext Spk", NULL, "HPLCOM"}, {"Ext Spk", NULL, "HPRCOM"}, - {"Headphone Jack", NULL, "LLOUT"}, - {"Headphone Jack", NULL, "RLOUT"}, {"FM Transmitter", NULL, "LLOUT"}, {"FM Transmitter", NULL, "RLOUT"}, + {"Headphone Jack", NULL, "TPA6130A2 HPLEFT"}, + {"Headphone Jack", NULL, "TPA6130A2 HPRIGHT"}, + {"TPA6130A2 LEFTIN", NULL, "LLOUT"}, + {"TPA6130A2 RIGHTIN", NULL, "RLOUT"}, + {"DMic Rate 64", NULL, "DMic"}, {"DMic", NULL, "Mic Bias"}, @@ -286,16 +275,10 @@ static const struct snd_kcontrol_new aic34_rx51_controls[] = { static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_codec *codec = rtd->codec; struct snd_soc_card *card = rtd->card; struct rx51_audio_pdata *pdata = snd_soc_card_get_drvdata(card); int err; - err = tpa6130a2_add_controls(codec); - if (err < 0) { - dev_err(card->dev, "Failed to add TPA6130A2 controls\n"); - return err; - } snd_soc_limit_volume(card, "TPA6130A2 Headphone Playback Volume", 42); err = omap_mcbsp_st_add_controls(rtd, 2); @@ -357,6 +340,10 @@ static struct snd_soc_aux_dev rx51_aux_dev[] = { .name = "TLV320AIC34b", .codec_name = "tlv320aic3x-codec.2-0019", }, + { + .name = "TPA61320A2", + .codec_name = "tpa6130a2.2-0060", + }, }; static struct snd_soc_codec_conf rx51_codec_conf[] = { @@ -364,6 +351,10 @@ static struct snd_soc_codec_conf rx51_codec_conf[] = { .dev_name = "tlv320aic3x-codec.2-0019", .name_prefix = "b", }, + { + .dev_name = "tpa6130a2.2-0060", + .name_prefix = "TPA6130A2", + }, }; /* Audio card */ @@ -435,11 +426,10 @@ static int rx51_soc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Headphone amplifier node is not provided\n"); return -EINVAL; } - - /* TODO: tpa6130a2a driver supports only a single instance, so - * this driver ignores the headphone-amplifier node for now. - * It's already mandatory in the DT binding to be future proof. - */ + rx51_aux_dev[1].codec_name = NULL; + rx51_aux_dev[1].codec_of_node = dai_node; + rx51_codec_conf[1].dev_name = NULL; + rx51_codec_conf[1].of_node = dai_node; } pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); diff --git a/sound/soc/rockchip/rockchip_i2s.c b/sound/soc/rockchip/rockchip_i2s.c index 574c6af28c06..652e8c5ea166 100644 --- a/sound/soc/rockchip/rockchip_i2s.c +++ b/sound/soc/rockchip/rockchip_i2s.c @@ -11,8 +11,10 @@ */ #include <linux/module.h> +#include <linux/mfd/syscon.h> #include <linux/delay.h> #include <linux/of_gpio.h> +#include <linux/of_device.h> #include <linux/clk.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -23,6 +25,11 @@ #define DRV_NAME "rockchip-i2s" +struct rk_i2s_pins { + u32 reg_offset; + u32 shift; +}; + struct rk_i2s_dev { struct device *dev; @@ -33,6 +40,7 @@ struct rk_i2s_dev { struct snd_dmaengine_dai_dma_data playback_dma_data; struct regmap *regmap; + struct regmap *grf; /* * Used to indicate the tx/rx status. @@ -42,6 +50,7 @@ struct rk_i2s_dev { bool tx_start; bool rx_start; bool is_master_mode; + const struct rk_i2s_pins *pins; }; static int i2s_runtime_suspend(struct device *dev) @@ -300,14 +309,38 @@ static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream, I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, val); + if (!IS_ERR(i2s->grf) && i2s->pins) { + regmap_read(i2s->regmap, I2S_TXCR, &val); + val &= I2S_TXCR_CSR_MASK; + + switch (val) { + case I2S_CHN_4: + val = I2S_IO_4CH_OUT_6CH_IN; + break; + case I2S_CHN_6: + val = I2S_IO_6CH_OUT_4CH_IN; + break; + case I2S_CHN_8: + val = I2S_IO_8CH_OUT_2CH_IN; + break; + default: + val = I2S_IO_2CH_OUT_8CH_IN; + break; + } + + val <<= i2s->pins->shift; + val |= (I2S_IO_DIRECTION_MASK << i2s->pins->shift) << 16; + regmap_write(i2s->grf, i2s->pins->reg_offset, val); + } + regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_TDL_MASK, I2S_DMACR_TDL(16)); regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_RDL_MASK, I2S_DMACR_RDL(16)); val = I2S_CKR_TRCM_TXRX; - if (dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates) - val = I2S_CKR_TRCM_TXSHARE; + if (dai->driver->symmetric_rates && rtd->dai_link->symmetric_rates) + val = I2S_CKR_TRCM_TXONLY; regmap_update_bits(i2s->regmap, I2S_CKR, I2S_CKR_TRCM_MASK, @@ -485,9 +518,23 @@ static const struct regmap_config rockchip_i2s_regmap_config = { .cache_type = REGCACHE_FLAT, }; +static const struct rk_i2s_pins rk3399_i2s_pins = { + .reg_offset = 0xe220, + .shift = 11, +}; + +static const struct of_device_id rockchip_i2s_match[] = { + { .compatible = "rockchip,rk3066-i2s", }, + { .compatible = "rockchip,rk3188-i2s", }, + { .compatible = "rockchip,rk3288-i2s", }, + { .compatible = "rockchip,rk3399-i2s", .data = &rk3399_i2s_pins }, + {}, +}; + static int rockchip_i2s_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; + const struct of_device_id *of_id; struct rk_i2s_dev *i2s; struct snd_soc_dai_driver *soc_dai; struct resource *res; @@ -501,6 +548,17 @@ static int rockchip_i2s_probe(struct platform_device *pdev) return -ENOMEM; } + i2s->dev = &pdev->dev; + + i2s->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf"); + if (!IS_ERR(i2s->grf)) { + of_id = of_match_device(rockchip_i2s_match, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + i2s->pins = of_id->data; + } + /* try to prepare related clocks */ i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk"); if (IS_ERR(i2s->hclk)) { @@ -540,7 +598,6 @@ static int rockchip_i2s_probe(struct platform_device *pdev) i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; i2s->capture_dma_data.maxburst = 4; - i2s->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, i2s); pm_runtime_enable(&pdev->dev); @@ -606,14 +663,6 @@ static int rockchip_i2s_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id rockchip_i2s_match[] = { - { .compatible = "rockchip,rk3066-i2s", }, - { .compatible = "rockchip,rk3188-i2s", }, - { .compatible = "rockchip,rk3288-i2s", }, - { .compatible = "rockchip,rk3399-i2s", }, - {}, -}; - static const struct dev_pm_ops rockchip_i2s_pm_ops = { SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume, NULL) diff --git a/sound/soc/rockchip/rockchip_i2s.h b/sound/soc/rockchip/rockchip_i2s.h index dc6e2c74d088..31f11fd25393 100644 --- a/sound/soc/rockchip/rockchip_i2s.h +++ b/sound/soc/rockchip/rockchip_i2s.h @@ -81,8 +81,8 @@ #define I2S_CKR_TRCM_SHIFT 28 #define I2S_CKR_TRCM(x) (x << I2S_CKR_TRCM_SHIFT) #define I2S_CKR_TRCM_TXRX (0 << I2S_CKR_TRCM_SHIFT) -#define I2S_CKR_TRCM_TXSHARE (1 << I2S_CKR_TRCM_SHIFT) -#define I2S_CKR_TRCM_RXSHARE (2 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXONLY (1 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_RXONLY (2 << I2S_CKR_TRCM_SHIFT) #define I2S_CKR_TRCM_MASK (3 << I2S_CKR_TRCM_SHIFT) #define I2S_CKR_MSS_SHIFT 27 #define I2S_CKR_MSS_MASTER (0 << I2S_CKR_MSS_SHIFT) @@ -236,4 +236,11 @@ enum { #define I2S_TXDR (0x0024) #define I2S_RXDR (0x0028) +/* io direction cfg register */ +#define I2S_IO_DIRECTION_MASK (7) +#define I2S_IO_8CH_OUT_2CH_IN (0) +#define I2S_IO_6CH_OUT_4CH_IN (4) +#define I2S_IO_4CH_OUT_6CH_IN (6) +#define I2S_IO_2CH_OUT_8CH_IN (7) + #endif /* _ROCKCHIP_IIS_H */ diff --git a/sound/soc/rockchip/rockchip_max98090.c b/sound/soc/rockchip/rockchip_max98090.c index 543610282cdb..e70ffad07184 100644 --- a/sound/soc/rockchip/rockchip_max98090.c +++ b/sound/soc/rockchip/rockchip_max98090.c @@ -34,13 +34,18 @@ #define DRV_NAME "rockchip-snd-max98090" static struct snd_soc_jack headset_jack; + +/* Headset jack detection DAPM pins */ static struct snd_soc_jack_pin headset_jack_pins[] = { { - .pin = "Headset Jack", - .mask = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | - SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, }, + }; static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { @@ -53,7 +58,7 @@ static const struct snd_soc_dapm_widget rk_dapm_widgets[] = { static const struct snd_soc_dapm_route rk_audio_map[] = { {"IN34", NULL, "Headset Mic"}, {"IN34", NULL, "MICBIAS"}, - {"MICBIAS", NULL, "Headset Mic"}, + {"Headset Mic", NULL, "MICBIAS"}, {"DMICL", NULL, "Int Mic"}, {"Headphone", NULL, "HPL"}, {"Headphone", NULL, "HPR"}, @@ -114,43 +119,27 @@ static int rk_aif1_hw_params(struct snd_pcm_substream *substream, return ret; } -static int rk_init(struct snd_soc_pcm_runtime *runtime) -{ - /* Enable Headset and 4 Buttons Jack detection */ - return snd_soc_card_jack_new(runtime->card, "Headset Jack", - SND_JACK_HEADSET | - SND_JACK_BTN_0 | SND_JACK_BTN_1 | - SND_JACK_BTN_2 | SND_JACK_BTN_3, - &headset_jack, - headset_jack_pins, - ARRAY_SIZE(headset_jack_pins)); -} - -static int rk_98090_headset_init(struct snd_soc_component *component) -{ - return ts3a227e_enable_jack_detect(component, &headset_jack); -} - static struct snd_soc_ops rk_aif1_ops = { .hw_params = rk_aif1_hw_params, }; -static struct snd_soc_aux_dev rk_98090_headset_dev = { - .name = "Headset Chip", - .init = rk_98090_headset_init, -}; - static struct snd_soc_dai_link rk_dailink = { .name = "max98090", .stream_name = "Audio", .codec_dai_name = "HiFi", - .init = rk_init, .ops = &rk_aif1_ops, /* set max98090 as slave */ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, }; +static int rk_98090_headset_init(struct snd_soc_component *component); + +static struct snd_soc_aux_dev rk_98090_headset_dev = { + .name = "Headset Chip", + .init = rk_98090_headset_init, +}; + static struct snd_soc_card snd_soc_card_rk = { .name = "ROCKCHIP-I2S", .owner = THIS_MODULE, @@ -166,6 +155,26 @@ static struct snd_soc_card snd_soc_card_rk = { .num_controls = ARRAY_SIZE(rk_mc_controls), }; +static int rk_98090_headset_init(struct snd_soc_component *component) +{ + int ret; + + /* Enable Headset and 4 Buttons Jack detection */ + ret = snd_soc_card_jack_new(&snd_soc_card_rk, "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &headset_jack, + headset_jack_pins, + ARRAY_SIZE(headset_jack_pins)); + if (ret) + return ret; + + ret = ts3a227e_enable_jack_detect(component, &headset_jack); + + return ret; +} + static int snd_rk_mc_probe(struct platform_device *pdev) { int ret = 0; diff --git a/sound/soc/rockchip/rockchip_spdif.c b/sound/soc/rockchip/rockchip_spdif.c index 100781e37848..4ca265737eda 100644 --- a/sound/soc/rockchip/rockchip_spdif.c +++ b/sound/soc/rockchip/rockchip_spdif.c @@ -101,21 +101,7 @@ static int rk_spdif_hw_params(struct snd_pcm_substream *substream, int ret; srate = params_rate(params); - switch (srate) { - case 32000: - case 48000: - case 96000: - mclk = 96000 * 128; /* 12288000 hz */ - break; - case 44100: - mclk = 44100 * 256; /* 11289600 hz */ - break; - case 192000: - mclk = 192000 * 128; /* 24576000 hz */ - break; - default: - return -EINVAL; - } + mclk = srate * 128; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: @@ -139,7 +125,6 @@ static int rk_spdif_hw_params(struct snd_pcm_substream *substream, return ret; } - val |= SPDIF_CFGR_CLK_DIV(mclk/(srate * 256)); ret = regmap_update_bits(spdif->regmap, SPDIF_CFGR, SPDIF_CFGR_CLK_DIV_MASK | SPDIF_CFGR_HALFWORD_ENABLE | SDPIF_CFGR_VDW_MASK, diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 78baa26e938b..7b722b0094d9 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -224,14 +224,6 @@ config SND_SOC_SNOW Say Y if you want to add audio support for various Snow boards based on Exynos5 series of SoCs. -config SND_SOC_ODROIDX2 - tristate "Audio support for Odroid-X2 and Odroid-U3" - depends on SND_SOC_SAMSUNG && I2C - select SND_SOC_MAX98090 - select SND_SAMSUNG_I2S - help - Say Y here to enable audio support for the Odroid-X2/U3. - config SND_SOC_ARNDALE_RT5631_ALC5631 tristate "Audio support for RT5631(ALC5631) on Arndale Board" depends on SND_SOC_SAMSUNG && I2C diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index 052fe71be518..5d03f5ce6916 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -43,7 +43,6 @@ snd-soc-tobermory-objs := tobermory.o snd-soc-lowland-objs := lowland.o snd-soc-littlemill-objs := littlemill.o snd-soc-bells-objs := bells.o -snd-soc-odroidx2-max98090-objs := odroidx2_max98090.o snd-soc-arndale-rt5631-objs := arndale_rt5631.o obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o @@ -69,5 +68,4 @@ obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o -obj-$(CONFIG_SND_SOC_ODROIDX2) += snd-soc-odroidx2-max98090.o obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c index 4a7a503fe13c..547d31032088 100644 --- a/sound/soc/samsung/ac97.c +++ b/sound/soc/samsung/ac97.c @@ -389,7 +389,8 @@ static int s3c_ac97_probe(struct platform_device *pdev) goto err5; ret = samsung_asoc_dma_platform_register(&pdev->dev, - ac97_pdata->dma_filter); + ac97_pdata->dma_filter, + NULL, NULL); if (ret) { dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); goto err5; diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h index a7616cc9b39e..3830f297e0b6 100644 --- a/sound/soc/samsung/dma.h +++ b/sound/soc/samsung/dma.h @@ -26,7 +26,10 @@ struct s3c_dma_params { void samsung_asoc_init_dma_data(struct snd_soc_dai *dai, struct s3c_dma_params *playback, struct s3c_dma_params *capture); -int samsung_asoc_dma_platform_register(struct device *dev, - dma_filter_fn fn); - +/* + * @tx, @rx arguments can be NULL if the DMA channel names are "tx", "rx", + * otherwise actual DMA channel names must be passed to this function. + */ +int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter, + const char *tx, const char *rx); #endif diff --git a/sound/soc/samsung/dmaengine.c b/sound/soc/samsung/dmaengine.c index 063125937311..2c87f380bfc4 100644 --- a/sound/soc/samsung/dmaengine.c +++ b/sound/soc/samsung/dmaengine.c @@ -28,10 +28,6 @@ #include "dma.h" -static struct snd_dmaengine_pcm_config samsung_dmaengine_pcm_config = { - .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, -}; - void samsung_asoc_init_dma_data(struct snd_soc_dai *dai, struct s3c_dma_params *playback, struct s3c_dma_params *capture) @@ -58,15 +54,28 @@ void samsung_asoc_init_dma_data(struct snd_soc_dai *dai, } EXPORT_SYMBOL_GPL(samsung_asoc_init_dma_data); -int samsung_asoc_dma_platform_register(struct device *dev, - dma_filter_fn filter) +int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter, + const char *tx, const char *rx) { - samsung_dmaengine_pcm_config.compat_filter_fn = filter; + unsigned int flags = SND_DMAENGINE_PCM_FLAG_COMPAT; + + struct snd_dmaengine_pcm_config *pcm_conf; + + pcm_conf = devm_kzalloc(dev, sizeof(*pcm_conf), GFP_KERNEL); + if (!pcm_conf) + return -ENOMEM; + + pcm_conf->prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config; + pcm_conf->compat_filter_fn = filter; + + if (dev->of_node) { + pcm_conf->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = tx; + pcm_conf->chan_names[SNDRV_PCM_STREAM_CAPTURE] = rx; + } else { + flags |= SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME; + } - return devm_snd_dmaengine_pcm_register(dev, - &samsung_dmaengine_pcm_config, - SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME | - SND_DMAENGINE_PCM_FLAG_COMPAT); + return devm_snd_dmaengine_pcm_register(dev, pcm_conf, flags); } EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 70a2559b63f9..50635ee8ff20 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -18,6 +18,7 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/of_gpio.h> #include <linux/pm_runtime.h> @@ -1106,19 +1107,9 @@ static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) return i2s; } -static const struct of_device_id exynos_i2s_match[]; - -static inline const struct samsung_i2s_dai_data *samsung_i2s_get_driver_data( - struct platform_device *pdev) +static void i2s_free_sec_dai(struct i2s_dai *i2s) { - if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { - const struct of_device_id *match; - match = of_match_node(exynos_i2s_match, pdev->dev.of_node); - return match ? match->data : NULL; - } else { - return (struct samsung_i2s_dai_data *) - platform_get_device_id(pdev)->driver_data; - } + platform_device_del(i2s->pdev); } #ifdef CONFIG_PM @@ -1233,9 +1224,13 @@ static int samsung_i2s_probe(struct platform_device *pdev) const struct samsung_i2s_dai_data *i2s_dai_data; int ret; - /* Call during Seconday interface registration */ - i2s_dai_data = samsung_i2s_get_driver_data(pdev); + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) + i2s_dai_data = of_device_get_match_data(&pdev->dev); + else + i2s_dai_data = (struct samsung_i2s_dai_data *) + platform_get_device_id(pdev)->driver_data; + /* Call during the secondary interface registration */ if (i2s_dai_data->dai_type == TYPE_SEC) { sec_dai = dev_get_drvdata(&pdev->dev); if (!sec_dai) { @@ -1249,7 +1244,7 @@ static int samsung_i2s_probe(struct platform_device *pdev) return ret; return samsung_asoc_dma_platform_register(&pdev->dev, - sec_dai->filter); + sec_dai->filter, "tx-sec", NULL); } pri_dai = i2s_alloc_dai(pdev, false); @@ -1350,17 +1345,28 @@ static int samsung_i2s_probe(struct platform_device *pdev) return -EINVAL; } - devm_snd_soc_register_component(&pri_dai->pdev->dev, + ret = devm_snd_soc_register_component(&pri_dai->pdev->dev, &samsung_i2s_component, &pri_dai->i2s_dai_drv, 1); + if (ret < 0) + goto err_free_dai; + + ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter, + NULL, NULL); + if (ret < 0) + goto err_free_dai; pm_runtime_enable(&pdev->dev); - ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter); - if (ret != 0) - return ret; + ret = i2s_register_clock_provider(pdev); + if (!ret) + return 0; - return i2s_register_clock_provider(pdev); + pm_runtime_disable(&pdev->dev); +err_free_dai: + if (sec_dai) + i2s_free_sec_dai(sec_dai); + return ret; } static int samsung_i2s_remove(struct platform_device *pdev) @@ -1477,10 +1483,6 @@ static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 = { .i2s_variant_regs = &i2sv5_i2s1_regs, }; -static const struct samsung_i2s_dai_data samsung_dai_type_pri = { - .dai_type = TYPE_PRI, -}; - static const struct samsung_i2s_dai_data samsung_dai_type_sec = { .dai_type = TYPE_SEC, }; @@ -1492,9 +1494,6 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = { }, { .name = "samsung-i2s-sec", .driver_data = (kernel_ulong_t)&samsung_dai_type_sec, - }, { - .name = "samsung-i2sv4", - .driver_data = (kernel_ulong_t)&i2sv5_dai_type, }, {}, }; diff --git a/sound/soc/samsung/odroidx2_max98090.c b/sound/soc/samsung/odroidx2_max98090.c deleted file mode 100644 index 04217279fe25..000000000000 --- a/sound/soc/samsung/odroidx2_max98090.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2014 Samsung Electronics Co., Ltd. - * - * 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/of.h> -#include <linux/module.h> -#include <sound/soc.h> -#include <sound/pcm_params.h> -#include "i2s.h" - -struct odroidx2_drv_data { - const struct snd_soc_dapm_widget *dapm_widgets; - unsigned int num_dapm_widgets; -}; - -/* The I2S CDCLK output clock frequency for the MAX98090 codec */ -#define MAX98090_MCLK 19200000 - -static struct snd_soc_dai_link odroidx2_dai[]; - -static int odroidx2_late_probe(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - struct snd_soc_dai *codec_dai; - struct snd_soc_dai *cpu_dai; - int ret; - - rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); - codec_dai = rtd->codec_dai; - cpu_dai = rtd->cpu_dai; - - ret = snd_soc_dai_set_sysclk(codec_dai, 0, MAX98090_MCLK, - SND_SOC_CLOCK_IN); - - if (ret < 0 || of_find_property(odroidx2_dai[0].codec_of_node, - "clocks", NULL)) - return ret; - - /* Set the cpu DAI configuration in order to use CDCLK */ - return snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, - 0, SND_SOC_CLOCK_OUT); -} - -static const struct snd_soc_dapm_widget odroidx2_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_MIC("Mic Jack", NULL), - SND_SOC_DAPM_MIC("DMIC", NULL), -}; - -static const struct snd_soc_dapm_widget odroidu3_dapm_widgets[] = { - SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_SPK("Speakers", NULL), -}; - -static struct snd_soc_dai_link odroidx2_dai[] = { - { - .name = "MAX98090", - .stream_name = "MAX98090 PCM", - .codec_dai_name = "HiFi", - .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM, - } -}; - -static struct snd_soc_card odroidx2 = { - .owner = THIS_MODULE, - .dai_link = odroidx2_dai, - .num_links = ARRAY_SIZE(odroidx2_dai), - .fully_routed = true, - .late_probe = odroidx2_late_probe, -}; - -static const struct odroidx2_drv_data odroidx2_drvdata = { - .dapm_widgets = odroidx2_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(odroidx2_dapm_widgets), -}; - -static const struct odroidx2_drv_data odroidu3_drvdata = { - .dapm_widgets = odroidu3_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(odroidu3_dapm_widgets), -}; - -static const struct of_device_id odroidx2_audio_of_match[] = { - { - .compatible = "samsung,odroidx2-audio", - .data = &odroidx2_drvdata, - }, { - .compatible = "samsung,odroidu3-audio", - .data = &odroidu3_drvdata, - }, - { }, -}; -MODULE_DEVICE_TABLE(of, odroidx2_audio_of_match); - -static int odroidx2_audio_probe(struct platform_device *pdev) -{ - struct device_node *snd_node = pdev->dev.of_node; - struct snd_soc_card *card = &odroidx2; - struct device_node *i2s_node, *codec_node; - struct odroidx2_drv_data *dd; - const struct of_device_id *of_id; - int ret; - - of_id = of_match_node(odroidx2_audio_of_match, snd_node); - dd = (struct odroidx2_drv_data *)of_id->data; - - card->num_dapm_widgets = dd->num_dapm_widgets; - card->dapm_widgets = dd->dapm_widgets; - - card->dev = &pdev->dev; - - ret = snd_soc_of_parse_card_name(card, "samsung,model"); - if (ret < 0) - return ret; - - ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing"); - if (ret < 0) - return ret; - - codec_node = of_parse_phandle(snd_node, "samsung,audio-codec", 0); - if (!codec_node) { - dev_err(&pdev->dev, - "Failed parsing samsung,i2s-codec property\n"); - return -EINVAL; - } - - i2s_node = of_parse_phandle(snd_node, "samsung,i2s-controller", 0); - if (!i2s_node) { - dev_err(&pdev->dev, - "Failed parsing samsung,i2s-controller property\n"); - ret = -EINVAL; - goto err_put_codec_n; - } - - odroidx2_dai[0].codec_of_node = codec_node; - odroidx2_dai[0].cpu_of_node = i2s_node; - odroidx2_dai[0].platform_of_node = i2s_node; - - ret = snd_soc_register_card(card); - if (ret) { - dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", - ret); - goto err_put_i2s_n; - } - return 0; - -err_put_i2s_n: - of_node_put(i2s_node); -err_put_codec_n: - of_node_put(codec_node); - return ret; -} - -static int odroidx2_audio_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - - snd_soc_unregister_card(card); - - of_node_put(odroidx2_dai[0].cpu_of_node); - of_node_put(odroidx2_dai[0].codec_of_node); - - return 0; -} - -static struct platform_driver odroidx2_audio_driver = { - .driver = { - .name = "odroidx2-audio", - .of_match_table = odroidx2_audio_of_match, - .pm = &snd_soc_pm_ops, - }, - .probe = odroidx2_audio_probe, - .remove = odroidx2_audio_remove, -}; -module_platform_driver(odroidx2_audio_driver); - -MODULE_AUTHOR("Chen Zhen <zhen1.chen@samsung.com>"); -MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); -MODULE_DESCRIPTION("ALSA SoC Odroid X2/U3 Audio Support"); -MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 498f563a4c9c..490c1a87fd66 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -576,7 +576,8 @@ static int s3c_pcm_dev_probe(struct platform_device *pdev) goto err5; } - ret = samsung_asoc_dma_platform_register(&pdev->dev, filter); + ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, + NULL, NULL); if (ret) { dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); goto err5; diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index b6ab3fc5789e..bf8ae79b0fd2 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -268,7 +268,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod &= ~S3C2412_IISMOD_SLAVE; break; default: - pr_err("unknwon master/slave format\n"); + pr_err("unknown master/slave format\n"); return -EINVAL; } diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c index 204029d12f5b..d45dffb297d8 100644 --- a/sound/soc/samsung/s3c2412-i2s.c +++ b/sound/soc/samsung/s3c2412-i2s.c @@ -177,7 +177,8 @@ static int s3c2412_iis_dev_probe(struct platform_device *pdev) } ret = samsung_asoc_dma_platform_register(&pdev->dev, - pdata->dma_filter); + pdata->dma_filter, + NULL, NULL); if (ret) pr_err("failed to register the DMA: %d\n", ret); diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c index b3a475d73ba7..3e76f2a75a24 100644 --- a/sound/soc/samsung/s3c24xx-i2s.c +++ b/sound/soc/samsung/s3c24xx-i2s.c @@ -482,7 +482,8 @@ static int s3c24xx_iis_dev_probe(struct platform_device *pdev) } ret = samsung_asoc_dma_platform_register(&pdev->dev, - pdata->dma_filter); + pdata->dma_filter, + NULL, NULL); if (ret) pr_err("failed to register the dma: %d\n", ret); diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index 4687f521197c..0cb9c8567546 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -435,7 +435,8 @@ static int spdif_probe(struct platform_device *pdev) spdif->dma_playback = &spdif_stereo_out; - ret = samsung_asoc_dma_platform_register(&pdev->dev, filter); + ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, + NULL, NULL); if (ret) { dev_err(&pdev->dev, "failed to register DMA: %d\n", ret); goto err4; diff --git a/sound/soc/sh/Kconfig b/sound/soc/sh/Kconfig index c9902a6d6fa0..9311f119feb5 100644 --- a/sound/soc/sh/Kconfig +++ b/sound/soc/sh/Kconfig @@ -44,6 +44,7 @@ config SND_SOC_RCAR config SND_SOC_RSRC_CARD tristate "Renesas Sampling Rate Convert Sound Card" + select SND_SIMPLE_CARD_UTILS help This option enables simple sound if you need sampling rate convert diff --git a/sound/soc/sh/rcar/adg.c b/sound/soc/sh/rcar/adg.c index c4c51a4d3c8f..2145957d0229 100644 --- a/sound/soc/sh/rcar/adg.c +++ b/sound/soc/sh/rcar/adg.c @@ -33,11 +33,15 @@ struct rsnd_adg { struct clk *clkout[CLKOUTMAX]; struct clk_onecell_data onecell; struct rsnd_mod mod; + u32 flags; int rbga_rate_for_441khz; /* RBGA */ int rbgb_rate_for_48khz; /* RBGB */ }; +#define LRCLK_ASYNC (1 << 0) +#define adg_mode_flags(adg) (adg->flags) + #define for_each_rsnd_clk(pos, adg, i) \ for (i = 0; \ (i < CLKMAX) && \ @@ -355,6 +359,16 @@ found_clock: rsnd_adg_set_ssi_clk(ssi_mod, data); + if (!(adg_mode_flags(adg) & LRCLK_ASYNC)) { + struct rsnd_mod *adg_mod = rsnd_mod_get(adg); + u32 ckr = 0; + + if (0 == (rate % 8000)) + ckr = 0x80000000; + + rsnd_mod_bset(adg_mod, SSICKR, 0x80000000, ckr); + } + dev_dbg(dev, "ADG: %s[%d] selects 0x%x for %d\n", rsnd_mod_name(ssi_mod), rsnd_mod_id(ssi_mod), data, rate); @@ -532,6 +546,7 @@ int rsnd_adg_probe(struct rsnd_priv *priv) { struct rsnd_adg *adg; struct device *dev = rsnd_priv_to_dev(priv); + struct device_node *np = dev->of_node; adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL); if (!adg) { @@ -545,6 +560,9 @@ int rsnd_adg_probe(struct rsnd_priv *priv) rsnd_adg_get_clkin(priv, adg); rsnd_adg_get_clkout(priv, adg); + if (of_get_property(np, "clkout-lr-asynchronous", NULL)) + adg->flags = LRCLK_ASYNC; + priv->adg = adg; return 0; diff --git a/sound/soc/sh/rcar/gen.c b/sound/soc/sh/rcar/gen.c index 46c0ba7b6414..7d2fdf8dd188 100644 --- a/sound/soc/sh/rcar/gen.c +++ b/sound/soc/sh/rcar/gen.c @@ -206,7 +206,7 @@ static int _rsnd_gen_regmap_init(struct rsnd_priv *priv, */ static int rsnd_gen2_probe(struct rsnd_priv *priv) { - const static struct rsnd_regmap_field_conf conf_ssiu[] = { + static const struct rsnd_regmap_field_conf conf_ssiu[] = { RSND_GEN_S_REG(SSI_MODE0, 0x800), RSND_GEN_S_REG(SSI_MODE1, 0x804), RSND_GEN_S_REG(SSI_MODE2, 0x808), @@ -221,7 +221,7 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) RSND_GEN_M_REG(SSI_INT_ENABLE, 0x18, 0x80), }; - const static struct rsnd_regmap_field_conf conf_scu[] = { + static const struct rsnd_regmap_field_conf conf_scu[] = { RSND_GEN_M_REG(SRC_I_BUSIF_MODE,0x0, 0x20), RSND_GEN_M_REG(SRC_O_BUSIF_MODE,0x4, 0x20), RSND_GEN_M_REG(SRC_BUSIF_DALIGN,0x8, 0x20), @@ -308,7 +308,7 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) RSND_GEN_M_REG(DVC_VOL7R, 0xe44, 0x100), RSND_GEN_M_REG(DVC_DVUER, 0xe48, 0x100), }; - const static struct rsnd_regmap_field_conf conf_adg[] = { + static const struct rsnd_regmap_field_conf conf_adg[] = { RSND_GEN_S_REG(BRRA, 0x00), RSND_GEN_S_REG(BRRB, 0x04), RSND_GEN_S_REG(SSICKR, 0x08), @@ -328,7 +328,7 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) RSND_GEN_S_REG(SRCOUT_TIMSEL4, 0x58), RSND_GEN_S_REG(CMDOUT_TIMSEL, 0x5c), }; - const static struct rsnd_regmap_field_conf conf_ssi[] = { + static const struct rsnd_regmap_field_conf conf_ssi[] = { RSND_GEN_M_REG(SSICR, 0x00, 0x40), RSND_GEN_M_REG(SSISR, 0x04, 0x40), RSND_GEN_M_REG(SSITDR, 0x08, 0x40), @@ -359,14 +359,14 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv) static int rsnd_gen1_probe(struct rsnd_priv *priv) { - const static struct rsnd_regmap_field_conf conf_adg[] = { + static const struct rsnd_regmap_field_conf conf_adg[] = { RSND_GEN_S_REG(BRRA, 0x00), RSND_GEN_S_REG(BRRB, 0x04), RSND_GEN_S_REG(SSICKR, 0x08), RSND_GEN_S_REG(AUDIO_CLK_SEL0, 0x0c), RSND_GEN_S_REG(AUDIO_CLK_SEL1, 0x10), }; - const static struct rsnd_regmap_field_conf conf_ssi[] = { + static const struct rsnd_regmap_field_conf conf_ssi[] = { RSND_GEN_M_REG(SSICR, 0x00, 0x40), RSND_GEN_M_REG(SSISR, 0x04, 0x40), RSND_GEN_M_REG(SSITDR, 0x08, 0x40), diff --git a/sound/soc/sh/rcar/rsrc-card.c b/sound/soc/sh/rcar/rsrc-card.c index 1bc7ecfc42a9..fa37f842b62f 100644 --- a/sound/soc/sh/rcar/rsrc-card.c +++ b/sound/soc/sh/rcar/rsrc-card.c @@ -20,6 +20,7 @@ #include <sound/jack.h> #include <sound/soc.h> #include <sound/soc-dai.h> +#include <sound/simple_card_utils.h> struct rsrc_card_of_data { const char *prefix; @@ -46,25 +47,13 @@ static const struct of_device_id rsrc_card_of_match[] = { }; MODULE_DEVICE_TABLE(of, rsrc_card_of_match); -#define DAI_NAME_NUM 32 -struct rsrc_card_dai { - unsigned int sysclk; - unsigned int tx_slot_mask; - unsigned int rx_slot_mask; - int slots; - int slot_width; - struct clk *clk; - char dai_name[DAI_NAME_NUM]; -}; - #define IDX_CPU 0 #define IDX_CODEC 1 struct rsrc_card_priv { struct snd_soc_card snd_card; struct snd_soc_codec_conf codec_conf; - struct rsrc_card_dai *dai_props; + struct asoc_simple_dai *dai_props; struct snd_soc_dai_link *dai_link; - int dai_num; u32 convert_rate; u32 convert_channels; }; @@ -77,7 +66,7 @@ static int rsrc_card_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct rsrc_card_dai *dai_props = + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, rtd->num); return clk_prepare_enable(dai_props->clk); @@ -87,7 +76,7 @@ static void rsrc_card_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); - struct rsrc_card_dai *dai_props = + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, rtd->num); clk_disable_unprepare(dai_props->clk); @@ -103,7 +92,7 @@ static int rsrc_card_dai_init(struct snd_soc_pcm_runtime *rtd) struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); struct snd_soc_dai *dai; struct snd_soc_dai_link *dai_link; - struct rsrc_card_dai *dai_props; + struct asoc_simple_dai *dai_props; int num = rtd->num; int ret; @@ -159,44 +148,13 @@ static int rsrc_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, return 0; } -static int rsrc_card_parse_daifmt(struct device_node *node, - struct device_node *codec, - struct rsrc_card_priv *priv, - struct snd_soc_dai_link *dai_link, - unsigned int *retfmt) -{ - struct device_node *bitclkmaster = NULL; - struct device_node *framemaster = NULL; - unsigned int daifmt; - - daifmt = snd_soc_of_parse_daifmt(node, NULL, - &bitclkmaster, &framemaster); - daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; - - if (!bitclkmaster && !framemaster) - return -EINVAL; - - if (codec == bitclkmaster) - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; - else - daifmt |= (codec == framemaster) ? - SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; - - of_node_put(bitclkmaster); - of_node_put(framemaster); - - *retfmt = daifmt; - - return 0; -} - static int rsrc_card_parse_links(struct device_node *np, struct rsrc_card_priv *priv, int idx, bool is_fe) { + struct device *dev = rsrc_priv_to_dev(priv); struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); - struct rsrc_card_dai *dai_props = rsrc_priv_to_props(priv, idx); + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, idx); struct of_phandle_args args; int ret; @@ -232,9 +190,11 @@ static int rsrc_card_parse_links(struct device_node *np, if (ret < 0) return ret; - /* set dai_name */ - snprintf(dai_props->dai_name, DAI_NAME_NUM, "fe.%s", - dai_link->cpu_dai_name); + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "fe.%s", + dai_link->cpu_dai_name); + if (ret < 0) + return ret; /* * In soc_bind_dai_link() will check cpu name after @@ -248,7 +208,6 @@ static int rsrc_card_parse_links(struct device_node *np, if (!args.args_count) dai_link->cpu_dai_name = NULL; } else { - struct device *dev = rsrc_priv_to_dev(priv); const struct rsrc_card_of_data *of_data; of_data = of_device_get_match_data(dev); @@ -266,6 +225,12 @@ static int rsrc_card_parse_links(struct device_node *np, if (ret < 0) return ret; + ret = asoc_simple_card_set_dailink_name(dev, dai_link, + "be.%s", + dai_link->codec_dai_name); + if (ret < 0) + return ret; + /* additional name prefix */ if (of_data) { priv->codec_conf.of_node = dai_link->codec_of_node; @@ -276,18 +241,12 @@ static int rsrc_card_parse_links(struct device_node *np, dai_link->codec_of_node, "audio-prefix"); } - - /* set dai_name */ - snprintf(dai_props->dai_name, DAI_NAME_NUM, "be.%s", - dai_link->codec_dai_name); } /* Simple Card assumes platform == cpu */ dai_link->platform_of_node = dai_link->cpu_of_node; dai_link->dpcm_playback = 1; dai_link->dpcm_capture = 1; - dai_link->name = dai_props->dai_name; - dai_link->stream_name = dai_props->dai_name; dai_link->ops = &rsrc_card_ops; dai_link->init = rsrc_card_dai_init; @@ -299,7 +258,7 @@ static int rsrc_card_parse_clk(struct device_node *np, int idx, bool is_fe) { struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); - struct rsrc_card_dai *dai_props = rsrc_priv_to_props(priv, idx); + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, idx); struct clk *clk; struct device_node *of_np = is_fe ? dai_link->cpu_of_node : dai_link->codec_of_node; @@ -336,7 +295,7 @@ static int rsrc_card_dai_sub_link_of(struct device_node *node, { struct device *dev = rsrc_priv_to_dev(priv); struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); - struct rsrc_card_dai *dai_props = rsrc_priv_to_props(priv, idx); + struct asoc_simple_dai *dai_props = rsrc_priv_to_props(priv, idx); int ret; ret = rsrc_card_parse_links(np, priv, idx, is_fe); @@ -348,7 +307,7 @@ static int rsrc_card_dai_sub_link_of(struct device_node *node, return ret; dev_dbg(dev, "\t%s / %04x / %d\n", - dai_props->dai_name, + dai_link->name, dai_link->dai_fmt, dai_props->sysclk); @@ -358,6 +317,7 @@ static int rsrc_card_dai_sub_link_of(struct device_node *node, static int rsrc_card_dai_link_of(struct device_node *node, struct rsrc_card_priv *priv) { + struct device *dev = rsrc_priv_to_dev(priv); struct snd_soc_dai_link *dai_link; struct device_node *np; unsigned int daifmt = 0; @@ -370,8 +330,8 @@ static int rsrc_card_dai_link_of(struct device_node *node, dai_link = rsrc_priv_to_link(priv, i); if (strcmp(np->name, "codec") == 0) { - ret = rsrc_card_parse_daifmt(node, np, priv, - dai_link, &daifmt); + ret = asoc_simple_card_parse_daifmt(dev, node, np, + NULL, &daifmt); if (ret < 0) return ret; break; @@ -402,7 +362,7 @@ static int rsrc_card_parse_of(struct device_node *node, struct device *dev) { const struct rsrc_card_of_data *of_data = of_device_get_match_data(dev); - struct rsrc_card_dai *props; + struct asoc_simple_dai *props; struct snd_soc_dai_link *links; int ret; int num; @@ -418,7 +378,6 @@ static int rsrc_card_parse_of(struct device_node *node, priv->dai_props = props; priv->dai_link = links; - priv->dai_num = num; /* Init snd_soc_card */ priv->snd_card.owner = THIS_MODULE; @@ -436,9 +395,6 @@ static int rsrc_card_parse_of(struct device_node *node, "audio-routing"); } - /* Parse the card name from DT */ - snd_soc_of_parse_card_name(&priv->snd_card, "card-name"); - /* sampling rate convert */ of_property_read_u32(node, "convert-rate", &priv->convert_rate); @@ -454,8 +410,9 @@ static int rsrc_card_parse_of(struct device_node *node, if (ret < 0) return ret; - if (!priv->snd_card.name) - priv->snd_card.name = priv->snd_card.dai_link->name; + ret = asoc_simple_card_parse_card_name(&priv->snd_card, "card-"); + if (ret < 0) + return ret; return 0; } diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 875733c52953..d2df46c14c68 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -530,14 +530,15 @@ static int soc_compr_pointer(struct snd_compr_stream *cstream, { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_platform *platform = rtd->platform; + int ret = 0; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); if (platform->driver->compr_ops && platform->driver->compr_ops->pointer) - platform->driver->compr_ops->pointer(cstream, tstamp); + ret = platform->driver->compr_ops->pointer(cstream, tstamp); mutex_unlock(&rtd->pcm_mutex); - return 0; + return ret; } static int soc_compr_copy(struct snd_compr_stream *cstream, diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index c4464858bf01..8698c26773b3 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1073,7 +1073,11 @@ static int dapm_widget_list_create(struct snd_soc_dapm_widget_list **list, */ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, struct list_head *list, enum snd_soc_dapm_direction dir, - int (*fn)(struct snd_soc_dapm_widget *, struct list_head *)) + int (*fn)(struct snd_soc_dapm_widget *, struct list_head *, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)), + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)) { enum snd_soc_dapm_direction rdir = SND_SOC_DAPM_DIR_REVERSE(dir); struct snd_soc_dapm_path *path; @@ -1088,6 +1092,11 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, if (list) list_add_tail(&widget->work_list, list); + if (custom_stop_condition && custom_stop_condition(widget, dir)) { + widget->endpoints[dir] = 1; + return widget->endpoints[dir]; + } + if ((widget->is_ep & SND_SOC_DAPM_DIR_TO_EP(dir)) && widget->connected) { widget->endpoints[dir] = snd_soc_dapm_suspend_check(widget); return widget->endpoints[dir]; @@ -1106,7 +1115,7 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, if (path->connect) { path->walking = 1; - con += fn(path->node[dir], list); + con += fn(path->node[dir], list, custom_stop_condition); path->walking = 0; } } @@ -1119,23 +1128,37 @@ static __always_inline int is_connected_ep(struct snd_soc_dapm_widget *widget, /* * Recursively check for a completed path to an active or physically connected * output widget. Returns number of complete paths. + * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. */ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget, - struct list_head *list) + struct list_head *list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, + enum snd_soc_dapm_direction)) { return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_OUT, - is_connected_output_ep); + is_connected_output_ep, custom_stop_condition); } /* * Recursively check for a completed path to an active or physically connected * input widget. Returns number of complete paths. + * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. */ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, - struct list_head *list) + struct list_head *list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *i, + enum snd_soc_dapm_direction)) { return is_connected_ep(widget, list, SND_SOC_DAPM_DIR_IN, - is_connected_input_ep); + is_connected_input_ep, custom_stop_condition); } /** @@ -1143,15 +1166,24 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget, * @dai: the soc DAI. * @stream: stream direction. * @list: list of active widgets for this stream. + * @custom_stop_condition: (optional) a function meant to stop the widget graph + * walk based on custom logic. * * Queries DAPM graph as to whether an valid audio stream path exists for * the initial stream specified by name. This takes into account * current mixer and mux kcontrol settings. Creates list of valid widgets. * + * Optionally, can be supplied with a function acting as a stopping condition. + * This function takes the dapm widget currently being examined and the walk + * direction as an arguments, it should return true if the walk should be + * stopped and false otherwise. + * * Returns the number of valid paths or negative error. */ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, - struct snd_soc_dapm_widget_list **list) + struct snd_soc_dapm_widget_list **list, + bool (*custom_stop_condition)(struct snd_soc_dapm_widget *, + enum snd_soc_dapm_direction)) { struct snd_soc_card *card = dai->component->card; struct snd_soc_dapm_widget *w; @@ -1171,9 +1203,11 @@ int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream, } if (stream == SNDRV_PCM_STREAM_PLAYBACK) - paths = is_connected_output_ep(dai->playback_widget, &widgets); + paths = is_connected_output_ep(dai->playback_widget, &widgets, + custom_stop_condition); else - paths = is_connected_input_ep(dai->capture_widget, &widgets); + paths = is_connected_input_ep(dai->capture_widget, &widgets, + custom_stop_condition); /* Drop starting point */ list_del(widgets.next); @@ -1268,8 +1302,8 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) DAPM_UPDATE_STAT(w, power_checks); - in = is_connected_input_ep(w, NULL); - out = is_connected_output_ep(w, NULL); + in = is_connected_input_ep(w, NULL, NULL); + out = is_connected_output_ep(w, NULL, NULL); return out != 0 && in != 0; } @@ -1928,8 +1962,8 @@ static ssize_t dapm_widget_power_read_file(struct file *file, in = 0; out = 0; } else { - in = is_connected_input_ep(w, NULL); - out = is_connected_output_ep(w, NULL); + in = is_connected_input_ep(w, NULL, NULL); + out = is_connected_output_ep(w, NULL, NULL); } ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d", @@ -3282,6 +3316,7 @@ snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm, mutex_unlock(&dapm->card->dapm_mutex); return w; } +EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control); struct snd_soc_dapm_widget * snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index aa99dac31b3b..60d702f8b9f0 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1287,6 +1287,46 @@ static int widget_in_list(struct snd_soc_dapm_widget_list *list, return 0; } +static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, + enum snd_soc_dapm_direction dir) +{ + struct snd_soc_card *card = widget->dapm->card; + struct snd_soc_pcm_runtime *rtd; + int i; + + if (dir == SND_SOC_DAPM_DIR_OUT) { + list_for_each_entry(rtd, &card->rtd_list, list) { + if (!rtd->dai_link->no_pcm) + continue; + + if (rtd->cpu_dai->playback_widget == widget) + return true; + + for (i = 0; i < rtd->num_codecs; ++i) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + if (dai->playback_widget == widget) + return true; + } + } + } else { /* SND_SOC_DAPM_DIR_IN */ + list_for_each_entry(rtd, &card->rtd_list, list) { + if (!rtd->dai_link->no_pcm) + continue; + + if (rtd->cpu_dai->capture_widget == widget) + return true; + + for (i = 0; i < rtd->num_codecs; ++i) { + struct snd_soc_dai *dai = rtd->codec_dais[i]; + if (dai->capture_widget == widget) + return true; + } + } + } + + return false; +} + int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int stream, struct snd_soc_dapm_widget_list **list) { @@ -1294,7 +1334,8 @@ int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int paths; /* get number of valid DAI paths and their widgets */ - paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list); + paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list, + dpcm_end_walk_at_be); dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, stream ? "capture" : "playback"); diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index ee1c7c245bc7..1ac2db205a0d 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1029,9 +1029,9 @@ static int uni_player_parse_dt_audio_glue(struct platform_device *pdev, regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); - if (!regmap) { + if (IS_ERR(regmap)) { dev_err(&pdev->dev, "sti-audio-clk-glue syscf not found\n"); - return -EINVAL; + return PTR_ERR(regmap); } player->clk_sel = regmap_field_alloc(regmap, regfield[0]); diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index ae42294ef688..2a954bd01fd8 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -8,6 +8,15 @@ config SND_SUN4I_CODEC Select Y or M to add support for the Codec embedded in the Allwinner A10 and affiliated SoCs. +config SND_SUN4I_I2S + tristate "Allwinner A10 I2S Support" + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to + the Allwinner A10 I2S. You will also need to select the + individual machine drivers to support below. + config SND_SUN4I_SPDIF tristate "Allwinner A10 SPDIF Support" depends on OF diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile index 8f5e889667f1..604c7b842837 100644 --- a/sound/soc/sunxi/Makefile +++ b/sound/soc/sunxi/Makefile @@ -1,3 +1,3 @@ obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o - +obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c new file mode 100644 index 000000000000..687a8f83dbe5 --- /dev/null +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2015 Andrea Venturi + * Andrea Venturi <be17068@iperbole.bo.it> + * + * Copyright (C) 2016 Maxime Ripard + * Maxime Ripard <maxime.ripard@free-electrons.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/clk.h> +#include <linux/dmaengine.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#define SUN4I_I2S_CTRL_REG 0x00 +#define SUN4I_I2S_CTRL_SDO_EN_MASK GENMASK(11, 8) +#define SUN4I_I2S_CTRL_SDO_EN(sdo) BIT(8 + (sdo)) +#define SUN4I_I2S_CTRL_MODE_MASK BIT(5) +#define SUN4I_I2S_CTRL_MODE_SLAVE (1 << 5) +#define SUN4I_I2S_CTRL_MODE_MASTER (0 << 5) +#define SUN4I_I2S_CTRL_TX_EN BIT(2) +#define SUN4I_I2S_CTRL_RX_EN BIT(1) +#define SUN4I_I2S_CTRL_GL_EN BIT(0) + +#define SUN4I_I2S_FMT0_REG 0x04 +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_MASK BIT(7) +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED (1 << 7) +#define SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL (0 << 7) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_MASK BIT(6) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED (1 << 6) +#define SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL (0 << 6) +#define SUN4I_I2S_FMT0_SR_MASK GENMASK(5, 4) +#define SUN4I_I2S_FMT0_SR(sr) ((sr) << 4) +#define SUN4I_I2S_FMT0_WSS_MASK GENMASK(3, 2) +#define SUN4I_I2S_FMT0_WSS(wss) ((wss) << 2) +#define SUN4I_I2S_FMT0_FMT_MASK GENMASK(1, 0) +#define SUN4I_I2S_FMT0_FMT_RIGHT_J (2 << 0) +#define SUN4I_I2S_FMT0_FMT_LEFT_J (1 << 0) +#define SUN4I_I2S_FMT0_FMT_I2S (0 << 0) + +#define SUN4I_I2S_FMT1_REG 0x08 +#define SUN4I_I2S_FIFO_TX_REG 0x0c +#define SUN4I_I2S_FIFO_RX_REG 0x10 + +#define SUN4I_I2S_FIFO_CTRL_REG 0x14 +#define SUN4I_I2S_FIFO_CTRL_FLUSH_TX BIT(25) +#define SUN4I_I2S_FIFO_CTRL_FLUSH_RX BIT(24) +#define SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK BIT(2) +#define SUN4I_I2S_FIFO_CTRL_TX_MODE(mode) ((mode) << 2) +#define SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK GENMASK(1, 0) +#define SUN4I_I2S_FIFO_CTRL_RX_MODE(mode) (mode) + +#define SUN4I_I2S_FIFO_STA_REG 0x18 + +#define SUN4I_I2S_DMA_INT_CTRL_REG 0x1c +#define SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN BIT(7) +#define SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN BIT(3) + +#define SUN4I_I2S_INT_STA_REG 0x20 + +#define SUN4I_I2S_CLK_DIV_REG 0x24 +#define SUN4I_I2S_CLK_DIV_MCLK_EN BIT(7) +#define SUN4I_I2S_CLK_DIV_BCLK_MASK GENMASK(6, 4) +#define SUN4I_I2S_CLK_DIV_BCLK(bclk) ((bclk) << 4) +#define SUN4I_I2S_CLK_DIV_MCLK_MASK GENMASK(3, 0) +#define SUN4I_I2S_CLK_DIV_MCLK(mclk) ((mclk) << 0) + +#define SUN4I_I2S_RX_CNT_REG 0x28 +#define SUN4I_I2S_TX_CNT_REG 0x2c + +#define SUN4I_I2S_TX_CHAN_SEL_REG 0x30 +#define SUN4I_I2S_TX_CHAN_SEL(num_chan) (((num_chan) - 1) << 0) + +#define SUN4I_I2S_TX_CHAN_MAP_REG 0x34 +#define SUN4I_I2S_TX_CHAN_MAP(chan, sample) ((sample) << (chan << 2)) + +#define SUN4I_I2S_RX_CHAN_SEL_REG 0x38 +#define SUN4I_I2S_RX_CHAN_MAP_REG 0x3c + +struct sun4i_i2s { + struct clk *bus_clk; + struct clk *mod_clk; + struct regmap *regmap; + + struct snd_dmaengine_dai_dma_data playback_dma_data; +}; + +struct sun4i_i2s_clk_div { + u8 div; + u8 val; +}; + +static const struct sun4i_i2s_clk_div sun4i_i2s_bclk_div[] = { + { .div = 2, .val = 0 }, + { .div = 4, .val = 1 }, + { .div = 6, .val = 2 }, + { .div = 8, .val = 3 }, + { .div = 12, .val = 4 }, + { .div = 16, .val = 5 }, +}; + +static const struct sun4i_i2s_clk_div sun4i_i2s_mclk_div[] = { + { .div = 1, .val = 0 }, + { .div = 2, .val = 1 }, + { .div = 4, .val = 2 }, + { .div = 6, .val = 3 }, + { .div = 8, .val = 4 }, + { .div = 12, .val = 5 }, + { .div = 16, .val = 6 }, + { .div = 24, .val = 7 }, +}; + +static int sun4i_i2s_get_bclk_div(struct sun4i_i2s *i2s, + unsigned int oversample_rate, + unsigned int word_size) +{ + int div = oversample_rate / word_size / 2; + int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_i2s_bclk_div); i++) { + const struct sun4i_i2s_clk_div *bdiv = &sun4i_i2s_bclk_div[i]; + + if (bdiv->div == div) + return bdiv->val; + } + + return -EINVAL; +} + +static int sun4i_i2s_get_mclk_div(struct sun4i_i2s *i2s, + unsigned int oversample_rate, + unsigned int module_rate, + unsigned int sampling_rate) +{ + int div = module_rate / sampling_rate / oversample_rate; + int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_i2s_mclk_div); i++) { + const struct sun4i_i2s_clk_div *mdiv = &sun4i_i2s_mclk_div[i]; + + if (mdiv->div == div) + return mdiv->val; + } + + return -EINVAL; +} + +static int sun4i_i2s_oversample_rates[] = { 128, 192, 256, 384, 512, 768 }; + +static int sun4i_i2s_set_clk_rate(struct sun4i_i2s *i2s, + unsigned int rate, + unsigned int word_size) +{ + unsigned int clk_rate; + int bclk_div, mclk_div; + int ret, i; + + switch (rate) { + case 176400: + case 88200: + case 44100: + case 22050: + case 11025: + clk_rate = 22579200; + break; + + case 192000: + case 128000: + case 96000: + case 64000: + case 48000: + case 32000: + case 24000: + case 16000: + case 12000: + case 8000: + clk_rate = 24576000; + break; + + default: + return -EINVAL; + } + + ret = clk_set_rate(i2s->mod_clk, clk_rate); + if (ret) + return ret; + + /* Always favor the highest oversampling rate */ + for (i = (ARRAY_SIZE(sun4i_i2s_oversample_rates) - 1); i >= 0; i--) { + unsigned int oversample_rate = sun4i_i2s_oversample_rates[i]; + + bclk_div = sun4i_i2s_get_bclk_div(i2s, oversample_rate, + word_size); + mclk_div = sun4i_i2s_get_mclk_div(i2s, oversample_rate, + clk_rate, + rate); + + if ((bclk_div >= 0) && (mclk_div >= 0)) + break; + } + + if ((bclk_div < 0) || (mclk_div < 0)) + return -EINVAL; + + regmap_write(i2s->regmap, SUN4I_I2S_CLK_DIV_REG, + SUN4I_I2S_CLK_DIV_BCLK(bclk_div) | + SUN4I_I2S_CLK_DIV_MCLK(mclk_div) | + SUN4I_I2S_CLK_DIV_MCLK_EN); + + return 0; +} + +static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + int sr, wss; + u32 width; + + if (params_channels(params) != 2) + return -EINVAL; + + switch (params_physical_width(params)) { + case 16: + width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + default: + return -EINVAL; + } + i2s->playback_dma_data.addr_width = width; + + switch (params_width(params)) { + case 16: + sr = 0; + wss = 0; + break; + + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, + SUN4I_I2S_FMT0_WSS_MASK | SUN4I_I2S_FMT0_SR_MASK, + SUN4I_I2S_FMT0_WSS(wss) | SUN4I_I2S_FMT0_SR(sr)); + + return sun4i_i2s_set_clk_rate(i2s, params_rate(params), + params_width(params)); +} + +static int sun4i_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 val; + + /* DAI Mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val = SUN4I_I2S_FMT0_FMT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = SUN4I_I2S_FMT0_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val = SUN4I_I2S_FMT0_FMT_RIGHT_J; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, + SUN4I_I2S_FMT0_FMT_MASK, + val); + + /* DAI clock polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + val = SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED | + SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED; + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + val = SUN4I_I2S_FMT0_BCLK_POLARITY_INVERTED | + SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + val = SUN4I_I2S_FMT0_LRCLK_POLARITY_INVERTED | + SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL; + break; + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + val = SUN4I_I2S_FMT0_BCLK_POLARITY_NORMAL | + SUN4I_I2S_FMT0_LRCLK_POLARITY_NORMAL; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, + SUN4I_I2S_FMT0_BCLK_POLARITY_MASK | + SUN4I_I2S_FMT0_LRCLK_POLARITY_MASK, + val); + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* BCLK and LRCLK master */ + val = SUN4I_I2S_CTRL_MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* BCLK and LRCLK slave */ + val = SUN4I_I2S_CTRL_MODE_SLAVE; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_MODE_MASK, + val); + + /* Set significant bits in our FIFOs */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_TX_MODE_MASK | + SUN4I_I2S_FIFO_CTRL_RX_MODE_MASK, + SUN4I_I2S_FIFO_CTRL_TX_MODE(1) | + SUN4I_I2S_FIFO_CTRL_RX_MODE(1)); + return 0; +} + +static void sun4i_i2s_start_playback(struct sun4i_i2s *i2s) +{ + /* Flush TX FIFO */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_FLUSH_TX, + SUN4I_I2S_FIFO_CTRL_FLUSH_TX); + + /* Clear TX counter */ + regmap_write(i2s->regmap, SUN4I_I2S_TX_CNT_REG, 0); + + /* Enable TX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_TX_EN, + SUN4I_I2S_CTRL_TX_EN); + + /* Enable TX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN); +} + + +static void sun4i_i2s_stop_playback(struct sun4i_i2s *i2s) +{ + /* Disable TX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_TX_EN, + 0); + + /* Disable TX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN, + 0); +} + +static int sun4i_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_i2s_start_playback(i2s); + else + return -EINVAL; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sun4i_i2s_stop_playback(i2s); + else + return -EINVAL; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int sun4i_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + /* Enable the whole hardware block */ + regmap_write(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_GL_EN); + + /* Enable the first output line */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_SDO_EN_MASK, + SUN4I_I2S_CTRL_SDO_EN(0)); + + /* Enable the first two channels */ + regmap_write(i2s->regmap, SUN4I_I2S_TX_CHAN_SEL_REG, + SUN4I_I2S_TX_CHAN_SEL(2)); + + /* Map them to the two first samples coming in */ + regmap_write(i2s->regmap, SUN4I_I2S_TX_CHAN_MAP_REG, + SUN4I_I2S_TX_CHAN_MAP(0, 0) | SUN4I_I2S_TX_CHAN_MAP(1, 1)); + + return clk_prepare_enable(i2s->mod_clk); +} + +static void sun4i_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(i2s->mod_clk); + + /* Disable our output lines */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_SDO_EN_MASK, 0); + + /* Disable the whole hardware block */ + regmap_write(i2s->regmap, SUN4I_I2S_CTRL_REG, 0); +} + +static const struct snd_soc_dai_ops sun4i_i2s_dai_ops = { + .hw_params = sun4i_i2s_hw_params, + .set_fmt = sun4i_i2s_set_fmt, + .shutdown = sun4i_i2s_shutdown, + .startup = sun4i_i2s_startup, + .trigger = sun4i_i2s_trigger, +}; + +static int sun4i_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, NULL); + + snd_soc_dai_set_drvdata(dai, i2s); + + return 0; +} + +static struct snd_soc_dai_driver sun4i_i2s_dai = { + .probe = sun4i_i2s_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sun4i_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver sun4i_i2s_component = { + .name = "sun4i-dai", +}; + +static bool sun4i_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_TX_REG: + return false; + + default: + return true; + } +} + +static bool sun4i_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_RX_REG: + case SUN4I_I2S_FIFO_STA_REG: + return false; + + default: + return true; + } +} + +static bool sun4i_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SUN4I_I2S_FIFO_RX_REG: + case SUN4I_I2S_INT_STA_REG: + case SUN4I_I2S_RX_CNT_REG: + case SUN4I_I2S_TX_CNT_REG: + return true; + + default: + return false; + } +} + +static const struct reg_default sun4i_i2s_reg_defaults[] = { + { SUN4I_I2S_CTRL_REG, 0x00000000 }, + { SUN4I_I2S_FMT0_REG, 0x0000000c }, + { SUN4I_I2S_FMT1_REG, 0x00004020 }, + { SUN4I_I2S_FIFO_CTRL_REG, 0x000400f0 }, + { SUN4I_I2S_DMA_INT_CTRL_REG, 0x00000000 }, + { SUN4I_I2S_CLK_DIV_REG, 0x00000000 }, + { SUN4I_I2S_TX_CHAN_SEL_REG, 0x00000001 }, + { SUN4I_I2S_TX_CHAN_MAP_REG, 0x76543210 }, + { SUN4I_I2S_RX_CHAN_SEL_REG, 0x00000001 }, + { SUN4I_I2S_RX_CHAN_MAP_REG, 0x00003210 }, +}; + +static const struct regmap_config sun4i_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN4I_I2S_RX_CHAN_MAP_REG, + + .cache_type = REGCACHE_FLAT, + .reg_defaults = sun4i_i2s_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(sun4i_i2s_reg_defaults), + .writeable_reg = sun4i_i2s_wr_reg, + .readable_reg = sun4i_i2s_rd_reg, + .volatile_reg = sun4i_i2s_volatile_reg, +}; + +static int sun4i_i2s_runtime_resume(struct device *dev) +{ + struct sun4i_i2s *i2s = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s->bus_clk); + if (ret) { + dev_err(dev, "Failed to enable bus clock\n"); + return ret; + } + + regcache_cache_only(i2s->regmap, false); + regcache_mark_dirty(i2s->regmap); + + ret = regcache_sync(i2s->regmap); + if (ret) { + dev_err(dev, "Failed to sync regmap cache\n"); + goto err_disable_clk; + } + + return 0; + +err_disable_clk: + clk_disable_unprepare(i2s->bus_clk); + return ret; +} + +static int sun4i_i2s_runtime_suspend(struct device *dev) +{ + struct sun4i_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + clk_disable_unprepare(i2s->bus_clk); + + return 0; +} + +static int sun4i_i2s_probe(struct platform_device *pdev) +{ + struct sun4i_i2s *i2s; + struct resource *res; + void __iomem *regs; + int irq, ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + platform_set_drvdata(pdev, i2s); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Can't retrieve our interrupt\n"); + return irq; + } + + i2s->bus_clk = devm_clk_get(&pdev->dev, "apb"); + if (IS_ERR(i2s->bus_clk)) { + dev_err(&pdev->dev, "Can't get our bus clock\n"); + return PTR_ERR(i2s->bus_clk); + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &sun4i_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "Regmap initialisation failed\n"); + return PTR_ERR(i2s->regmap); + } + + i2s->mod_clk = devm_clk_get(&pdev->dev, "mod"); + if (IS_ERR(i2s->mod_clk)) { + dev_err(&pdev->dev, "Can't get our mod clock\n"); + return PTR_ERR(i2s->mod_clk); + } + + i2s->playback_dma_data.addr = res->start + SUN4I_I2S_FIFO_TX_REG; + i2s->playback_dma_data.maxburst = 4; + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = sun4i_i2s_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &sun4i_i2s_component, + &sun4i_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI\n"); + goto err_suspend; + } + + ret = snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM\n"); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int sun4i_i2s_remove(struct platform_device *pdev) +{ + snd_dmaengine_pcm_unregister(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + sun4i_i2s_runtime_suspend(&pdev->dev); + + return 0; +} + +static const struct of_device_id sun4i_i2s_match[] = { + { .compatible = "allwinner,sun4i-a10-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, sun4i_i2s_match); + +static const struct dev_pm_ops sun4i_i2s_pm_ops = { + .runtime_resume = sun4i_i2s_runtime_resume, + .runtime_suspend = sun4i_i2s_runtime_suspend, +}; + +static struct platform_driver sun4i_i2s_driver = { + .probe = sun4i_i2s_probe, + .remove = sun4i_i2s_remove, + .driver = { + .name = "sun4i-i2s", + .of_match_table = sun4i_i2s_match, + .pm = &sun4i_i2s_pm_ops, + }, +}; +module_platform_driver(sun4i_i2s_driver); + +MODULE_AUTHOR("Andrea Venturi <be17068@iperbole.bo.it>"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 I2S driver"); +MODULE_LICENSE("GPL"); |