diff options
Diffstat (limited to 'sound/soc/stm/stm32_i2s.c')
| -rw-r--r-- | sound/soc/stm/stm32_i2s.c | 136 | 
1 files changed, 73 insertions, 63 deletions
diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c index 6d0bf78d114d..47c334de6b09 100644 --- a/sound/soc/stm/stm32_i2s.c +++ b/sound/soc/stm/stm32_i2s.c @@ -186,8 +186,9 @@ enum i2s_datlen {  #define STM32_I2S_IS_SLAVE(x)		((x)->ms_flg == I2S_MS_SLAVE)  /** + * struct stm32_i2s_data - private data of I2S   * @regmap_conf: I2S register map configuration pointer - * @egmap: I2S register map pointer + * @regmap: I2S register map pointer   * @pdev: device data pointer   * @dai_drv: DAI driver pointer   * @dma_data_tx: dma configuration data for tx channel @@ -200,6 +201,7 @@ enum i2s_datlen {   * @base:  mmio register base virtual address   * @phys_addr: I2S registers physical base address   * @lock_fd: lock to manage race conditions in full duplex mode + * @irq_lock: prevent race condition with IRQ   * @dais_name: DAI name   * @mclk_rate: master clock frequency (Hz)   * @fmt: DAI protocol @@ -221,6 +223,7 @@ struct stm32_i2s_data {  	void __iomem *base;  	dma_addr_t phys_addr;  	spinlock_t lock_fd; /* Manage race conditions for full duplex */ +	spinlock_t irq_lock; /* used to prevent race condition with IRQ */  	char dais_name[STM32_I2S_DAI_NAME_SIZE];  	unsigned int mclk_rate;  	unsigned int fmt; @@ -246,8 +249,8 @@ static irqreturn_t stm32_i2s_isr(int irq, void *devid)  		return IRQ_NONE;  	} -	regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG, -			   I2S_IFCR_MASK, flags); +	regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG, +			  I2S_IFCR_MASK, flags);  	if (flags & I2S_SR_OVR) {  		dev_dbg(&pdev->dev, "Overrun\n"); @@ -262,8 +265,10 @@ static irqreturn_t stm32_i2s_isr(int irq, void *devid)  	if (flags & I2S_SR_TIFRE)  		dev_dbg(&pdev->dev, "Frame error\n"); -	if (err) +	spin_lock(&i2s->irq_lock); +	if (err && i2s->substream)  		snd_pcm_stop_xrun(i2s->substream); +	spin_unlock(&i2s->irq_lock);  	return IRQ_HANDLED;  } @@ -276,7 +281,6 @@ static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg)  	case STM32_I2S_CFG2_REG:  	case STM32_I2S_IER_REG:  	case STM32_I2S_SR_REG: -	case STM32_I2S_IFCR_REG:  	case STM32_I2S_TXDR_REG:  	case STM32_I2S_RXDR_REG:  	case STM32_I2S_CGFR_REG: @@ -488,7 +492,7 @@ static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai,  {  	struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai);  	int format = params_width(params); -	u32 cfgr, cfgr_mask, cfg1, cfg1_mask; +	u32 cfgr, cfgr_mask, cfg1;  	unsigned int fthlv;  	int ret; @@ -501,7 +505,7 @@ static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai,  	switch (format) {  	case 16:  		cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_16); -		cfgr_mask = I2S_CGFR_DATLEN_MASK; +		cfgr_mask = I2S_CGFR_DATLEN_MASK | I2S_CGFR_CHLEN;  		break;  	case 32:  		cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_32) | @@ -529,30 +533,32 @@ static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai,  	if (ret < 0)  		return ret; -	cfg1 = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; -	cfg1_mask = cfg1; -  	fthlv = STM32_I2S_FIFO_SIZE * I2S_FIFO_TH_ONE_QUARTER / 4; -	cfg1 |= I2S_CFG1_FTHVL_SET(fthlv - 1); -	cfg1_mask |= I2S_CFG1_FTHVL_MASK; +	cfg1 = I2S_CFG1_FTHVL_SET(fthlv - 1);  	return regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, -				  cfg1_mask, cfg1); +				  I2S_CFG1_FTHVL_MASK, cfg1);  }  static int stm32_i2s_startup(struct snd_pcm_substream *substream,  			     struct snd_soc_dai *cpu_dai)  {  	struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); +	unsigned long flags; +	int ret; +	spin_lock_irqsave(&i2s->irq_lock, flags);  	i2s->substream = substream; +	spin_unlock_irqrestore(&i2s->irq_lock, flags); -	spin_lock(&i2s->lock_fd); -	i2s->refcount++; -	spin_unlock(&i2s->lock_fd); +	ret = clk_prepare_enable(i2s->i2sclk); +	if (ret < 0) { +		dev_err(cpu_dai->dev, "Failed to enable clock: %d\n", ret); +		return ret; +	} -	return regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG, -				  I2S_IFCR_MASK, I2S_IFCR_MASK); +	return regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG, +				 I2S_IFCR_MASK, I2S_IFCR_MASK);  }  static int stm32_i2s_hw_params(struct snd_pcm_substream *substream, @@ -589,6 +595,10 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,  		/* Enable i2s */  		dev_dbg(cpu_dai->dev, "start I2S\n"); +		cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; +		regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, +				   cfg1_mask, cfg1_mask); +  		ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG,  					 I2S_CR1_SPE, I2S_CR1_SPE);  		if (ret < 0) { @@ -596,28 +606,29 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,  			return ret;  		} -		ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, -					 I2S_CR1_CSTART, I2S_CR1_CSTART); +		ret = regmap_write_bits(i2s->regmap, STM32_I2S_CR1_REG, +					I2S_CR1_CSTART, I2S_CR1_CSTART);  		if (ret < 0) {  			dev_err(cpu_dai->dev, "Error %d starting I2S\n", ret);  			return ret;  		} -		regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG, -				   I2S_IFCR_MASK, I2S_IFCR_MASK); +		regmap_write_bits(i2s->regmap, STM32_I2S_IFCR_REG, +				  I2S_IFCR_MASK, I2S_IFCR_MASK); +		spin_lock(&i2s->lock_fd); +		i2s->refcount++;  		if (playback_flg) {  			ier = I2S_IER_UDRIE;  		} else {  			ier = I2S_IER_OVRIE; -			spin_lock(&i2s->lock_fd); -			if (i2s->refcount == 1) -				/* dummy write to trigger capture */ +			if (STM32_I2S_IS_MASTER(i2s) && i2s->refcount == 1) +				/* dummy write to gate bus clocks */  				regmap_write(i2s->regmap,  					     STM32_I2S_TXDR_REG, 0); -			spin_unlock(&i2s->lock_fd);  		} +		spin_unlock(&i2s->lock_fd);  		if (STM32_I2S_IS_SLAVE(i2s))  			ier |= I2S_IER_TIFREIE; @@ -642,7 +653,6 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,  			spin_unlock(&i2s->lock_fd);  			break;  		} -		spin_unlock(&i2s->lock_fd);  		dev_dbg(cpu_dai->dev, "stop I2S\n"); @@ -650,8 +660,10 @@ static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd,  					 I2S_CR1_SPE, 0);  		if (ret < 0) {  			dev_err(cpu_dai->dev, "Error %d disabling I2S\n", ret); +			spin_unlock(&i2s->lock_fd);  			return ret;  		} +		spin_unlock(&i2s->lock_fd);  		cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN;  		regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, @@ -668,11 +680,16 @@ static void stm32_i2s_shutdown(struct snd_pcm_substream *substream,  			       struct snd_soc_dai *cpu_dai)  {  	struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); - -	i2s->substream = NULL; +	unsigned long flags;  	regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG,  			   I2S_CGFR_MCKOE, (unsigned int)~I2S_CGFR_MCKOE); + +	clk_disable_unprepare(i2s->i2sclk); + +	spin_lock_irqsave(&i2s->irq_lock, flags); +	i2s->substream = NULL; +	spin_unlock_irqrestore(&i2s->irq_lock, flags);  }  static int stm32_i2s_dai_probe(struct snd_soc_dai *cpu_dai) @@ -703,6 +720,7 @@ static const struct regmap_config stm32_h7_i2s_regmap_conf = {  	.volatile_reg = stm32_i2s_volatile_reg,  	.writeable_reg = stm32_i2s_writeable_reg,  	.fast_io = true, +	.cache_type = REGCACHE_FLAT,  };  static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { @@ -866,76 +884,68 @@ static int stm32_i2s_probe(struct platform_device *pdev)  	i2s->pdev = pdev;  	i2s->ms_flg = I2S_MS_NOT_SET;  	spin_lock_init(&i2s->lock_fd); +	spin_lock_init(&i2s->irq_lock);  	platform_set_drvdata(pdev, i2s);  	ret = stm32_i2s_dais_init(pdev, i2s);  	if (ret)  		return ret; -	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->base, -					    i2s->regmap_conf); +	i2s->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "pclk", +						i2s->base, i2s->regmap_conf);  	if (IS_ERR(i2s->regmap)) {  		dev_err(&pdev->dev, "regmap init failed\n");  		return PTR_ERR(i2s->regmap);  	} -	ret = clk_prepare_enable(i2s->pclk); -	if (ret) { -		dev_err(&pdev->dev, "Enable pclk failed: %d\n", ret); -		return ret; -	} - -	ret = clk_prepare_enable(i2s->i2sclk); -	if (ret) { -		dev_err(&pdev->dev, "Enable i2sclk failed: %d\n", ret); -		goto err_pclk_disable; -	} -  	ret = devm_snd_soc_register_component(&pdev->dev, &stm32_i2s_component,  					      i2s->dai_drv, 1);  	if (ret) -		goto err_clocks_disable; +		return ret;  	ret = devm_snd_dmaengine_pcm_register(&pdev->dev,  					      &stm32_i2s_pcm_config, 0);  	if (ret) -		goto err_clocks_disable; +		return ret;  	/* Set SPI/I2S in i2s mode */ -	ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, -				 I2S_CGFR_I2SMOD, I2S_CGFR_I2SMOD); -	if (ret) -		goto err_clocks_disable; +	return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, +				  I2S_CGFR_I2SMOD, I2S_CGFR_I2SMOD); +} -	return ret; +MODULE_DEVICE_TABLE(of, stm32_i2s_ids); -err_clocks_disable: -	clk_disable_unprepare(i2s->i2sclk); -err_pclk_disable: -	clk_disable_unprepare(i2s->pclk); +#ifdef CONFIG_PM_SLEEP +static int stm32_i2s_suspend(struct device *dev) +{ +	struct stm32_i2s_data *i2s = dev_get_drvdata(dev); -	return ret; +	regcache_cache_only(i2s->regmap, true); +	regcache_mark_dirty(i2s->regmap); + +	return 0;  } -static int stm32_i2s_remove(struct platform_device *pdev) +static int stm32_i2s_resume(struct device *dev)  { -	struct stm32_i2s_data *i2s = platform_get_drvdata(pdev); - -	clk_disable_unprepare(i2s->i2sclk); -	clk_disable_unprepare(i2s->pclk); +	struct stm32_i2s_data *i2s = dev_get_drvdata(dev); -	return 0; +	regcache_cache_only(i2s->regmap, false); +	return regcache_sync(i2s->regmap);  } +#endif /* CONFIG_PM_SLEEP */ -MODULE_DEVICE_TABLE(of, stm32_i2s_ids); +static const struct dev_pm_ops stm32_i2s_pm_ops = { +	SET_SYSTEM_SLEEP_PM_OPS(stm32_i2s_suspend, stm32_i2s_resume) +};  static struct platform_driver stm32_i2s_driver = {  	.driver = {  		.name = "st,stm32-i2s",  		.of_match_table = stm32_i2s_ids, +		.pm = &stm32_i2s_pm_ops,  	},  	.probe = stm32_i2s_probe, -	.remove = stm32_i2s_remove,  };  module_platform_driver(stm32_i2s_driver);  |