Skip to content

Commit

Permalink
ASoC: sun4i-i2s: Add support for H6 I2S
Browse files Browse the repository at this point in the history
H6 I2S is very similar to that in H3, except it supports up to 16
channels.

Signed-off-by: Jernej Skrabec <[email protected]>
Signed-off-by: Marcus Cooper <[email protected]>
Reviewed-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
Signed-off-by: Clément Péron <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Mark Brown <[email protected]>
  • Loading branch information
jernejsk authored and broonie committed Oct 30, 2020
1 parent c779e2d commit 73adf87
Showing 1 changed file with 222 additions and 0 deletions.
222 changes: 222 additions & 0 deletions sound/soc/sunxi/sun4i-i2s.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,21 @@
#define SUN8I_I2S_RX_CHAN_SEL_REG 0x54
#define SUN8I_I2S_RX_CHAN_MAP_REG 0x58

/* Defines required for sun50i-h6 support */
#define SUN50I_H6_I2S_TX_CHAN_SEL_OFFSET_MASK GENMASK(21, 20)
#define SUN50I_H6_I2S_TX_CHAN_SEL_OFFSET(offset) ((offset) << 20)
#define SUN50I_H6_I2S_TX_CHAN_SEL_MASK GENMASK(19, 16)
#define SUN50I_H6_I2S_TX_CHAN_SEL(chan) ((chan - 1) << 16)
#define SUN50I_H6_I2S_TX_CHAN_EN_MASK GENMASK(15, 0)
#define SUN50I_H6_I2S_TX_CHAN_EN(num_chan) (((1 << num_chan) - 1))

#define SUN50I_H6_I2S_TX_CHAN_MAP0_REG 0x44
#define SUN50I_H6_I2S_TX_CHAN_MAP1_REG 0x48

#define SUN50I_H6_I2S_RX_CHAN_SEL_REG 0x64
#define SUN50I_H6_I2S_RX_CHAN_MAP0_REG 0x68
#define SUN50I_H6_I2S_RX_CHAN_MAP1_REG 0x6C

struct sun4i_i2s;

/**
Expand Down Expand Up @@ -476,6 +491,60 @@ static int sun8i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s,
return 0;
}

static int sun50i_h6_i2s_set_chan_cfg(const struct sun4i_i2s *i2s,
unsigned int channels, unsigned int slots,
unsigned int slot_width)
{
unsigned int lrck_period;

/* Map the channels for playback and capture */
regmap_write(i2s->regmap, SUN50I_H6_I2S_TX_CHAN_MAP0_REG, 0xFEDCBA98);
regmap_write(i2s->regmap, SUN50I_H6_I2S_TX_CHAN_MAP1_REG, 0x76543210);
regmap_write(i2s->regmap, SUN50I_H6_I2S_RX_CHAN_MAP0_REG, 0xFEDCBA98);
regmap_write(i2s->regmap, SUN50I_H6_I2S_RX_CHAN_MAP1_REG, 0x76543210);

/* Configure the channels */
regmap_update_bits(i2s->regmap, SUN8I_I2S_TX_CHAN_SEL_REG,
SUN50I_H6_I2S_TX_CHAN_SEL_MASK,
SUN50I_H6_I2S_TX_CHAN_SEL(channels));
regmap_update_bits(i2s->regmap, SUN50I_H6_I2S_RX_CHAN_SEL_REG,
SUN50I_H6_I2S_TX_CHAN_SEL_MASK,
SUN50I_H6_I2S_TX_CHAN_SEL(channels));

regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG,
SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM_MASK,
SUN8I_I2S_CHAN_CFG_TX_SLOT_NUM(channels));
regmap_update_bits(i2s->regmap, SUN8I_I2S_CHAN_CFG_REG,
SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM_MASK,
SUN8I_I2S_CHAN_CFG_RX_SLOT_NUM(channels));

switch (i2s->format & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
lrck_period = slot_width * slots;
break;

case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_I2S:
lrck_period = slot_width;
break;

default:
return -EINVAL;
}

regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG,
SUN8I_I2S_FMT0_LRCK_PERIOD_MASK,
SUN8I_I2S_FMT0_LRCK_PERIOD(lrck_period));

regmap_update_bits(i2s->regmap, SUN8I_I2S_TX_CHAN_SEL_REG,
SUN50I_H6_I2S_TX_CHAN_EN_MASK,
SUN50I_H6_I2S_TX_CHAN_EN(channels));

return 0;
}

static int sun4i_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
Expand Down Expand Up @@ -703,6 +772,108 @@ static int sun8i_i2s_set_soc_fmt(const struct sun4i_i2s *i2s,
return 0;
}

static int sun50i_h6_i2s_set_soc_fmt(const struct sun4i_i2s *i2s,
unsigned int fmt)
{
u32 mode, val;
u8 offset;

/*
* DAI clock polarity
*
* The setup for LRCK contradicts the datasheet, but under a
* scope it's clear that the LRCK polarity is reversed
* compared to the expected polarity on the bus.
*/
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_IF:
/* Invert both clocks */
val = SUN8I_I2S_FMT0_BCLK_POLARITY_INVERTED;
break;
case SND_SOC_DAIFMT_IB_NF:
/* Invert bit clock */
val = SUN8I_I2S_FMT0_BCLK_POLARITY_INVERTED |
SUN8I_I2S_FMT0_LRCLK_POLARITY_INVERTED;
break;
case SND_SOC_DAIFMT_NB_IF:
/* Invert frame clock */
val = 0;
break;
case SND_SOC_DAIFMT_NB_NF:
val = SUN8I_I2S_FMT0_LRCLK_POLARITY_INVERTED;
break;
default:
return -EINVAL;
}

regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG,
SUN8I_I2S_FMT0_LRCLK_POLARITY_MASK |
SUN8I_I2S_FMT0_BCLK_POLARITY_MASK,
val);

/* DAI Mode */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_DSP_A:
mode = SUN8I_I2S_CTRL_MODE_PCM;
offset = 1;
break;

case SND_SOC_DAIFMT_DSP_B:
mode = SUN8I_I2S_CTRL_MODE_PCM;
offset = 0;
break;

case SND_SOC_DAIFMT_I2S:
mode = SUN8I_I2S_CTRL_MODE_LEFT;
offset = 1;
break;

case SND_SOC_DAIFMT_LEFT_J:
mode = SUN8I_I2S_CTRL_MODE_LEFT;
offset = 0;
break;

case SND_SOC_DAIFMT_RIGHT_J:
mode = SUN8I_I2S_CTRL_MODE_RIGHT;
offset = 0;
break;

default:
return -EINVAL;
}

regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN8I_I2S_CTRL_MODE_MASK, mode);
regmap_update_bits(i2s->regmap, SUN8I_I2S_TX_CHAN_SEL_REG,
SUN50I_H6_I2S_TX_CHAN_SEL_OFFSET_MASK,
SUN50I_H6_I2S_TX_CHAN_SEL_OFFSET(offset));
regmap_update_bits(i2s->regmap, SUN50I_H6_I2S_RX_CHAN_SEL_REG,
SUN50I_H6_I2S_TX_CHAN_SEL_OFFSET_MASK,
SUN50I_H6_I2S_TX_CHAN_SEL_OFFSET(offset));

/* DAI clock master masks */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
/* BCLK and LRCLK master */
val = SUN8I_I2S_CTRL_BCLK_OUT | SUN8I_I2S_CTRL_LRCK_OUT;
break;

case SND_SOC_DAIFMT_CBM_CFM:
/* BCLK and LRCLK slave */
val = 0;
break;

default:
return -EINVAL;
}

regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG,
SUN8I_I2S_CTRL_BCLK_OUT | SUN8I_I2S_CTRL_LRCK_OUT,
val);

return 0;
}

static int sun4i_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai);
Expand Down Expand Up @@ -983,6 +1154,22 @@ static const struct reg_default sun8i_i2s_reg_defaults[] = {
{ SUN8I_I2S_RX_CHAN_MAP_REG, 0x00000000 },
};

static const struct reg_default sun50i_h6_i2s_reg_defaults[] = {
{ SUN4I_I2S_CTRL_REG, 0x00060000 },
{ SUN4I_I2S_FMT0_REG, 0x00000033 },
{ SUN4I_I2S_FMT1_REG, 0x00000030 },
{ SUN4I_I2S_FIFO_CTRL_REG, 0x000400f0 },
{ SUN4I_I2S_DMA_INT_CTRL_REG, 0x00000000 },
{ SUN4I_I2S_CLK_DIV_REG, 0x00000000 },
{ SUN8I_I2S_CHAN_CFG_REG, 0x00000000 },
{ SUN8I_I2S_TX_CHAN_SEL_REG, 0x00000000 },
{ SUN50I_H6_I2S_TX_CHAN_MAP0_REG, 0x00000000 },
{ SUN50I_H6_I2S_TX_CHAN_MAP1_REG, 0x00000000 },
{ SUN50I_H6_I2S_RX_CHAN_SEL_REG, 0x00000000 },
{ SUN50I_H6_I2S_RX_CHAN_MAP0_REG, 0x00000000 },
{ SUN50I_H6_I2S_RX_CHAN_MAP1_REG, 0x00000000 },
};

static const struct regmap_config sun4i_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
Expand Down Expand Up @@ -1010,6 +1197,19 @@ static const struct regmap_config sun8i_i2s_regmap_config = {
.volatile_reg = sun8i_i2s_volatile_reg,
};

static const struct regmap_config sun50i_h6_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUN50I_H6_I2S_RX_CHAN_MAP1_REG,
.cache_type = REGCACHE_FLAT,
.reg_defaults = sun50i_h6_i2s_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(sun50i_h6_i2s_reg_defaults),
.writeable_reg = sun4i_i2s_wr_reg,
.readable_reg = sun8i_i2s_rd_reg,
.volatile_reg = sun8i_i2s_volatile_reg,
};

static int sun4i_i2s_runtime_resume(struct device *dev)
{
struct sun4i_i2s *i2s = dev_get_drvdata(dev);
Expand Down Expand Up @@ -1168,6 +1368,24 @@ static const struct sun4i_i2s_quirks sun50i_a64_codec_i2s_quirks = {
.set_fmt = sun4i_i2s_set_soc_fmt,
};

static const struct sun4i_i2s_quirks sun50i_h6_i2s_quirks = {
.has_reset = true,
.reg_offset_txdata = SUN8I_I2S_FIFO_TX_REG,
.sun4i_i2s_regmap = &sun50i_h6_i2s_regmap_config,
.field_clkdiv_mclk_en = REG_FIELD(SUN4I_I2S_CLK_DIV_REG, 8, 8),
.field_fmt_wss = REG_FIELD(SUN4I_I2S_FMT0_REG, 0, 2),
.field_fmt_sr = REG_FIELD(SUN4I_I2S_FMT0_REG, 4, 6),
.bclk_dividers = sun8i_i2s_clk_div,
.num_bclk_dividers = ARRAY_SIZE(sun8i_i2s_clk_div),
.mclk_dividers = sun8i_i2s_clk_div,
.num_mclk_dividers = ARRAY_SIZE(sun8i_i2s_clk_div),
.get_bclk_parent_rate = sun8i_i2s_get_bclk_parent_rate,
.get_sr = sun8i_i2s_get_sr_wss,
.get_wss = sun8i_i2s_get_sr_wss,
.set_chan_cfg = sun50i_h6_i2s_set_chan_cfg,
.set_fmt = sun50i_h6_i2s_set_soc_fmt,
};

static int sun4i_i2s_init_regmap_fields(struct device *dev,
struct sun4i_i2s *i2s)
{
Expand Down Expand Up @@ -1337,6 +1555,10 @@ static const struct of_device_id sun4i_i2s_match[] = {
.compatible = "allwinner,sun50i-a64-codec-i2s",
.data = &sun50i_a64_codec_i2s_quirks,
},
{
.compatible = "allwinner,sun50i-h6-i2s",
.data = &sun50i_h6_i2s_quirks,
},
{}
};
MODULE_DEVICE_TABLE(of, sun4i_i2s_match);
Expand Down

0 comments on commit 73adf87

Please sign in to comment.