// SPDX-License-Identifier: GPL-2.0 // // Socionext UniPhier AIO ALSA common driver. // // Copyright (c) 2016-2018 Socionext Inc. #include #include #include #include #include #include #include #include #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); }