diff options
Diffstat (limited to 'sound/soc/uniphier')
-rw-r--r-- | sound/soc/uniphier/Kconfig | 50 | ||||
-rw-r--r-- | sound/soc/uniphier/Makefile | 11 | ||||
-rw-r--r-- | sound/soc/uniphier/aio-compress.c | 425 | ||||
-rw-r--r-- | sound/soc/uniphier/aio-core.c | 1251 | ||||
-rw-r--r-- | sound/soc/uniphier/aio-cpu.c | 713 | ||||
-rw-r--r-- | sound/soc/uniphier/aio-dma.c | 306 | ||||
-rw-r--r-- | sound/soc/uniphier/aio-ld11.c | 418 | ||||
-rw-r--r-- | sound/soc/uniphier/aio-pxs2.c | 320 | ||||
-rw-r--r-- | sound/soc/uniphier/aio-reg.h | 476 | ||||
-rw-r--r-- | sound/soc/uniphier/aio.h | 354 | ||||
-rw-r--r-- | sound/soc/uniphier/evea.c | 574 |
11 files changed, 4898 insertions, 0 deletions
diff --git a/sound/soc/uniphier/Kconfig b/sound/soc/uniphier/Kconfig new file mode 100644 index 000000000..ddfa6424c --- /dev/null +++ b/sound/soc/uniphier/Kconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0 +config SND_SOC_UNIPHIER + tristate "ASoC support for UniPhier" + depends on (ARCH_UNIPHIER || COMPILE_TEST) + help + Say Y or M if you want to add support for the Socionext + UniPhier SoC audio interfaces. You will also need to select the + audio interfaces to support below. + If unsure select "N". + +config SND_SOC_UNIPHIER_AIO + tristate "UniPhier AIO DAI Driver" + select REGMAP_MMIO + select SND_SOC_COMPRESS + depends on SND_SOC_UNIPHIER + help + This adds ASoC driver support for Socionext UniPhier + 'AIO' Audio Input/Output subsystem. + Select Y if you use such device. + If unsure select "N". + +config SND_SOC_UNIPHIER_LD11 + tristate "UniPhier LD11/LD20 Device Driver" + depends on SND_SOC_UNIPHIER + select SND_SOC_UNIPHIER_AIO + help + This adds ASoC driver for Socionext UniPhier LD11/LD20 + input and output that can be used with other codecs. + Select Y if you use such device. + If unsure select "N". + +config SND_SOC_UNIPHIER_PXS2 + tristate "UniPhier PXs2 Device Driver" + depends on SND_SOC_UNIPHIER + select SND_SOC_UNIPHIER_AIO + help + This adds ASoC driver for Socionext UniPhier PXs2 + input and output that can be used with other codecs. + Select Y if you use such device. + If unsure select "N". + +config SND_SOC_UNIPHIER_EVEA_CODEC + tristate "UniPhier SoC internal audio codec" + depends on SND_SOC_UNIPHIER + select REGMAP_MMIO + help + This adds Codec driver for Socionext UniPhier LD11/20 SoC + internal DAC. This driver supports Line In / Out and HeadPhone. + Select Y if you use such device. + If unsure select "N". diff --git a/sound/soc/uniphier/Makefile b/sound/soc/uniphier/Makefile new file mode 100644 index 000000000..88169395f --- /dev/null +++ b/sound/soc/uniphier/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-soc-uniphier-aio-cpu-objs := aio-core.o aio-dma.o aio-cpu.o aio-compress.o +snd-soc-uniphier-aio-ld11-objs := aio-ld11.o +snd-soc-uniphier-aio-pxs2-objs := aio-pxs2.o + +obj-$(CONFIG_SND_SOC_UNIPHIER_AIO) += snd-soc-uniphier-aio-cpu.o +obj-$(CONFIG_SND_SOC_UNIPHIER_LD11) += snd-soc-uniphier-aio-ld11.o +obj-$(CONFIG_SND_SOC_UNIPHIER_PXS2) += snd-soc-uniphier-aio-pxs2.o + +snd-soc-uniphier-evea-objs := evea.o +obj-$(CONFIG_SND_SOC_UNIPHIER_EVEA_CODEC) += snd-soc-uniphier-evea.o diff --git a/sound/soc/uniphier/aio-compress.c b/sound/soc/uniphier/aio-compress.c new file mode 100644 index 000000000..17f773ac5 --- /dev/null +++ b/sound/soc/uniphier/aio-compress.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO Compress Audio driver. +// +// Copyright (c) 2017-2018 Socionext Inc. + +#include <linux/bitfield.h> +#include <linux/circ_buf.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "aio.h" + +static int uniphier_aio_compr_prepare(struct snd_compr_stream *cstream); +static int uniphier_aio_compr_hw_free(struct snd_compr_stream *cstream); + +static int uniphier_aio_comprdma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_compr *compr = rtd->compr; + struct device *dev = compr->card->dev; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[compr->direction]; + size_t size = AUD_RING_SIZE; + int dma_dir = DMA_FROM_DEVICE, ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(33)); + if (ret) + return ret; + + sub->compr_area = kzalloc(size, GFP_KERNEL); + if (!sub->compr_area) + return -ENOMEM; + + if (sub->swm->dir == PORT_DIR_OUTPUT) + dma_dir = DMA_TO_DEVICE; + + sub->compr_addr = dma_map_single(dev, sub->compr_area, size, dma_dir); + if (dma_mapping_error(dev, sub->compr_addr)) { + kfree(sub->compr_area); + sub->compr_area = NULL; + + return -ENOMEM; + } + + sub->compr_bytes = size; + + return 0; +} + +static int uniphier_aio_comprdma_free(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_compr *compr = rtd->compr; + struct device *dev = compr->card->dev; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[compr->direction]; + int dma_dir = DMA_FROM_DEVICE; + + if (sub->swm->dir == PORT_DIR_OUTPUT) + dma_dir = DMA_TO_DEVICE; + + dma_unmap_single(dev, sub->compr_addr, sub->compr_bytes, dma_dir); + kfree(sub->compr_area); + sub->compr_area = NULL; + + return 0; +} + +static int uniphier_aio_compr_open(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int ret; + + if (sub->cstream) + return -EBUSY; + + sub->cstream = cstream; + sub->pass_through = 1; + sub->use_mmap = false; + + ret = uniphier_aio_comprdma_new(rtd); + if (ret) + return ret; + + ret = aio_init(sub); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aio_compr_free(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int ret; + + ret = uniphier_aio_compr_hw_free(cstream); + if (ret) + return ret; + ret = uniphier_aio_comprdma_free(rtd); + if (ret) + return ret; + + sub->cstream = NULL; + + return 0; +} + +static int uniphier_aio_compr_get_params(struct snd_compr_stream *cstream, + struct snd_codec *params) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + + *params = sub->cparams.codec; + + return 0; +} + +static int uniphier_aio_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + struct device *dev = &aio->chip->pdev->dev; + int ret; + + if (params->codec.id != SND_AUDIOCODEC_IEC61937) { + dev_err(dev, "Codec ID is not supported(%d)\n", + params->codec.id); + return -EINVAL; + } + if (params->codec.profile != SND_AUDIOPROFILE_IEC61937_SPDIF) { + dev_err(dev, "Codec profile is not supported(%d)\n", + params->codec.profile); + return -EINVAL; + } + + /* IEC frame type will be changed after received valid data */ + sub->iec_pc = IEC61937_PC_AAC; + + sub->cparams = *params; + sub->setting = 1; + + aio_port_reset(sub); + aio_src_reset(sub); + + ret = uniphier_aio_compr_prepare(cstream); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aio_compr_hw_free(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + + sub->setting = 0; + + return 0; +} + +static int uniphier_aio_compr_prepare(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int bytes = runtime->fragment_size; + unsigned long flags; + int ret; + + ret = aiodma_ch_set_param(sub); + if (ret) + return ret; + + spin_lock_irqsave(&sub->lock, flags); + ret = aiodma_rb_set_buffer(sub, sub->compr_addr, + sub->compr_addr + sub->compr_bytes, + bytes); + spin_unlock_irqrestore(&sub->lock, flags); + if (ret) + return ret; + + ret = aio_port_set_param(sub, sub->pass_through, &sub->params); + if (ret) + return ret; + ret = aio_oport_set_stream_type(sub, sub->iec_pc); + if (ret) + return ret; + aio_port_set_enable(sub, 1); + + ret = aio_if_set_param(sub, sub->pass_through); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aio_compr_trigger(struct snd_compr_stream *cstream, + int cmd) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + struct device *dev = &aio->chip->pdev->dev; + int bytes = runtime->fragment_size, ret = 0; + unsigned long flags; + + spin_lock_irqsave(&sub->lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + aiodma_ch_set_enable(sub, 1); + sub->running = 1; + + break; + case SNDRV_PCM_TRIGGER_STOP: + sub->running = 0; + aiodma_ch_set_enable(sub, 0); + + break; + default: + dev_warn(dev, "Unknown trigger(%d)\n", cmd); + ret = -EINVAL; + } + spin_unlock_irqrestore(&sub->lock, flags); + + return ret; +} + +static int uniphier_aio_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + int bytes = runtime->fragment_size; + unsigned long flags; + u32 pos; + + spin_lock_irqsave(&sub->lock, flags); + + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + pos = sub->rd_offs; + /* Size of AIO output format is double of IEC61937 */ + tstamp->copied_total = sub->rd_total / 2; + } else { + pos = sub->wr_offs; + tstamp->copied_total = sub->rd_total; + } + tstamp->byte_offset = pos; + + spin_unlock_irqrestore(&sub->lock, flags); + + return 0; +} + +static int aio_compr_send_to_hw(struct uniphier_aio_sub *sub, + char __user *buf, size_t dstsize) +{ + u32 __user *srcbuf = (u32 __user *)buf; + u32 *dstbuf = (u32 *)(sub->compr_area + sub->wr_offs); + int src = 0, dst = 0, ret; + u32 frm, frm_a, frm_b; + + while (dstsize > 0) { + ret = get_user(frm, srcbuf + src); + if (ret) + return ret; + src++; + + frm_a = frm & 0xffff; + frm_b = (frm >> 16) & 0xffff; + + if (frm == IEC61937_HEADER_SIGN) { + frm_a |= 0x01000000; + + /* Next data is Pc and Pd */ + sub->iec_header = true; + } else { + u16 pc = be16_to_cpu((__be16)frm_a); + + if (sub->iec_header && sub->iec_pc != pc) { + /* Force overwrite IEC frame type */ + sub->iec_pc = pc; + ret = aio_oport_set_stream_type(sub, pc); + if (ret) + return ret; + } + sub->iec_header = false; + } + dstbuf[dst++] = frm_a; + dstbuf[dst++] = frm_b; + + dstsize -= sizeof(u32) * 2; + } + + return 0; +} + +static int uniphier_aio_compr_copy(struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct device *carddev = rtd->compr->card->dev; + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[cstream->direction]; + size_t cnt = min_t(size_t, count, aio_rb_space_to_end(sub) / 2); + int bytes = runtime->fragment_size; + unsigned long flags; + size_t s; + int ret; + + if (cnt < sizeof(u32)) + return 0; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + dma_addr_t dmapos = sub->compr_addr + sub->wr_offs; + + /* Size of AIO output format is double of IEC61937 */ + s = cnt * 2; + + dma_sync_single_for_cpu(carddev, dmapos, s, DMA_TO_DEVICE); + ret = aio_compr_send_to_hw(sub, buf, s); + dma_sync_single_for_device(carddev, dmapos, s, DMA_TO_DEVICE); + } else { + dma_addr_t dmapos = sub->compr_addr + sub->rd_offs; + + s = cnt; + + dma_sync_single_for_cpu(carddev, dmapos, s, DMA_FROM_DEVICE); + ret = copy_to_user(buf, sub->compr_area + sub->rd_offs, s); + dma_sync_single_for_device(carddev, dmapos, s, DMA_FROM_DEVICE); + } + if (ret) + return -EFAULT; + + spin_lock_irqsave(&sub->lock, flags); + + sub->threshold = 2 * bytes; + aiodma_rb_set_threshold(sub, sub->compr_bytes, 2 * bytes); + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + sub->wr_offs += s; + if (sub->wr_offs >= sub->compr_bytes) + sub->wr_offs -= sub->compr_bytes; + } else { + sub->rd_offs += s; + if (sub->rd_offs >= sub->compr_bytes) + sub->rd_offs -= sub->compr_bytes; + } + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + + spin_unlock_irqrestore(&sub->lock, flags); + + return cnt; +} + +static int uniphier_aio_compr_get_caps(struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->num_codecs = 1; + caps->min_fragment_size = AUD_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = AUD_MAX_FRAGMENT_SIZE; + caps->min_fragments = AUD_MIN_FRAGMENT; + caps->max_fragments = AUD_MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_IEC61937; + + return 0; +} + +static const struct snd_compr_codec_caps caps_iec = { + .num_descriptors = 1, + .descriptor[0].max_ch = 8, + .descriptor[0].num_sample_rates = 0, + .descriptor[0].num_bitrates = 0, + .descriptor[0].profiles = SND_AUDIOPROFILE_IEC61937_SPDIF, + .descriptor[0].modes = SND_AUDIOMODE_IEC_AC3 | + SND_AUDIOMODE_IEC_MPEG1 | + SND_AUDIOMODE_IEC_MP3 | + SND_AUDIOMODE_IEC_DTS, + .descriptor[0].formats = 0, +}; + +static int uniphier_aio_compr_get_codec_caps(struct snd_compr_stream *stream, + struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_IEC61937) + *codec = caps_iec; + else + return -EINVAL; + + return 0; +} + +const struct snd_compr_ops uniphier_aio_compr_ops = { + .open = uniphier_aio_compr_open, + .free = uniphier_aio_compr_free, + .get_params = uniphier_aio_compr_get_params, + .set_params = uniphier_aio_compr_set_params, + .trigger = uniphier_aio_compr_trigger, + .pointer = uniphier_aio_compr_pointer, + .copy = uniphier_aio_compr_copy, + .get_caps = uniphier_aio_compr_get_caps, + .get_codec_caps = uniphier_aio_compr_get_codec_caps, +}; diff --git a/sound/soc/uniphier/aio-core.c b/sound/soc/uniphier/aio-core.c new file mode 100644 index 000000000..9bcba06ba --- /dev/null +++ b/sound/soc/uniphier/aio-core.c @@ -0,0 +1,1251 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA common driver. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include <linux/bitfield.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "aio.h" +#include "aio-reg.h" + +static u64 rb_cnt(u64 wr, u64 rd, u64 len) +{ + if (rd <= wr) + return wr - rd; + else + return len - (rd - wr); +} + +static u64 rb_cnt_to_end(u64 wr, u64 rd, u64 len) +{ + if (rd <= wr) + return wr - rd; + else + return len - rd; +} + +static u64 rb_space(u64 wr, u64 rd, u64 len) +{ + if (rd <= wr) + return len - (wr - rd) - 8; + else + return rd - wr - 8; +} + +static u64 rb_space_to_end(u64 wr, u64 rd, u64 len) +{ + if (rd > wr) + return rd - wr - 8; + else if (rd > 0) + return len - wr; + else + return len - wr - 8; +} + +u64 aio_rb_cnt(struct uniphier_aio_sub *sub) +{ + return rb_cnt(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +u64 aio_rbt_cnt_to_end(struct uniphier_aio_sub *sub) +{ + return rb_cnt_to_end(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +u64 aio_rb_space(struct uniphier_aio_sub *sub) +{ + return rb_space(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +u64 aio_rb_space_to_end(struct uniphier_aio_sub *sub) +{ + return rb_space_to_end(sub->wr_offs, sub->rd_offs, sub->compr_bytes); +} + +/** + * aio_iecout_set_enable - setup IEC output via SoC glue + * @chip: the AIO chip pointer + * @enable: false to stop the output, true to start + * + * Set enabled or disabled S/PDIF signal output to out of SoC via AOnIEC pins. + * This function need to call at driver startup. + * + * The regmap of SoC glue is specified by 'socionext,syscon' optional property + * of DT. This function has no effect if no property. + */ +void aio_iecout_set_enable(struct uniphier_aio_chip *chip, bool enable) +{ + struct regmap *r = chip->regmap_sg; + + if (!r) + return; + + regmap_write(r, SG_AOUTEN, (enable) ? ~0 : 0); +} + +/** + * aio_chip_set_pll - set frequency to audio PLL + * @chip : the AIO chip pointer + * @source: PLL + * @freq : frequency in Hz, 0 is ignored + * + * Sets frequency of audio PLL. This function can be called anytime, + * but it takes time till PLL is locked. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_chip_set_pll(struct uniphier_aio_chip *chip, int pll_id, + unsigned int freq) +{ + struct device *dev = &chip->pdev->dev; + struct regmap *r = chip->regmap; + int shift; + u32 v; + + /* Not change */ + if (freq == 0) + return 0; + + switch (pll_id) { + case AUD_PLL_A1: + shift = 0; + break; + case AUD_PLL_F1: + shift = 1; + break; + case AUD_PLL_A2: + shift = 2; + break; + case AUD_PLL_F2: + shift = 3; + break; + default: + dev_err(dev, "PLL(%d) not supported\n", pll_id); + return -EINVAL; + } + + switch (freq) { + case 36864000: + v = A2APLLCTR1_APLLX_36MHZ; + break; + case 33868800: + v = A2APLLCTR1_APLLX_33MHZ; + break; + default: + dev_err(dev, "PLL frequency not supported(%d)\n", freq); + return -EINVAL; + } + chip->plls[pll_id].freq = freq; + + regmap_update_bits(r, A2APLLCTR1, A2APLLCTR1_APLLX_MASK << shift, + v << shift); + + return 0; +} + +/** + * aio_chip_init - initialize AIO whole settings + * @chip: the AIO chip pointer + * + * Sets AIO fixed and whole device settings to AIO. + * This function need to call once at driver startup. + * + * The register area that is changed by this function is shared by all + * modules of AIO. But there is not race condition since this function + * has always set the same initialize values. + */ +void aio_chip_init(struct uniphier_aio_chip *chip) +{ + struct regmap *r = chip->regmap; + + regmap_update_bits(r, A2APLLCTR0, + A2APLLCTR0_APLLXPOW_MASK, + A2APLLCTR0_APLLXPOW_PWON); + + regmap_update_bits(r, A2EXMCLKSEL0, + A2EXMCLKSEL0_EXMCLK_MASK, + A2EXMCLKSEL0_EXMCLK_OUTPUT); + + regmap_update_bits(r, A2AIOINPUTSEL, A2AIOINPUTSEL_RXSEL_MASK, + A2AIOINPUTSEL_RXSEL_PCMI1_HDMIRX1 | + A2AIOINPUTSEL_RXSEL_PCMI2_SIF | + A2AIOINPUTSEL_RXSEL_PCMI3_EVEA | + A2AIOINPUTSEL_RXSEL_IECI1_HDMIRX1); + + if (chip->chip_spec->addr_ext) + regmap_update_bits(r, CDA2D_TEST, CDA2D_TEST_DDR_MODE_MASK, + CDA2D_TEST_DDR_MODE_EXTON0); + else + regmap_update_bits(r, CDA2D_TEST, CDA2D_TEST_DDR_MODE_MASK, + CDA2D_TEST_DDR_MODE_EXTOFF1); +} + +/** + * aio_init - initialize AIO substream + * @sub: the AIO substream pointer + * + * Sets fixed settings of each AIO substreams. + * This function need to call once at substream startup. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_init(struct uniphier_aio_sub *sub) +{ + struct device *dev = &sub->aio->chip->pdev->dev; + struct regmap *r = sub->aio->chip->regmap; + + regmap_write(r, A2RBNMAPCTR0(sub->swm->rb.hw), + MAPCTR0_EN | sub->swm->rb.map); + regmap_write(r, A2CHNMAPCTR0(sub->swm->ch.hw), + MAPCTR0_EN | sub->swm->ch.map); + + switch (sub->swm->type) { + case PORT_TYPE_I2S: + case PORT_TYPE_SPDIF: + case PORT_TYPE_EVE: + if (sub->swm->dir == PORT_DIR_INPUT) { + regmap_write(r, A2IIFNMAPCTR0(sub->swm->iif.hw), + MAPCTR0_EN | sub->swm->iif.map); + regmap_write(r, A2IPORTNMAPCTR0(sub->swm->iport.hw), + MAPCTR0_EN | sub->swm->iport.map); + } else { + regmap_write(r, A2OIFNMAPCTR0(sub->swm->oif.hw), + MAPCTR0_EN | sub->swm->oif.map); + regmap_write(r, A2OPORTNMAPCTR0(sub->swm->oport.hw), + MAPCTR0_EN | sub->swm->oport.map); + } + break; + case PORT_TYPE_CONV: + regmap_write(r, A2OIFNMAPCTR0(sub->swm->oif.hw), + MAPCTR0_EN | sub->swm->oif.map); + regmap_write(r, A2OPORTNMAPCTR0(sub->swm->oport.hw), + MAPCTR0_EN | sub->swm->oport.map); + regmap_write(r, A2CHNMAPCTR0(sub->swm->och.hw), + MAPCTR0_EN | sub->swm->och.map); + regmap_write(r, A2IIFNMAPCTR0(sub->swm->iif.hw), + MAPCTR0_EN | sub->swm->iif.map); + break; + default: + dev_err(dev, "Unknown port type %d.\n", sub->swm->type); + return -EINVAL; + } + + return 0; +} + +/** + * aio_port_reset - reset AIO port block + * @sub: the AIO substream pointer + * + * Resets the digital signal input/output port block of AIO. + */ +void aio_port_reset(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + regmap_write(r, AOUTRSTCTR0, BIT(sub->swm->oport.map)); + regmap_write(r, AOUTRSTCTR1, BIT(sub->swm->oport.map)); + } else { + regmap_update_bits(r, IPORTMXRSTCTR(sub->swm->iport.map), + IPORTMXRSTCTR_RSTPI_MASK, + IPORTMXRSTCTR_RSTPI_RESET); + regmap_update_bits(r, IPORTMXRSTCTR(sub->swm->iport.map), + IPORTMXRSTCTR_RSTPI_MASK, + IPORTMXRSTCTR_RSTPI_RELEASE); + } +} + +/** + * aio_port_set_ch - set channels of LPCM + * @sub: the AIO substream pointer, PCM substream only + * @ch : count of channels + * + * Set suitable slot selecting to input/output port block of AIO. + * + * This function may return error if non-PCM substream. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_ch(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 slotsel_2ch[] = { + 0, 0, 0, 0, 0, + }; + u32 slotsel_multi[] = { + OPORTMXTYSLOTCTR_SLOTSEL_SLOT0, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT1, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT2, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT3, + OPORTMXTYSLOTCTR_SLOTSEL_SLOT4, + }; + u32 mode, *slotsel; + int i; + + switch (params_channels(&sub->params)) { + case 8: + case 6: + mode = OPORTMXTYSLOTCTR_MODE; + slotsel = slotsel_multi; + break; + case 2: + mode = 0; + slotsel = slotsel_2ch; + break; + default: + return -EINVAL; + } + + for (i = 0; i < AUD_MAX_SLOTSEL; i++) { + regmap_update_bits(r, OPORTMXTYSLOTCTR(sub->swm->oport.map, i), + OPORTMXTYSLOTCTR_MODE, mode); + regmap_update_bits(r, OPORTMXTYSLOTCTR(sub->swm->oport.map, i), + OPORTMXTYSLOTCTR_SLOTSEL_MASK, slotsel[i]); + } + + return 0; +} + +/** + * aio_port_set_rate - set sampling rate of LPCM + * @sub: the AIO substream pointer, PCM substream only + * @rate: Sampling rate in Hz. + * + * Set suitable I2S format settings to input/output port block of AIO. + * Parameter is specified by hw_params(). + * + * This function may return error if non-PCM substream. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_rate(struct uniphier_aio_sub *sub, int rate) +{ + struct regmap *r = sub->aio->chip->regmap; + struct device *dev = &sub->aio->chip->pdev->dev; + u32 v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + switch (rate) { + case 8000: + v = OPORTMXCTR1_FSSEL_8; + break; + case 11025: + v = OPORTMXCTR1_FSSEL_11_025; + break; + case 12000: + v = OPORTMXCTR1_FSSEL_12; + break; + case 16000: + v = OPORTMXCTR1_FSSEL_16; + break; + case 22050: + v = OPORTMXCTR1_FSSEL_22_05; + break; + case 24000: + v = OPORTMXCTR1_FSSEL_24; + break; + case 32000: + v = OPORTMXCTR1_FSSEL_32; + break; + case 44100: + v = OPORTMXCTR1_FSSEL_44_1; + break; + case 48000: + v = OPORTMXCTR1_FSSEL_48; + break; + case 88200: + v = OPORTMXCTR1_FSSEL_88_2; + break; + case 96000: + v = OPORTMXCTR1_FSSEL_96; + break; + case 176400: + v = OPORTMXCTR1_FSSEL_176_4; + break; + case 192000: + v = OPORTMXCTR1_FSSEL_192; + break; + default: + dev_err(dev, "Rate not supported(%d)\n", rate); + return -EINVAL; + } + + regmap_update_bits(r, OPORTMXCTR1(sub->swm->oport.map), + OPORTMXCTR1_FSSEL_MASK, v); + } else { + switch (rate) { + case 8000: + v = IPORTMXCTR1_FSSEL_8; + break; + case 11025: + v = IPORTMXCTR1_FSSEL_11_025; + break; + case 12000: + v = IPORTMXCTR1_FSSEL_12; + break; + case 16000: + v = IPORTMXCTR1_FSSEL_16; + break; + case 22050: + v = IPORTMXCTR1_FSSEL_22_05; + break; + case 24000: + v = IPORTMXCTR1_FSSEL_24; + break; + case 32000: + v = IPORTMXCTR1_FSSEL_32; + break; + case 44100: + v = IPORTMXCTR1_FSSEL_44_1; + break; + case 48000: + v = IPORTMXCTR1_FSSEL_48; + break; + case 88200: + v = IPORTMXCTR1_FSSEL_88_2; + break; + case 96000: + v = IPORTMXCTR1_FSSEL_96; + break; + case 176400: + v = IPORTMXCTR1_FSSEL_176_4; + break; + case 192000: + v = IPORTMXCTR1_FSSEL_192; + break; + default: + dev_err(dev, "Rate not supported(%d)\n", rate); + return -EINVAL; + } + + regmap_update_bits(r, IPORTMXCTR1(sub->swm->iport.map), + IPORTMXCTR1_FSSEL_MASK, v); + } + + return 0; +} + +/** + * aio_port_set_fmt - set format of I2S data + * @sub: the AIO substream pointer, PCM substream only + * This parameter has no effect if substream is I2S or PCM. + * + * Set suitable I2S format settings to input/output port block of AIO. + * Parameter is specified by set_fmt(). + * + * This function may return error if non-PCM substream. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_fmt(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + struct device *dev = &sub->aio->chip->pdev->dev; + u32 v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + switch (sub->aio->fmt) { + case SND_SOC_DAIFMT_LEFT_J: + v = OPORTMXCTR1_I2SLRSEL_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + v = OPORTMXCTR1_I2SLRSEL_RIGHT; + break; + case SND_SOC_DAIFMT_I2S: + v = OPORTMXCTR1_I2SLRSEL_I2S; + break; + default: + dev_err(dev, "Format is not supported(%d)\n", + sub->aio->fmt); + return -EINVAL; + } + + v |= OPORTMXCTR1_OUTBITSEL_24; + regmap_update_bits(r, OPORTMXCTR1(sub->swm->oport.map), + OPORTMXCTR1_I2SLRSEL_MASK | + OPORTMXCTR1_OUTBITSEL_MASK, v); + } else { + switch (sub->aio->fmt) { + case SND_SOC_DAIFMT_LEFT_J: + v = IPORTMXCTR1_LRSEL_LEFT; + break; + case SND_SOC_DAIFMT_RIGHT_J: + v = IPORTMXCTR1_LRSEL_RIGHT; + break; + case SND_SOC_DAIFMT_I2S: + v = IPORTMXCTR1_LRSEL_I2S; + break; + default: + dev_err(dev, "Format is not supported(%d)\n", + sub->aio->fmt); + return -EINVAL; + } + + v |= IPORTMXCTR1_OUTBITSEL_24 | + IPORTMXCTR1_CHSEL_ALL; + regmap_update_bits(r, IPORTMXCTR1(sub->swm->iport.map), + IPORTMXCTR1_LRSEL_MASK | + IPORTMXCTR1_OUTBITSEL_MASK | + IPORTMXCTR1_CHSEL_MASK, v); + } + + return 0; +} + +/** + * aio_port_set_clk - set clock and divider of AIO port block + * @sub: the AIO substream pointer + * + * Set suitable PLL clock divider and relational settings to + * input/output port block of AIO. Parameters are specified by + * set_sysclk() and set_pll(). + * + * Return: Zero if successful, otherwise a negative value on error. + */ +static int aio_port_set_clk(struct uniphier_aio_sub *sub) +{ + struct uniphier_aio_chip *chip = sub->aio->chip; + struct device *dev = &sub->aio->chip->pdev->dev; + struct regmap *r = sub->aio->chip->regmap; + u32 v_pll[] = { + OPORTMXCTR2_ACLKSEL_A1, OPORTMXCTR2_ACLKSEL_F1, + OPORTMXCTR2_ACLKSEL_A2, OPORTMXCTR2_ACLKSEL_F2, + OPORTMXCTR2_ACLKSEL_A2PLL, + OPORTMXCTR2_ACLKSEL_RX1, + }; + u32 v_div[] = { + OPORTMXCTR2_DACCKSEL_1_2, OPORTMXCTR2_DACCKSEL_1_3, + OPORTMXCTR2_DACCKSEL_1_1, OPORTMXCTR2_DACCKSEL_2_3, + }; + u32 v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + if (sub->swm->type == PORT_TYPE_I2S) { + if (sub->aio->pll_out >= ARRAY_SIZE(v_pll)) { + dev_err(dev, "PLL(%d) is invalid\n", + sub->aio->pll_out); + return -EINVAL; + } + if (sub->aio->plldiv >= ARRAY_SIZE(v_div)) { + dev_err(dev, "PLL divider(%d) is invalid\n", + sub->aio->plldiv); + return -EINVAL; + } + + v = v_pll[sub->aio->pll_out] | + OPORTMXCTR2_MSSEL_MASTER | + v_div[sub->aio->plldiv]; + + switch (chip->plls[sub->aio->pll_out].freq) { + case 0: + case 36864000: + case 33868800: + v |= OPORTMXCTR2_EXTLSIFSSEL_36; + break; + default: + v |= OPORTMXCTR2_EXTLSIFSSEL_24; + break; + } + } else if (sub->swm->type == PORT_TYPE_EVE) { + v = OPORTMXCTR2_ACLKSEL_A2PLL | + OPORTMXCTR2_MSSEL_MASTER | + OPORTMXCTR2_EXTLSIFSSEL_36 | + OPORTMXCTR2_DACCKSEL_1_2; + } else if (sub->swm->type == PORT_TYPE_SPDIF) { + if (sub->aio->pll_out >= ARRAY_SIZE(v_pll)) { + dev_err(dev, "PLL(%d) is invalid\n", + sub->aio->pll_out); + return -EINVAL; + } + v = v_pll[sub->aio->pll_out] | + OPORTMXCTR2_MSSEL_MASTER | + OPORTMXCTR2_DACCKSEL_1_2; + + switch (chip->plls[sub->aio->pll_out].freq) { + case 0: + case 36864000: + case 33868800: + v |= OPORTMXCTR2_EXTLSIFSSEL_36; + break; + default: + v |= OPORTMXCTR2_EXTLSIFSSEL_24; + break; + } + } else { + v = OPORTMXCTR2_ACLKSEL_A1 | + OPORTMXCTR2_MSSEL_MASTER | + OPORTMXCTR2_EXTLSIFSSEL_36 | + OPORTMXCTR2_DACCKSEL_1_2; + } + regmap_write(r, OPORTMXCTR2(sub->swm->oport.map), v); + } else { + v = IPORTMXCTR2_ACLKSEL_A1 | + IPORTMXCTR2_MSSEL_SLAVE | + IPORTMXCTR2_EXTLSIFSSEL_36 | + IPORTMXCTR2_DACCKSEL_1_2; + regmap_write(r, IPORTMXCTR2(sub->swm->iport.map), v); + } + + return 0; +} + +/** + * aio_port_set_param - set parameters of AIO port block + * @sub: the AIO substream pointer + * @pass_through: Zero if sound data is LPCM, otherwise if data is not LPCM. + * This parameter has no effect if substream is I2S or PCM. + * @params: hardware parameters of ALSA + * + * Set suitable setting to input/output port block of AIO to process the + * specified in params. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_port_set_param(struct uniphier_aio_sub *sub, int pass_through, + const struct snd_pcm_hw_params *params) +{ + struct regmap *r = sub->aio->chip->regmap; + unsigned int rate; + u32 v; + int ret; + + if (!pass_through) { + if (sub->swm->type == PORT_TYPE_EVE || + sub->swm->type == PORT_TYPE_CONV) { + rate = 48000; + } else { + rate = params_rate(params); + } + + ret = aio_port_set_ch(sub); + if (ret) + return ret; + + ret = aio_port_set_rate(sub, rate); + if (ret) + return ret; + + ret = aio_port_set_fmt(sub); + if (ret) + return ret; + } + + ret = aio_port_set_clk(sub); + if (ret) + return ret; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + if (pass_through) + v = OPORTMXCTR3_SRCSEL_STREAM | + OPORTMXCTR3_VALID_STREAM; + else + v = OPORTMXCTR3_SRCSEL_PCM | + OPORTMXCTR3_VALID_PCM; + + v |= OPORTMXCTR3_IECTHUR_IECOUT | + OPORTMXCTR3_PMSEL_PAUSE | + OPORTMXCTR3_PMSW_MUTE_OFF; + regmap_write(r, OPORTMXCTR3(sub->swm->oport.map), v); + } else { + regmap_write(r, IPORTMXACLKSEL0EX(sub->swm->iport.map), + IPORTMXACLKSEL0EX_ACLKSEL0EX_INTERNAL); + regmap_write(r, IPORTMXEXNOE(sub->swm->iport.map), + IPORTMXEXNOE_PCMINOE_INPUT); + } + + return 0; +} + +/** + * aio_port_set_enable - start or stop of AIO port block + * @sub: the AIO substream pointer + * @enable: zero to stop the block, otherwise to start + * + * Start or stop the signal input/output port block of AIO. + */ +void aio_port_set_enable(struct uniphier_aio_sub *sub, int enable) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + regmap_write(r, OPORTMXPATH(sub->swm->oport.map), + sub->swm->oif.map); + + regmap_update_bits(r, OPORTMXMASK(sub->swm->oport.map), + OPORTMXMASK_IUDXMSK_MASK | + OPORTMXMASK_IUXCKMSK_MASK | + OPORTMXMASK_DXMSK_MASK | + OPORTMXMASK_XCKMSK_MASK, + OPORTMXMASK_IUDXMSK_OFF | + OPORTMXMASK_IUXCKMSK_OFF | + OPORTMXMASK_DXMSK_OFF | + OPORTMXMASK_XCKMSK_OFF); + + if (enable) + regmap_write(r, AOUTENCTR0, BIT(sub->swm->oport.map)); + else + regmap_write(r, AOUTENCTR1, BIT(sub->swm->oport.map)); + } else { + regmap_update_bits(r, IPORTMXMASK(sub->swm->iport.map), + IPORTMXMASK_IUXCKMSK_MASK | + IPORTMXMASK_XCKMSK_MASK, + IPORTMXMASK_IUXCKMSK_OFF | + IPORTMXMASK_XCKMSK_OFF); + + if (enable) + regmap_update_bits(r, + IPORTMXCTR2(sub->swm->iport.map), + IPORTMXCTR2_REQEN_MASK, + IPORTMXCTR2_REQEN_ENABLE); + else + regmap_update_bits(r, + IPORTMXCTR2(sub->swm->iport.map), + IPORTMXCTR2_REQEN_MASK, + IPORTMXCTR2_REQEN_DISABLE); + } +} + +/** + * aio_port_get_volume - get volume of AIO port block + * @sub: the AIO substream pointer + * + * Return: current volume, range is 0x0000 - 0xffff + */ +int aio_port_get_volume(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + regmap_read(r, OPORTMXTYVOLGAINSTATUS(sub->swm->oport.map, 0), &v); + + return FIELD_GET(OPORTMXTYVOLGAINSTATUS_CUR_MASK, v); +} + +/** + * aio_port_set_volume - set volume of AIO port block + * @sub: the AIO substream pointer + * @vol: target volume, range is 0x0000 - 0xffff. + * + * Change digital volume and perfome fade-out/fade-in effect for specified + * output slot of port. Gained PCM value can calculate as the following: + * Gained = Original * vol / 0x4000 + */ +void aio_port_set_volume(struct uniphier_aio_sub *sub, int vol) +{ + struct regmap *r = sub->aio->chip->regmap; + int oport_map = sub->swm->oport.map; + int cur, diff, slope = 0, fs; + + if (sub->swm->dir == PORT_DIR_INPUT) + return; + + cur = aio_port_get_volume(sub); + diff = abs(vol - cur); + fs = params_rate(&sub->params); + if (fs) + slope = diff / AUD_VOL_FADE_TIME * 1000 / fs; + slope = max(1, slope); + + regmap_update_bits(r, OPORTMXTYVOLPARA1(oport_map, 0), + OPORTMXTYVOLPARA1_SLOPEU_MASK, slope << 16); + regmap_update_bits(r, OPORTMXTYVOLPARA2(oport_map, 0), + OPORTMXTYVOLPARA2_TARGET_MASK, vol); + + if (cur < vol) + regmap_update_bits(r, OPORTMXTYVOLPARA2(oport_map, 0), + OPORTMXTYVOLPARA2_FADE_MASK, + OPORTMXTYVOLPARA2_FADE_FADEIN); + else + regmap_update_bits(r, OPORTMXTYVOLPARA2(oport_map, 0), + OPORTMXTYVOLPARA2_FADE_MASK, + OPORTMXTYVOLPARA2_FADE_FADEOUT); + + regmap_write(r, AOUTFADECTR0, BIT(oport_map)); +} + +/** + * aio_if_set_param - set parameters of AIO DMA I/F block + * @sub: the AIO substream pointer + * @pass_through: Zero if sound data is LPCM, otherwise if data is not LPCM. + * This parameter has no effect if substream is I2S or PCM. + * + * Set suitable setting to DMA interface block of AIO to process the + * specified in settings. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_if_set_param(struct uniphier_aio_sub *sub, int pass_through) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 memfmt, v; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + if (pass_through) { + v = PBOUTMXCTR0_ENDIAN_0123 | + PBOUTMXCTR0_MEMFMT_STREAM; + } else { + switch (params_channels(&sub->params)) { + case 2: + memfmt = PBOUTMXCTR0_MEMFMT_2CH; + break; + case 6: + memfmt = PBOUTMXCTR0_MEMFMT_6CH; + break; + case 8: + memfmt = PBOUTMXCTR0_MEMFMT_8CH; + break; + default: + return -EINVAL; + } + v = PBOUTMXCTR0_ENDIAN_3210 | memfmt; + } + + regmap_write(r, PBOUTMXCTR0(sub->swm->oif.map), v); + regmap_write(r, PBOUTMXCTR1(sub->swm->oif.map), 0); + } else { + regmap_write(r, PBINMXCTR(sub->swm->iif.map), + PBINMXCTR_NCONNECT_CONNECT | + PBINMXCTR_INOUTSEL_IN | + (sub->swm->iport.map << PBINMXCTR_PBINSEL_SHIFT) | + PBINMXCTR_ENDIAN_3210 | + PBINMXCTR_MEMFMT_D0); + } + + return 0; +} + +/** + * aio_oport_set_stream_type - set parameters of AIO playback port block + * @sub: the AIO substream pointer + * @pc: Pc type of IEC61937 + * + * Set special setting to output port block of AIO to output the stream + * via S/PDIF. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_oport_set_stream_type(struct uniphier_aio_sub *sub, + enum IEC61937_PC pc) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 repet = 0, pause = OPORTMXPAUDAT_PAUSEPC_CMN; + + switch (pc) { + case IEC61937_PC_AC3: + repet = OPORTMXREPET_STRLENGTH_AC3 | + OPORTMXREPET_PMLENGTH_AC3; + pause |= OPORTMXPAUDAT_PAUSEPD_AC3; + break; + case IEC61937_PC_MPA: + repet = OPORTMXREPET_STRLENGTH_MPA | + OPORTMXREPET_PMLENGTH_MPA; + pause |= OPORTMXPAUDAT_PAUSEPD_MPA; + break; + case IEC61937_PC_MP3: + repet = OPORTMXREPET_STRLENGTH_MP3 | + OPORTMXREPET_PMLENGTH_MP3; + pause |= OPORTMXPAUDAT_PAUSEPD_MP3; + break; + case IEC61937_PC_DTS1: + repet = OPORTMXREPET_STRLENGTH_DTS1 | + OPORTMXREPET_PMLENGTH_DTS1; + pause |= OPORTMXPAUDAT_PAUSEPD_DTS1; + break; + case IEC61937_PC_DTS2: + repet = OPORTMXREPET_STRLENGTH_DTS2 | + OPORTMXREPET_PMLENGTH_DTS2; + pause |= OPORTMXPAUDAT_PAUSEPD_DTS2; + break; + case IEC61937_PC_DTS3: + repet = OPORTMXREPET_STRLENGTH_DTS3 | + OPORTMXREPET_PMLENGTH_DTS3; + pause |= OPORTMXPAUDAT_PAUSEPD_DTS3; + break; + case IEC61937_PC_AAC: + repet = OPORTMXREPET_STRLENGTH_AAC | + OPORTMXREPET_PMLENGTH_AAC; + pause |= OPORTMXPAUDAT_PAUSEPD_AAC; + break; + case IEC61937_PC_PAUSE: + /* Do nothing */ + break; + } + + regmap_write(r, OPORTMXREPET(sub->swm->oport.map), repet); + regmap_write(r, OPORTMXPAUDAT(sub->swm->oport.map), pause); + + return 0; +} + +/** + * aio_src_reset - reset AIO SRC block + * @sub: the AIO substream pointer + * + * Resets the digital signal input/output port with sampling rate converter + * block of AIO. + * This function has no effect if substream is not supported rate converter. + */ +void aio_src_reset(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir != PORT_DIR_OUTPUT) + return; + + regmap_write(r, AOUTSRCRSTCTR0, BIT(sub->swm->oport.map)); + regmap_write(r, AOUTSRCRSTCTR1, BIT(sub->swm->oport.map)); +} + +/** + * aio_src_set_param - set parameters of AIO SRC block + * @sub: the AIO substream pointer + * @params: hardware parameters of ALSA + * + * Set suitable setting to input/output port with sampling rate converter + * block of AIO to process the specified in params. + * This function has no effect if substream is not supported rate converter. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int aio_src_set_param(struct uniphier_aio_sub *sub, + const struct snd_pcm_hw_params *params) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + if (sub->swm->dir != PORT_DIR_OUTPUT) + return 0; + + regmap_write(r, OPORTMXSRC1CTR(sub->swm->oport.map), + OPORTMXSRC1CTR_THMODE_SRC | + OPORTMXSRC1CTR_SRCPATH_CALC | + OPORTMXSRC1CTR_SYNC_ASYNC | + OPORTMXSRC1CTR_FSIIPSEL_INNER | + OPORTMXSRC1CTR_FSISEL_ACLK); + + switch (params_rate(params)) { + default: + case 48000: + v = OPORTMXRATE_I_ACLKSEL_APLLA1 | + OPORTMXRATE_I_MCKSEL_36 | + OPORTMXRATE_I_FSSEL_48; + break; + case 44100: + v = OPORTMXRATE_I_ACLKSEL_APLLA2 | + OPORTMXRATE_I_MCKSEL_33 | + OPORTMXRATE_I_FSSEL_44_1; + break; + case 32000: + v = OPORTMXRATE_I_ACLKSEL_APLLA1 | + OPORTMXRATE_I_MCKSEL_36 | + OPORTMXRATE_I_FSSEL_32; + break; + } + + regmap_write(r, OPORTMXRATE_I(sub->swm->oport.map), + v | OPORTMXRATE_I_ACLKSRC_APLL | + OPORTMXRATE_I_LRCKSTP_STOP); + regmap_update_bits(r, OPORTMXRATE_I(sub->swm->oport.map), + OPORTMXRATE_I_LRCKSTP_MASK, + OPORTMXRATE_I_LRCKSTP_START); + + return 0; +} + +int aio_srcif_set_param(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + regmap_write(r, PBINMXCTR(sub->swm->iif.map), + PBINMXCTR_NCONNECT_CONNECT | + PBINMXCTR_INOUTSEL_OUT | + (sub->swm->oport.map << PBINMXCTR_PBINSEL_SHIFT) | + PBINMXCTR_ENDIAN_3210 | + PBINMXCTR_MEMFMT_D0); + + return 0; +} + +int aio_srcch_set_param(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + regmap_write(r, CDA2D_CHMXCTRL1(sub->swm->och.map), + CDA2D_CHMXCTRL1_INDSIZE_INFINITE); + + regmap_write(r, CDA2D_CHMXSRCAMODE(sub->swm->och.map), + CDA2D_CHMXAMODE_ENDIAN_3210 | + CDA2D_CHMXAMODE_AUPDT_FIX | + CDA2D_CHMXAMODE_TYPE_NORMAL); + + regmap_write(r, CDA2D_CHMXDSTAMODE(sub->swm->och.map), + CDA2D_CHMXAMODE_ENDIAN_3210 | + CDA2D_CHMXAMODE_AUPDT_INC | + CDA2D_CHMXAMODE_TYPE_RING | + (sub->swm->och.map << CDA2D_CHMXAMODE_RSSEL_SHIFT)); + + return 0; +} + +void aio_srcch_set_enable(struct uniphier_aio_sub *sub, int enable) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + if (enable) + v = CDA2D_STRT0_STOP_START; + else + v = CDA2D_STRT0_STOP_STOP; + + regmap_write(r, CDA2D_STRT0, + v | BIT(sub->swm->och.map)); +} + +int aiodma_ch_set_param(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 v; + + regmap_write(r, CDA2D_CHMXCTRL1(sub->swm->ch.map), + CDA2D_CHMXCTRL1_INDSIZE_INFINITE); + + v = CDA2D_CHMXAMODE_ENDIAN_3210 | + CDA2D_CHMXAMODE_AUPDT_INC | + CDA2D_CHMXAMODE_TYPE_NORMAL | + (sub->swm->rb.map << CDA2D_CHMXAMODE_RSSEL_SHIFT); + if (sub->swm->dir == PORT_DIR_OUTPUT) + regmap_write(r, CDA2D_CHMXSRCAMODE(sub->swm->ch.map), v); + else + regmap_write(r, CDA2D_CHMXDSTAMODE(sub->swm->ch.map), v); + + return 0; +} + +void aiodma_ch_set_enable(struct uniphier_aio_sub *sub, int enable) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (enable) { + regmap_write(r, CDA2D_STRT0, + CDA2D_STRT0_STOP_START | BIT(sub->swm->ch.map)); + + regmap_update_bits(r, INTRBIM(0), + BIT(sub->swm->rb.map), + BIT(sub->swm->rb.map)); + } else { + regmap_write(r, CDA2D_STRT0, + CDA2D_STRT0_STOP_STOP | BIT(sub->swm->ch.map)); + + regmap_update_bits(r, INTRBIM(0), + BIT(sub->swm->rb.map), + 0); + } +} + +static u64 aiodma_rb_get_rp(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 pos_u, pos_l; + int i; + + regmap_write(r, CDA2D_RDPTRLOAD, + CDA2D_RDPTRLOAD_LSFLAG_STORE | BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), &pos_l); + + regmap_read(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), &pos_l); + regmap_read(r, CDA2D_RBMXRDPTRU(sub->swm->rb.map), &pos_u); + pos_u = FIELD_GET(CDA2D_RBMXPTRU_PTRU_MASK, pos_u); + + return ((u64)pos_u << 32) | pos_l; +} + +static void aiodma_rb_set_rp(struct uniphier_aio_sub *sub, u64 pos) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 tmp; + int i; + + regmap_write(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), (u32)pos); + regmap_write(r, CDA2D_RBMXRDPTRU(sub->swm->rb.map), (u32)(pos >> 32)); + regmap_write(r, CDA2D_RDPTRLOAD, BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXRDPTR(sub->swm->rb.map), &tmp); +} + +static u64 aiodma_rb_get_wp(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 pos_u, pos_l; + int i; + + regmap_write(r, CDA2D_WRPTRLOAD, + CDA2D_WRPTRLOAD_LSFLAG_STORE | BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), &pos_l); + + regmap_read(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), &pos_l); + regmap_read(r, CDA2D_RBMXWRPTRU(sub->swm->rb.map), &pos_u); + pos_u = FIELD_GET(CDA2D_RBMXPTRU_PTRU_MASK, pos_u); + + return ((u64)pos_u << 32) | pos_l; +} + +static void aiodma_rb_set_wp(struct uniphier_aio_sub *sub, u64 pos) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 tmp; + int i; + + regmap_write(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), + lower_32_bits(pos)); + regmap_write(r, CDA2D_RBMXWRPTRU(sub->swm->rb.map), + upper_32_bits(pos)); + regmap_write(r, CDA2D_WRPTRLOAD, BIT(sub->swm->rb.map)); + /* Wait for setup */ + for (i = 0; i < 6; i++) + regmap_read(r, CDA2D_RBMXWRPTR(sub->swm->rb.map), &tmp); +} + +int aiodma_rb_set_threshold(struct uniphier_aio_sub *sub, u64 size, u32 th) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (size <= th) + return -EINVAL; + + regmap_write(r, CDA2D_RBMXBTH(sub->swm->rb.map), th); + regmap_write(r, CDA2D_RBMXRTH(sub->swm->rb.map), th); + + return 0; +} + +int aiodma_rb_set_buffer(struct uniphier_aio_sub *sub, u64 start, u64 end, + int period) +{ + struct regmap *r = sub->aio->chip->regmap; + u64 size = end - start; + int ret; + + if (end < start || period < 0) + return -EINVAL; + + regmap_write(r, CDA2D_RBMXCNFG(sub->swm->rb.map), 0); + regmap_write(r, CDA2D_RBMXBGNADRS(sub->swm->rb.map), + lower_32_bits(start)); + regmap_write(r, CDA2D_RBMXBGNADRSU(sub->swm->rb.map), + upper_32_bits(start)); + regmap_write(r, CDA2D_RBMXENDADRS(sub->swm->rb.map), + lower_32_bits(end)); + regmap_write(r, CDA2D_RBMXENDADRSU(sub->swm->rb.map), + upper_32_bits(end)); + + regmap_write(r, CDA2D_RBADRSLOAD, BIT(sub->swm->rb.map)); + + ret = aiodma_rb_set_threshold(sub, size, 2 * period); + if (ret) + return ret; + + if (sub->swm->dir == PORT_DIR_OUTPUT) { + aiodma_rb_set_rp(sub, start); + aiodma_rb_set_wp(sub, end - period); + + regmap_update_bits(r, CDA2D_RBMXIE(sub->swm->rb.map), + CDA2D_RBMXIX_SPACE, + CDA2D_RBMXIX_SPACE); + } else { + aiodma_rb_set_rp(sub, end - period); + aiodma_rb_set_wp(sub, start); + + regmap_update_bits(r, CDA2D_RBMXIE(sub->swm->rb.map), + CDA2D_RBMXIX_REMAIN, + CDA2D_RBMXIX_REMAIN); + } + + sub->threshold = 2 * period; + sub->rd_offs = 0; + sub->wr_offs = 0; + sub->rd_org = 0; + sub->wr_org = 0; + sub->rd_total = 0; + sub->wr_total = 0; + + return 0; +} + +void aiodma_rb_sync(struct uniphier_aio_sub *sub, u64 start, u64 size, + int period) +{ + if (sub->swm->dir == PORT_DIR_OUTPUT) { + sub->rd_offs = aiodma_rb_get_rp(sub) - start; + + if (sub->use_mmap) { + sub->threshold = 2 * period; + aiodma_rb_set_threshold(sub, size, 2 * period); + + sub->wr_offs = sub->rd_offs - period; + if (sub->rd_offs < period) + sub->wr_offs += size; + } + aiodma_rb_set_wp(sub, sub->wr_offs + start); + } else { + sub->wr_offs = aiodma_rb_get_wp(sub) - start; + + if (sub->use_mmap) { + sub->threshold = 2 * period; + aiodma_rb_set_threshold(sub, size, 2 * period); + + sub->rd_offs = sub->wr_offs - period; + if (sub->wr_offs < period) + sub->rd_offs += size; + } + aiodma_rb_set_rp(sub, sub->rd_offs + start); + } + + sub->rd_total += sub->rd_offs - sub->rd_org; + if (sub->rd_offs < sub->rd_org) + sub->rd_total += size; + sub->wr_total += sub->wr_offs - sub->wr_org; + if (sub->wr_offs < sub->wr_org) + sub->wr_total += size; + + sub->rd_org = sub->rd_offs; + sub->wr_org = sub->wr_offs; +} + +bool aiodma_rb_is_irq(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + u32 ir; + + regmap_read(r, CDA2D_RBMXIR(sub->swm->rb.map), &ir); + + if (sub->swm->dir == PORT_DIR_OUTPUT) + return !!(ir & CDA2D_RBMXIX_SPACE); + else + return !!(ir & CDA2D_RBMXIX_REMAIN); +} + +void aiodma_rb_clear_irq(struct uniphier_aio_sub *sub) +{ + struct regmap *r = sub->aio->chip->regmap; + + if (sub->swm->dir == PORT_DIR_OUTPUT) + regmap_write(r, CDA2D_RBMXIR(sub->swm->rb.map), + CDA2D_RBMXIX_SPACE); + else + regmap_write(r, CDA2D_RBMXIR(sub->swm->rb.map), + CDA2D_RBMXIX_REMAIN); +} diff --git a/sound/soc/uniphier/aio-cpu.c b/sound/soc/uniphier/aio-cpu.c new file mode 100644 index 000000000..2ae582a99 --- /dev/null +++ b/sound/soc/uniphier/aio-cpu.c @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA CPU DAI driver. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "aio.h" + +static bool is_valid_pll(struct uniphier_aio_chip *chip, int pll_id) +{ + struct device *dev = &chip->pdev->dev; + + if (pll_id < 0 || chip->num_plls <= pll_id) { + dev_err(dev, "PLL(%d) is not supported\n", pll_id); + return false; + } + + return chip->plls[pll_id].enable; +} + +/** + * find_volume - find volume supported HW port by HW port number + * @chip: the AIO chip pointer + * @oport_hw: HW port number, one of AUD_HW_XXXX + * + * Find AIO device from device list by HW port number. Volume feature is + * available only in Output and PCM ports, this limitation comes from HW + * specifications. + * + * Return: The pointer of AIO substream if successful, otherwise NULL on error. + */ +static struct uniphier_aio_sub *find_volume(struct uniphier_aio_chip *chip, + int oport_hw) +{ + int i; + + for (i = 0; i < chip->num_aios; i++) { + struct uniphier_aio_sub *sub = &chip->aios[i].sub[0]; + + if (!sub->swm) + continue; + + if (sub->swm->oport.hw == oport_hw) + return sub; + } + + return NULL; +} + +static bool match_spec(const struct uniphier_aio_spec *spec, + const char *name, int dir) +{ + if (dir == SNDRV_PCM_STREAM_PLAYBACK && + spec->swm.dir != PORT_DIR_OUTPUT) { + return false; + } + + if (dir == SNDRV_PCM_STREAM_CAPTURE && + spec->swm.dir != PORT_DIR_INPUT) { + return false; + } + + if (spec->name && strcmp(spec->name, name) == 0) + return true; + + if (spec->gname && strcmp(spec->gname, name) == 0) + return true; + + return false; +} + +/** + * find_spec - find HW specification info by name + * @aio: the AIO device pointer + * @name: name of device + * @direction: the direction of substream, SNDRV_PCM_STREAM_* + * + * Find hardware specification information from list by device name. This + * information is used for telling the difference of SoCs to driver. + * + * Specification list is array of 'struct uniphier_aio_spec' which is defined + * in each drivers (see: aio-i2s.c). + * + * Return: The pointer of hardware specification of AIO if successful, + * otherwise NULL on error. + */ +static const struct uniphier_aio_spec *find_spec(struct uniphier_aio *aio, + const char *name, + int direction) +{ + const struct uniphier_aio_chip_spec *chip_spec = aio->chip->chip_spec; + int i; + + for (i = 0; i < chip_spec->num_specs; i++) { + const struct uniphier_aio_spec *spec = &chip_spec->specs[i]; + + if (match_spec(spec, name, direction)) + return spec; + } + + return NULL; +} + +/** + * find_divider - find clock divider by frequency + * @aio: the AIO device pointer + * @pll_id: PLL ID, should be AUD_PLL_XX + * @freq: required frequency + * + * Find suitable clock divider by frequency. + * + * Return: The ID of PLL if successful, otherwise negative error value. + */ +static int find_divider(struct uniphier_aio *aio, int pll_id, unsigned int freq) +{ + struct uniphier_aio_pll *pll; + int mul[] = { 1, 1, 1, 2, }; + int div[] = { 2, 3, 1, 3, }; + int i; + + if (!is_valid_pll(aio->chip, pll_id)) + return -EINVAL; + + pll = &aio->chip->plls[pll_id]; + for (i = 0; i < ARRAY_SIZE(mul); i++) + if (pll->freq * mul[i] / div[i] == freq) + return i; + + return -ENOTSUPP; +} + +static int uniphier_aio_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct device *dev = &aio->chip->pdev->dev; + bool pll_auto = false; + int pll_id, div_id; + + switch (clk_id) { + case AUD_CLK_IO: + return -ENOTSUPP; + case AUD_CLK_A1: + pll_id = AUD_PLL_A1; + break; + case AUD_CLK_F1: + pll_id = AUD_PLL_F1; + break; + case AUD_CLK_A2: + pll_id = AUD_PLL_A2; + break; + case AUD_CLK_F2: + pll_id = AUD_PLL_F2; + break; + case AUD_CLK_A: + pll_id = AUD_PLL_A1; + pll_auto = true; + break; + case AUD_CLK_F: + pll_id = AUD_PLL_F1; + pll_auto = true; + break; + case AUD_CLK_APLL: + pll_id = AUD_PLL_APLL; + break; + case AUD_CLK_RX0: + pll_id = AUD_PLL_RX0; + break; + case AUD_CLK_USB0: + pll_id = AUD_PLL_USB0; + break; + case AUD_CLK_HSC0: + pll_id = AUD_PLL_HSC0; + break; + default: + dev_err(dev, "Sysclk(%d) is not supported\n", clk_id); + return -EINVAL; + } + + if (pll_auto) { + for (pll_id = 0; pll_id < aio->chip->num_plls; pll_id++) { + div_id = find_divider(aio, pll_id, freq); + if (div_id >= 0) { + aio->plldiv = div_id; + break; + } + } + if (pll_id == aio->chip->num_plls) { + dev_err(dev, "Sysclk frequency is not supported(%d)\n", + freq); + return -EINVAL; + } + } + + if (dir == SND_SOC_CLOCK_OUT) + aio->pll_out = pll_id; + else + aio->pll_in = pll_id; + + return 0; +} + +static int uniphier_aio_set_pll(struct snd_soc_dai *dai, int pll_id, + int source, unsigned int freq_in, + unsigned int freq_out) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + int ret; + + if (!is_valid_pll(aio->chip, pll_id)) + return -EINVAL; + + ret = aio_chip_set_pll(aio->chip, pll_id, freq_out); + if (ret < 0) + return ret; + + return 0; +} + +static int uniphier_aio_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct device *dev = &aio->chip->pdev->dev; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_I2S: + aio->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(dev, "Format is not supported(%d)\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + return 0; +} + +static int uniphier_aio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int ret; + + sub->substream = substream; + sub->pass_through = 0; + sub->use_mmap = true; + + ret = aio_init(sub); + if (ret) + return ret; + + return 0; +} + +static void uniphier_aio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + + sub->substream = NULL; +} + +static int uniphier_aio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + struct device *dev = &aio->chip->pdev->dev; + int freq, ret; + + switch (params_rate(params)) { + case 48000: + case 32000: + case 24000: + freq = 12288000; + break; + case 44100: + case 22050: + freq = 11289600; + break; + default: + dev_err(dev, "Rate is not supported(%d)\n", + params_rate(params)); + return -EINVAL; + } + ret = snd_soc_dai_set_sysclk(dai, AUD_CLK_A, + freq, SND_SOC_CLOCK_OUT); + if (ret) + return ret; + + sub->params = *params; + sub->setting = 1; + + aio_port_reset(sub); + aio_port_set_volume(sub, sub->vol); + aio_src_reset(sub); + + return 0; +} + +static int uniphier_aio_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + + sub->setting = 0; + + return 0; +} + +static int uniphier_aio_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int ret; + + ret = aio_port_set_param(sub, sub->pass_through, &sub->params); + if (ret) + return ret; + ret = aio_src_set_param(sub, &sub->params); + if (ret) + return ret; + aio_port_set_enable(sub, 1); + + ret = aio_if_set_param(sub, sub->pass_through); + if (ret) + return ret; + + if (sub->swm->type == PORT_TYPE_CONV) { + ret = aio_srcif_set_param(sub); + if (ret) + return ret; + ret = aio_srcch_set_param(sub); + if (ret) + return ret; + aio_srcch_set_enable(sub, 1); + } + + return 0; +} + +const struct snd_soc_dai_ops uniphier_aio_i2s_ops = { + .set_sysclk = uniphier_aio_set_sysclk, + .set_pll = uniphier_aio_set_pll, + .set_fmt = uniphier_aio_set_fmt, + .startup = uniphier_aio_startup, + .shutdown = uniphier_aio_shutdown, + .hw_params = uniphier_aio_hw_params, + .hw_free = uniphier_aio_hw_free, + .prepare = uniphier_aio_prepare, +}; +EXPORT_SYMBOL_GPL(uniphier_aio_i2s_ops); + +const struct snd_soc_dai_ops uniphier_aio_spdif_ops = { + .set_sysclk = uniphier_aio_set_sysclk, + .set_pll = uniphier_aio_set_pll, + .startup = uniphier_aio_startup, + .shutdown = uniphier_aio_shutdown, + .hw_params = uniphier_aio_hw_params, + .hw_free = uniphier_aio_hw_free, + .prepare = uniphier_aio_prepare, +}; +EXPORT_SYMBOL_GPL(uniphier_aio_spdif_ops); + +int uniphier_aio_dai_probe(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + int i; + + for (i = 0; i < ARRAY_SIZE(aio->sub); i++) { + struct uniphier_aio_sub *sub = &aio->sub[i]; + const struct uniphier_aio_spec *spec; + + spec = find_spec(aio, dai->name, i); + if (!spec) + continue; + + sub->swm = &spec->swm; + sub->spec = spec; + + sub->vol = AUD_VOL_INIT; + } + + aio_iecout_set_enable(aio->chip, true); + aio_chip_init(aio->chip); + aio->chip->active = 1; + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_probe); + +int uniphier_aio_dai_remove(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + + aio->chip->active = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_remove); + +int uniphier_aio_dai_suspend(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + + aio->chip->num_wup_aios--; + if (!aio->chip->num_wup_aios) { + reset_control_assert(aio->chip->rst); + clk_disable_unprepare(aio->chip->clk); + } + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_suspend); + +int uniphier_aio_dai_resume(struct snd_soc_dai *dai) +{ + struct uniphier_aio *aio = uniphier_priv(dai); + int ret, i; + + if (!aio->chip->active) + return 0; + + if (!aio->chip->num_wup_aios) { + ret = clk_prepare_enable(aio->chip->clk); + if (ret) + return ret; + + ret = reset_control_deassert(aio->chip->rst); + if (ret) + goto err_out_clock; + } + + aio_iecout_set_enable(aio->chip, true); + aio_chip_init(aio->chip); + + for (i = 0; i < ARRAY_SIZE(aio->sub); i++) { + struct uniphier_aio_sub *sub = &aio->sub[i]; + + if (!sub->spec || !sub->substream) + continue; + + ret = aio_init(sub); + if (ret) + goto err_out_reset; + + if (!sub->setting) + continue; + + aio_port_reset(sub); + aio_src_reset(sub); + } + aio->chip->num_wup_aios++; + + return 0; + +err_out_reset: + if (!aio->chip->num_wup_aios) + reset_control_assert(aio->chip->rst); +err_out_clock: + if (!aio->chip->num_wup_aios) + clk_disable_unprepare(aio->chip->clk); + + return ret; +} +EXPORT_SYMBOL_GPL(uniphier_aio_dai_resume); + +static int uniphier_aio_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = AUD_VOL_MAX; + + return 0; +} + +static int uniphier_aio_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct uniphier_aio_chip *chip = snd_soc_component_get_drvdata(comp); + struct uniphier_aio_sub *sub; + int oport_hw = kcontrol->private_value; + + sub = find_volume(chip, oport_hw); + if (!sub) + return 0; + + ucontrol->value.integer.value[0] = sub->vol; + + return 0; +} + +static int uniphier_aio_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct uniphier_aio_chip *chip = snd_soc_component_get_drvdata(comp); + struct uniphier_aio_sub *sub; + int oport_hw = kcontrol->private_value; + + sub = find_volume(chip, oport_hw); + if (!sub) + return 0; + + if (sub->vol == ucontrol->value.integer.value[0]) + return 0; + sub->vol = ucontrol->value.integer.value[0]; + + aio_port_set_volume(sub, sub->vol); + + return 0; +} + +static const struct snd_kcontrol_new uniphier_aio_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "HPCMOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_HPCMOUT1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "PCMOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_PCMOUT1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "PCMOUT2 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_PCMOUT2, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "PCMOUT3 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_PCMOUT3, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "HIECOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_HIECOUT1, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .name = "IECOUT1 Volume", + .info = uniphier_aio_vol_info, + .get = uniphier_aio_vol_get, + .put = uniphier_aio_vol_put, + .private_value = AUD_HW_IECOUT1, + }, +}; + +static const struct snd_soc_component_driver uniphier_aio_component = { + .name = "uniphier-aio", + .controls = uniphier_aio_controls, + .num_controls = ARRAY_SIZE(uniphier_aio_controls), +}; + +int uniphier_aio_probe(struct platform_device *pdev) +{ + struct uniphier_aio_chip *chip; + struct device *dev = &pdev->dev; + int ret, i, j; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->chip_spec = of_device_get_match_data(dev); + if (!chip->chip_spec) + return -EINVAL; + + chip->regmap_sg = syscon_regmap_lookup_by_phandle(dev->of_node, + "socionext,syscon"); + if (IS_ERR(chip->regmap_sg)) { + if (PTR_ERR(chip->regmap_sg) == -EPROBE_DEFER) + return -EPROBE_DEFER; + chip->regmap_sg = NULL; + } + + chip->clk = devm_clk_get(dev, "aio"); + if (IS_ERR(chip->clk)) + return PTR_ERR(chip->clk); + + chip->rst = devm_reset_control_get_shared(dev, "aio"); + if (IS_ERR(chip->rst)) + return PTR_ERR(chip->rst); + + chip->num_aios = chip->chip_spec->num_dais; + chip->num_wup_aios = chip->num_aios; + chip->aios = devm_kcalloc(dev, + chip->num_aios, sizeof(struct uniphier_aio), + GFP_KERNEL); + if (!chip->aios) + return -ENOMEM; + + chip->num_plls = chip->chip_spec->num_plls; + chip->plls = devm_kcalloc(dev, + chip->num_plls, + sizeof(struct uniphier_aio_pll), + GFP_KERNEL); + if (!chip->plls) + return -ENOMEM; + memcpy(chip->plls, chip->chip_spec->plls, + sizeof(struct uniphier_aio_pll) * chip->num_plls); + + for (i = 0; i < chip->num_aios; i++) { + struct uniphier_aio *aio = &chip->aios[i]; + + aio->chip = chip; + aio->fmt = SND_SOC_DAIFMT_I2S; + + for (j = 0; j < ARRAY_SIZE(aio->sub); j++) { + struct uniphier_aio_sub *sub = &aio->sub[j]; + + sub->aio = aio; + spin_lock_init(&sub->lock); + } + } + + chip->pdev = pdev; + platform_set_drvdata(pdev, chip); + + ret = clk_prepare_enable(chip->clk); + if (ret) + return ret; + + ret = reset_control_deassert(chip->rst); + if (ret) + goto err_out_clock; + + ret = devm_snd_soc_register_component(dev, &uniphier_aio_component, + chip->chip_spec->dais, + chip->chip_spec->num_dais); + if (ret) { + dev_err(dev, "Register component failed.\n"); + goto err_out_reset; + } + + ret = uniphier_aiodma_soc_register_platform(pdev); + if (ret) { + dev_err(dev, "Register platform failed.\n"); + goto err_out_reset; + } + + return 0; + +err_out_reset: + reset_control_assert(chip->rst); + +err_out_clock: + clk_disable_unprepare(chip->clk); + + return ret; +} +EXPORT_SYMBOL_GPL(uniphier_aio_probe); + +int uniphier_aio_remove(struct platform_device *pdev) +{ + struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + + reset_control_assert(chip->rst); + clk_disable_unprepare(chip->clk); + + return 0; +} +EXPORT_SYMBOL_GPL(uniphier_aio_remove); + +MODULE_AUTHOR("Katsuhiro Suzuki <suzuki.katsuhiro@socionext.com>"); +MODULE_DESCRIPTION("UniPhier AIO CPU DAI driver."); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/uniphier/aio-dma.c b/sound/soc/uniphier/aio-dma.c new file mode 100644 index 000000000..4ec6b65bf --- /dev/null +++ b/sound/soc/uniphier/aio-dma.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO DMA driver. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "aio.h" + +static struct snd_pcm_hardware uniphier_aiodma_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .period_bytes_min = 256, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 1024, + .buffer_bytes_max = 128 * 1024, +}; + +static void aiodma_pcm_irq(struct uniphier_aio_sub *sub) +{ + struct snd_pcm_runtime *runtime = sub->substream->runtime; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + int ret; + + spin_lock(&sub->lock); + ret = aiodma_rb_set_threshold(sub, runtime->dma_bytes, + sub->threshold + bytes); + if (!ret) + sub->threshold += bytes; + + aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, bytes); + aiodma_rb_clear_irq(sub); + spin_unlock(&sub->lock); + + snd_pcm_period_elapsed(sub->substream); +} + +static void aiodma_compr_irq(struct uniphier_aio_sub *sub) +{ + struct snd_compr_runtime *runtime = sub->cstream->runtime; + int bytes = runtime->fragment_size; + int ret; + + spin_lock(&sub->lock); + ret = aiodma_rb_set_threshold(sub, sub->compr_bytes, + sub->threshold + bytes); + if (!ret) + sub->threshold += bytes; + + aiodma_rb_sync(sub, sub->compr_addr, sub->compr_bytes, bytes); + aiodma_rb_clear_irq(sub); + spin_unlock(&sub->lock); + + snd_compr_fragment_elapsed(sub->cstream); +} + +static irqreturn_t aiodma_irq(int irq, void *p) +{ + struct platform_device *pdev = p; + struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + irqreturn_t ret = IRQ_NONE; + int i, j; + + for (i = 0; i < chip->num_aios; i++) { + struct uniphier_aio *aio = &chip->aios[i]; + + for (j = 0; j < ARRAY_SIZE(aio->sub); j++) { + struct uniphier_aio_sub *sub = &aio->sub[j]; + + /* Skip channel that does not trigger */ + if (!sub->running || !aiodma_rb_is_irq(sub)) + continue; + + if (sub->substream) + aiodma_pcm_irq(sub); + if (sub->cstream) + aiodma_compr_irq(sub); + + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static int uniphier_aiodma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_set_runtime_hwparams(substream, &uniphier_aiodma_hw); + + return snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256); +} + +static int uniphier_aiodma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + substream->runtime->dma_bytes = params_buffer_bytes(params); + + return 0; +} + +static int uniphier_aiodma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + substream->runtime->dma_bytes = 0; + + return 0; +} + +static int uniphier_aiodma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + unsigned long flags; + int ret; + + ret = aiodma_ch_set_param(sub); + if (ret) + return ret; + + spin_lock_irqsave(&sub->lock, flags); + ret = aiodma_rb_set_buffer(sub, runtime->dma_addr, + runtime->dma_addr + runtime->dma_bytes, + bytes); + spin_unlock_irqrestore(&sub->lock, flags); + if (ret) + return ret; + + return 0; +} + +static int uniphier_aiodma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + struct device *dev = &aio->chip->pdev->dev; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + unsigned long flags; + + spin_lock_irqsave(&sub->lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, + bytes); + aiodma_ch_set_enable(sub, 1); + sub->running = 1; + + break; + case SNDRV_PCM_TRIGGER_STOP: + sub->running = 0; + aiodma_ch_set_enable(sub, 0); + + break; + default: + dev_warn(dev, "Unknown trigger(%d) ignored\n", cmd); + break; + } + spin_unlock_irqrestore(&sub->lock, flags); + + return 0; +} + +static snd_pcm_uframes_t uniphier_aiodma_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct uniphier_aio *aio = uniphier_priv(rtd->cpu_dai); + struct uniphier_aio_sub *sub = &aio->sub[substream->stream]; + int bytes = runtime->period_size * + runtime->channels * samples_to_bytes(runtime, 1); + unsigned long flags; + snd_pcm_uframes_t pos; + + spin_lock_irqsave(&sub->lock, flags); + aiodma_rb_sync(sub, runtime->dma_addr, runtime->dma_bytes, bytes); + + if (sub->swm->dir == PORT_DIR_OUTPUT) + pos = bytes_to_frames(runtime, sub->rd_offs); + else + pos = bytes_to_frames(runtime, sub->wr_offs); + spin_unlock_irqrestore(&sub->lock, flags); + + return pos; +} + +static int uniphier_aiodma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static const struct snd_pcm_ops uniphier_aiodma_ops = { + .open = uniphier_aiodma_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = uniphier_aiodma_hw_params, + .hw_free = uniphier_aiodma_hw_free, + .prepare = uniphier_aiodma_prepare, + .trigger = uniphier_aiodma_trigger, + .pointer = uniphier_aiodma_pointer, + .mmap = uniphier_aiodma_mmap, +}; + +static int uniphier_aiodma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct device *dev = rtd->card->snd_card->dev; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(33)); + if (ret) + return ret; + + return snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, dev, + uniphier_aiodma_hw.buffer_bytes_max, + uniphier_aiodma_hw.buffer_bytes_max); +} + +static void uniphier_aiodma_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static const struct snd_soc_component_driver uniphier_soc_platform = { + .pcm_new = uniphier_aiodma_new, + .pcm_free = uniphier_aiodma_free, + .ops = &uniphier_aiodma_ops, + .compr_ops = &uniphier_aio_compr_ops, +}; + +static const struct regmap_config aiodma_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x7fffc, + .cache_type = REGCACHE_NONE, +}; + +/** + * uniphier_aiodma_soc_register_platform - register the AIO DMA + * @pdev: the platform device + * + * Register and setup the DMA of AIO to transfer the sound data to device. + * This function need to call once at driver startup and need NOT to call + * unregister function. + * + * Return: Zero if successful, otherwise a negative value on error. + */ +int uniphier_aiodma_soc_register_platform(struct platform_device *pdev) +{ + struct uniphier_aio_chip *chip = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *preg; + int irq, ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + preg = devm_ioremap_resource(dev, res); + if (IS_ERR(preg)) + return PTR_ERR(preg); + + chip->regmap = devm_regmap_init_mmio(dev, preg, + &aiodma_regmap_config); + if (IS_ERR(chip->regmap)) + return PTR_ERR(chip->regmap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "Could not get irq.\n"); + return irq; + } + + ret = devm_request_irq(dev, irq, aiodma_irq, + IRQF_SHARED, dev_name(dev), pdev); + if (ret) + return ret; + + return devm_snd_soc_register_component(dev, &uniphier_soc_platform, + NULL, 0); +} +EXPORT_SYMBOL_GPL(uniphier_aiodma_soc_register_platform); diff --git a/sound/soc/uniphier/aio-ld11.c b/sound/soc/uniphier/aio-ld11.c new file mode 100644 index 000000000..de962df24 --- /dev/null +++ b/sound/soc/uniphier/aio-ld11.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA driver for LD11/LD20. +// +// Copyright (c) 2016-2018 Socionext Inc. + +#include <linux/module.h> + +#include "aio.h" + +static const struct uniphier_aio_spec uniphier_aio_ld11[] = { + /* for HDMI PCM In, Pin:AI1Dx */ + { + .name = AUD_NAME_PCMIN1, + .gname = AUD_GNAME_HDMI, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 21, 14, }, + .ch = { 21, 14, }, + .iif = { 5, 3, }, + .iport = { 0, AUD_HW_PCMIN1, }, + }, + }, + + /* for SIF In, Pin:AI2Dx */ + { + .name = AUD_NAME_PCMIN2, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 22, 15, }, + .ch = { 22, 15, }, + .iif = { 6, 4, }, + .iport = { 1, AUD_HW_PCMIN2, }, + }, + }, + + /* for Line In, Pin:AI3Dx */ + { + .name = AUD_NAME_PCMIN3, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_EVE, + .dir = PORT_DIR_INPUT, + .rb = { 23, 16, }, + .ch = { 23, 16, }, + .iif = { 7, 5, }, + .iport = { 2, AUD_HW_PCMIN3, }, + }, + }, + + /* for S/PDIF In, Pin:AI1IEC */ + { + .name = AUD_NAME_IECIN1, + .gname = AUD_GNAME_IEC, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_INPUT, + .rb = { 26, 17, }, + .ch = { 26, 17, }, + .iif = { 10, 6, }, + .iport = { 3, AUD_HW_IECIN1, }, + }, + }, + + /* for Speaker, Pin:AO1Dx */ + { + .name = AUD_NAME_HPCMOUT1, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 0, 0, }, + .ch = { 0, 0, }, + .oif = { 0, 0, }, + .oport = { 0, AUD_HW_HPCMOUT1, }, + }, + }, + + /* for HDMI PCM, Pin:AO2Dx */ + { + .name = AUD_NAME_PCMOUT1, + .gname = AUD_GNAME_HDMI, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 0, 0, }, + .ch = { 0, 0, }, + .oif = { 0, 0, }, + .oport = { 3, AUD_HW_PCMOUT1, }, + }, + }, + + /* for Line Out, Pin:LO2_x */ + { + .name = AUD_NAME_PCMOUT2, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_EVE, + .dir = PORT_DIR_OUTPUT, + .rb = { 2, 2, }, + .ch = { 2, 2, }, + .oif = { 2, 2, }, + .oport = { 1, AUD_HW_PCMOUT2, }, + }, + }, + + /* for Headphone, Pin:HP1_x */ + { + .name = AUD_NAME_PCMOUT3, + .swm = { + .type = PORT_TYPE_EVE, + .dir = PORT_DIR_OUTPUT, + .rb = { 3, 3, }, + .ch = { 3, 3, }, + .oif = { 3, 3, }, + .oport = { 2, AUD_HW_PCMOUT3, }, + }, + }, + + /* for HW Sampling Rate Converter */ + { + .name = AUD_NAME_EPCMOUT2, + .swm = { + .type = PORT_TYPE_CONV, + .dir = PORT_DIR_OUTPUT, + .rb = { 7, 5, }, + .ch = { 7, 5, }, + .oif = { 7, 5, }, + .oport = { 6, AUD_HW_EPCMOUT2, }, + .och = { 17, 12, }, + .iif = { 1, 1, }, + }, + }, + + /* for HW Sampling Rate Converter 2 */ + { + .name = AUD_NAME_EPCMOUT3, + .swm = { + .type = PORT_TYPE_CONV, + .dir = PORT_DIR_OUTPUT, + .rb = { 8, 6, }, + .ch = { 8, 6, }, + .oif = { 8, 6, }, + .oport = { 7, AUD_HW_EPCMOUT3, }, + .och = { 18, 13, }, + .iif = { 2, 2, }, + }, + }, + + /* for S/PDIF Out, Pin:AO1IEC */ + { + .name = AUD_NAME_HIECOUT1, + .gname = AUD_GNAME_IEC, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 1, 1, }, + .ch = { 1, 1, }, + .oif = { 1, 1, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, + + /* for S/PDIF Out, Pin:AO1IEC, Compress */ + { + .name = AUD_NAME_HIECCOMPOUT1, + .gname = AUD_GNAME_IEC, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 1, 1, }, + .ch = { 1, 1, }, + .oif = { 1, 1, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, +}; + +static const struct uniphier_aio_pll uniphier_aio_pll_ld11[] = { + [AUD_PLL_A1] = { .enable = true, }, + [AUD_PLL_F1] = { .enable = true, }, + [AUD_PLL_A2] = { .enable = true, }, + [AUD_PLL_F2] = { .enable = true, }, + [AUD_PLL_APLL] = { .enable = true, }, + [AUD_PLL_RX0] = { .enable = true, }, + [AUD_PLL_USB0] = { .enable = true, }, + [AUD_PLL_HSC0] = { .enable = true, }, +}; + +static int uniphier_aio_ld11_probe(struct snd_soc_dai *dai) +{ + int ret; + + ret = uniphier_aio_dai_probe(dai); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A1, 0, 0, 36864000); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F1, 0, 0, 36864000); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A2, 0, 0, 33868800); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F2, 0, 0, 33868800); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_dai_driver uniphier_aio_dai_ld11[] = { + { + .name = AUD_GNAME_HDMI, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_PCMIN2, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .capture = { + .stream_name = AUD_NAME_PCMIN2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_GNAME_LINE, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN3, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HPCMOUT1, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HPCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 8, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_PCMOUT3, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT3, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HIECOUT1, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HIECOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_EPCMOUT2, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_EPCMOUT2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_EPCMOUT3, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_EPCMOUT3, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_32000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HIECCOMPOUT1, + .probe = uniphier_aio_ld11_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .compress_new = snd_soc_new_compress, + .playback = { + .stream_name = AUD_NAME_HIECCOMPOUT1, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &uniphier_aio_spdif_ops, + }, +}; + +static const struct uniphier_aio_chip_spec uniphier_aio_ld11_spec = { + .specs = uniphier_aio_ld11, + .num_specs = ARRAY_SIZE(uniphier_aio_ld11), + .dais = uniphier_aio_dai_ld11, + .num_dais = ARRAY_SIZE(uniphier_aio_dai_ld11), + .plls = uniphier_aio_pll_ld11, + .num_plls = ARRAY_SIZE(uniphier_aio_pll_ld11), + .addr_ext = 0, +}; + +static const struct uniphier_aio_chip_spec uniphier_aio_ld20_spec = { + .specs = uniphier_aio_ld11, + .num_specs = ARRAY_SIZE(uniphier_aio_ld11), + .dais = uniphier_aio_dai_ld11, + .num_dais = ARRAY_SIZE(uniphier_aio_dai_ld11), + .plls = uniphier_aio_pll_ld11, + .num_plls = ARRAY_SIZE(uniphier_aio_pll_ld11), + .addr_ext = 1, +}; + +static const struct of_device_id uniphier_aio_of_match[] = { + { + .compatible = "socionext,uniphier-ld11-aio", + .data = &uniphier_aio_ld11_spec, + }, + { + .compatible = "socionext,uniphier-ld20-aio", + .data = &uniphier_aio_ld20_spec, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, uniphier_aio_of_match); + +static struct platform_driver uniphier_aio_driver = { + .driver = { + .name = "snd-uniphier-aio-ld11", + .of_match_table = of_match_ptr(uniphier_aio_of_match), + }, + .probe = uniphier_aio_probe, + .remove = uniphier_aio_remove, +}; +module_platform_driver(uniphier_aio_driver); + +MODULE_AUTHOR("Katsuhiro Suzuki <suzuki.katsuhiro@socionext.com>"); +MODULE_DESCRIPTION("UniPhier LD11/LD20 AIO driver."); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/uniphier/aio-pxs2.c b/sound/soc/uniphier/aio-pxs2.c new file mode 100644 index 000000000..69cd5b0af --- /dev/null +++ b/sound/soc/uniphier/aio-pxs2.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier AIO ALSA driver for PXs2. +// +// Copyright (c) 2018 Socionext Inc. + +#include <linux/module.h> + +#include "aio.h" + +static const struct uniphier_aio_spec uniphier_aio_pxs2[] = { + /* for Line PCM In, Pin:AI1Dx */ + { + .name = AUD_NAME_PCMIN1, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 16, 11, }, + .ch = { 16, 11, }, + .iif = { 0, 0, }, + .iport = { 0, AUD_HW_PCMIN1, }, + }, + }, + + /* for Speaker/Headphone/Mic PCM In, Pin:AI2Dx */ + { + .name = AUD_NAME_PCMIN2, + .gname = AUD_GNAME_AUX, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_INPUT, + .rb = { 17, 12, }, + .ch = { 17, 12, }, + .iif = { 1, 1, }, + .iport = { 1, AUD_HW_PCMIN2, }, + }, + }, + + /* for HDMI PCM Out, Pin:AO1Dx (inner) */ + { + .name = AUD_NAME_HPCMOUT1, + .gname = AUD_GNAME_HDMI, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 0, 0, }, + .ch = { 0, 0, }, + .oif = { 0, 0, }, + .oport = { 3, AUD_HW_HPCMOUT1, }, + }, + }, + + /* for Line PCM Out, Pin:AO2Dx */ + { + .name = AUD_NAME_PCMOUT1, + .gname = AUD_GNAME_LINE, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 1, 1, }, + .ch = { 1, 1, }, + .oif = { 1, 1, }, + .oport = { 0, AUD_HW_PCMOUT1, }, + }, + }, + + /* for Speaker/Headphone/Mic PCM Out, Pin:AO3Dx */ + { + .name = AUD_NAME_PCMOUT2, + .gname = AUD_GNAME_AUX, + .swm = { + .type = PORT_TYPE_I2S, + .dir = PORT_DIR_OUTPUT, + .rb = { 2, 2, }, + .ch = { 2, 2, }, + .oif = { 2, 2, }, + .oport = { 1, AUD_HW_PCMOUT2, }, + }, + }, + + /* for HDMI Out, Pin:AO1IEC */ + { + .name = AUD_NAME_HIECOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 6, 4, }, + .ch = { 6, 4, }, + .oif = { 6, 4, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, + + /* for HDMI Out, Pin:AO1IEC, Compress */ + { + .name = AUD_NAME_HIECCOMPOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 6, 4, }, + .ch = { 6, 4, }, + .oif = { 6, 4, }, + .oport = { 12, AUD_HW_HIECOUT1, }, + }, + }, + + /* for S/PDIF Out, Pin:AO2IEC */ + { + .name = AUD_NAME_IECOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 7, 5, }, + .ch = { 7, 5, }, + .oif = { 7, 5, }, + .oport = { 13, AUD_HW_IECOUT1, }, + }, + }, + + /* for S/PDIF Out, Pin:AO2IEC */ + { + .name = AUD_NAME_IECCOMPOUT1, + .swm = { + .type = PORT_TYPE_SPDIF, + .dir = PORT_DIR_OUTPUT, + .rb = { 7, 5, }, + .ch = { 7, 5, }, + .oif = { 7, 5, }, + .oport = { 13, AUD_HW_IECOUT1, }, + }, + }, +}; + +static const struct uniphier_aio_pll uniphier_aio_pll_pxs2[] = { + [AUD_PLL_A1] = { .enable = true, }, + [AUD_PLL_F1] = { .enable = true, }, + [AUD_PLL_A2] = { .enable = true, }, + [AUD_PLL_F2] = { .enable = true, }, + [AUD_PLL_APLL] = { .enable = true, }, + [AUD_PLL_HSC0] = { .enable = true, }, +}; + +static int uniphier_aio_pxs2_probe(struct snd_soc_dai *dai) +{ + int ret; + + ret = uniphier_aio_dai_probe(dai); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A1, 0, 0, 36864000); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F1, 0, 0, 36864000); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(dai, AUD_PLL_A2, 0, 0, 33868800); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_pll(dai, AUD_PLL_F2, 0, 0, 33868800); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_dai_driver uniphier_aio_dai_pxs2[] = { + { + .name = AUD_GNAME_HDMI, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HPCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_GNAME_LINE, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_GNAME_AUX, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_PCMOUT2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = AUD_NAME_PCMIN2, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_i2s_ops, + }, + { + .name = AUD_NAME_HIECOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_HIECOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_IECOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .playback = { + .stream_name = AUD_NAME_IECOUT1, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_HIECCOMPOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .compress_new = snd_soc_new_compress, + .playback = { + .stream_name = AUD_NAME_HIECCOMPOUT1, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &uniphier_aio_spdif_ops, + }, + { + .name = AUD_NAME_IECCOMPOUT1, + .probe = uniphier_aio_pxs2_probe, + .remove = uniphier_aio_dai_remove, + .suspend = uniphier_aio_dai_suspend, + .resume = uniphier_aio_dai_resume, + .compress_new = snd_soc_new_compress, + .playback = { + .stream_name = AUD_NAME_IECCOMPOUT1, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &uniphier_aio_spdif_ops, + }, +}; + +static const struct uniphier_aio_chip_spec uniphier_aio_pxs2_spec = { + .specs = uniphier_aio_pxs2, + .num_specs = ARRAY_SIZE(uniphier_aio_pxs2), + .dais = uniphier_aio_dai_pxs2, + .num_dais = ARRAY_SIZE(uniphier_aio_dai_pxs2), + .plls = uniphier_aio_pll_pxs2, + .num_plls = ARRAY_SIZE(uniphier_aio_pll_pxs2), + .addr_ext = 0, +}; + +static const struct of_device_id uniphier_aio_of_match[] = { + { + .compatible = "socionext,uniphier-pxs2-aio", + .data = &uniphier_aio_pxs2_spec, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, uniphier_aio_of_match); + +static struct platform_driver uniphier_aio_driver = { + .driver = { + .name = "snd-uniphier-aio-pxs2", + .of_match_table = of_match_ptr(uniphier_aio_of_match), + }, + .probe = uniphier_aio_probe, + .remove = uniphier_aio_remove, +}; +module_platform_driver(uniphier_aio_driver); + +MODULE_AUTHOR("Katsuhiro Suzuki <suzuki.katsuhiro@socionext.com>"); +MODULE_DESCRIPTION("UniPhier PXs2 AIO driver."); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/uniphier/aio-reg.h b/sound/soc/uniphier/aio-reg.h new file mode 100644 index 000000000..734395dbc --- /dev/null +++ b/sound/soc/uniphier/aio-reg.h @@ -0,0 +1,476 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Socionext UniPhier AIO ALSA driver. + * + * Copyright (c) 2016-2018 Socionext Inc. + */ + +#ifndef SND_UNIPHIER_AIO_REG_H__ +#define SND_UNIPHIER_AIO_REG_H__ + +#include <linux/bitops.h> + +/* soc-glue */ +#define SG_AOUTEN 0x1c04 + +/* SW view */ +#define A2CHNMAPCTR0(n) (0x00000 + 0x40 * (n)) +#define A2RBNMAPCTR0(n) (0x01000 + 0x40 * (n)) +#define A2IPORTNMAPCTR0(n) (0x02000 + 0x40 * (n)) +#define A2IPORTNMAPCTR1(n) (0x02004 + 0x40 * (n)) +#define A2IIFNMAPCTR0(n) (0x03000 + 0x40 * (n)) +#define A2OPORTNMAPCTR0(n) (0x04000 + 0x40 * (n)) +#define A2OPORTNMAPCTR1(n) (0x04004 + 0x40 * (n)) +#define A2OPORTNMAPCTR2(n) (0x04008 + 0x40 * (n)) +#define A2OIFNMAPCTR0(n) (0x05000 + 0x40 * (n)) +#define A2ATNMAPCTR0(n) (0x06000 + 0x40 * (n)) + +#define MAPCTR0_EN 0x80000000 + +/* CTL */ +#define A2APLLCTR0 0x07000 +#define A2APLLCTR0_APLLXPOW_MASK GENMASK(3, 0) +#define A2APLLCTR0_APLLXPOW_PWOFF (0x0 << 0) +#define A2APLLCTR0_APLLXPOW_PWON (0xf << 0) +#define A2APLLCTR1 0x07004 +#define A2APLLCTR1_APLLX_MASK 0x00010101 +#define A2APLLCTR1_APLLX_36MHZ 0x00000000 +#define A2APLLCTR1_APLLX_33MHZ 0x00000001 +#define A2EXMCLKSEL0 0x07030 +#define A2EXMCLKSEL0_EXMCLK_MASK GENMASK(2, 0) +#define A2EXMCLKSEL0_EXMCLK_OUTPUT (0x0 << 0) +#define A2EXMCLKSEL0_EXMCLK_INPUT (0x7 << 0) +#define A2SSIFSW 0x07050 +#define A2CH22_2CTR 0x07054 +#define A2AIOINPUTSEL 0x070e0 +#define A2AIOINPUTSEL_RXSEL_PCMI1_MASK GENMASK(2, 0) +#define A2AIOINPUTSEL_RXSEL_PCMI1_HDMIRX1 (0x2 << 0) +#define A2AIOINPUTSEL_RXSEL_PCMI2_MASK GENMASK(6, 4) +#define A2AIOINPUTSEL_RXSEL_PCMI2_SIF (0x7 << 4) +#define A2AIOINPUTSEL_RXSEL_PCMI3_MASK GENMASK(10, 8) +#define A2AIOINPUTSEL_RXSEL_PCMI3_EVEA (0x1 << 8) +#define A2AIOINPUTSEL_RXSEL_IECI1_MASK GENMASK(14, 12) +#define A2AIOINPUTSEL_RXSEL_IECI1_HDMIRX1 (0x2 << 12) +#define A2AIOINPUTSEL_RXSEL_MASK (A2AIOINPUTSEL_RXSEL_PCMI1_MASK | \ + A2AIOINPUTSEL_RXSEL_PCMI2_MASK | \ + A2AIOINPUTSEL_RXSEL_PCMI3_MASK | \ + A2AIOINPUTSEL_RXSEL_IECI1_HDMIRX1) + +/* INTC */ +#define INTCHIM(m) (0x9028 + 0x80 * (m)) +#define INTRBIM(m) (0x9030 + 0x80 * (m)) +#define INTCHID(m) (0xa028 + 0x80 * (m)) +#define INTRBID(m) (0xa030 + 0x80 * (m)) + +/* AIN(PCMINN) */ +#define IPORTMXCTR1(n) (0x22000 + 0x400 * (n)) +#define IPORTMXCTR1_LRSEL_MASK GENMASK(11, 10) +#define IPORTMXCTR1_LRSEL_RIGHT (0x0 << 10) +#define IPORTMXCTR1_LRSEL_LEFT (0x1 << 10) +#define IPORTMXCTR1_LRSEL_I2S (0x2 << 10) +#define IPORTMXCTR1_OUTBITSEL_MASK (0x800003U << 8) +#define IPORTMXCTR1_OUTBITSEL_32 (0x800000U << 8) +#define IPORTMXCTR1_OUTBITSEL_24 (0x000000U << 8) +#define IPORTMXCTR1_OUTBITSEL_20 (0x000001U << 8) +#define IPORTMXCTR1_OUTBITSEL_16 (0x000002U << 8) +#define IPORTMXCTR1_CHSEL_MASK GENMASK(6, 4) +#define IPORTMXCTR1_CHSEL_ALL (0x0 << 4) +#define IPORTMXCTR1_CHSEL_D0_D2 (0x1 << 4) +#define IPORTMXCTR1_CHSEL_D0 (0x2 << 4) +#define IPORTMXCTR1_CHSEL_D1 (0x3 << 4) +#define IPORTMXCTR1_CHSEL_D2 (0x4 << 4) +#define IPORTMXCTR1_CHSEL_DMIX (0x5 << 4) +#define IPORTMXCTR1_FSSEL_MASK GENMASK(3, 0) +#define IPORTMXCTR1_FSSEL_48 (0x0 << 0) +#define IPORTMXCTR1_FSSEL_96 (0x1 << 0) +#define IPORTMXCTR1_FSSEL_192 (0x2 << 0) +#define IPORTMXCTR1_FSSEL_32 (0x3 << 0) +#define IPORTMXCTR1_FSSEL_44_1 (0x4 << 0) +#define IPORTMXCTR1_FSSEL_88_2 (0x5 << 0) +#define IPORTMXCTR1_FSSEL_176_4 (0x6 << 0) +#define IPORTMXCTR1_FSSEL_16 (0x8 << 0) +#define IPORTMXCTR1_FSSEL_22_05 (0x9 << 0) +#define IPORTMXCTR1_FSSEL_24 (0xa << 0) +#define IPORTMXCTR1_FSSEL_8 (0xb << 0) +#define IPORTMXCTR1_FSSEL_11_025 (0xc << 0) +#define IPORTMXCTR1_FSSEL_12 (0xd << 0) +#define IPORTMXCTR2(n) (0x22004 + 0x400 * (n)) +#define IPORTMXCTR2_ACLKSEL_MASK GENMASK(19, 16) +#define IPORTMXCTR2_ACLKSEL_A1 (0x0 << 16) +#define IPORTMXCTR2_ACLKSEL_F1 (0x1 << 16) +#define IPORTMXCTR2_ACLKSEL_A2 (0x2 << 16) +#define IPORTMXCTR2_ACLKSEL_F2 (0x3 << 16) +#define IPORTMXCTR2_ACLKSEL_A2PLL (0x4 << 16) +#define IPORTMXCTR2_ACLKSEL_RX1 (0x5 << 16) +#define IPORTMXCTR2_ACLKSEL_RX2 (0x6 << 16) +#define IPORTMXCTR2_MSSEL_MASK BIT(15) +#define IPORTMXCTR2_MSSEL_SLAVE (0x0 << 15) +#define IPORTMXCTR2_MSSEL_MASTER (0x1 << 15) +#define IPORTMXCTR2_EXTLSIFSSEL_MASK BIT(14) +#define IPORTMXCTR2_EXTLSIFSSEL_36 (0x0 << 14) +#define IPORTMXCTR2_EXTLSIFSSEL_24 (0x1 << 14) +#define IPORTMXCTR2_DACCKSEL_MASK GENMASK(9, 8) +#define IPORTMXCTR2_DACCKSEL_1_2 (0x0 << 8) +#define IPORTMXCTR2_DACCKSEL_1_3 (0x1 << 8) +#define IPORTMXCTR2_DACCKSEL_1_1 (0x2 << 8) +#define IPORTMXCTR2_DACCKSEL_2_3 (0x3 << 8) +#define IPORTMXCTR2_REQEN_MASK BIT(0) +#define IPORTMXCTR2_REQEN_DISABLE (0x0 << 0) +#define IPORTMXCTR2_REQEN_ENABLE (0x1 << 0) +#define IPORTMXCNTCTR(n) (0x22010 + 0x400 * (n)) +#define IPORTMXCOUNTER(n) (0x22014 + 0x400 * (n)) +#define IPORTMXCNTMONI(n) (0x22018 + 0x400 * (n)) +#define IPORTMXACLKSEL0EX(n) (0x22020 + 0x400 * (n)) +#define IPORTMXACLKSEL0EX_ACLKSEL0EX_MASK GENMASK(3, 0) +#define IPORTMXACLKSEL0EX_ACLKSEL0EX_INTERNAL (0x0 << 0) +#define IPORTMXACLKSEL0EX_ACLKSEL0EX_EXTERNAL (0xf << 0) +#define IPORTMXEXNOE(n) (0x22070 + 0x400 * (n)) +#define IPORTMXEXNOE_PCMINOE_MASK BIT(0) +#define IPORTMXEXNOE_PCMINOE_OUTPUT (0x0 << 0) +#define IPORTMXEXNOE_PCMINOE_INPUT (0x1 << 0) +#define IPORTMXMASK(n) (0x22078 + 0x400 * (n)) +#define IPORTMXMASK_IUXCKMSK_MASK GENMASK(18, 16) +#define IPORTMXMASK_IUXCKMSK_ON (0x0 << 16) +#define IPORTMXMASK_IUXCKMSK_OFF (0x7 << 16) +#define IPORTMXMASK_XCKMSK_MASK GENMASK(2, 0) +#define IPORTMXMASK_XCKMSK_ON (0x0 << 0) +#define IPORTMXMASK_XCKMSK_OFF (0x7 << 0) +#define IPORTMXRSTCTR(n) (0x2207c + 0x400 * (n)) +#define IPORTMXRSTCTR_RSTPI_MASK BIT(7) +#define IPORTMXRSTCTR_RSTPI_RELEASE (0x0 << 7) +#define IPORTMXRSTCTR_RSTPI_RESET (0x1 << 7) + +/* AIN(PBinMX) */ +#define PBINMXCTR(n) (0x20200 + 0x40 * (n)) +#define PBINMXCTR_NCONNECT_MASK BIT(15) +#define PBINMXCTR_NCONNECT_CONNECT (0x0 << 15) +#define PBINMXCTR_NCONNECT_DISCONNECT (0x1 << 15) +#define PBINMXCTR_INOUTSEL_MASK BIT(14) +#define PBINMXCTR_INOUTSEL_IN (0x0 << 14) +#define PBINMXCTR_INOUTSEL_OUT (0x1 << 14) +#define PBINMXCTR_PBINSEL_SHIFT (8) +#define PBINMXCTR_ENDIAN_MASK GENMASK(5, 4) +#define PBINMXCTR_ENDIAN_3210 (0x0 << 4) +#define PBINMXCTR_ENDIAN_0123 (0x1 << 4) +#define PBINMXCTR_ENDIAN_1032 (0x2 << 4) +#define PBINMXCTR_ENDIAN_2301 (0x3 << 4) +#define PBINMXCTR_MEMFMT_MASK GENMASK(3, 0) +#define PBINMXCTR_MEMFMT_D0 (0x0 << 0) +#define PBINMXCTR_MEMFMT_5_1CH_DMIX (0x1 << 0) +#define PBINMXCTR_MEMFMT_6CH (0x2 << 0) +#define PBINMXCTR_MEMFMT_4CH (0x3 << 0) +#define PBINMXCTR_MEMFMT_DMIX (0x4 << 0) +#define PBINMXCTR_MEMFMT_1CH (0x5 << 0) +#define PBINMXCTR_MEMFMT_16LR (0x6 << 0) +#define PBINMXCTR_MEMFMT_7_1CH (0x7 << 0) +#define PBINMXCTR_MEMFMT_7_1CH_DMIX (0x8 << 0) +#define PBINMXCTR_MEMFMT_STREAM (0xf << 0) +#define PBINMXPAUSECTR0(n) (0x20204 + 0x40 * (n)) +#define PBINMXPAUSECTR1(n) (0x20208 + 0x40 * (n)) + +/* AOUT */ +#define AOUTFADECTR0 0x40020 +#define AOUTENCTR0 0x40040 +#define AOUTENCTR1 0x40044 +#define AOUTENCTR2 0x40048 +#define AOUTRSTCTR0 0x40060 +#define AOUTRSTCTR1 0x40064 +#define AOUTRSTCTR2 0x40068 +#define AOUTSRCRSTCTR0 0x400c0 +#define AOUTSRCRSTCTR1 0x400c4 +#define AOUTSRCRSTCTR2 0x400c8 + +/* AOUT PCMOUT has 5 slots, slot0-3: D0-3, slot4: DMIX */ +#define OPORT_SLOT_MAX 5 + +/* AOUT(PCMOUTN) */ +#define OPORTMXCTR1(n) (0x42000 + 0x400 * (n)) +#define OPORTMXCTR1_I2SLRSEL_MASK (0x11 << 10) +#define OPORTMXCTR1_I2SLRSEL_RIGHT (0x00 << 10) +#define OPORTMXCTR1_I2SLRSEL_LEFT (0x01 << 10) +#define OPORTMXCTR1_I2SLRSEL_I2S (0x11 << 10) +#define OPORTMXCTR1_OUTBITSEL_MASK (0x800003U << 8) +#define OPORTMXCTR1_OUTBITSEL_32 (0x800000U << 8) +#define OPORTMXCTR1_OUTBITSEL_24 (0x000000U << 8) +#define OPORTMXCTR1_OUTBITSEL_20 (0x000001U << 8) +#define OPORTMXCTR1_OUTBITSEL_16 (0x000002U << 8) +#define OPORTMXCTR1_FSSEL_MASK GENMASK(3, 0) +#define OPORTMXCTR1_FSSEL_48 (0x0 << 0) +#define OPORTMXCTR1_FSSEL_96 (0x1 << 0) +#define OPORTMXCTR1_FSSEL_192 (0x2 << 0) +#define OPORTMXCTR1_FSSEL_32 (0x3 << 0) +#define OPORTMXCTR1_FSSEL_44_1 (0x4 << 0) +#define OPORTMXCTR1_FSSEL_88_2 (0x5 << 0) +#define OPORTMXCTR1_FSSEL_176_4 (0x6 << 0) +#define OPORTMXCTR1_FSSEL_16 (0x8 << 0) +#define OPORTMXCTR1_FSSEL_22_05 (0x9 << 0) +#define OPORTMXCTR1_FSSEL_24 (0xa << 0) +#define OPORTMXCTR1_FSSEL_8 (0xb << 0) +#define OPORTMXCTR1_FSSEL_11_025 (0xc << 0) +#define OPORTMXCTR1_FSSEL_12 (0xd << 0) +#define OPORTMXCTR2(n) (0x42004 + 0x400 * (n)) +#define OPORTMXCTR2_ACLKSEL_MASK GENMASK(19, 16) +#define OPORTMXCTR2_ACLKSEL_A1 (0x0 << 16) +#define OPORTMXCTR2_ACLKSEL_F1 (0x1 << 16) +#define OPORTMXCTR2_ACLKSEL_A2 (0x2 << 16) +#define OPORTMXCTR2_ACLKSEL_F2 (0x3 << 16) +#define OPORTMXCTR2_ACLKSEL_A2PLL (0x4 << 16) +#define OPORTMXCTR2_ACLKSEL_RX1 (0x5 << 16) +#define OPORTMXCTR2_ACLKSEL_RX2 (0x6 << 16) +#define OPORTMXCTR2_MSSEL_MASK BIT(15) +#define OPORTMXCTR2_MSSEL_SLAVE (0x0 << 15) +#define OPORTMXCTR2_MSSEL_MASTER (0x1 << 15) +#define OPORTMXCTR2_EXTLSIFSSEL_MASK BIT(14) +#define OPORTMXCTR2_EXTLSIFSSEL_36 (0x0 << 14) +#define OPORTMXCTR2_EXTLSIFSSEL_24 (0x1 << 14) +#define OPORTMXCTR2_DACCKSEL_MASK GENMASK(9, 8) +#define OPORTMXCTR2_DACCKSEL_1_2 (0x0 << 8) +#define OPORTMXCTR2_DACCKSEL_1_3 (0x1 << 8) +#define OPORTMXCTR2_DACCKSEL_1_1 (0x2 << 8) +#define OPORTMXCTR2_DACCKSEL_2_3 (0x3 << 8) +#define OPORTMXCTR3(n) (0x42008 + 0x400 * (n)) +#define OPORTMXCTR3_IECTHUR_MASK BIT(19) +#define OPORTMXCTR3_IECTHUR_IECOUT (0x0 << 19) +#define OPORTMXCTR3_IECTHUR_IECIN (0x1 << 19) +#define OPORTMXCTR3_SRCSEL_MASK GENMASK(18, 16) +#define OPORTMXCTR3_SRCSEL_PCM (0x0 << 16) +#define OPORTMXCTR3_SRCSEL_STREAM (0x1 << 16) +#define OPORTMXCTR3_SRCSEL_CDDTS (0x2 << 16) +#define OPORTMXCTR3_VALID_MASK BIT(12) +#define OPORTMXCTR3_VALID_PCM (0x0 << 12) +#define OPORTMXCTR3_VALID_STREAM (0x1 << 12) +#define OPORTMXCTR3_PMSEL_MASK BIT(3) +#define OPORTMXCTR3_PMSEL_MUTE (0x0 << 3) +#define OPORTMXCTR3_PMSEL_PAUSE (0x1 << 3) +#define OPORTMXCTR3_PMSW_MASK BIT(2) +#define OPORTMXCTR3_PMSW_MUTE_OFF (0x0 << 2) +#define OPORTMXCTR3_PMSW_MUTE_ON (0x1 << 2) +#define OPORTMXSRC1CTR(n) (0x4200c + 0x400 * (n)) +#define OPORTMXSRC1CTR_FSIIPNUM_SHIFT (24) +#define OPORTMXSRC1CTR_THMODE_MASK BIT(23) +#define OPORTMXSRC1CTR_THMODE_SRC (0x0 << 23) +#define OPORTMXSRC1CTR_THMODE_BYPASS (0x1 << 23) +#define OPORTMXSRC1CTR_LOCK_MASK BIT(16) +#define OPORTMXSRC1CTR_LOCK_UNLOCK (0x0 << 16) +#define OPORTMXSRC1CTR_LOCK_LOCK (0x1 << 16) +#define OPORTMXSRC1CTR_SRCPATH_MASK BIT(15) +#define OPORTMXSRC1CTR_SRCPATH_BYPASS (0x0 << 15) +#define OPORTMXSRC1CTR_SRCPATH_CALC (0x1 << 15) +#define OPORTMXSRC1CTR_SYNC_MASK BIT(14) +#define OPORTMXSRC1CTR_SYNC_ASYNC (0x0 << 14) +#define OPORTMXSRC1CTR_SYNC_SYNC (0x1 << 14) +#define OPORTMXSRC1CTR_FSOCK_MASK GENMASK(11, 10) +#define OPORTMXSRC1CTR_FSOCK_44_1 (0x0 << 10) +#define OPORTMXSRC1CTR_FSOCK_48 (0x1 << 10) +#define OPORTMXSRC1CTR_FSOCK_32 (0x2 << 10) +#define OPORTMXSRC1CTR_FSICK_MASK GENMASK(9, 8) +#define OPORTMXSRC1CTR_FSICK_44_1 (0x0 << 8) +#define OPORTMXSRC1CTR_FSICK_48 (0x1 << 8) +#define OPORTMXSRC1CTR_FSICK_32 (0x2 << 8) +#define OPORTMXSRC1CTR_FSIIPSEL_MASK GENMASK(5, 4) +#define OPORTMXSRC1CTR_FSIIPSEL_INNER (0x0 << 4) +#define OPORTMXSRC1CTR_FSIIPSEL_OUTER (0x1 << 4) +#define OPORTMXSRC1CTR_FSISEL_MASK GENMASK(3, 0) +#define OPORTMXSRC1CTR_FSISEL_ACLK (0x0 << 0) +#define OPORTMXSRC1CTR_FSISEL_DD (0x1 << 0) +#define OPORTMXDSDMUTEDAT(n) (0x42020 + 0x400 * (n)) +#define OPORTMXDXDFREQMODE(n) (0x42024 + 0x400 * (n)) +#define OPORTMXDSDSEL(n) (0x42028 + 0x400 * (n)) +#define OPORTMXDSDPORT(n) (0x4202c + 0x400 * (n)) +#define OPORTMXACLKSEL0EX(n) (0x42030 + 0x400 * (n)) +#define OPORTMXPATH(n) (0x42040 + 0x400 * (n)) +#define OPORTMXSYNC(n) (0x42044 + 0x400 * (n)) +#define OPORTMXREPET(n) (0x42050 + 0x400 * (n)) +#define OPORTMXREPET_STRLENGTH_AC3 SBF_(IEC61937_FRM_STR_AC3, 16) +#define OPORTMXREPET_STRLENGTH_MPA SBF_(IEC61937_FRM_STR_MPA, 16) +#define OPORTMXREPET_STRLENGTH_MP3 SBF_(IEC61937_FRM_STR_MP3, 16) +#define OPORTMXREPET_STRLENGTH_DTS1 SBF_(IEC61937_FRM_STR_DTS1, 16) +#define OPORTMXREPET_STRLENGTH_DTS2 SBF_(IEC61937_FRM_STR_DTS2, 16) +#define OPORTMXREPET_STRLENGTH_DTS3 SBF_(IEC61937_FRM_STR_DTS3, 16) +#define OPORTMXREPET_STRLENGTH_AAC SBF_(IEC61937_FRM_STR_AAC, 16) +#define OPORTMXREPET_PMLENGTH_AC3 SBF_(IEC61937_FRM_PAU_AC3, 0) +#define OPORTMXREPET_PMLENGTH_MPA SBF_(IEC61937_FRM_PAU_MPA, 0) +#define OPORTMXREPET_PMLENGTH_MP3 SBF_(IEC61937_FRM_PAU_MP3, 0) +#define OPORTMXREPET_PMLENGTH_DTS1 SBF_(IEC61937_FRM_PAU_DTS1, 0) +#define OPORTMXREPET_PMLENGTH_DTS2 SBF_(IEC61937_FRM_PAU_DTS2, 0) +#define OPORTMXREPET_PMLENGTH_DTS3 SBF_(IEC61937_FRM_PAU_DTS3, 0) +#define OPORTMXREPET_PMLENGTH_AAC SBF_(IEC61937_FRM_PAU_AAC, 0) +#define OPORTMXPAUDAT(n) (0x42054 + 0x400 * (n)) +#define OPORTMXPAUDAT_PAUSEPC_CMN (IEC61937_PC_PAUSE << 16) +#define OPORTMXPAUDAT_PAUSEPD_AC3 (IEC61937_FRM_PAU_AC3 * 4) +#define OPORTMXPAUDAT_PAUSEPD_MPA (IEC61937_FRM_PAU_MPA * 4) +#define OPORTMXPAUDAT_PAUSEPD_MP3 (IEC61937_FRM_PAU_MP3 * 4) +#define OPORTMXPAUDAT_PAUSEPD_DTS1 (IEC61937_FRM_PAU_DTS1 * 4) +#define OPORTMXPAUDAT_PAUSEPD_DTS2 (IEC61937_FRM_PAU_DTS2 * 4) +#define OPORTMXPAUDAT_PAUSEPD_DTS3 (IEC61937_FRM_PAU_DTS3 * 4) +#define OPORTMXPAUDAT_PAUSEPD_AAC (IEC61937_FRM_PAU_AAC * 4) +#define OPORTMXRATE_I(n) (0x420e4 + 0x400 * (n)) +#define OPORTMXRATE_I_EQU_MASK BIT(31) +#define OPORTMXRATE_I_EQU_NOTEQUAL (0x0 << 31) +#define OPORTMXRATE_I_EQU_EQUAL (0x1 << 31) +#define OPORTMXRATE_I_SRCBPMD_MASK BIT(29) +#define OPORTMXRATE_I_SRCBPMD_BYPASS (0x0 << 29) +#define OPORTMXRATE_I_SRCBPMD_SRC (0x1 << 29) +#define OPORTMXRATE_I_LRCKSTP_MASK BIT(24) +#define OPORTMXRATE_I_LRCKSTP_START (0x0 << 24) +#define OPORTMXRATE_I_LRCKSTP_STOP (0x1 << 24) +#define OPORTMXRATE_I_ACLKSRC_MASK GENMASK(15, 12) +#define OPORTMXRATE_I_ACLKSRC_APLL (0x0 << 12) +#define OPORTMXRATE_I_ACLKSRC_USB (0x1 << 12) +#define OPORTMXRATE_I_ACLKSRC_HSC (0x3 << 12) +/* if OPORTMXRATE_I_ACLKSRC_APLL */ +#define OPORTMXRATE_I_ACLKSEL_MASK GENMASK(11, 8) +#define OPORTMXRATE_I_ACLKSEL_APLLA1 (0x0 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLLF1 (0x1 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLLA2 (0x2 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLLF2 (0x3 << 8) +#define OPORTMXRATE_I_ACLKSEL_APLL (0x4 << 8) +#define OPORTMXRATE_I_ACLKSEL_HDMI1 (0x5 << 8) +#define OPORTMXRATE_I_ACLKSEL_HDMI2 (0x6 << 8) +#define OPORTMXRATE_I_ACLKSEL_AI1ADCCK (0xc << 8) +#define OPORTMXRATE_I_ACLKSEL_AI2ADCCK (0xd << 8) +#define OPORTMXRATE_I_ACLKSEL_AI3ADCCK (0xe << 8) +#define OPORTMXRATE_I_MCKSEL_MASK GENMASK(7, 4) +#define OPORTMXRATE_I_MCKSEL_36 (0x0 << 4) +#define OPORTMXRATE_I_MCKSEL_33 (0x1 << 4) +#define OPORTMXRATE_I_MCKSEL_HSC27 (0xb << 4) +#define OPORTMXRATE_I_FSSEL_MASK GENMASK(3, 0) +#define OPORTMXRATE_I_FSSEL_48 (0x0 << 0) +#define OPORTMXRATE_I_FSSEL_96 (0x1 << 0) +#define OPORTMXRATE_I_FSSEL_192 (0x2 << 0) +#define OPORTMXRATE_I_FSSEL_32 (0x3 << 0) +#define OPORTMXRATE_I_FSSEL_44_1 (0x4 << 0) +#define OPORTMXRATE_I_FSSEL_88_2 (0x5 << 0) +#define OPORTMXRATE_I_FSSEL_176_4 (0x6 << 0) +#define OPORTMXRATE_I_FSSEL_16 (0x8 << 0) +#define OPORTMXRATE_I_FSSEL_22_05 (0x9 << 0) +#define OPORTMXRATE_I_FSSEL_24 (0xa << 0) +#define OPORTMXRATE_I_FSSEL_8 (0xb << 0) +#define OPORTMXRATE_I_FSSEL_11_025 (0xc << 0) +#define OPORTMXRATE_I_FSSEL_12 (0xd << 0) +#define OPORTMXEXNOE(n) (0x420f0 + 0x400 * (n)) +#define OPORTMXMASK(n) (0x420f8 + 0x400 * (n)) +#define OPORTMXMASK_IUDXMSK_MASK GENMASK(28, 24) +#define OPORTMXMASK_IUDXMSK_ON (0x00 << 24) +#define OPORTMXMASK_IUDXMSK_OFF (0x1f << 24) +#define OPORTMXMASK_IUXCKMSK_MASK GENMASK(18, 16) +#define OPORTMXMASK_IUXCKMSK_ON (0x0 << 16) +#define OPORTMXMASK_IUXCKMSK_OFF (0x7 << 16) +#define OPORTMXMASK_DXMSK_MASK GENMASK(12, 8) +#define OPORTMXMASK_DXMSK_ON (0x00 << 8) +#define OPORTMXMASK_DXMSK_OFF (0x1f << 8) +#define OPORTMXMASK_XCKMSK_MASK GENMASK(2, 0) +#define OPORTMXMASK_XCKMSK_ON (0x0 << 0) +#define OPORTMXMASK_XCKMSK_OFF (0x7 << 0) +#define OPORTMXDEBUG(n) (0x420fc + 0x400 * (n)) +#define OPORTMXTYVOLPARA1(n, m) (0x42100 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYVOLPARA1_SLOPEU_MASK GENMASK(31, 16) +#define OPORTMXTYVOLPARA2(n, m) (0x42104 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYVOLPARA2_FADE_MASK GENMASK(17, 16) +#define OPORTMXTYVOLPARA2_FADE_NOOP (0x0 << 16) +#define OPORTMXTYVOLPARA2_FADE_FADEOUT (0x1 << 16) +#define OPORTMXTYVOLPARA2_FADE_FADEIN (0x2 << 16) +#define OPORTMXTYVOLPARA2_TARGET_MASK GENMASK(15, 0) +#define OPORTMXTYVOLGAINSTATUS(n, m) (0x42108 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYVOLGAINSTATUS_CUR_MASK GENMASK(15, 0) +#define OPORTMXTYSLOTCTR(n, m) (0x42114 + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXTYSLOTCTR_MODE BIT(15) +#define OPORTMXTYSLOTCTR_SLOTSEL_MASK GENMASK(11, 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT0 (0x8 << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT1 (0x9 << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT2 (0xa << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT3 (0xb << 8) +#define OPORTMXTYSLOTCTR_SLOTSEL_SLOT4 (0xc << 8) +#define OPORTMXT0SLOTCTR_MUTEOFF_MASK BIT(1) +#define OPORTMXT0SLOTCTR_MUTEOFF_MUTE (0x0 << 1) +#define OPORTMXT0SLOTCTR_MUTEOFF_UNMUTE (0x1 << 1) +#define OPORTMXTYRSTCTR(n, m) (0x4211c + 0x400 * (n) + 0x20 * (m)) +#define OPORTMXT0RSTCTR_RST_MASK BIT(1) +#define OPORTMXT0RSTCTR_RST_OFF (0x0 << 1) +#define OPORTMXT0RSTCTR_RST_ON (0x1 << 1) + +#define SBF_(frame, shift) (((frame) * 2 - 1) << shift) + +/* AOUT(PBoutMX) */ +#define PBOUTMXCTR0(n) (0x40200 + 0x40 * (n)) +#define PBOUTMXCTR0_ENDIAN_MASK GENMASK(5, 4) +#define PBOUTMXCTR0_ENDIAN_3210 (0x0 << 4) +#define PBOUTMXCTR0_ENDIAN_0123 (0x1 << 4) +#define PBOUTMXCTR0_ENDIAN_1032 (0x2 << 4) +#define PBOUTMXCTR0_ENDIAN_2301 (0x3 << 4) +#define PBOUTMXCTR0_MEMFMT_MASK GENMASK(3, 0) +#define PBOUTMXCTR0_MEMFMT_10CH (0x0 << 0) +#define PBOUTMXCTR0_MEMFMT_8CH (0x1 << 0) +#define PBOUTMXCTR0_MEMFMT_6CH (0x2 << 0) +#define PBOUTMXCTR0_MEMFMT_4CH (0x3 << 0) +#define PBOUTMXCTR0_MEMFMT_2CH (0x4 << 0) +#define PBOUTMXCTR0_MEMFMT_STREAM (0x5 << 0) +#define PBOUTMXCTR0_MEMFMT_1CH (0x6 << 0) +#define PBOUTMXCTR1(n) (0x40204 + 0x40 * (n)) +#define PBOUTMXINTCTR(n) (0x40208 + 0x40 * (n)) + +/* A2D(subsystem) */ +#define CDA2D_STRT0 0x10000 +#define CDA2D_STRT0_STOP_MASK BIT(31) +#define CDA2D_STRT0_STOP_START (0x0 << 31) +#define CDA2D_STRT0_STOP_STOP (0x1 << 31) +#define CDA2D_STAT0 0x10020 +#define CDA2D_TEST 0x100a0 +#define CDA2D_TEST_DDR_MODE_MASK GENMASK(3, 2) +#define CDA2D_TEST_DDR_MODE_EXTON0 (0x0 << 2) +#define CDA2D_TEST_DDR_MODE_EXTOFF1 (0x3 << 2) +#define CDA2D_STRTADRSLOAD 0x100b0 + +#define CDA2D_CHMXCTRL1(n) (0x12000 + 0x80 * (n)) +#define CDA2D_CHMXCTRL1_INDSIZE_MASK BIT(0) +#define CDA2D_CHMXCTRL1_INDSIZE_FINITE (0x0 << 0) +#define CDA2D_CHMXCTRL1_INDSIZE_INFINITE (0x1 << 0) +#define CDA2D_CHMXCTRL2(n) (0x12004 + 0x80 * (n)) +#define CDA2D_CHMXSRCAMODE(n) (0x12020 + 0x80 * (n)) +#define CDA2D_CHMXDSTAMODE(n) (0x12024 + 0x80 * (n)) +#define CDA2D_CHMXAMODE_ENDIAN_MASK GENMASK(17, 16) +#define CDA2D_CHMXAMODE_ENDIAN_3210 (0x0 << 16) +#define CDA2D_CHMXAMODE_ENDIAN_0123 (0x1 << 16) +#define CDA2D_CHMXAMODE_ENDIAN_1032 (0x2 << 16) +#define CDA2D_CHMXAMODE_ENDIAN_2301 (0x3 << 16) +#define CDA2D_CHMXAMODE_RSSEL_SHIFT (8) +#define CDA2D_CHMXAMODE_AUPDT_MASK GENMASK(5, 4) +#define CDA2D_CHMXAMODE_AUPDT_INC (0x0 << 4) +#define CDA2D_CHMXAMODE_AUPDT_FIX (0x2 << 4) +#define CDA2D_CHMXAMODE_TYPE_MASK GENMASK(3, 2) +#define CDA2D_CHMXAMODE_TYPE_NORMAL (0x0 << 2) +#define CDA2D_CHMXAMODE_TYPE_RING (0x1 << 2) +#define CDA2D_CHMXSRCSTRTADRS(n) (0x12030 + 0x80 * (n)) +#define CDA2D_CHMXSRCSTRTADRSU(n) (0x12034 + 0x80 * (n)) +#define CDA2D_CHMXDSTSTRTADRS(n) (0x12038 + 0x80 * (n)) +#define CDA2D_CHMXDSTSTRTADRSU(n) (0x1203c + 0x80 * (n)) + +/* A2D(ring buffer) */ +#define CDA2D_RBFLUSH0 0x10040 +#define CDA2D_RBADRSLOAD 0x100b4 +#define CDA2D_RDPTRLOAD 0x100b8 +#define CDA2D_RDPTRLOAD_LSFLAG_LOAD (0x0 << 31) +#define CDA2D_RDPTRLOAD_LSFLAG_STORE (0x1 << 31) +#define CDA2D_WRPTRLOAD 0x100bc +#define CDA2D_WRPTRLOAD_LSFLAG_LOAD (0x0 << 31) +#define CDA2D_WRPTRLOAD_LSFLAG_STORE (0x1 << 31) + +#define CDA2D_RBMXBGNADRS(n) (0x14000 + 0x80 * (n)) +#define CDA2D_RBMXBGNADRSU(n) (0x14004 + 0x80 * (n)) +#define CDA2D_RBMXENDADRS(n) (0x14008 + 0x80 * (n)) +#define CDA2D_RBMXENDADRSU(n) (0x1400c + 0x80 * (n)) +#define CDA2D_RBMXBTH(n) (0x14038 + 0x80 * (n)) +#define CDA2D_RBMXRTH(n) (0x1403c + 0x80 * (n)) +#define CDA2D_RBMXRDPTR(n) (0x14020 + 0x80 * (n)) +#define CDA2D_RBMXRDPTRU(n) (0x14024 + 0x80 * (n)) +#define CDA2D_RBMXWRPTR(n) (0x14028 + 0x80 * (n)) +#define CDA2D_RBMXWRPTRU(n) (0x1402c + 0x80 * (n)) +#define CDA2D_RBMXPTRU_PTRU_MASK GENMASK(1, 0) +#define CDA2D_RBMXCNFG(n) (0x14030 + 0x80 * (n)) +#define CDA2D_RBMXIR(n) (0x14014 + 0x80 * (n)) +#define CDA2D_RBMXIE(n) (0x14018 + 0x80 * (n)) +#define CDA2D_RBMXID(n) (0x1401c + 0x80 * (n)) +#define CDA2D_RBMXIX_SPACE BIT(3) +#define CDA2D_RBMXIX_REMAIN BIT(4) + +#endif /* SND_UNIPHIER_AIO_REG_H__ */ diff --git a/sound/soc/uniphier/aio.h b/sound/soc/uniphier/aio.h new file mode 100644 index 000000000..a7ff7e556 --- /dev/null +++ b/sound/soc/uniphier/aio.h @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Socionext UniPhier AIO ALSA driver. + * + * Copyright (c) 2016-2018 Socionext Inc. + */ + +#ifndef SND_UNIPHIER_AIO_H__ +#define SND_UNIPHIER_AIO_H__ + +#include <linux/spinlock.h> +#include <linux/types.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +struct platform_device; + +enum ID_PORT_TYPE { + PORT_TYPE_UNKNOWN, + PORT_TYPE_I2S, + PORT_TYPE_SPDIF, + PORT_TYPE_EVE, + PORT_TYPE_CONV, +}; + +enum ID_PORT_DIR { + PORT_DIR_OUTPUT, + PORT_DIR_INPUT, +}; + +enum IEC61937_PC { + IEC61937_PC_AC3 = 0x0001, + IEC61937_PC_PAUSE = 0x0003, + IEC61937_PC_MPA = 0x0004, + IEC61937_PC_MP3 = 0x0005, + IEC61937_PC_DTS1 = 0x000b, + IEC61937_PC_DTS2 = 0x000c, + IEC61937_PC_DTS3 = 0x000d, + IEC61937_PC_AAC = 0x0007, +}; + +/* IEC61937 Repetition period of data-burst in IEC60958 frames */ +#define IEC61937_FRM_STR_AC3 1536 +#define IEC61937_FRM_STR_MPA 1152 +#define IEC61937_FRM_STR_MP3 1152 +#define IEC61937_FRM_STR_DTS1 512 +#define IEC61937_FRM_STR_DTS2 1024 +#define IEC61937_FRM_STR_DTS3 2048 +#define IEC61937_FRM_STR_AAC 1024 + +/* IEC61937 Repetition period of Pause data-burst in IEC60958 frames */ +#define IEC61937_FRM_PAU_AC3 3 +#define IEC61937_FRM_PAU_MPA 32 +#define IEC61937_FRM_PAU_MP3 32 +#define IEC61937_FRM_PAU_DTS1 3 +#define IEC61937_FRM_PAU_DTS2 3 +#define IEC61937_FRM_PAU_DTS3 3 +#define IEC61937_FRM_PAU_AAC 32 + +/* IEC61937 Pa and Pb */ +#define IEC61937_HEADER_SIGN 0x1f4e72f8 + +#define AUD_HW_PCMIN1 0 +#define AUD_HW_PCMIN2 1 +#define AUD_HW_PCMIN3 2 +#define AUD_HW_IECIN1 3 +#define AUD_HW_DIECIN1 4 + +#define AUD_NAME_PCMIN1 "aio-pcmin1" +#define AUD_NAME_PCMIN2 "aio-pcmin2" +#define AUD_NAME_PCMIN3 "aio-pcmin3" +#define AUD_NAME_IECIN1 "aio-iecin1" +#define AUD_NAME_DIECIN1 "aio-diecin1" + +#define AUD_HW_HPCMOUT1 0 +#define AUD_HW_PCMOUT1 1 +#define AUD_HW_PCMOUT2 2 +#define AUD_HW_PCMOUT3 3 +#define AUD_HW_EPCMOUT1 4 +#define AUD_HW_EPCMOUT2 5 +#define AUD_HW_EPCMOUT3 6 +#define AUD_HW_EPCMOUT6 9 +#define AUD_HW_HIECOUT1 10 +#define AUD_HW_IECOUT1 11 +#define AUD_HW_CMASTER 31 + +#define AUD_NAME_HPCMOUT1 "aio-hpcmout1" +#define AUD_NAME_PCMOUT1 "aio-pcmout1" +#define AUD_NAME_PCMOUT2 "aio-pcmout2" +#define AUD_NAME_PCMOUT3 "aio-pcmout3" +#define AUD_NAME_EPCMOUT1 "aio-epcmout1" +#define AUD_NAME_EPCMOUT2 "aio-epcmout2" +#define AUD_NAME_EPCMOUT3 "aio-epcmout3" +#define AUD_NAME_EPCMOUT6 "aio-epcmout6" +#define AUD_NAME_HIECOUT1 "aio-hiecout1" +#define AUD_NAME_IECOUT1 "aio-iecout1" +#define AUD_NAME_CMASTER "aio-cmaster" +#define AUD_NAME_HIECCOMPOUT1 "aio-hieccompout1" +#define AUD_NAME_IECCOMPOUT1 "aio-ieccompout1" + +#define AUD_GNAME_HDMI "aio-hdmi" +#define AUD_GNAME_LINE "aio-line" +#define AUD_GNAME_AUX "aio-aux" +#define AUD_GNAME_IEC "aio-iec" + +#define AUD_CLK_IO 0 +#define AUD_CLK_A1 1 +#define AUD_CLK_F1 2 +#define AUD_CLK_A2 3 +#define AUD_CLK_F2 4 +#define AUD_CLK_A 5 +#define AUD_CLK_F 6 +#define AUD_CLK_APLL 7 +#define AUD_CLK_RX0 8 +#define AUD_CLK_USB0 9 +#define AUD_CLK_HSC0 10 + +#define AUD_PLL_A1 0 +#define AUD_PLL_F1 1 +#define AUD_PLL_A2 2 +#define AUD_PLL_F2 3 +#define AUD_PLL_APLL 4 +#define AUD_PLL_RX0 5 +#define AUD_PLL_USB0 6 +#define AUD_PLL_HSC0 7 + +#define AUD_PLLDIV_1_2 0 +#define AUD_PLLDIV_1_3 1 +#define AUD_PLLDIV_1_1 2 +#define AUD_PLLDIV_2_3 3 + +#define AUD_VOL_INIT 0x4000 /* +0dB */ +#define AUD_VOL_MAX 0xffff /* +6dB */ +#define AUD_VOL_FADE_TIME 20 /* 20ms */ + +#define AUD_RING_SIZE (128 * 1024) + +#define AUD_MIN_FRAGMENT 4 +#define AUD_MAX_FRAGMENT 8 +#define AUD_MIN_FRAGMENT_SIZE (4 * 1024) +#define AUD_MAX_FRAGMENT_SIZE (16 * 1024) + +/* max 5 slots, 10 channels, 2 channel in 1 slot */ +#define AUD_MAX_SLOTSEL 5 + +/* + * This is a selector for virtual register map of AIO. + * + * map: Specify the index of virtual register map. + * hw : Specify the ID of real register map, selector uses this value. + * A meaning of this value depends specification of SoC. + */ +struct uniphier_aio_selector { + int map; + int hw; +}; + +/** + * 'SoftWare MAPping' setting of UniPhier AIO registers. + * + * We have to setup 'virtual' register maps to access 'real' registers of AIO. + * This feature is legacy and meaningless but AIO needs this to work. + * + * Each hardware blocks have own virtual register maps as following: + * + * Address Virtual Real + * ------- --------- --------------- + * 0x12000 DMAC map0 --> [selector] --> DMAC hardware 3 + * 0x12080 DMAC map1 --> [selector] --> DMAC hardware 1 + * ... + * 0x42000 Port map0 --> [selector] --> Port hardware 1 + * 0x42400 Port map1 --> [selector] --> Port hardware 2 + * ... + * + * ch : Input or output channel of DMAC + * rb : Ring buffer + * iport: PCM input port + * iif : Input interface + * oport: PCM output port + * oif : Output interface + * och : Output channel of DMAC for sampling rate converter + * + * These are examples for sound data paths: + * + * For caputure device: + * (outer of AIO) -> iport -> iif -> ch -> rb -> (CPU) + * For playback device: + * (CPU) -> rb -> ch -> oif -> oport -> (outer of AIO) + * For sampling rate converter device: + * (CPU) -> rb -> ch -> oif -> (HW SRC) -> iif -> och -> orb -> (CPU) + */ +struct uniphier_aio_swmap { + int type; + int dir; + + struct uniphier_aio_selector ch; + struct uniphier_aio_selector rb; + struct uniphier_aio_selector iport; + struct uniphier_aio_selector iif; + struct uniphier_aio_selector oport; + struct uniphier_aio_selector oif; + struct uniphier_aio_selector och; +}; + +struct uniphier_aio_spec { + const char *name; + const char *gname; + struct uniphier_aio_swmap swm; +}; + +struct uniphier_aio_pll { + bool enable; + unsigned int freq; +}; + +struct uniphier_aio_chip_spec { + const struct uniphier_aio_spec *specs; + int num_specs; + const struct uniphier_aio_pll *plls; + int num_plls; + struct snd_soc_dai_driver *dais; + int num_dais; + + /* DMA access mode, this is workaround for DMA hungup */ + int addr_ext; +}; + +struct uniphier_aio_sub { + struct uniphier_aio *aio; + + /* Guard sub->rd_offs and wr_offs from IRQ handler. */ + spinlock_t lock; + + const struct uniphier_aio_swmap *swm; + const struct uniphier_aio_spec *spec; + + /* For PCM audio */ + struct snd_pcm_substream *substream; + struct snd_pcm_hw_params params; + int vol; + + /* For compress audio */ + struct snd_compr_stream *cstream; + struct snd_compr_params cparams; + unsigned char *compr_area; + dma_addr_t compr_addr; + size_t compr_bytes; + int pass_through; + enum IEC61937_PC iec_pc; + bool iec_header; + + /* Both PCM and compress audio */ + bool use_mmap; + int setting; + int running; + u64 rd_offs; + u64 wr_offs; + u32 threshold; + u64 rd_org; + u64 wr_org; + u64 rd_total; + u64 wr_total; +}; + +struct uniphier_aio { + struct uniphier_aio_chip *chip; + + struct uniphier_aio_sub sub[2]; + + unsigned int fmt; + /* Set one of AUD_CLK_X */ + int clk_in; + int clk_out; + /* Set one of AUD_PLL_X */ + int pll_in; + int pll_out; + /* Set one of AUD_PLLDIV_X */ + int plldiv; +}; + +struct uniphier_aio_chip { + struct platform_device *pdev; + const struct uniphier_aio_chip_spec *chip_spec; + + struct uniphier_aio *aios; + int num_aios; + int num_wup_aios; + struct uniphier_aio_pll *plls; + int num_plls; + + struct clk *clk; + struct reset_control *rst; + struct regmap *regmap; + struct regmap *regmap_sg; + int active; +}; + +static inline struct uniphier_aio *uniphier_priv(struct snd_soc_dai *dai) +{ + struct uniphier_aio_chip *chip = snd_soc_dai_get_drvdata(dai); + + return &chip->aios[dai->id]; +} + +int uniphier_aiodma_soc_register_platform(struct platform_device *pdev); +extern const struct snd_compr_ops uniphier_aio_compr_ops; + +int uniphier_aio_dai_probe(struct snd_soc_dai *dai); +int uniphier_aio_dai_remove(struct snd_soc_dai *dai); +int uniphier_aio_dai_suspend(struct snd_soc_dai *dai); +int uniphier_aio_dai_resume(struct snd_soc_dai *dai); +int uniphier_aio_probe(struct platform_device *pdev); +int uniphier_aio_remove(struct platform_device *pdev); +extern const struct snd_soc_dai_ops uniphier_aio_i2s_ops; +extern const struct snd_soc_dai_ops uniphier_aio_spdif_ops; + +u64 aio_rb_cnt(struct uniphier_aio_sub *sub); +u64 aio_rbt_cnt_to_end(struct uniphier_aio_sub *sub); +u64 aio_rb_space(struct uniphier_aio_sub *sub); +u64 aio_rb_space_to_end(struct uniphier_aio_sub *sub); + +void aio_iecout_set_enable(struct uniphier_aio_chip *chip, bool enable); +int aio_chip_set_pll(struct uniphier_aio_chip *chip, int pll_id, + unsigned int freq); +void aio_chip_init(struct uniphier_aio_chip *chip); +int aio_init(struct uniphier_aio_sub *sub); +void aio_port_reset(struct uniphier_aio_sub *sub); +int aio_port_set_param(struct uniphier_aio_sub *sub, int pass_through, + const struct snd_pcm_hw_params *params); +void aio_port_set_enable(struct uniphier_aio_sub *sub, int enable); +int aio_port_get_volume(struct uniphier_aio_sub *sub); +void aio_port_set_volume(struct uniphier_aio_sub *sub, int vol); +int aio_if_set_param(struct uniphier_aio_sub *sub, int pass_through); +int aio_oport_set_stream_type(struct uniphier_aio_sub *sub, + enum IEC61937_PC pc); +void aio_src_reset(struct uniphier_aio_sub *sub); +int aio_src_set_param(struct uniphier_aio_sub *sub, + const struct snd_pcm_hw_params *params); +int aio_srcif_set_param(struct uniphier_aio_sub *sub); +int aio_srcch_set_param(struct uniphier_aio_sub *sub); +void aio_srcch_set_enable(struct uniphier_aio_sub *sub, int enable); + +int aiodma_ch_set_param(struct uniphier_aio_sub *sub); +void aiodma_ch_set_enable(struct uniphier_aio_sub *sub, int enable); +int aiodma_rb_set_threshold(struct uniphier_aio_sub *sub, u64 size, u32 th); +int aiodma_rb_set_buffer(struct uniphier_aio_sub *sub, u64 start, u64 end, + int period); +void aiodma_rb_sync(struct uniphier_aio_sub *sub, u64 start, u64 size, + int period); +bool aiodma_rb_is_irq(struct uniphier_aio_sub *sub); +void aiodma_rb_clear_irq(struct uniphier_aio_sub *sub); + +#endif /* SND_UNIPHIER_AIO_H__ */ diff --git a/sound/soc/uniphier/evea.c b/sound/soc/uniphier/evea.c new file mode 100644 index 000000000..f9c10165f --- /dev/null +++ b/sound/soc/uniphier/evea.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Socionext UniPhier EVEA ADC/DAC codec driver. +// +// Copyright (c) 2016-2017 Socionext Inc. + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#define DRV_NAME "evea" +#define EVEA_RATES SNDRV_PCM_RATE_48000 +#define EVEA_FORMATS SNDRV_PCM_FMTBIT_S32_LE + +#define AADCPOW(n) (0x0078 + 0x04 * (n)) +#define AADCPOW_AADC_POWD BIT(0) +#define ALINSW1 0x0088 +#define ALINSW1_SEL1_SHIFT 3 +#define AHPOUTPOW 0x0098 +#define AHPOUTPOW_HP_ON BIT(4) +#define ALINEPOW 0x009c +#define ALINEPOW_LIN2_POWD BIT(3) +#define ALINEPOW_LIN1_POWD BIT(4) +#define ALO1OUTPOW 0x00a8 +#define ALO1OUTPOW_LO1_ON BIT(4) +#define ALO2OUTPOW 0x00ac +#define ALO2OUTPOW_ADAC2_MUTE BIT(0) +#define ALO2OUTPOW_LO2_ON BIT(4) +#define AANAPOW 0x00b8 +#define AANAPOW_A_POWD BIT(4) +#define ADACSEQ1(n) (0x0144 + 0x40 * (n)) +#define ADACSEQ1_MMUTE BIT(1) +#define ADACSEQ2(n) (0x0160 + 0x40 * (n)) +#define ADACSEQ2_ADACIN_FIX BIT(0) +#define ADAC1ODC 0x0200 +#define ADAC1ODC_HP_DIS_RES_MASK GENMASK(2, 1) +#define ADAC1ODC_HP_DIS_RES_OFF (0x0 << 1) +#define ADAC1ODC_HP_DIS_RES_ON (0x3 << 1) +#define ADAC1ODC_ADAC_RAMPCLT_MASK GENMASK(8, 7) +#define ADAC1ODC_ADAC_RAMPCLT_NORMAL (0x0 << 7) +#define ADAC1ODC_ADAC_RAMPCLT_REDUCE (0x1 << 7) + +struct evea_priv { + struct clk *clk, *clk_exiv; + struct reset_control *rst, *rst_exiv, *rst_adamv; + struct regmap *regmap; + + int switch_lin; + int switch_lo; + int switch_hp; +}; + +static const char * const linsw1_sel1_text[] = { + "LIN1", "LIN2", "LIN3" +}; + +static SOC_ENUM_SINGLE_DECL(linsw1_sel1_enum, + ALINSW1, ALINSW1_SEL1_SHIFT, + linsw1_sel1_text); + +static const struct snd_kcontrol_new linesw1_mux[] = { + SOC_DAPM_ENUM("Line In 1 Source", linsw1_sel1_enum), +}; + +static const struct snd_soc_dapm_widget evea_widgets[] = { + SND_SOC_DAPM_ADC("ADC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("Line In 1 Mux", SND_SOC_NOPM, 0, 0, linesw1_mux), + SND_SOC_DAPM_INPUT("LIN1_LP"), + SND_SOC_DAPM_INPUT("LIN1_RP"), + SND_SOC_DAPM_INPUT("LIN2_LP"), + SND_SOC_DAPM_INPUT("LIN2_RP"), + SND_SOC_DAPM_INPUT("LIN3_LP"), + SND_SOC_DAPM_INPUT("LIN3_RP"), + + SND_SOC_DAPM_DAC("DAC HP", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC LO1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC LO2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("HP1_L"), + SND_SOC_DAPM_OUTPUT("HP1_R"), + SND_SOC_DAPM_OUTPUT("LO2_L"), + SND_SOC_DAPM_OUTPUT("LO2_R"), +}; + +static const struct snd_soc_dapm_route evea_routes[] = { + { "Line In 1", NULL, "ADC" }, + { "ADC", NULL, "Line In 1 Mux" }, + { "Line In 1 Mux", "LIN1", "LIN1_LP" }, + { "Line In 1 Mux", "LIN1", "LIN1_RP" }, + { "Line In 1 Mux", "LIN2", "LIN2_LP" }, + { "Line In 1 Mux", "LIN2", "LIN2_RP" }, + { "Line In 1 Mux", "LIN3", "LIN3_LP" }, + { "Line In 1 Mux", "LIN3", "LIN3_RP" }, + + { "DAC HP", NULL, "Headphone 1" }, + { "DAC LO1", NULL, "Line Out 1" }, + { "DAC LO2", NULL, "Line Out 2" }, + { "HP1_L", NULL, "DAC HP" }, + { "HP1_R", NULL, "DAC HP" }, + { "LO2_L", NULL, "DAC LO2" }, + { "LO2_R", NULL, "DAC LO2" }, +}; + +static void evea_set_power_state_on(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + regmap_update_bits(map, AANAPOW, AANAPOW_A_POWD, + AANAPOW_A_POWD); + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_ON); + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_ADAC_RAMPCLT_MASK, + ADAC1ODC_ADAC_RAMPCLT_REDUCE); + + regmap_update_bits(map, ADACSEQ2(0), ADACSEQ2_ADACIN_FIX, 0); + regmap_update_bits(map, ADACSEQ2(1), ADACSEQ2_ADACIN_FIX, 0); + regmap_update_bits(map, ADACSEQ2(2), ADACSEQ2_ADACIN_FIX, 0); +} + +static void evea_set_power_state_off(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_ON); + + regmap_update_bits(map, ADACSEQ1(0), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + regmap_update_bits(map, ADACSEQ1(1), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + regmap_update_bits(map, ADACSEQ1(2), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + + regmap_update_bits(map, ALO1OUTPOW, ALO1OUTPOW_LO1_ON, 0); + regmap_update_bits(map, ALO2OUTPOW, ALO2OUTPOW_LO2_ON, 0); + regmap_update_bits(map, AHPOUTPOW, AHPOUTPOW_HP_ON, 0); +} + +static int evea_update_switch_lin(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + if (evea->switch_lin) { + regmap_update_bits(map, ALINEPOW, + ALINEPOW_LIN2_POWD | ALINEPOW_LIN1_POWD, + ALINEPOW_LIN2_POWD | ALINEPOW_LIN1_POWD); + + regmap_update_bits(map, AADCPOW(0), AADCPOW_AADC_POWD, + AADCPOW_AADC_POWD); + regmap_update_bits(map, AADCPOW(1), AADCPOW_AADC_POWD, + AADCPOW_AADC_POWD); + } else { + regmap_update_bits(map, AADCPOW(0), AADCPOW_AADC_POWD, 0); + regmap_update_bits(map, AADCPOW(1), AADCPOW_AADC_POWD, 0); + + regmap_update_bits(map, ALINEPOW, + ALINEPOW_LIN2_POWD | ALINEPOW_LIN1_POWD, 0); + } + + return 0; +} + +static int evea_update_switch_lo(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + if (evea->switch_lo) { + regmap_update_bits(map, ADACSEQ1(0), ADACSEQ1_MMUTE, 0); + regmap_update_bits(map, ADACSEQ1(2), ADACSEQ1_MMUTE, 0); + + regmap_update_bits(map, ALO1OUTPOW, ALO1OUTPOW_LO1_ON, + ALO1OUTPOW_LO1_ON); + regmap_update_bits(map, ALO2OUTPOW, + ALO2OUTPOW_ADAC2_MUTE | ALO2OUTPOW_LO2_ON, + ALO2OUTPOW_ADAC2_MUTE | ALO2OUTPOW_LO2_ON); + } else { + regmap_update_bits(map, ADACSEQ1(0), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + regmap_update_bits(map, ADACSEQ1(2), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + + regmap_update_bits(map, ALO1OUTPOW, ALO1OUTPOW_LO1_ON, 0); + regmap_update_bits(map, ALO2OUTPOW, + ALO2OUTPOW_ADAC2_MUTE | ALO2OUTPOW_LO2_ON, + 0); + } + + return 0; +} + +static int evea_update_switch_hp(struct evea_priv *evea) +{ + struct regmap *map = evea->regmap; + + if (evea->switch_hp) { + regmap_update_bits(map, ADACSEQ1(1), ADACSEQ1_MMUTE, 0); + + regmap_update_bits(map, AHPOUTPOW, AHPOUTPOW_HP_ON, + AHPOUTPOW_HP_ON); + + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_OFF); + } else { + regmap_update_bits(map, ADAC1ODC, ADAC1ODC_HP_DIS_RES_MASK, + ADAC1ODC_HP_DIS_RES_ON); + + regmap_update_bits(map, ADACSEQ1(1), ADACSEQ1_MMUTE, + ADACSEQ1_MMUTE); + + regmap_update_bits(map, AHPOUTPOW, AHPOUTPOW_HP_ON, 0); + } + + return 0; +} + +static void evea_update_switch_all(struct evea_priv *evea) +{ + evea_update_switch_lin(evea); + evea_update_switch_lo(evea); + evea_update_switch_hp(evea); +} + +static int evea_get_switch_lin(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = evea->switch_lin; + + return 0; +} + +static int evea_set_switch_lin(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + if (evea->switch_lin == ucontrol->value.integer.value[0]) + return 0; + + evea->switch_lin = ucontrol->value.integer.value[0]; + + return evea_update_switch_lin(evea); +} + +static int evea_get_switch_lo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = evea->switch_lo; + + return 0; +} + +static int evea_set_switch_lo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + if (evea->switch_lo == ucontrol->value.integer.value[0]) + return 0; + + evea->switch_lo = ucontrol->value.integer.value[0]; + + return evea_update_switch_lo(evea); +} + +static int evea_get_switch_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = evea->switch_hp; + + return 0; +} + +static int evea_set_switch_hp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + if (evea->switch_hp == ucontrol->value.integer.value[0]) + return 0; + + evea->switch_hp = ucontrol->value.integer.value[0]; + + return evea_update_switch_hp(evea); +} + +static const struct snd_kcontrol_new evea_controls[] = { + SOC_SINGLE_BOOL_EXT("Line Capture Switch", 0, + evea_get_switch_lin, evea_set_switch_lin), + SOC_SINGLE_BOOL_EXT("Line Playback Switch", 0, + evea_get_switch_lo, evea_set_switch_lo), + SOC_SINGLE_BOOL_EXT("Headphone Playback Switch", 0, + evea_get_switch_hp, evea_set_switch_hp), +}; + +static int evea_codec_probe(struct snd_soc_component *component) +{ + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + evea->switch_lin = 1; + evea->switch_lo = 1; + evea->switch_hp = 1; + + evea_set_power_state_on(evea); + evea_update_switch_all(evea); + + return 0; +} + +static int evea_codec_suspend(struct snd_soc_component *component) +{ + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + + evea_set_power_state_off(evea); + + reset_control_assert(evea->rst_adamv); + reset_control_assert(evea->rst_exiv); + reset_control_assert(evea->rst); + + clk_disable_unprepare(evea->clk_exiv); + clk_disable_unprepare(evea->clk); + + return 0; +} + +static int evea_codec_resume(struct snd_soc_component *component) +{ + struct evea_priv *evea = snd_soc_component_get_drvdata(component); + int ret; + + ret = clk_prepare_enable(evea->clk); + if (ret) + return ret; + + ret = clk_prepare_enable(evea->clk_exiv); + if (ret) + goto err_out_clock; + + ret = reset_control_deassert(evea->rst); + if (ret) + goto err_out_clock_exiv; + + ret = reset_control_deassert(evea->rst_exiv); + if (ret) + goto err_out_reset; + + ret = reset_control_deassert(evea->rst_adamv); + if (ret) + goto err_out_reset_exiv; + + evea_set_power_state_on(evea); + evea_update_switch_all(evea); + + return 0; + +err_out_reset_exiv: + reset_control_assert(evea->rst_exiv); + +err_out_reset: + reset_control_assert(evea->rst); + +err_out_clock_exiv: + clk_disable_unprepare(evea->clk_exiv); + +err_out_clock: + clk_disable_unprepare(evea->clk); + + return ret; +} + +static struct snd_soc_component_driver soc_codec_evea = { + .probe = evea_codec_probe, + .suspend = evea_codec_suspend, + .resume = evea_codec_resume, + .dapm_widgets = evea_widgets, + .num_dapm_widgets = ARRAY_SIZE(evea_widgets), + .dapm_routes = evea_routes, + .num_dapm_routes = ARRAY_SIZE(evea_routes), + .controls = evea_controls, + .num_controls = ARRAY_SIZE(evea_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver soc_dai_evea[] = { + { + .name = DRV_NAME "-line1", + .playback = { + .stream_name = "Line Out 1", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = "Line In 1", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + }, + { + .name = DRV_NAME "-hp1", + .playback = { + .stream_name = "Headphone 1", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + }, + { + .name = DRV_NAME "-lo2", + .playback = { + .stream_name = "Line Out 2", + .formats = EVEA_FORMATS, + .rates = EVEA_RATES, + .channels_min = 2, + .channels_max = 2, + }, + }, +}; + +static const struct regmap_config evea_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xffc, + .cache_type = REGCACHE_NONE, +}; + +static int evea_probe(struct platform_device *pdev) +{ + struct evea_priv *evea; + struct resource *res; + void __iomem *preg; + int ret; + + evea = devm_kzalloc(&pdev->dev, sizeof(struct evea_priv), GFP_KERNEL); + if (!evea) + return -ENOMEM; + + evea->clk = devm_clk_get(&pdev->dev, "evea"); + if (IS_ERR(evea->clk)) + return PTR_ERR(evea->clk); + + evea->clk_exiv = devm_clk_get(&pdev->dev, "exiv"); + if (IS_ERR(evea->clk_exiv)) + return PTR_ERR(evea->clk_exiv); + + evea->rst = devm_reset_control_get_shared(&pdev->dev, "evea"); + if (IS_ERR(evea->rst)) + return PTR_ERR(evea->rst); + + evea->rst_exiv = devm_reset_control_get_shared(&pdev->dev, "exiv"); + if (IS_ERR(evea->rst_exiv)) + return PTR_ERR(evea->rst_exiv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + preg = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(preg)) + return PTR_ERR(preg); + + evea->regmap = devm_regmap_init_mmio(&pdev->dev, preg, + &evea_regmap_config); + if (IS_ERR(evea->regmap)) + return PTR_ERR(evea->regmap); + + ret = clk_prepare_enable(evea->clk); + if (ret) + return ret; + + ret = clk_prepare_enable(evea->clk_exiv); + if (ret) + goto err_out_clock; + + ret = reset_control_deassert(evea->rst); + if (ret) + goto err_out_clock_exiv; + + ret = reset_control_deassert(evea->rst_exiv); + if (ret) + goto err_out_reset; + + /* ADAMV will hangup if EXIV reset is asserted */ + evea->rst_adamv = devm_reset_control_get_shared(&pdev->dev, "adamv"); + if (IS_ERR(evea->rst_adamv)) { + ret = PTR_ERR(evea->rst_adamv); + goto err_out_reset_exiv; + } + + ret = reset_control_deassert(evea->rst_adamv); + if (ret) + goto err_out_reset_exiv; + + platform_set_drvdata(pdev, evea); + + ret = devm_snd_soc_register_component(&pdev->dev, &soc_codec_evea, + soc_dai_evea, ARRAY_SIZE(soc_dai_evea)); + if (ret) + goto err_out_reset_adamv; + + return 0; + +err_out_reset_adamv: + reset_control_assert(evea->rst_adamv); + +err_out_reset_exiv: + reset_control_assert(evea->rst_exiv); + +err_out_reset: + reset_control_assert(evea->rst); + +err_out_clock_exiv: + clk_disable_unprepare(evea->clk_exiv); + +err_out_clock: + clk_disable_unprepare(evea->clk); + + return ret; +} + +static int evea_remove(struct platform_device *pdev) +{ + struct evea_priv *evea = platform_get_drvdata(pdev); + + reset_control_assert(evea->rst_adamv); + reset_control_assert(evea->rst_exiv); + reset_control_assert(evea->rst); + + clk_disable_unprepare(evea->clk_exiv); + clk_disable_unprepare(evea->clk); + + return 0; +} + +static const struct of_device_id evea_of_match[] = { + { .compatible = "socionext,uniphier-evea", }, + {} +}; +MODULE_DEVICE_TABLE(of, evea_of_match); + +static struct platform_driver evea_codec_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(evea_of_match), + }, + .probe = evea_probe, + .remove = evea_remove, +}; +module_platform_driver(evea_codec_driver); + +MODULE_AUTHOR("Katsuhiro Suzuki <suzuki.katsuhiro@socionext.com>"); +MODULE_DESCRIPTION("UniPhier EVEA codec driver"); +MODULE_LICENSE("GPL v2"); |