diff options
Diffstat (limited to 'sound/soc/sh/rcar/ssi.c')
-rw-r--r-- | sound/soc/sh/rcar/ssi.c | 141 |
1 files changed, 93 insertions, 48 deletions
diff --git a/sound/soc/sh/rcar/ssi.c b/sound/soc/sh/rcar/ssi.c index 46feddd78ee2..fffc07e72627 100644 --- a/sound/soc/sh/rcar/ssi.c +++ b/sound/soc/sh/rcar/ssi.c @@ -72,6 +72,7 @@ struct rsnd_ssi { u32 cr_own; u32 cr_clk; u32 cr_mode; + u32 cr_en; u32 wsr; int chan; int rate; @@ -89,6 +90,7 @@ struct rsnd_ssi { #define RSND_SSI_NO_BUSIF (1 << 1) /* SSI+DMA without BUSIF */ #define RSND_SSI_HDMI0 (1 << 2) /* for HDMI0 */ #define RSND_SSI_HDMI1 (1 << 3) /* for HDMI1 */ +#define RSND_SSI_PROBED (1 << 4) #define for_each_rsnd_ssi(pos, priv, i) \ for (i = 0; \ @@ -97,25 +99,27 @@ struct rsnd_ssi { i++) #define rsnd_ssi_get(priv, id) ((struct rsnd_ssi *)(priv->ssi) + id) -#define rsnd_ssi_to_dma(mod) ((ssi)->dma) #define rsnd_ssi_nr(priv) ((priv)->ssi_nr) #define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod) -#define rsnd_ssi_mode_flags(p) ((p)->flags) +#define rsnd_ssi_flags_has(p, f) ((p)->flags & f) +#define rsnd_ssi_flags_set(p, f) ((p)->flags |= f) +#define rsnd_ssi_flags_del(p, f) ((p)->flags = ((p)->flags & ~f)) #define rsnd_ssi_is_parent(ssi, io) ((ssi) == rsnd_io_to_mod_ssip(io)) #define rsnd_ssi_is_multi_slave(mod, io) \ (rsnd_ssi_multi_slaves(io) & (1 << rsnd_mod_id(mod))) #define rsnd_ssi_is_run_mods(mod, io) \ (rsnd_ssi_run_mods(io) & (1 << rsnd_mod_id(mod))) +#define rsnd_ssi_can_output_clk(mod) (!__rsnd_ssi_is_pin_sharing(mod)) int rsnd_ssi_hdmi_port(struct rsnd_dai_stream *io) { struct rsnd_mod *mod = rsnd_io_to_mod_ssi(io); struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - if (rsnd_ssi_mode_flags(ssi) & RSND_SSI_HDMI0) + if (rsnd_ssi_flags_has(ssi, RSND_SSI_HDMI0)) return RSND_SSI_HDMI_PORT0; - if (rsnd_ssi_mode_flags(ssi) & RSND_SSI_HDMI1) + if (rsnd_ssi_flags_has(ssi, RSND_SSI_HDMI1)) return RSND_SSI_HDMI_PORT1; return 0; @@ -130,7 +134,7 @@ int rsnd_ssi_use_busif(struct rsnd_dai_stream *io) if (!rsnd_ssi_is_dma_mode(mod)) return 0; - if (!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_NO_BUSIF)) + if (!(rsnd_ssi_flags_has(ssi, RSND_SSI_NO_BUSIF))) use_busif = 1; if (rsnd_io_to_mod_src(io)) use_busif = 1; @@ -255,7 +259,6 @@ static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod, struct device *dev = rsnd_priv_to_dev(priv); struct rsnd_dai *rdai = rsnd_io_to_rdai(io); struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io); int chan = rsnd_runtime_channel_for_ssi(io); int idx, ret; unsigned int main_rate; @@ -266,7 +269,7 @@ static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod, if (!rsnd_rdai_is_clk_master(rdai)) return 0; - if (ssi_parent_mod && !rsnd_ssi_is_parent(mod, io)) + if (!rsnd_ssi_can_output_clk(mod)) return 0; if (rsnd_ssi_is_multi_slave(mod, io)) @@ -291,6 +294,16 @@ static int rsnd_ssi_master_clk_start(struct rsnd_mod *mod, if (ret < 0) return ret; + /* + * SSI clock will be output contiguously + * by below settings. + * This means, rsnd_ssi_master_clk_start() + * and rsnd_ssi_register_setup() are necessary + * for SSI parent + * + * SSICR : FORCE, SCKD, SWSD + * SSIWSR : CONT + */ ssi->cr_clk = FORCE | SWL_32 | SCKD | SWSD | CKDV(idx); ssi->wsr = CONT; ssi->rate = rate; @@ -307,12 +320,11 @@ static void rsnd_ssi_master_clk_stop(struct rsnd_mod *mod, { struct rsnd_dai *rdai = rsnd_io_to_rdai(io); struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io); if (!rsnd_rdai_is_clk_master(rdai)) return; - if (ssi_parent_mod && !rsnd_ssi_is_parent(mod, io)) + if (!rsnd_ssi_can_output_clk(mod)) return; if (ssi->usrcnt > 1) @@ -335,6 +347,9 @@ static void rsnd_ssi_config_init(struct rsnd_mod *mod, u32 wsr; int is_tdm; + if (rsnd_ssi_is_parent(mod, io)) + return; + is_tdm = rsnd_runtime_is_ssi_tdm(io); /* @@ -393,7 +408,8 @@ static void rsnd_ssi_register_setup(struct rsnd_mod *mod) rsnd_mod_write(mod, SSIWSR, ssi->wsr); rsnd_mod_write(mod, SSICR, ssi->cr_own | ssi->cr_clk | - ssi->cr_mode); /* without EN */ + ssi->cr_mode | + ssi->cr_en); } static void rsnd_ssi_pointer_init(struct rsnd_mod *mod, @@ -472,8 +488,7 @@ static int rsnd_ssi_init(struct rsnd_mod *mod, if (ret < 0) return ret; - if (!rsnd_ssi_is_parent(mod, io)) - rsnd_ssi_config_init(mod, io); + rsnd_ssi_config_init(mod, io); rsnd_ssi_register_setup(mod); @@ -544,6 +559,8 @@ 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); + if (!rsnd_ssi_is_run_mods(mod, io)) return 0; @@ -554,7 +571,19 @@ static int rsnd_ssi_start(struct rsnd_mod *mod, if (rsnd_ssi_multi_slaves_runtime(io)) return 0; - rsnd_mod_bset(mod, SSICR, EN, EN); + /* + * EN is for data output. + * SSI parent EN is not needed. + */ + if (rsnd_ssi_is_parent(mod, io)) + return 0; + + ssi->cr_en = EN; + + rsnd_mod_write(mod, SSICR, ssi->cr_own | + ssi->cr_clk | + ssi->cr_mode | + ssi->cr_en); return 0; } @@ -569,13 +598,7 @@ static int rsnd_ssi_stop(struct rsnd_mod *mod, if (!rsnd_ssi_is_run_mods(mod, io)) return 0; - /* - * don't stop if not last user - * see also - * rsnd_ssi_start - * rsnd_ssi_interrupt - */ - if (ssi->usrcnt > 1) + if (rsnd_ssi_is_parent(mod, io)) return 0; /* @@ -595,6 +618,8 @@ static int rsnd_ssi_stop(struct rsnd_mod *mod, rsnd_mod_write(mod, SSICR, cr); /* disabled all */ rsnd_ssi_status_check(mod, IIRQ); + ssi->cr_en = 0; + return 0; } @@ -760,15 +785,47 @@ static int rsnd_ssi_common_probe(struct rsnd_mod *mod, /* * SSI might be called again as PIO fallback * It is easy to manual handling for IRQ request/free + * + * OTOH, this function might be called many times if platform is + * using MIX. It needs xxx_attach() many times on xxx_probe(). + * Because of it, we can't control .probe/.remove calling count by + * mod->status. + * But it don't need to call request_irq() many times. + * Let's control it by RSND_SSI_PROBED flag. */ - ret = request_irq(ssi->irq, - rsnd_ssi_interrupt, - IRQF_SHARED, - dev_name(dev), mod); + if (!rsnd_ssi_flags_has(ssi, RSND_SSI_PROBED)) { + ret = request_irq(ssi->irq, + rsnd_ssi_interrupt, + IRQF_SHARED, + dev_name(dev), mod); + + rsnd_ssi_flags_set(ssi, RSND_SSI_PROBED); + } return ret; } +static int rsnd_ssi_common_remove(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); + struct rsnd_mod *pure_ssi_mod = rsnd_io_to_mod_ssi(io); + + /* Do nothing if non SSI (= SSI parent, multi SSI) mod */ + if (pure_ssi_mod != mod) + return 0; + + /* PIO will request IRQ again */ + if (rsnd_ssi_flags_has(ssi, RSND_SSI_PROBED)) { + free_irq(ssi->irq, mod); + + rsnd_ssi_flags_del(ssi, RSND_SSI_PROBED); + } + + return 0; +} + static int rsnd_ssi_pointer(struct rsnd_mod *mod, struct rsnd_dai_stream *io, snd_pcm_uframes_t *pointer) @@ -784,6 +841,7 @@ static int rsnd_ssi_pointer(struct rsnd_mod *mod, static struct rsnd_mod_ops rsnd_ssi_pio_ops = { .name = SSI_NAME, .probe = rsnd_ssi_common_probe, + .remove = rsnd_ssi_common_remove, .init = rsnd_ssi_init, .quit = rsnd_ssi_quit, .start = rsnd_ssi_start, @@ -818,23 +876,6 @@ static int rsnd_ssi_dma_probe(struct rsnd_mod *mod, return ret; } -static int rsnd_ssi_dma_remove(struct rsnd_mod *mod, - struct rsnd_dai_stream *io, - struct rsnd_priv *priv) -{ - struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - struct rsnd_mod *ssi_parent_mod = rsnd_io_to_mod_ssip(io); - - /* Do nothing for SSI parent mod */ - if (ssi_parent_mod == mod) - return 0; - - /* PIO will request IRQ again */ - free_irq(ssi->irq, mod); - - return 0; -} - static int rsnd_ssi_fallback(struct rsnd_mod *mod, struct rsnd_dai_stream *io, struct rsnd_priv *priv) @@ -876,7 +917,7 @@ static struct rsnd_mod_ops rsnd_ssi_dma_ops = { .name = SSI_NAME, .dma_req = rsnd_ssi_dma_req, .probe = rsnd_ssi_dma_probe, - .remove = rsnd_ssi_dma_remove, + .remove = rsnd_ssi_common_remove, .init = rsnd_ssi_init, .quit = rsnd_ssi_quit, .start = rsnd_ssi_start, @@ -962,13 +1003,13 @@ static void __rsnd_ssi_parse_hdmi_connection(struct rsnd_priv *priv, ssi = rsnd_mod_to_ssi(mod); if (strstr(remote_ep->full_name, "hdmi0")) { - ssi->flags |= RSND_SSI_HDMI0; + rsnd_ssi_flags_set(ssi, RSND_SSI_HDMI0); dev_dbg(dev, "%s[%d] connected to HDMI0\n", rsnd_mod_name(mod), rsnd_mod_id(mod)); } if (strstr(remote_ep->full_name, "hdmi1")) { - ssi->flags |= RSND_SSI_HDMI1; + rsnd_ssi_flags_set(ssi, RSND_SSI_HDMI1); dev_dbg(dev, "%s[%d] connected to HDMI1\n", rsnd_mod_name(mod), rsnd_mod_id(mod)); } @@ -1001,7 +1042,7 @@ int __rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod) { struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod); - return !!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_CLK_PIN_SHARE); + return !!(rsnd_ssi_flags_has(ssi, RSND_SSI_CLK_PIN_SHARE)); } static u32 *rsnd_ssi_get_status(struct rsnd_dai_stream *io, @@ -1079,18 +1120,20 @@ int rsnd_ssi_probe(struct rsnd_priv *priv) clk = devm_clk_get(dev, name); if (IS_ERR(clk)) { ret = PTR_ERR(clk); + of_node_put(np); goto rsnd_ssi_probe_done; } if (of_get_property(np, "shared-pin", NULL)) - ssi->flags |= RSND_SSI_CLK_PIN_SHARE; + rsnd_ssi_flags_set(ssi, RSND_SSI_CLK_PIN_SHARE); if (of_get_property(np, "no-busif", NULL)) - ssi->flags |= RSND_SSI_NO_BUSIF; + rsnd_ssi_flags_set(ssi, RSND_SSI_NO_BUSIF); ssi->irq = irq_of_parse_and_map(np, 0); if (!ssi->irq) { ret = -EINVAL; + of_node_put(np); goto rsnd_ssi_probe_done; } @@ -1101,8 +1144,10 @@ int rsnd_ssi_probe(struct rsnd_priv *priv) ret = rsnd_mod_init(priv, rsnd_mod_get(ssi), ops, clk, rsnd_ssi_get_status, RSND_MOD_SSI, i); - if (ret) + if (ret) { + of_node_put(np); goto rsnd_ssi_probe_done; + } i++; } |