| // 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 | 
 |  * @pll_id: 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 | 
 |  * | 
 |  * 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); | 
 | } |