ASoC: rsnd: add Multi channel support

This patch adds Multi channel support on Renesas R-Car sound.
This patch is tested on Salvator-X board, but it can't use
Multi channel, because supported format is different between
codec chip and R-Car.
Thus, it was tested on board which doesn't mount codec chip,
with oscilloscope.

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
Kuninori Morimoto 2015-12-17 03:00:10 +00:00 committed by Mark Brown
parent 44bf5361e2
commit b4c83b1715
6 changed files with 194 additions and 17 deletions

View file

@ -308,3 +308,21 @@ Example: simple sound card for TDM
sound-dai = <&xxx>;
};
};
Example: simple sound card for Multi channel
&rcar_sound {
pinctrl-0 = <&sound_pins &sound_clk_pins>;
pinctrl-names = "default";
/* Single DAI */
#sound-dai-cells = <0>;
status = "okay";
rcar_sound,dai {
dai0 {
playback = <&ssi0 &ssi1 &ssi2 &src0 &dvc0>;
};
};
};

View file

@ -215,7 +215,11 @@ int rsnd_get_slot_num(struct rsnd_dai_stream *io)
int rsnd_get_slot_width(struct rsnd_dai_stream *io)
{
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
int chan = runtime->channels / rsnd_get_slot_num(io);
int chan = runtime->channels;
/* Multi channel Mode */
if (rsnd_ssi_multi_slaves(io))
chan /= rsnd_get_slot_num(io);
/* TDM Extend Mode needs 8ch */
if (chan == 6)

View file

@ -226,6 +226,9 @@ static int rsnd_gen2_probe(struct rsnd_priv *priv)
const static struct rsnd_regmap_field_conf conf_ssiu[] = {
RSND_GEN_S_REG(SSI_MODE0, 0x800),
RSND_GEN_S_REG(SSI_MODE1, 0x804),
RSND_GEN_S_REG(SSI_MODE2, 0x808),
RSND_GEN_S_REG(SSI_CONTROL, 0x810),
/* FIXME: it needs SSI_MODE2/3 in the future */
RSND_GEN_M_REG(SSI_BUSIF_MODE, 0x0, 0x80),
RSND_GEN_M_REG(SSI_BUSIF_ADINR, 0x4, 0x80),

View file

@ -47,6 +47,8 @@ enum rsnd_reg {
RSND_REG_SSI_MODE, /* Gen2 only */
RSND_REG_SSI_MODE0,
RSND_REG_SSI_MODE1,
RSND_REG_SSI_MODE2,
RSND_REG_SSI_CONTROL,
RSND_REG_SSI_CTRL, /* Gen2 only */
RSND_REG_SSI_BUSIF_MODE, /* Gen2 only */
RSND_REG_SSI_BUSIF_ADINR, /* Gen2 only */
@ -181,7 +183,10 @@ enum rsnd_mod_type {
RSND_MOD_CTU,
RSND_MOD_CMD,
RSND_MOD_SRC,
RSND_MOD_SSIP, /* SSI parent */
RSND_MOD_SSIM3, /* SSI multi 3 */
RSND_MOD_SSIM2, /* SSI multi 2 */
RSND_MOD_SSIM1, /* SSI multi 1 */
RSND_MOD_SSIP, /* SSI parent */
RSND_MOD_SSI,
RSND_MOD_SSIU,
RSND_MOD_MAX,
@ -542,6 +547,7 @@ void rsnd_ssi_remove(struct rsnd_priv *priv);
struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id);
int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod);
int rsnd_ssi_use_busif(struct rsnd_dai_stream *io);
u32 rsnd_ssi_multi_slaves(struct rsnd_dai_stream *io);
#define rsnd_ssi_is_pin_sharing(io) \
__rsnd_ssi_is_pin_sharing(rsnd_io_to_mod_ssi(io))
@ -549,10 +555,9 @@ int __rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod);
#define rsnd_ssi_of_node(priv) \
of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,ssi")
#define rsnd_parse_connect_ssi(rdai, playback, capture) \
rsnd_parse_connect_common(rdai, rsnd_ssi_mod_get, \
rsnd_ssi_of_node(rsnd_rdai_to_priv(rdai)), \
playback, capture)
void rsnd_parse_connect_ssi(struct rsnd_dai *rdai,
struct device_node *playback,
struct device_node *capture);
/*
* R-Car SSIU

View file

@ -96,6 +96,7 @@ struct rsnd_ssi {
#define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod)
#define rsnd_ssi_mode_flags(p) ((p)->flags)
#define rsnd_ssi_is_parent(ssi, io) ((ssi) == rsnd_io_to_mod_ssip(io))
#define rsnd_ssi_is_multi_slave(ssi, io) ((mod) != rsnd_io_to_mod_ssi(io))
int rsnd_ssi_use_busif(struct rsnd_dai_stream *io)
{
@ -171,6 +172,41 @@ static int rsnd_ssi_irq_disable(struct rsnd_mod *ssi_mod)
return 0;
}
u32 rsnd_ssi_multi_slaves(struct rsnd_dai_stream *io)
{
struct rsnd_mod *mod;
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
struct rsnd_priv *priv = rsnd_io_to_priv(io);
struct device *dev = rsnd_priv_to_dev(priv);
enum rsnd_mod_type types[] = {
RSND_MOD_SSIM1,
RSND_MOD_SSIM2,
RSND_MOD_SSIM3,
};
int i, mask;
switch (runtime->channels) {
case 2: /* Multi channel is not needed for Stereo */
return 0;
case 6:
break;
default:
dev_err(dev, "unsupported channel\n");
return 0;
}
mask = 0;
for (i = 0; i < ARRAY_SIZE(types); i++) {
mod = rsnd_io_to_mod(io, types[i]);
if (!mod)
continue;
mask |= 1 << rsnd_mod_id(mod);
}
return mask;
}
static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi,
struct rsnd_dai_stream *io)
{
@ -194,6 +230,9 @@ static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi,
if (ssi_parent_mod && !rsnd_ssi_is_parent(mod, io))
return 0;
if (rsnd_ssi_is_multi_slave(mod, io))
return 0;
if (ssi->usrcnt > 1) {
if (ssi->rate != rate) {
dev_err(dev, "SSI parent/child should use same rate\n");
@ -437,8 +476,14 @@ static int __rsnd_ssi_start(struct rsnd_mod *mod,
cr = ssi->cr_own |
ssi->cr_clk |
ssi->cr_mode |
EN;
ssi->cr_mode;
/*
* EN will be set via SSIU :: SSI_CONTROL
* if Multi channel mode
*/
if (!rsnd_ssi_multi_slaves(io))
cr |= EN;
rsnd_mod_write(mod, SSICR, cr);
rsnd_mod_write(mod, SSIWSR, ssi->wsr);
@ -609,6 +654,13 @@ static int rsnd_ssi_common_probe(struct rsnd_mod *mod,
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
int ret;
/*
* SSIP/SSIU/IRQ are not needed on
* SSI Multi slaves
*/
if (rsnd_ssi_is_multi_slave(mod, io))
return 0;
rsnd_ssi_parent_attach(mod, io, priv);
ret = rsnd_ssiu_attach(io, mod);
@ -641,6 +693,13 @@ static int rsnd_ssi_dma_probe(struct rsnd_mod *mod,
int dma_id = 0; /* not needed */
int ret;
/*
* SSIP/SSIU/IRQ/DMA are not needed on
* SSI Multi slaves
*/
if (rsnd_ssi_is_multi_slave(mod, io))
return 0;
ret = rsnd_ssi_common_probe(mod, io, priv);
if (ret)
return ret;
@ -732,6 +791,57 @@ static struct rsnd_mod_ops rsnd_ssi_non_ops = {
/*
* ssi mod function
*/
static void rsnd_ssi_connect(struct rsnd_mod *mod,
struct rsnd_dai_stream *io)
{
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
enum rsnd_mod_type types[] = {
RSND_MOD_SSI,
RSND_MOD_SSIM1,
RSND_MOD_SSIM2,
RSND_MOD_SSIM3,
};
enum rsnd_mod_type type;
int i;
/* try SSI -> SSIM1 -> SSIM2 -> SSIM3 */
for (i = 0; i < ARRAY_SIZE(types); i++) {
type = types[i];
if (!rsnd_io_to_mod(io, type)) {
rsnd_dai_connect(mod, io, type);
rsnd_set_slot(rdai, 2 * (i + 1), (i + 1));
return;
}
}
}
void rsnd_parse_connect_ssi(struct rsnd_dai *rdai,
struct device_node *playback,
struct device_node *capture)
{
struct rsnd_priv *priv = rsnd_rdai_to_priv(rdai);
struct device_node *node;
struct device_node *np;
struct rsnd_mod *mod;
int i;
node = rsnd_ssi_of_node(priv);
if (!node)
return;
i = 0;
for_each_child_of_node(node, np) {
mod = rsnd_ssi_mod_get(priv, i);
if (np == playback)
rsnd_ssi_connect(mod, &rdai->playback);
if (np == capture)
rsnd_ssi_connect(mod, &rdai->capture);
i++;
}
of_node_put(node);
}
struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id)
{
if (WARN_ON(id < 0 || id >= rsnd_ssi_nr(priv)))

View file

@ -27,8 +27,11 @@ static int rsnd_ssiu_init(struct rsnd_mod *mod,
struct rsnd_priv *priv)
{
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
u32 multi_ssi_slaves = rsnd_ssi_multi_slaves(io);
int use_busif = rsnd_ssi_use_busif(io);
int id = rsnd_mod_id(mod);
u32 mask1, val1;
u32 mask2, val2;
/*
* SSI_MODE0
@ -38,6 +41,9 @@ static int rsnd_ssiu_init(struct rsnd_mod *mod,
/*
* SSI_MODE1
*/
mask1 = (1 << 4) | (1 << 20); /* mask sync bit */
mask2 = (1 << 4); /* mask sync bit */
val1 = val2 = 0;
if (rsnd_ssi_is_pin_sharing(io)) {
int shift = -1;
@ -51,15 +57,36 @@ static int rsnd_ssiu_init(struct rsnd_mod *mod,
case 4:
shift = 16;
break;
default:
return -EINVAL;
}
if (shift >= 0)
rsnd_mod_bset(mod, SSI_MODE1,
0x3 << shift,
rsnd_rdai_is_clk_master(rdai) ?
0x2 << shift : 0x1 << shift);
mask1 |= 0x3 << shift;
val1 = rsnd_rdai_is_clk_master(rdai) ?
0x2 << shift : 0x1 << shift;
} else if (multi_ssi_slaves) {
mask2 |= 0x00000007;
mask1 |= 0x0000000f;
switch (multi_ssi_slaves) {
case 0x0206: /* SSI0/1/2/9 */
val2 = (1 << 4) | /* SSI0129 sync */
rsnd_rdai_is_clk_master(rdai) ? 0x2 : 0x1;
/* fall through */
case 0x0006: /* SSI0/1/2 */
val1 = rsnd_rdai_is_clk_master(rdai) ?
0xa : 0x5;
if (!val2) /* SSI012 sync */
val1 |= (1 << 4);
}
}
rsnd_mod_bset(mod, SSI_MODE1, mask1, val1);
rsnd_mod_bset(mod, SSI_MODE2, mask2, val2);
return 0;
}
@ -104,8 +131,13 @@ static int rsnd_ssiu_start_gen2(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
{
if (rsnd_ssi_use_busif(io))
rsnd_mod_write(mod, SSI_CTRL, 0x1);
if (!rsnd_ssi_use_busif(io))
return 0;
rsnd_mod_write(mod, SSI_CTRL, 0x1);
if (rsnd_ssi_multi_slaves(io))
rsnd_mod_write(mod, SSI_CONTROL, 0x1);
return 0;
}
@ -114,8 +146,13 @@ static int rsnd_ssiu_stop_gen2(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
{
if (rsnd_ssi_use_busif(io))
rsnd_mod_write(mod, SSI_CTRL, 0);
if (!rsnd_ssi_use_busif(io))
return 0;
rsnd_mod_write(mod, SSI_CTRL, 0);
if (rsnd_ssi_multi_slaves(io))
rsnd_mod_write(mod, SSI_CONTROL, 0);
return 0;
}