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); |