u32 cr_own;
u32 cr_clk;
+ u32 cr_mode;
int chan;
+ int rate;
int err;
unsigned int usrcnt;
};
#define rsnd_ssi_nr(priv) ((priv)->ssi_nr)
#define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod)
#define rsnd_ssi_pio_available(ssi) ((ssi)->info->irq > 0)
-#define rsnd_ssi_parent(ssi) ((ssi)->parent)
#define rsnd_ssi_mode_flags(p) ((p)->info->flags)
#define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id)
+#define rsnd_ssi_is_parent(ssi, io) ((ssi) == rsnd_io_to_mod_ssip(io))
#define rsnd_ssi_of_node(priv) \
of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,ssi")
struct rsnd_priv *priv = rsnd_io_to_priv(io);
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
struct device *dev = rsnd_priv_to_dev(priv);
+ struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
struct rsnd_mod *mod = rsnd_mod_get(ssi);
+ struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io);
int j, ret;
int ssi_clk_mul_table[] = {
1, 2, 4, 8, 16, 6, 12,
unsigned int main_rate;
unsigned int rate = rsnd_src_get_ssi_rate(priv, io, runtime);
+ if (!rsnd_rdai_is_clk_master(rdai))
+ return 0;
+
+ if (ssi_parent_mod && !rsnd_ssi_is_parent(mod, io))
+ return 0;
+
+ if (ssi->usrcnt > 1) {
+ if (ssi->rate != rate) {
+ dev_err(dev, "SSI parent/child should use same rate\n");
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
/*
* Find best clock, and try to start ADG
*/
ssi->cr_clk = FORCE | SWL_32 |
SCKD | SWSD | CKDV(j);
+ ssi->rate = rate;
+
+ rsnd_mod_write(mod, SSIWSR, CONT);
+
dev_dbg(dev, "%s[%d] outputs %u Hz\n",
rsnd_mod_name(mod),
rsnd_mod_id(mod), rate);
return -EIO;
}
-static void rsnd_ssi_master_clk_stop(struct rsnd_ssi *ssi)
-{
- struct rsnd_mod *mod = rsnd_mod_get(ssi);
-
- ssi->cr_clk = 0;
- rsnd_adg_ssi_clk_stop(mod);
-}
-
-static void rsnd_ssi_hw_start(struct rsnd_ssi *ssi,
- struct rsnd_dai_stream *io)
+static void rsnd_ssi_master_clk_stop(struct rsnd_ssi *ssi,
+ struct rsnd_dai_stream *io)
{
- struct rsnd_priv *priv = rsnd_io_to_priv(io);
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
- struct device *dev = rsnd_priv_to_dev(priv);
struct rsnd_mod *mod = rsnd_mod_get(ssi);
- u32 cr_mode;
- u32 cr;
-
- if (0 == ssi->usrcnt) {
- rsnd_mod_power_on(mod);
-
- if (rsnd_rdai_is_clk_master(rdai)) {
- struct rsnd_ssi *ssi_parent = rsnd_ssi_parent(ssi);
-
- if (ssi_parent)
- rsnd_ssi_hw_start(ssi_parent, io);
- else
- rsnd_ssi_master_clk_start(ssi, io);
- }
- }
-
- if (rsnd_ssi_is_dma_mode(mod)) {
- cr_mode = UIEN | OIEN | /* over/under run */
- DMEN; /* DMA : enable DMA */
- } else {
- cr_mode = DIEN; /* PIO : enable Data interrupt */
- }
-
- cr = ssi->cr_own |
- ssi->cr_clk |
- cr_mode |
- EN;
-
- rsnd_mod_write(mod, SSICR, cr);
-
- /* enable WS continue */
- if (rsnd_rdai_is_clk_master(rdai))
- rsnd_mod_write(mod, SSIWSR, CONT);
-
- /* clear error status */
- rsnd_ssi_status_clear(mod);
+ struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io);
- ssi->usrcnt++;
-
- dev_dbg(dev, "%s[%d] hw started\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod));
-}
-
-static void rsnd_ssi_hw_stop(struct rsnd_dai_stream *io, struct rsnd_ssi *ssi)
-{
- struct rsnd_mod *mod = rsnd_mod_get(ssi);
- struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
- struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
- struct device *dev = rsnd_priv_to_dev(priv);
- u32 cr;
-
- if (0 == ssi->usrcnt) {
- dev_err(dev, "%s called without starting\n", __func__);
+ if (!rsnd_rdai_is_clk_master(rdai))
return;
- }
-
- ssi->usrcnt--;
-
- if (0 == ssi->usrcnt) {
- /*
- * disable all IRQ,
- * and, wait all data was sent
- */
- cr = ssi->cr_own |
- ssi->cr_clk;
-
- rsnd_mod_write(mod, SSICR, cr | EN);
- rsnd_ssi_status_check(mod, DIRQ);
-
- /*
- * disable SSI,
- * and, wait idle state
- */
- rsnd_mod_write(mod, SSICR, cr); /* disabled all */
- rsnd_ssi_status_check(mod, IIRQ);
- if (rsnd_rdai_is_clk_master(rdai)) {
- struct rsnd_ssi *ssi_parent = rsnd_ssi_parent(ssi);
-
- if (ssi_parent)
- rsnd_ssi_hw_stop(io, ssi_parent);
- else
- rsnd_ssi_master_clk_stop(ssi);
- }
+ if (ssi_parent_mod && !rsnd_ssi_is_parent(mod, io))
+ return;
- rsnd_mod_power_off(mod);
+ if (ssi->usrcnt > 1)
+ return;
- ssi->chan = 0;
- }
+ ssi->cr_clk = 0;
+ ssi->rate = 0;
- dev_dbg(dev, "%s[%d] hw stopped\n",
- rsnd_mod_name(mod), rsnd_mod_id(mod));
+ rsnd_adg_ssi_clk_stop(mod);
}
/*
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
u32 cr;
+ int ret;
+
+ ssi->usrcnt++;
+
+ rsnd_mod_power_on(mod);
+
+ ret = rsnd_ssi_master_clk_start(ssi, io);
+ if (ret < 0)
+ return ret;
+
+ if (rsnd_ssi_is_parent(mod, io))
+ return 0;
cr = FORCE;
if (rsnd_io_is_play(io))
cr |= TRMD;
- /*
- * set ssi parameter
- */
ssi->cr_own = cr;
+
+ if (rsnd_ssi_is_dma_mode(mod)) {
+ cr = UIEN | OIEN | /* over/under run */
+ DMEN; /* DMA : enable DMA */
+ } else {
+ cr = DIEN; /* PIO : enable Data interrupt */
+ }
+
+ ssi->cr_mode = cr;
+
ssi->err = -1; /* ignore 1st error */
+ /* clear error status */
+ rsnd_ssi_status_clear(mod);
+
+ rsnd_ssi_irq_enable(mod);
+
return 0;
}
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
struct device *dev = rsnd_priv_to_dev(priv);
+ if (rsnd_ssi_is_parent(mod, io))
+ goto rsnd_ssi_quit_end;
+
if (ssi->err > 0)
dev_warn(dev, "%s[%d] under/over flow err = %d\n",
rsnd_mod_name(mod), rsnd_mod_id(mod), ssi->err);
ssi->cr_own = 0;
ssi->err = 0;
+ rsnd_ssi_irq_disable(mod);
+
+rsnd_ssi_quit_end:
+ rsnd_ssi_master_clk_stop(ssi, io);
+
+ rsnd_mod_power_off(mod);
+
+ ssi->usrcnt--;
+
+ if (ssi->usrcnt < 0)
+ dev_err(dev, "%s[%d] usrcnt error\n",
+ rsnd_mod_name(mod), rsnd_mod_id(mod));
+
return 0;
}
struct snd_pcm_hw_params *params)
{
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
- struct rsnd_ssi *ssi_parent = rsnd_ssi_parent(ssi);
int chan = params_channels(params);
/*
* Already working.
* It will happen if SSI has parent/child connection.
*/
- if (ssi->usrcnt) {
+ if (ssi->usrcnt > 1) {
/*
* it is error if child <-> parent SSI uses
* different channels.
return -EIO;
}
- /* It will be removed on rsnd_ssi_hw_stop */
ssi->chan = chan;
- if (ssi_parent)
- return rsnd_ssi_hw_params(rsnd_mod_get(ssi_parent), io,
- substream, params);
return 0;
}
return status;
}
+static int __rsnd_ssi_start(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv)
+{
+ struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+ u32 cr;
+
+ cr = ssi->cr_own |
+ ssi->cr_clk |
+ ssi->cr_mode |
+ EN;
+
+ rsnd_mod_write(mod, SSICR, cr);
+
+ return 0;
+}
+
static int rsnd_ssi_start(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
+{
+ /*
+ * no limit to start
+ * see also
+ * rsnd_ssi_stop
+ * rsnd_ssi_interrupt
+ */
+ return __rsnd_ssi_start(mod, io, priv);
+}
+
+static int __rsnd_ssi_stop(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv)
{
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
+ u32 cr;
+
+ /*
+ * disable all IRQ,
+ * and, wait all data was sent
+ */
+ cr = ssi->cr_own |
+ ssi->cr_clk;
- rsnd_ssi_hw_start(ssi, io);
+ rsnd_mod_write(mod, SSICR, cr | EN);
+ rsnd_ssi_status_check(mod, DIRQ);
- rsnd_ssi_irq_enable(mod);
+ /*
+ * disable SSI,
+ * and, wait idle state
+ */
+ rsnd_mod_write(mod, SSICR, cr); /* disabled all */
+ rsnd_ssi_status_check(mod, IIRQ);
return 0;
}
{
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
- rsnd_ssi_irq_disable(mod);
-
- rsnd_ssi_record_error(ssi);
-
- rsnd_ssi_hw_stop(io, ssi);
+ /*
+ * don't stop if not last user
+ * see also
+ * rsnd_ssi_start
+ * rsnd_ssi_interrupt
+ */
+ if (ssi->usrcnt > 1)
+ return 0;
- return 0;
+ return __rsnd_ssi_stop(mod, io, priv);
}
static void __rsnd_ssi_interrupt(struct rsnd_mod *mod,
dev_dbg(dev, "%s[%d] restart\n",
rsnd_mod_name(mod), rsnd_mod_id(mod));
- rsnd_ssi_stop(mod, io, priv);
- rsnd_ssi_start(mod, io, priv);
+ __rsnd_ssi_stop(mod, io, priv);
+ __rsnd_ssi_start(mod, io, priv);
}
if (ssi->err > 1024) {
/*
* SSI PIO
*/
+static void rsnd_ssi_parent_attach(struct rsnd_mod *mod,
+ struct rsnd_dai_stream *io,
+ struct rsnd_priv *priv)
+{
+ if (!__rsnd_ssi_is_pin_sharing(mod))
+ return;
+
+ switch (rsnd_mod_id(mod)) {
+ case 1:
+ case 2:
+ rsnd_dai_connect(rsnd_ssi_mod_get(priv, 0), io, RSND_MOD_SSIP);
+ break;
+ case 4:
+ rsnd_dai_connect(rsnd_ssi_mod_get(priv, 3), io, RSND_MOD_SSIP);
+ break;
+ case 8:
+ rsnd_dai_connect(rsnd_ssi_mod_get(priv, 7), io, RSND_MOD_SSIP);
+ break;
+ }
+}
+
static int rsnd_ssi_common_probe(struct rsnd_mod *mod,
struct rsnd_dai_stream *io,
struct rsnd_priv *priv)
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
int ret;
+ rsnd_ssi_parent_attach(mod, io, priv);
+
ret = rsnd_ssiu_attach(io, mod);
if (ret < 0)
return ret;
return !!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_CLK_PIN_SHARE);
}
-static void rsnd_ssi_parent_setup(struct rsnd_priv *priv, struct rsnd_ssi *ssi)
-{
- struct rsnd_mod *mod = rsnd_mod_get(ssi);
-
- if (!__rsnd_ssi_is_pin_sharing(mod))
- return;
-
- switch (rsnd_mod_id(mod)) {
- case 1:
- case 2:
- ssi->parent = rsnd_mod_to_ssi(rsnd_ssi_mod_get(priv, 0));
- break;
- case 4:
- ssi->parent = rsnd_mod_to_ssi(rsnd_ssi_mod_get(priv, 3));
- break;
- case 8:
- ssi->parent = rsnd_mod_to_ssi(rsnd_ssi_mod_get(priv, 7));
- break;
- }
-}
-
-
static void rsnd_of_parse_ssi(struct platform_device *pdev,
const struct rsnd_of_data *of_data,
struct rsnd_priv *priv)
RSND_MOD_SSI, i);
if (ret)
return ret;
-
- rsnd_ssi_parent_setup(priv, ssi);
}
return 0;