Message ID | 20200704113902.336911-2-peron.clem@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add Allwinner H3/H5/H6/A64 HDMI audio | expand |
Hi, On Sat, Jul 04, 2020 at 01:38:47PM +0200, Clément Péron wrote: > From: Jernej Skrabec <jernej.skrabec@siol.net> > > H6 I2S is very similar to that in H3, except it supports up to 16 > channels. > > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net> > Signed-off-by: Marcus Cooper <codekipper@gmail.com> > Signed-off-by: Clément Péron <peron.clem@gmail.com> > --- > sound/soc/sunxi/sun4i-i2s.c | 227 ++++++++++++++++++++++++++++++++++++ > 1 file changed, 227 insertions(+) > > diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c > index d0a8d5810c0a..9690389cb68e 100644 > --- a/sound/soc/sunxi/sun4i-i2s.c > +++ b/sound/soc/sunxi/sun4i-i2s.c > @@ -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; > > /** > @@ -466,6 +481,65 @@ static int sun8i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, > return 0; > } > > +static int sun50i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, > + const struct snd_pcm_hw_params *params) We should have sun50i_h6 as prefix. The A64 is also part of the sun50i family and supported through the sun8i callbacks, so it's pretty confusing if you don't have the soc name in there. Maxime
On 7/4/20 6:38 AM, Clément Péron wrote: > From: Jernej Skrabec <jernej.skrabec@siol.net> > > H6 I2S is very similar to that in H3, except it supports up to 16 > channels. > > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net> > Signed-off-by: Marcus Cooper <codekipper@gmail.com> > Signed-off-by: Clément Péron <peron.clem@gmail.com> > --- > sound/soc/sunxi/sun4i-i2s.c | 227 ++++++++++++++++++++++++++++++++++++ > 1 file changed, 227 insertions(+) > > diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c > index d0a8d5810c0a..9690389cb68e 100644 > --- a/sound/soc/sunxi/sun4i-i2s.c > +++ b/sound/soc/sunxi/sun4i-i2s.c > @@ -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; > > /** > @@ -466,6 +481,65 @@ static int sun8i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, > return 0; > } > > +static int sun50i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, > + const struct snd_pcm_hw_params *params) > +{ > + unsigned int channels = params_channels(params); > + unsigned int slots = channels; > + unsigned int lrck_period; > + > + if (i2s->slots) > + slots = i2s->slots; > + > + /* Map the channels for playback and capture */ > + regmap_write(i2s->regmap, SUN50I_H6_I2S_TX_CHAN_MAP1_REG, 0x76543210); > + 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: > + case SND_SOC_DAIFMT_LEFT_J: > + case SND_SOC_DAIFMT_RIGHT_J: According to the manual, LEFT_J and RIGHT_J should use the same calculation as I2S, not the one for PCM/DSP. > + lrck_period = params_physical_width(params) * slots; > + break; > + > + case SND_SOC_DAIFMT_I2S: > + lrck_period = params_physical_width(params); > + break; > + > + default: > + return -EINVAL; > + } > + > + if (i2s->slot_width) > + lrck_period = i2s->slot_width; > + > + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, > + SUN8I_I2S_FMT0_LRCK_PERIOD_MASK, > + SUN8I_I2S_FMT0_LRCK_PERIOD(lrck_period)); From the description in the manual, this looks off by one. The number of BCLKs per LRCK is LRCK_PERIOD + 1. > + > + 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) > @@ -691,6 +765,108 @@ static int sun8i_i2s_set_soc_fmt(const struct sun4i_i2s *i2s, > return 0; > } > > +static int sun50i_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. > + */ This comment makes us sound a lot more confident than I think we actually are. Regards, Samuel
Hi Samuel! Dne petek, 10. julij 2020 ob 07:44:33 CEST je Samuel Holland napisal(a): > On 7/4/20 6:38 AM, Clément Péron wrote: > > From: Jernej Skrabec <jernej.skrabec@siol.net> > > > > H6 I2S is very similar to that in H3, except it supports up to 16 > > channels. > > > > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net> > > Signed-off-by: Marcus Cooper <codekipper@gmail.com> > > Signed-off-by: Clément Péron <peron.clem@gmail.com> > > --- > > > > sound/soc/sunxi/sun4i-i2s.c | 227 ++++++++++++++++++++++++++++++++++++ > > 1 file changed, 227 insertions(+) > > > > diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c > > index d0a8d5810c0a..9690389cb68e 100644 > > --- a/sound/soc/sunxi/sun4i-i2s.c > > +++ b/sound/soc/sunxi/sun4i-i2s.c > > @@ -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; > > > > /** > > > > @@ -466,6 +481,65 @@ static int sun8i_i2s_set_chan_cfg(const struct > > sun4i_i2s *i2s,> > > return 0; > > > > } > > > > +static int sun50i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, > > + const struct snd_pcm_hw_params *params) > > +{ > > + unsigned int channels = params_channels(params); > > + unsigned int slots = channels; > > + unsigned int lrck_period; > > + > > + if (i2s->slots) > > + slots = i2s->slots; > > + > > + /* Map the channels for playback and capture */ > > + regmap_write(i2s->regmap, SUN50I_H6_I2S_TX_CHAN_MAP1_REG, 0x76543210); > > + 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: > > + case SND_SOC_DAIFMT_LEFT_J: > > > + case SND_SOC_DAIFMT_RIGHT_J: > According to the manual, LEFT_J and RIGHT_J should use the same calculation > as I2S, not the one for PCM/DSP. > > > + lrck_period = params_physical_width(params) * slots; > > + break; > > + > > + case SND_SOC_DAIFMT_I2S: > > + lrck_period = params_physical_width(params); > > + break; > > + > > + default: > > + return -EINVAL; > > + } > > + > > + if (i2s->slot_width) > > + lrck_period = i2s->slot_width; > > + > > + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, > > + SUN8I_I2S_FMT0_LRCK_PERIOD_MASK, > > + SUN8I_I2S_FMT0_LRCK_PERIOD(lrck_period)); > > From the description in the manual, this looks off by one. The number of > BCLKs per LRCK is LRCK_PERIOD + 1. Are you sure? Macro SUN8I_I2S_FMT0_LRCK_PERIOD() is defined as follows: #define SUN8I_I2S_FMT0_LRCK_PERIOD(period) ((period - 1) << 8) which already lowers value by 1. Best regards, Jernej > > > + > > + 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) > > > > @@ -691,6 +765,108 @@ static int sun8i_i2s_set_soc_fmt(const struct > > sun4i_i2s *i2s,> > > return 0; > > > > } > > > > +static int sun50i_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. > > + */ > > This comment makes us sound a lot more confident than I think we actually > are. > > Regards, > Samuel
Jernej, On 7/10/20 2:22 PM, Jernej Škrabec wrote: >> From the description in the manual, this looks off by one. The number of >> BCLKs per LRCK is LRCK_PERIOD + 1. > > Are you sure? Macro SUN8I_I2S_FMT0_LRCK_PERIOD() is defined as follows: > > #define SUN8I_I2S_FMT0_LRCK_PERIOD(period) ((period - 1) << 8) > > which already lowers value by 1. No, sorry, I had missed the subtraction happening in the macro. So there's no problem here. Thanks, Samuel
Hi Samuel, On Fri, 10 Jul 2020 at 07:44, Samuel Holland <samuel@sholland.org> wrote: > > On 7/4/20 6:38 AM, Clément Péron wrote: > > From: Jernej Skrabec <jernej.skrabec@siol.net> > > > > H6 I2S is very similar to that in H3, except it supports up to 16 > > channels. > > > > Signed-off-by: Jernej Skrabec <jernej.skrabec@siol.net> > > Signed-off-by: Marcus Cooper <codekipper@gmail.com> > > Signed-off-by: Clément Péron <peron.clem@gmail.com> > > --- > > sound/soc/sunxi/sun4i-i2s.c | 227 ++++++++++++++++++++++++++++++++++++ > > 1 file changed, 227 insertions(+) > > > > diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c > > index d0a8d5810c0a..9690389cb68e 100644 > > --- a/sound/soc/sunxi/sun4i-i2s.c > > +++ b/sound/soc/sunxi/sun4i-i2s.c > > @@ -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; > > > > /** > > @@ -466,6 +481,65 @@ static int sun8i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, > > return 0; > > } > > > > +static int sun50i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, > > + const struct snd_pcm_hw_params *params) > > +{ > > + unsigned int channels = params_channels(params); > > + unsigned int slots = channels; > > + unsigned int lrck_period; > > + > > + if (i2s->slots) > > + slots = i2s->slots; > > + > > + /* Map the channels for playback and capture */ > > + regmap_write(i2s->regmap, SUN50I_H6_I2S_TX_CHAN_MAP1_REG, 0x76543210); > > + 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: > > + case SND_SOC_DAIFMT_LEFT_J: > > + case SND_SOC_DAIFMT_RIGHT_J: > > According to the manual, LEFT_J and RIGHT_J should use the same calculation as > I2S, not the one for PCM/DSP. > > > + lrck_period = params_physical_width(params) * slots; > > + break; > > + > > + case SND_SOC_DAIFMT_I2S: > > + lrck_period = params_physical_width(params); > > + break; > > + > > + default: > > + return -EINVAL; > > + } > > + > > + if (i2s->slot_width) > > + lrck_period = i2s->slot_width; > > + > > + regmap_update_bits(i2s->regmap, SUN4I_I2S_FMT0_REG, > > + SUN8I_I2S_FMT0_LRCK_PERIOD_MASK, > > + SUN8I_I2S_FMT0_LRCK_PERIOD(lrck_period)); > > From the description in the manual, this looks off by one. The number of BCLKs > per LRCK is LRCK_PERIOD + 1. > > > + > > + 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) > > @@ -691,6 +765,108 @@ static int sun8i_i2s_set_soc_fmt(const struct sun4i_i2s *i2s, > > return 0; > > } > > > > +static int sun50i_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. > > + */ > > This comment makes us sound a lot more confident than I think we actually are. That's a comment that needs to be checked using a Logic analyzer (that I don't have). It's a copy paste from the previous generation. But this seems wrong as we need to specify "simple-audio-card,frame-inversion;" in the device-tree. Regards, Clement > > Regards, > Samuel
diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index d0a8d5810c0a..9690389cb68e 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -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; /** @@ -466,6 +481,65 @@ static int sun8i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, return 0; } +static int sun50i_i2s_set_chan_cfg(const struct sun4i_i2s *i2s, + const struct snd_pcm_hw_params *params) +{ + unsigned int channels = params_channels(params); + unsigned int slots = channels; + unsigned int lrck_period; + + if (i2s->slots) + slots = i2s->slots; + + /* Map the channels for playback and capture */ + regmap_write(i2s->regmap, SUN50I_H6_I2S_TX_CHAN_MAP1_REG, 0x76543210); + 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: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + lrck_period = params_physical_width(params) * slots; + break; + + case SND_SOC_DAIFMT_I2S: + lrck_period = params_physical_width(params); + break; + + default: + return -EINVAL; + } + + if (i2s->slot_width) + lrck_period = i2s->slot_width; + + 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) @@ -691,6 +765,108 @@ static int sun8i_i2s_set_soc_fmt(const struct sun4i_i2s *i2s, return 0; } +static int sun50i_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); @@ -971,6 +1147,22 @@ static const struct reg_default sun8i_i2s_reg_defaults[] = { { SUN8I_I2S_RX_CHAN_MAP_REG, 0x00000000 }, }; +static const struct reg_default sun50i_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, @@ -998,6 +1190,19 @@ static const struct regmap_config sun8i_i2s_regmap_config = { .volatile_reg = sun8i_i2s_volatile_reg, }; +static const struct regmap_config sun50i_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_i2s_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(sun50i_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); @@ -1156,6 +1361,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_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_i2s_set_chan_cfg, + .set_fmt = sun50i_i2s_set_soc_fmt, +}; + static int sun4i_i2s_init_regmap_fields(struct device *dev, struct sun4i_i2s *i2s) { @@ -1325,6 +1548,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);