mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 14:41:39 +00:00
Merge series "Add I2S-MCC support for Microchip's SAMA7G5" from Codrin Ciubotariu <codrin.ciubotariu@microchip.com>:
SAMA7G5 includes an updated version of I2S-MCC, found previously on SAM9X60. This controller includes 8 data pins, 4 for playback and 4 for capture. For I2S and LEFT_J formats, these pins can be used to send/receive up to 8 audio channels. For DSP_A, with TDM, any pins pair (DIN/DOUT) from these 4 can be selected to send/receive data. This version also includes 2 FIFOs (send and receive). This patch set starts by moving the driver's bindings to yaml and continues with adding a new compatible for the SAMA7G5 variant, followed by the changes needed for I2S/LEFT_J support, TDM pin pair selection and FIFO support, exclusively for SAMA7G5. Changes in v2: - moved DT binding conversion patch from the beginning to the end of the patch serieses - patches that update the DT binding are modified to change .txt file instead of .yaml Codrin Ciubotariu (7): dt-bindings: mchp,i2s-mcc: Add SAMA7G5 to binding ASoC: mchp-i2s-mcc: Add compatible for SAMA7G5 ASoC: mchp-i2s-mcc: Add multi-channel support for I2S and LEFT_J formats dt-bindings: mchp,i2s-mcc: Add property to specify pin pair for TDM ASoC: mchp-i2s-mcc: Add support to select TDM pins ASoC: mchp-i2s-mcc: Add FIFOs support ASoC: convert Microchip I2SMCC binding to yaml .../bindings/sound/mchp,i2s-mcc.yaml | 108 ++++++++++++ .../bindings/sound/mchp-i2s-mcc.txt | 43 ----- sound/soc/atmel/Kconfig | 3 + sound/soc/atmel/mchp-i2s-mcc.c | 161 +++++++++++++++--- 4 files changed, 252 insertions(+), 63 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/mchp,i2s-mcc.yaml delete mode 100644 Documentation/devicetree/bindings/sound/mchp-i2s-mcc.txt -- 2.27.0
This commit is contained in:
commit
842860f45d
@ -1,7 +1,8 @@
|
||||
* Microchip I2S Multi-Channel Controller
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "microchip,sam9x60-i2smcc".
|
||||
- compatible: Should be "microchip,sam9x60-i2smcc" or
|
||||
"microchip,sama7g5-i2smcc".
|
||||
- reg: Should be the physical base address of the controller and the
|
||||
length of memory mapped region.
|
||||
- interrupts: Should contain the interrupt for the controller.
|
||||
@ -18,7 +19,12 @@ Required properties:
|
||||
Optional properties:
|
||||
- pinctrl-0: Should specify pin control groups used for this controller.
|
||||
- princtrl-names: Should contain only one value - "default".
|
||||
|
||||
- microchip,tdm-data-pair: 8 bit value that represents the DIN/DOUT pair pins
|
||||
which are used to receive/send TDM data. It is optional
|
||||
and it is only needed if the controller uses the TDM
|
||||
mode. Not available for "microchip,sam9x60-i2smcc"
|
||||
compatible. If it's not present, the default value is 0,
|
||||
so the DIN/DOUT 0 pins are used.
|
||||
|
||||
(1) : Only the peripheral clock is required. The generated clock is optional
|
||||
and should be set mostly when Master Mode is required.
|
||||
|
@ -127,10 +127,13 @@ config SND_MCHP_SOC_I2S_MCC
|
||||
Say Y or M if you want to add support for I2S Multi-Channel ASoC
|
||||
driver on the following Microchip platforms:
|
||||
- sam9x60
|
||||
- sama7g5
|
||||
|
||||
The I2SMCC complies with the Inter-IC Sound (I2S) bus specification
|
||||
and supports a Time Division Multiplexed (TDM) interface with
|
||||
external multi-channel audio codecs.
|
||||
Starting with sama7g5, I2S and Left-Justified multi-channel is
|
||||
supported by using multiple data pins, output and input, without TDM.
|
||||
|
||||
config SND_MCHP_SOC_SPDIFTX
|
||||
tristate "Microchip ASoC driver for boards using S/PDIF TX"
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/clk.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/lcm.h>
|
||||
#include <linux/of_device.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;
|
||||
}
|
||||
@ -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;
|
||||
@ -869,15 +932,68 @@ static const struct snd_soc_component_driver mchp_i2s_mcc_component = {
|
||||
};
|
||||
|
||||
#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;
|
||||
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user