diff options
Diffstat (limited to 'sound/soc/atmel/mchp-i2s-mcc.c')
-rw-r--r-- | sound/soc/atmel/mchp-i2s-mcc.c | 213 |
1 files changed, 166 insertions, 47 deletions
diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c index 04acc18f2d72..193dd7acceb0 100644 --- a/sound/soc/atmel/mchp-i2s-mcc.c +++ b/sound/soc/atmel/mchp-i2s-mcc.c @@ -16,6 +16,7 @@ #include <linux/clk.h> #include <linux/mfd/syscon.h> #include <linux/lcm.h> +#include <linux/of.h> #include <sound/core.h> #include <sound/pcm.h> @@ -99,6 +100,8 @@ #define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS_COMPACT (7 << 1) #define MCHP_I2SMCC_MRA_WIRECFG_MASK GENMASK(5, 4) +#define MCHP_I2SMCC_MRA_WIRECFG_TDM(pin) (((pin) << 4) & \ + MCHP_I2SMCC_MRA_WIRECFG_MASK) #define MCHP_I2SMCC_MRA_WIRECFG_I2S_1_TDM_0 (0 << 4) #define MCHP_I2SMCC_MRA_WIRECFG_I2S_2_TDM_1 (1 << 4) #define MCHP_I2SMCC_MRA_WIRECFG_I2S_4_TDM_2 (2 << 4) @@ -173,7 +176,7 @@ */ #define MCHP_I2SMCC_MRB_CRAMODE_REGULAR (1 << 0) -#define MCHP_I2SMCC_MRB_FIFOEN BIT(1) +#define MCHP_I2SMCC_MRB_FIFOEN BIT(4) #define MCHP_I2SMCC_MRB_DMACHUNK_MASK GENMASK(9, 8) #define MCHP_I2SMCC_MRB_DMACHUNK(no_words) \ @@ -225,6 +228,11 @@ static const struct regmap_config mchp_i2s_mcc_regmap_config = { .max_register = MCHP_I2SMCC_VERSION, }; +struct mchp_i2s_mcc_soc_data { + unsigned int data_pin_pair_num; + bool has_fifo; +}; + struct mchp_i2s_mcc_dev { struct wait_queue_head wq_txrdy; struct wait_queue_head wq_rxrdy; @@ -232,6 +240,7 @@ struct mchp_i2s_mcc_dev { struct regmap *regmap; struct clk *pclk; struct clk *gclk; + const struct mchp_i2s_mcc_soc_data *soc; struct snd_dmaengine_dai_dma_data playback; struct snd_dmaengine_dai_dma_data capture; unsigned int fmt; @@ -239,6 +248,7 @@ struct mchp_i2s_mcc_dev { unsigned int frame_length; int tdm_slots; int channels; + u8 tdm_data_pair; unsigned int gclk_use:1; unsigned int gclk_running:1; unsigned int tx_rdy:1; @@ -248,7 +258,7 @@ struct mchp_i2s_mcc_dev { static irqreturn_t mchp_i2s_mcc_interrupt(int irq, void *dev_id) { struct mchp_i2s_mcc_dev *dev = dev_id; - u32 sra, imra, srb, imrb, pendinga, pendingb, idra = 0; + u32 sra, imra, srb, imrb, pendinga, pendingb, idra = 0, idrb = 0; irqreturn_t ret = IRQ_NONE; regmap_read(dev->regmap, MCHP_I2SMCC_IMRA, &imra); @@ -266,24 +276,36 @@ static irqreturn_t mchp_i2s_mcc_interrupt(int irq, void *dev_id) * Tx/Rx ready interrupts are enabled when stopping only, to assure * availability and to disable clocks if necessary */ - idra |= pendinga & (MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels) | - MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); - if (idra) + if (dev->soc->has_fifo) { + idrb |= pendingb & (MCHP_I2SMCC_INT_TXFFRDY | + MCHP_I2SMCC_INT_RXFFRDY); + } else { + idra |= pendinga & (MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels) | + MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); + } + if (idra || idrb) ret = IRQ_HANDLED; - if ((imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) && - (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) == - (idra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels))) { + if ((!dev->soc->has_fifo && + (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels))) || + (dev->soc->has_fifo && imrb & MCHP_I2SMCC_INT_TXFFRDY)) { dev->tx_rdy = 1; wake_up_interruptible(&dev->wq_txrdy); } - if ((imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) && - (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) == - (idra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels))) { + if ((!dev->soc->has_fifo && + (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels))) || + (dev->soc->has_fifo && imrb & MCHP_I2SMCC_INT_RXFFRDY)) { dev->rx_rdy = 1; wake_up_interruptible(&dev->wq_rxrdy); } - regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra); + if (dev->soc->has_fifo) + regmap_write(dev->regmap, MCHP_I2SMCC_IDRB, idrb); + else + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra); return ret; } @@ -328,7 +350,7 @@ static int mchp_i2s_mcc_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; /* We can't generate only FSYNC */ - if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFS) + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) == SND_SOC_DAIFMT_BC_FP) return -EINVAL; /* We can only reconfigure the IP when it's stopped */ @@ -524,20 +546,20 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: + switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: /* cpu is BCLK and LRC master */ mra |= MCHP_I2SMCC_MRA_MODE_MASTER; if (dev->sysclk) mra |= MCHP_I2SMCC_MRA_IMCKMODE_GEN; set_divs = 1; break; - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_BP_FC: /* cpu is BCLK master */ mrb |= MCHP_I2SMCC_MRB_CLKSEL_INT; set_divs = 1; fallthrough; - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_BC_FC: /* cpu is slave */ mra |= MCHP_I2SMCC_MRA_MODE_SLAVE; if (dev->sysclk) @@ -549,6 +571,17 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, } if (dev->fmt & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J)) { + /* for I2S and LEFT_J one pin is needed for every 2 channels */ + if (channels > dev->soc->data_pin_pair_num * 2) { + dev_err(dev->dev, + "unsupported number of audio channels: %d\n", + channels); + return -EINVAL; + } + + /* enable for interleaved format */ + mrb |= MCHP_I2SMCC_MRB_CRAMODE_REGULAR; + switch (channels) { case 1: if (is_playback) @@ -558,6 +591,12 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, break; case 2: break; + case 4: + mra |= MCHP_I2SMCC_MRA_WIRECFG_I2S_2_TDM_1; + break; + case 8: + mra |= MCHP_I2SMCC_MRA_WIRECFG_I2S_4_TDM_2; + break; default: dev_err(dev->dev, "unsupported number of audio channels\n"); return -EINVAL; @@ -566,6 +605,8 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, if (!frame_length) frame_length = 2 * params_physical_width(params); } else if (dev->fmt & SND_SOC_DAIFMT_DSP_A) { + mra |= MCHP_I2SMCC_MRA_WIRECFG_TDM(dev->tdm_data_pair); + if (dev->tdm_slots) { if (channels % 2 && channels * 2 <= dev->tdm_slots) { /* @@ -636,6 +677,10 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, } } + /* enable FIFO if available */ + if (dev->soc->has_fifo) + mrb |= MCHP_I2SMCC_MRB_FIFOEN; + /* * If we are already running, the wanted setup must be * the same with the one that's currently ongoing @@ -698,8 +743,13 @@ static int mchp_i2s_mcc_hw_free(struct snd_pcm_substream *substream, if (err == 0) { dev_warn_once(dev->dev, "Timeout waiting for Tx ready\n"); - regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, - MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)); + if (dev->soc->has_fifo) + regmap_write(dev->regmap, MCHP_I2SMCC_IDRB, + MCHP_I2SMCC_INT_TXFFRDY); + else + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, + MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)); + dev->tx_rdy = 1; } } else { @@ -709,8 +759,12 @@ static int mchp_i2s_mcc_hw_free(struct snd_pcm_substream *substream, if (err == 0) { dev_warn_once(dev->dev, "Timeout waiting for Rx ready\n"); - regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, - MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); + if (dev->soc->has_fifo) + regmap_write(dev->regmap, MCHP_I2SMCC_IDRB, + MCHP_I2SMCC_INT_RXFFRDY); + else + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, + MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); dev->rx_rdy = 1; } } @@ -737,7 +791,7 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd, struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); u32 cr = 0; - u32 iera = 0; + u32 iera = 0, ierb = 0; u32 sr; int err; @@ -761,7 +815,10 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd, * Enable Tx Ready interrupts on all channels * to assure all data is sent */ - iera = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels); + if (dev->soc->has_fifo) + ierb = MCHP_I2SMCC_INT_TXFFRDY; + else + iera = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels); } else if (!is_playback && (sr & MCHP_I2SMCC_SR_RXEN)) { cr = MCHP_I2SMCC_CR_RXDIS; dev->rx_rdy = 0; @@ -769,7 +826,10 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd, * Enable Rx Ready interrupts on all channels * to assure all data is received */ - iera = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels); + if (dev->soc->has_fifo) + ierb = MCHP_I2SMCC_INT_RXFFRDY; + else + iera = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels); } break; default: @@ -787,7 +847,10 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd, } } - regmap_write(dev->regmap, MCHP_I2SMCC_IERA, iera); + if (dev->soc->has_fifo) + regmap_write(dev->regmap, MCHP_I2SMCC_IERB, ierb); + else + regmap_write(dev->regmap, MCHP_I2SMCC_IERA, iera); regmap_write(dev->regmap, MCHP_I2SMCC_CR, cr); return 0; @@ -807,17 +870,6 @@ static int mchp_i2s_mcc_startup(struct snd_pcm_substream *substream, return 0; } -static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { - .set_sysclk = mchp_i2s_mcc_set_sysclk, - .set_bclk_ratio = mchp_i2s_mcc_set_bclk_ratio, - .startup = mchp_i2s_mcc_startup, - .trigger = mchp_i2s_mcc_trigger, - .hw_params = mchp_i2s_mcc_hw_params, - .hw_free = mchp_i2s_mcc_hw_free, - .set_fmt = mchp_i2s_mcc_set_dai_fmt, - .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, -}; - static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) { struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); @@ -832,6 +884,18 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) return 0; } +static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .probe = mchp_i2s_mcc_dai_probe, + .set_sysclk = mchp_i2s_mcc_set_sysclk, + .set_bclk_ratio = mchp_i2s_mcc_set_bclk_ratio, + .startup = mchp_i2s_mcc_startup, + .trigger = mchp_i2s_mcc_trigger, + .hw_params = mchp_i2s_mcc_hw_params, + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, +}; + #define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 #define MCHP_I2SMCC_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ @@ -843,7 +907,6 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) SNDRV_PCM_FMTBIT_S32_LE) static struct snd_soc_dai_driver mchp_i2s_mcc_dai = { - .probe = mchp_i2s_mcc_dai_probe, .playback = { .stream_name = "I2SMCC-Playback", .channels_min = 1, @@ -859,25 +922,79 @@ static struct snd_soc_dai_driver mchp_i2s_mcc_dai = { .formats = MCHP_I2SMCC_FORMATS, }, .ops = &mchp_i2s_mcc_dai_ops, - .symmetric_rates = 1, - .symmetric_samplebits = 1, + .symmetric_rate = 1, + .symmetric_sample_bits = 1, .symmetric_channels = 1, }; static const struct snd_soc_component_driver mchp_i2s_mcc_component = { - .name = "mchp-i2s-mcc", + .name = "mchp-i2s-mcc", + .legacy_dai_naming = 1, }; #ifdef CONFIG_OF +static struct mchp_i2s_mcc_soc_data mchp_i2s_mcc_sam9x60 = { + .data_pin_pair_num = 1, +}; + +static struct mchp_i2s_mcc_soc_data mchp_i2s_mcc_sama7g5 = { + .data_pin_pair_num = 4, + .has_fifo = true, +}; + static const struct of_device_id mchp_i2s_mcc_dt_ids[] = { { .compatible = "microchip,sam9x60-i2smcc", + .data = &mchp_i2s_mcc_sam9x60, + }, + { + .compatible = "microchip,sama7g5-i2smcc", + .data = &mchp_i2s_mcc_sama7g5, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mchp_i2s_mcc_dt_ids); #endif +static int mchp_i2s_mcc_soc_data_parse(struct platform_device *pdev, + struct mchp_i2s_mcc_dev *dev) +{ + int err; + + if (!dev->soc) { + dev_err(&pdev->dev, "failed to get soc data\n"); + return -ENODEV; + } + + if (dev->soc->data_pin_pair_num == 1) + return 0; + + err = of_property_read_u8(pdev->dev.of_node, "microchip,tdm-data-pair", + &dev->tdm_data_pair); + if (err < 0 && err != -EINVAL) { + dev_err(&pdev->dev, + "bad property data for 'microchip,tdm-data-pair': %d", + err); + return err; + } + if (err == -EINVAL) { + dev_info(&pdev->dev, + "'microchip,tdm-data-pair' not found; assuming DIN/DOUT 0 for TDM\n"); + dev->tdm_data_pair = 0; + } else { + if (dev->tdm_data_pair > dev->soc->data_pin_pair_num - 1) { + dev_err(&pdev->dev, + "invalid value for 'microchip,tdm-data-pair': %d\n", + dev->tdm_data_pair); + return -EINVAL; + } + dev_dbg(&pdev->dev, "TMD format on DIN/DOUT %d pins\n", + dev->tdm_data_pair); + } + + return 0; +} + static int mchp_i2s_mcc_probe(struct platform_device *pdev) { struct mchp_i2s_mcc_dev *dev; @@ -892,8 +1009,7 @@ static int mchp_i2s_mcc_probe(struct platform_device *pdev) if (!dev) return -ENOMEM; - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, mem); + base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); if (IS_ERR(base)) return PTR_ERR(base); @@ -929,6 +1045,11 @@ static int mchp_i2s_mcc_probe(struct platform_device *pdev) dev->gclk = NULL; } + dev->soc = of_device_get_match_data(&pdev->dev); + err = mchp_i2s_mcc_soc_data_parse(pdev, dev); + if (err < 0) + return err; + dev->dev = &pdev->dev; dev->regmap = regmap; platform_set_drvdata(pdev, dev); @@ -967,22 +1088,20 @@ static int mchp_i2s_mcc_probe(struct platform_device *pdev) return 0; } -static int mchp_i2s_mcc_remove(struct platform_device *pdev) +static void mchp_i2s_mcc_remove(struct platform_device *pdev) { struct mchp_i2s_mcc_dev *dev = platform_get_drvdata(pdev); clk_disable_unprepare(dev->pclk); - - return 0; } static struct platform_driver mchp_i2s_mcc_driver = { .driver = { .name = "mchp_i2s_mcc", - .of_match_table = of_match_ptr(mchp_i2s_mcc_dt_ids), + .of_match_table = mchp_i2s_mcc_dt_ids, }, .probe = mchp_i2s_mcc_probe, - .remove = mchp_i2s_mcc_remove, + .remove_new = mchp_i2s_mcc_remove, }; module_platform_driver(mchp_i2s_mcc_driver); |