1251 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1251 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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);
 | |
| }
 |