diff options
author | Dan Murphy <dmurphy@ti.com> | 2011-08-31 07:32:59 -0500 |
---|---|---|
committer | Dan Murphy <dmurphy@ti.com> | 2011-08-31 07:32:59 -0500 |
commit | d21743dc00ad185044ae01c6217a8fac9714c525 (patch) | |
tree | 7004932c624ac2b668496a7c41b1a49de66365e3 /sound | |
parent | ddbaa04f52ec447cbe86fb132b1da0cd8980c019 (diff) | |
parent | 87050c34e61d0ec1b0e72713ceb3ec15cffb9928 (diff) | |
download | kernel_samsung_espresso10-d21743dc00ad185044ae01c6217a8fac9714c525.zip kernel_samsung_espresso10-d21743dc00ad185044ae01c6217a8fac9714c525.tar.gz kernel_samsung_espresso10-d21743dc00ad185044ae01c6217a8fac9714c525.tar.bz2 |
Merge branch 'android-omap-3.0' into p-android-omap-3.0
Change-Id: I1160c3de134db29f5af54d55e2704bd4c52b0c6e
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/twl6040.c | 158 | ||||
-rw-r--r-- | sound/soc/omap/Kconfig | 4 | ||||
-rw-r--r-- | sound/soc/omap/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/omap/omap-abe-dsp.c | 107 | ||||
-rw-r--r-- | sound/soc/omap/omap-abe-dsp.h | 2 | ||||
-rw-r--r-- | sound/soc/omap/omap-abe.c | 50 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcasp.c | 678 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcasp.h | 36 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcpdm.c | 65 | ||||
-rw-r--r-- | sound/soc/omap/sdp4430.c | 28 | ||||
-rw-r--r-- | sound/soc/soc-core.c | 18 | ||||
-rw-r--r-- | sound/soc/soc-dapm.c | 37 | ||||
-rw-r--r-- | sound/soc/soc-dsp.c | 389 |
13 files changed, 1239 insertions, 335 deletions
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 2dcc8fe..f217421 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -164,59 +164,59 @@ static const u8 twl6040_reg[TWL6040_CACHEREGNUM] = { 0x00, /* TWL6040_STATUS (ro) 0x2E */ }; -/* - * twl6040 vio/gnd registers: - * registers under vio/gnd supply can be accessed - * before the power-up sequence, after NRESPWRON goes high - */ -static const int twl6040_vio_reg[TWL6040_VIOREGNUM] = { - TWL6040_REG_ASICID, - TWL6040_REG_ASICREV, - TWL6040_REG_INTID, - TWL6040_REG_INTMR, - TWL6040_REG_NCPCTL, - TWL6040_REG_LDOCTL, - TWL6040_REG_AMICBCTL, - TWL6040_REG_DMICBCTL, - TWL6040_REG_HKCTL1, - TWL6040_REG_HKCTL2, - TWL6040_REG_GPOCTL, - TWL6040_REG_TRIM1, - TWL6040_REG_TRIM2, - TWL6040_REG_TRIM3, - TWL6040_REG_HSOTRIM, - TWL6040_REG_HFOTRIM, - TWL6040_REG_ACCCTL, - TWL6040_REG_STATUS, -}; -/* - * twl6040 vdd/vss registers: - * registers under vdd/vss supplies can only be accessed - * after the power-up sequence - */ -static const int twl6040_vdd_reg[TWL6040_VDDREGNUM] = { - TWL6040_REG_HPPLLCTL, - TWL6040_REG_LPPLLCTL, - TWL6040_REG_LPPLLDIV, - TWL6040_REG_MICLCTL, - TWL6040_REG_MICRCTL, - TWL6040_REG_MICGAIN, - TWL6040_REG_LINEGAIN, - TWL6040_REG_HSLCTL, - TWL6040_REG_HSRCTL, - TWL6040_REG_HSGAIN, - TWL6040_REG_EARCTL, - TWL6040_REG_HFLCTL, - TWL6040_REG_HFLGAIN, - TWL6040_REG_HFRCTL, - TWL6040_REG_HFRGAIN, - TWL6040_REG_VIBCTLL, - TWL6040_REG_VIBDATL, - TWL6040_REG_VIBCTLR, - TWL6040_REG_VIBDATR, - TWL6040_REG_ALB, - TWL6040_REG_DLB, +/* twl6040 vio/gnd registers: registers under vio/gnd supply can be accessed + * twl6040 vdd/vss registers: registers under vdd/vss supplies can only be + * accessed after the power-up sequence */ + +static const u8 twl6040_reg_supply[TWL6040_CACHEREGNUM] = { + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_VIO_SUPPLY, /* TWL6040_ASICID (ro) */ + TWL6040_VIO_SUPPLY, /* TWL6040_ASICREV (ro) */ + TWL6040_VIO_SUPPLY, /* TWL6040_INTID */ + TWL6040_VIO_SUPPLY, /* TWL6040_INTMR */ + TWL6040_VIO_SUPPLY, /* TWL6040_NCPCTRL */ + TWL6040_VIO_SUPPLY, /* TWL6040_LDOCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_HPPLLCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_LPPLLCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_LPPLLDIV */ + TWL6040_VIO_SUPPLY, /* TWL6040_AMICBCTL */ + TWL6040_VIO_SUPPLY, /* TWL6040_DMICBCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_MICLCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_MICRCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_MICGAIN */ + TWL6040_VDD_SUPPLY, /* TWL6040_LINEGAIN */ + TWL6040_VDD_SUPPLY, /* TWL6040_HSLCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_HSRCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_HSGAIN */ + TWL6040_VDD_SUPPLY, /* TWL6040_EARCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_HFLCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_HFLGAIN */ + TWL6040_VDD_SUPPLY, /* TWL6040_HFRCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_HFRGAIN */ + TWL6040_VDD_SUPPLY, /* TWL6040_VIBCTLL */ + TWL6040_VDD_SUPPLY, /* TWL6040_VIBDATL */ + TWL6040_VDD_SUPPLY, /* TWL6040_VIBCTLR */ + TWL6040_VDD_SUPPLY, /* TWL6040_VIBDATR */ + TWL6040_VIO_SUPPLY, /* TWL6040_HKCTL1 */ + TWL6040_VIO_SUPPLY, /* TWL6040_HKCTL2 */ + TWL6040_VIO_SUPPLY, /* TWL6040_GPOCTL */ + TWL6040_VDD_SUPPLY, /* TWL6040_ALB */ + TWL6040_VDD_SUPPLY, /* TWL6040_DLB */ + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_NO_SUPPLY, /* not used */ + TWL6040_VIO_SUPPLY, /* TWL6040_TRIM1 */ + TWL6040_VIO_SUPPLY, /* TWL6040_TRIM2 */ + TWL6040_VIO_SUPPLY, /* TWL6040_TRIM3 */ + TWL6040_VIO_SUPPLY, /* TWL6040_HSOTRIM */ + TWL6040_VIO_SUPPLY, /* TWL6040_HFOTRIM */ + TWL6040_VIO_SUPPLY, /* TWL6040_ACCCTL */ + TWL6040_VIO_SUPPLY, /* TWL6040_STATUS (ro) */ }; /* @@ -250,14 +250,20 @@ static inline void twl6040_write_reg_cache(struct snd_soc_codec *codec, * read from twl6040 hardware register */ static int twl6040_read_reg_volatile(struct snd_soc_codec *codec, - unsigned int reg) + unsigned int reg) { struct twl6040 *twl6040 = codec->control_data; - u8 value; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + u8 value = 0; if (reg >= TWL6040_CACHEREGNUM) return -EIO; + /* read access not supported while in sleep state */ + if ((twl6040_reg_supply[reg] == TWL6040_VDD_SUPPLY) && + !priv->codec_powered) + return -EINVAL; + value = twl6040_reg_read(twl6040, reg); twl6040_write_reg_cache(codec, reg, value); @@ -271,21 +277,32 @@ static int twl6040_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { struct twl6040 *twl6040 = codec->control_data; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + int ret = 0; if (reg >= TWL6040_CACHEREGNUM) return -EIO; twl6040_write_reg_cache(codec, reg, value); - return twl6040_reg_write(twl6040, reg, value); + + if ((twl6040_reg_supply[reg] == TWL6040_VIO_SUPPLY) || + priv->codec_powered) + ret = twl6040_reg_write(twl6040, reg, value); + else + dev_dbg(codec->dev, "deferring register 0x%02x write: %02x\n", + reg, value); + + return ret; } static void twl6040_init_vio_regs(struct snd_soc_codec *codec) { u8 *cache = codec->reg_cache; - int reg, i; + int reg; - for (i = 0; i < TWL6040_VIOREGNUM; i++) { - reg = twl6040_vio_reg[i]; + for (reg = 0; reg < TWL6040_CACHEREGNUM; reg++) { + if (twl6040_reg_supply[reg] != TWL6040_VIO_SUPPLY) + continue; /* * skip read-only registers (ASICID, ASICREV, STATUS) * and registers shared among MFD children @@ -311,10 +328,11 @@ static void twl6040_init_vio_regs(struct snd_soc_codec *codec) static void twl6040_init_vdd_regs(struct snd_soc_codec *codec) { u8 *cache = codec->reg_cache; - int reg, i; + int reg; - for (i = 0; i < TWL6040_VDDREGNUM; i++) { - reg = twl6040_vdd_reg[i]; + for (reg = 0; reg < TWL6040_CACHEREGNUM; reg++) { + if (twl6040_reg_supply[reg] != TWL6040_VDD_SUPPLY) + continue; /* skip vibra and pll registers */ switch (reg) { case TWL6040_REG_VIBCTLL: @@ -1510,6 +1528,8 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai, struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); int ret; + priv->sysclk = twl6040_get_sysclk(twl6040); + switch (clk_id) { case TWL6040_SYSCLK_SEL_LPPLL: ret = twl6040_set_pll(twl6040, TWL6040_LPPLL_ID, @@ -1539,11 +1559,26 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai, return 0; } +static int twl6040_digital_mute(struct snd_soc_dai *dai, int mute) +{ + /* + * pop-noise reduction sequence requires to shutdown + * analog side before CPU DAI + */ + if (mute) + snd_soc_dapm_codec_stream_event(dai->codec, + dai->driver->playback.stream_name, + SND_SOC_DAPM_STREAM_STOP); + + return 0; +} + static struct snd_soc_dai_ops twl6040_dai_ops = { .startup = twl6040_startup, .hw_params = twl6040_hw_params, .prepare = twl6040_prepare, .set_sysclk = twl6040_set_dai_sysclk, + .digital_mute = twl6040_digital_mute, }; static struct snd_soc_dai_driver twl6040_dai[] = { @@ -1627,6 +1662,7 @@ static int twl6040_probe(struct snd_soc_codec *codec) priv->codec = codec; codec->control_data = dev_get_drvdata(codec->dev->parent); + codec->dapm.idle_bias_off = 1; if (pdata && pdata->hs_left_step && pdata->hs_right_step) { priv->hs_left_step = pdata->hs_left_step; diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 91425b3..2adf1f5 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -6,6 +6,10 @@ config SND_OMAP_SOC_ABE_DSP tristate select SND_DYNAMIC_MINORS +config SND_OMAP_SOC_MCASP + tristate + select SND_SOC_SPDIF + config SND_OMAP_SOC_MCBSP tristate select OMAP_MCBSP diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 7cc549d..2ada65c 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -1,5 +1,6 @@ # OMAP Platform Support snd-soc-omap-objs := omap-pcm.o +snd-soc-omap-mcasp-objs := omap-mcasp.o snd-soc-omap-mcbsp-objs := omap-mcbsp.o snd-soc-omap-mcpdm-objs := omap-mcpdm.o snd-soc-omap-dmic-objs := omap-dmic.o @@ -7,6 +8,7 @@ snd-soc-omap-abe-objs := omap-abe.o snd-soc-omap-abe-dsp-objs := omap-abe-dsp.o obj-$(CONFIG_SND_OMAP_SOC) += snd-soc-omap.o +obj-$(CONFIG_SND_OMAP_SOC_MCASP) += snd-soc-omap-mcasp.o obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o obj-$(CONFIG_SND_OMAP_SOC_DMIC) += snd-soc-omap-dmic.o diff --git a/sound/soc/omap/omap-abe-dsp.c b/sound/soc/omap/omap-abe-dsp.c index 5f4eecd..940c42c 100644 --- a/sound/soc/omap/omap-abe-dsp.c +++ b/sound/soc/omap/omap-abe-dsp.c @@ -98,6 +98,12 @@ struct fw_header { u32 num_equ; /* number of equalizers */ }; +struct abe_opp_req { + struct device *dev; + struct list_head node; + int opp; +}; + /* * ABE private data. */ @@ -108,6 +114,7 @@ struct abe_data { struct delayed_work delayed_work; struct mutex mutex; struct mutex opp_mutex; + struct mutex opp_req_mutex; struct clk *clk; void __iomem *io_base[5]; int irq; @@ -129,6 +136,9 @@ struct abe_data { /* DAPM mixer config - TODO: some of this can be replaced with HAL update */ u32 widget_opp[ABE_NUM_DAPM_REG + 1]; + struct list_head opp_req; + int opp_req_count; + u16 router[16]; int loss_count; @@ -172,6 +182,8 @@ struct abe_data { static struct abe_data *the_abe; +static int aess_set_runtime_opp_level(struct abe_data *abe); + // TODO: map to the new version of HAL static unsigned int abe_dsp_read(struct snd_soc_platform *platform, unsigned int reg) @@ -1806,6 +1818,91 @@ static const struct snd_pcm_hardware omap_abe_hardware = { .buffer_bytes_max = 24 * 1024 * 2, }; +static struct abe_opp_req *abe_opp_req_lookup(struct abe_data *abe, + struct device *dev) +{ + struct abe_opp_req *req, *tmp_req; + + req = NULL; + list_for_each_entry(tmp_req, &abe->opp_req, node) { + if (tmp_req->dev == dev) { + req = tmp_req; + break; + } + } + + return req; +} + +static int abe_get_opp_req(struct abe_data *abe) +{ + struct abe_opp_req *req; + int opp = 0; + + list_for_each_entry(req, &abe->opp_req, node) + opp |= req->opp; + + opp = (1 << (fls(opp) - 1)) * 25; + + return opp; +} + +int abe_add_opp_req(struct device *dev, int opp) +{ + struct abe_opp_req *req; + int ret = 0; + + mutex_lock(&the_abe->opp_req_mutex); + + req = abe_opp_req_lookup(the_abe, dev); + if (!req) { + req = kzalloc(sizeof(struct abe_opp_req), GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto out; + } + req->dev = dev; + /* use the same convention as ABE DSP DAPM */ + req->opp = 1 << opp; + list_add(&req->node, &the_abe->opp_req); + the_abe->opp_req_count++; + } else { + req->opp = opp; + } + + aess_set_runtime_opp_level(the_abe); + +out: + mutex_unlock(&the_abe->opp_req_mutex); + return ret; +} +EXPORT_SYMBOL(abe_add_opp_req); + +int abe_remove_opp_req(struct device *dev) +{ + struct abe_opp_req *req; + int ret = 0; + + mutex_lock(&the_abe->opp_req_mutex); + + req = abe_opp_req_lookup(the_abe, dev); + if (!req) { + dev_err(dev, "trying to remove an invalid opp req\n"); + ret = -EINVAL; + goto out; + } + + list_del(&req->node); + the_abe->opp_req_count--; + kfree(req); + + aess_set_runtime_opp_level(the_abe); + +out: + mutex_unlock(&the_abe->opp_req_mutex); + return ret; +} +EXPORT_SYMBOL(abe_remove_opp_req); static int abe_set_opp_mode(struct abe_data *abe, int opp) { @@ -1882,7 +1979,7 @@ err_scale: static int aess_set_runtime_opp_level(struct abe_data *abe) { - int i, opp = 0; + int i, req_opp, opp = 0; mutex_lock(&abe->opp_mutex); @@ -1896,8 +1993,11 @@ static int aess_set_runtime_opp_level(struct abe_data *abe) } opp = (1 << (fls(opp) - 1)) * 25; + /* opps requested outside ABE DSP driver (e.g. McPDM) */ + req_opp = abe_get_opp_req(abe); + pm_runtime_get_sync(abe->dev); - abe_set_opp_mode(abe, opp); + abe_set_opp_mode(abe, max(opp, req_opp)); pm_runtime_put_sync(abe->dev); mutex_unlock(&abe->opp_mutex); @@ -2625,6 +2725,9 @@ static int __devinit abe_engine_probe(struct platform_device *pdev) abe->dev = &pdev->dev; mutex_init(&abe->mutex); mutex_init(&abe->opp_mutex); + mutex_init(&abe->opp_req_mutex); + INIT_LIST_HEAD(&abe->opp_req); + abe->opp_req_count = 0; ret = snd_soc_register_platform(abe->dev, &omap_aess_platform); diff --git a/sound/soc/omap/omap-abe-dsp.h b/sound/soc/omap/omap-abe-dsp.h index 2e8ac53..5dae62a 100644 --- a/sound/soc/omap/omap-abe-dsp.h +++ b/sound/soc/omap/omap-abe-dsp.h @@ -168,5 +168,7 @@ void abe_dsp_shutdown(void); void abe_dsp_pm_get(void); void abe_dsp_pm_put(void); +int abe_add_opp_req(struct device *dev, int opp); +int abe_remove_opp_req(struct device *dev); #endif /* End of __OMAP_ABE_DSP_H__ */ diff --git a/sound/soc/omap/omap-abe.c b/sound/soc/omap/omap-abe.c index 740e553..c418e66 100644 --- a/sound/soc/omap/omap-abe.c +++ b/sound/soc/omap/omap-abe.c @@ -503,6 +503,7 @@ static void mute_fe_port(struct snd_pcm_substream *substream, abe_mute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); break; case ABE_FRONTEND_DAI_VOICE: + case ABE_FRONTEND_DAI_MODEM: if (omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) abe_mute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); @@ -543,6 +544,7 @@ static void unmute_fe_port(struct snd_pcm_substream *substream, abe_unmute_gain(MIXDL1, MIX_DL1_INPUT_MM_DL); break; case ABE_FRONTEND_DAI_VOICE: + case ABE_FRONTEND_DAI_MODEM: if (omap_abe_port_is_enabled(abe_priv->abe, abe_priv->port[OMAP_ABE_BE_PORT_PDM_DL2])) abe_unmute_gain(MIXDL2, MIX_DL2_INPUT_VX_DL); @@ -585,11 +587,11 @@ static void capture_trigger(struct snd_pcm_substream *substream, if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - /* is the BE already in the trigger START state ? */ - if (dsp_params->state == SND_SOC_DSP_LINK_STATE_START) + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_PREPARE) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_STOP)) continue; - be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + be_substream = snd_soc_dsp_get_substream(be, stream); /* mute the BE port */ mute_be(be, dai, stream); @@ -602,6 +604,8 @@ static void capture_trigger(struct snd_pcm_substream *substream, /* trigger the BE port */ snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); + + be->dsp[stream].state = SND_SOC_DSP_STATE_START; } /* does this trigger() apply to the FE ? */ @@ -650,12 +654,15 @@ static void capture_trigger(struct snd_pcm_substream *substream, if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - /* only STOP BE in FREE state */ - /* REVISIT: Investigate the appropriate state to check against */ - //if (dsp_params->state != SND_SOC_DSP_LINK_STATE_FREE) - // continue; + if (be->dsp[stream].state != SND_SOC_DSP_STATE_START) + continue; - be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + /* only stop if last running user */ + if (soc_dsp_fe_state_count(be, stream, + SND_SOC_DSP_STATE_START) > 1) + continue; + + be_substream = snd_soc_dsp_get_substream(be, stream); /* disable the BE port */ disable_be_port(be, dai, stream); @@ -665,6 +672,8 @@ static void capture_trigger(struct snd_pcm_substream *substream, /* trigger BE port */ snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); + + be->dsp[stream].state = SND_SOC_DSP_STATE_STOP; } break; default: @@ -693,11 +702,11 @@ static void playback_trigger(struct snd_pcm_substream *substream, if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - /* is the BE already in the trigger START state ? */ - if (dsp_params->state == SND_SOC_DSP_LINK_STATE_START) + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_PREPARE) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_STOP)) continue; - be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + be_substream = snd_soc_dsp_get_substream(be, stream); /* mute BE port */ mute_be(be, dai, stream); @@ -713,6 +722,8 @@ static void playback_trigger(struct snd_pcm_substream *substream, /* unmute the BE port */ unmute_be(be, dai, stream); + + be->dsp[stream].state = SND_SOC_DSP_STATE_START; } /* does this trigger() apply to the FE ? */ @@ -763,11 +774,15 @@ static void playback_trigger(struct snd_pcm_substream *substream, if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - /* only STOP BE in FREE state */ - if (dsp_params->state != SND_SOC_DSP_LINK_STATE_FREE) + if (be->dsp[stream].state != SND_SOC_DSP_STATE_START) + continue; + + /* only stop if last running user */ + if (soc_dsp_fe_state_count(be, stream, + SND_SOC_DSP_STATE_START) > 1) continue; - be_substream = snd_soc_dsp_get_substream(dsp_params->be, stream); + be_substream = snd_soc_dsp_get_substream(be, stream); /* disable the BE */ disable_be_port(be, dai, stream); @@ -777,6 +792,8 @@ static void playback_trigger(struct snd_pcm_substream *substream, /* trigger the BE port */ snd_soc_dai_trigger(be_substream, cmd, be->cpu_dai); + + be->dsp[stream].state = SND_SOC_DSP_STATE_STOP; } break; default: @@ -794,6 +811,8 @@ static int omap_abe_dai_startup(struct snd_pcm_substream *substream, abe_priv->active_dais++; + abe_dsp_pm_get(); + if (dai->id == ABE_FRONTEND_DAI_MODEM) { ret = modem_get_dai(substream, dai); @@ -1078,6 +1097,9 @@ static void omap_abe_dai_shutdown(struct snd_pcm_substream *substream, abe_priv->modem_dai); } + abe_dsp_shutdown(); + abe_dsp_pm_put(); + abe_priv->active_dais--; } diff --git a/sound/soc/omap/omap-mcasp.c b/sound/soc/omap/omap-mcasp.c new file mode 100644 index 0000000..2f680c2 --- /dev/null +++ b/sound/soc/omap/omap-mcasp.c @@ -0,0 +1,678 @@ +/* + * ALSA SoC McASP Audio Layer for TI OMAP processor + * + * Multi-channel Audio Serial Port Driver + * + * Author: Jon Hunter <jon-hunter@ti.com>, + * Dan Milea <dan.milea@ti.com>, + * + * Based upon McASP driver written for TI DaVinci + * + * Copyright: (C) 2011 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <plat/omap_hwmod.h> +#include <plat/clock.h> +#include <plat/dma.h> +#include <plat/dma-44xx.h> + +#include "omap-pcm.h" +#include "omap-mcasp.h" + +/* + * McASP register definitions + */ +#define OMAP_MCASP_PID_REG 0x00 +#define OMAP_MCASP_SYSCONFIG_REG 0x04 + +#define OMAP_MCASP_PFUNC_REG 0x10 +#define OMAP_MCASP_PDIR_REG 0x14 +#define OMAP_MCASP_PDOUT_REG 0x18 +#define OMAP_MCASP_PDIN_REG 0x1c +#define OMAP_MCASP_PDSET_REG 0x1c +#define OMAP_MCASP_PDCLR_REG 0x20 + +#define OMAP_MCASP_GBLCTL_REG 0x44 +#define OMAP_MCASP_AMUTE_REG 0x48 + +#define OMAP_MCASP_TXDITCTL_REG 0x50 + +#define OMAP_MCASP_TXMASK_REG 0xa4 +#define OMAP_MCASP_TXFMT_REG 0xa8 +#define OMAP_MCASP_TXFMCTL_REG 0xac + +#define OMAP_MCASP_ACLKXCTL_REG 0xb0 +#define OMAP_MCASP_AHCLKXCTL_REG 0xb4 +#define OMAP_MCASP_TXTDM_REG 0xb8 +#define OMAP_MCASP_EVTCTLX_REG 0xbc + +#define OMAP_MCASP_TXSTAT_REG 0xc0 +#define OMAP_MCASP_TXSTAT_MASK 0x1ff + +#define OMAP_MCASP_TXTDMSLOT_REG 0xc4 +#define OMAP_MCASP_TXCLKCHK_REG 0xc8 +#define OMAP_MCASP_TXEVTCTL_REG 0xcc + +/* Left(even TDM Slot) Channel Status Register File */ +#define OMAP_MCASP_DITCSRA_REG 0x100 +/* Right(odd TDM slot) Channel Status Register File */ +#define OMAP_MCASP_DITCSRB_REG 0x118 +/* Left(even TDM slot) User Data Register File */ +#define OMAP_MCASP_DITUDRA_REG 0x130 +/* Right(odd TDM Slot) User Data Register File */ +#define OMAP_MCASP_DITUDRB_REG 0x148 + +/* Serializer n Control Register */ +#define OMAP_MCASP_XRSRCTL0_REG 0x180 + +/* Transmit Buffer for Serializer */ +#define OMAP_MCASP_TXBUF0_REG 0x200 + +/* + * OMAP_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits + */ +#define AXR0 BIT(0) +#define PFUNC_AMUTE BIT(25) +#define ACLKX BIT(26) +#define AHCLKX BIT(27) +#define AFSX BIT(28) + +/* + * OMAP_MCASP_PDIR_REG - Pin Direction Register Bits + */ +#define AXR0 BIT(0) +#define PDIR_AMUTE BIT(25) +#define ACLKX BIT(26) +#define AHCLKX BIT(27) +#define AFSX BIT(28) + +/* + * OMAP_MCASP_TXDITCTL_REG - Transmit DIT Control Register Bits + */ +#define DITEN BIT(0) /* Transmit DIT mode enable/disable */ +#define VA BIT(2) +#define VB BIT(3) + +/* + * OMAP_MCASP_TXFMT_REG - Transmit Bitstream Format Register Bits + */ +#define TXROT(val) (val) +#define TXROT_MASK TXROT(0x7) +#define TXSEL BIT(3) +#define TXSSZ(val) (val<<4) +#define TXSSZ_MASK TXSSZ(0xf<<4) +#define TXPAD(val) (val<<13) +#define TXORD BIT(15) +#define FSXDLY(val) (val<<16) + +#define ROTATE_24 0x6 +#define SLOTSIZE_32 0xf + +/* + * OMAP_MCASP_TXFMCTL_REG - Transmit Frame Control Register Bits + */ +#define FSXPOL BIT(0) +#define AFSXE BIT(1) +#define FSXDUR BIT(4) +#define FSXMOD(val) (val<<7) + +/* + * OMAP_MCASP_ACLKXCTL_REG - Transmit Clock Control Register Bits + */ +#define ACLKXDIV(val) (val) +#define ACLKXE BIT(5) +#define TX_ASYNC BIT(6) + +/* + * OMAP_MCASP_AHCLKXCTL_REG - High Frequency Transmit Clock Control + * Register Bits + */ +#define AHCLKXDIV(val) (val) +#define AHCLKXE BIT(15) + +/* + * OMAP_MCASP_TXSTAT_REG - Transmit Status Register Bits + */ +#define TXSTAT_XUNDRN (0x1 << 0) +#define TXSTAT_XSYNCERR (0x1 << 1) +#define TXSTAT_XCKFAIL (0x1 << 2) +#define TXSTAT_XDMSLOT (0x1 << 3) +#define TXSTAT_XLAST (0x1 << 4) +#define TXSTAT_XDATA (0x1 << 5) +#define TXSTAT_XSTAFRM (0x1 << 6) +#define TXSTAT_XDMAERR (0x1 << 7) +#define TXSTAT_XERR (0x1 << 8) + +/* + * OMAP_MCASP_XRSRCTL_BASE_REG - Serializer Control Register Bits + */ +#define MODE(val) (val) +#define TXSTATE BIT(4) + +/* + * OMAP_MCASP_TXTDMSLOT_REG - Transmit TDM Slot Register configuration + */ +#define TXTDMS(n) (1<<n) + +/* + * OMAP_MCASP_GBLCTL_REG - Global Control Register Bits + */ +#define TXCLKRST BIT(8) /* Transmitter Clock Divider Reset */ +#define TXHCLKRST BIT(9) /* Transmitter High Frequency Clock Divider*/ +#define TXSERCLR BIT(10) /* Transmit Serializer Clear */ +#define TXSMRST BIT(11) /* Transmitter State Machine Reset */ +#define TXFSRST BIT(12) /* Frame Sync Generator Reset */ + +/* + * OMAP_MCASP_AMUTE_REG - Mute Control Register Bits + */ +#define MUTENA(val) (val) +#define MUTEINPOL BIT(2) +#define MUTEINENA BIT(3) +#define MUTEIN BIT(4) +#define MUTEX BIT(6) +#define MUTEFSX BIT(8) +#define MUTEBADCLKX BIT(10) +#define MUTETXDMAERR BIT(12) + +/* + * OMAP_MCASP_TXEVTCTL_REG - Transmitter DMA Event Control Register bits + */ +#define TXDATADMADIS BIT(0) + +/* + * Stream DMA parameters + */ +static struct omap_pcm_dma_data omap_mcasp_dai_dma_params[] = { + { + .name = "Audio playback", + .dma_req = OMAP44XX_DMA_MCASP1_AXEVT, + .data_type = OMAP_DMA_DATA_TYPE_S16, + .sync_mode = OMAP_DMA_SYNC_ELEMENT, + .port_addr = OMAP44XX_MCASP_DAT_BASE + OMAP_MCASP_TXBUF0_REG, + }, +}; + +static inline void mcasp_set_bits(void __iomem *reg, u32 val) +{ + __raw_writel(__raw_readl(reg) | val, reg); +} + +static inline void mcasp_clr_bits(void __iomem *reg, u32 val) +{ + __raw_writel((__raw_readl(reg) & ~(val)), reg); +} + +static inline void mcasp_mod_bits(void __iomem *reg, u32 val, u32 mask) +{ + __raw_writel((__raw_readl(reg) & ~mask) | val, reg); +} + +static inline void mcasp_set_reg(void __iomem *reg, u32 val) +{ + __raw_writel(val, reg); +} + +static inline u32 mcasp_get_reg(void __iomem *reg) +{ + return (unsigned int)__raw_readl(reg); +} + +static inline void mcasp_set_ctl_reg(void __iomem *regs, u32 val) +{ + int i = 0; + + mcasp_set_bits(regs, val); + + /* programming GBLCTL needs to read back from GBLCTL and verfiy */ + /* loop count is to avoid the lock-up */ + for (i = 0; i < 1000; i++) { + if ((mcasp_get_reg(regs) & val) == val) + break; + } + + if (i == 1000 && ((mcasp_get_reg(regs) & val) != val)) + printk(KERN_ERR "GBLCTL write error\n"); +} + +static void mcasp_clk_on(struct omap_mcasp *mcasp) +{ + if (mcasp->clk_active) + return; + if (!omap_hwmod_enable_clocks(mcasp->oh)) + mcasp->clk_active = 1; +} + +static void mcasp_clk_off(struct omap_mcasp *mcasp) +{ + if (!mcasp->clk_active) + return; + omap_hwmod_disable_clocks(mcasp->oh); + mcasp->clk_active = 0; +} + +static int mcasp_compute_clock_dividers(long fclk_rate, int tgt_sample_rate, + int *out_div_lo, int *out_div_hi) +{ + /* Given a particular functional clock rate and a target audio sample + * rate, determine the proper values for the ACLKXCTL and AHCLKXCTL, the + * dividers which produce the high frequency transmit master clock and + * the transmit clock. + */ + long divisor; + int i; + BUG_ON(!out_div_lo); + BUG_ON(!out_div_hi); + + /* Start by making sure the fclk is divisible by 128 (the number of + * clocks present in a single S/PDIF frame. + */ + if (fclk_rate & 0x7F) + return -EINVAL; + + fclk_rate >>= 7; + + /* Next, make sure that our target Fs divides fClk/128 */ + if (fclk_rate % tgt_sample_rate) + return -EINVAL; + + divisor = fclk_rate / tgt_sample_rate; + + /* At this point, divisor holds the product of the two divider values we + * need to use for ACLKXCTL and AHCLKXCTL. ACLKXCTL holds a 5 bit + * divider [1, 32], while AHCLKXCTL holds a 12 bit divider [1, 4096]. + * We need to make sure that we can factor divisor into two integers + * which will fit into these divider registers. Find the largest 5-bit + * + 1 value which divides divisor and use that as our smaller divider. + * After removing this factor from divisor, if the result is <= 4096, + * then we have succeeded and will be able to produce the target sample + * rate. + */ + for (i = 32; (i > 1) && (divisor % i); --i) + ; /* no body */ + + /* Make sure to subtract one, registers hold the value of the divider + * minus one (IOW, to divide by 5, the register gets programmed with the + * value 4. */ + *out_div_lo = i - 1; + *out_div_hi = (divisor / i) - 1; + + return (*out_div_hi <= 4096) ? 0 : -EINVAL; +} + +static int mcasp_compute_playback_rates(long fclk_rate) +{ + static const int rate_table[][2] = { + { 5512, SNDRV_PCM_RATE_5512 }, + { 8000, SNDRV_PCM_RATE_8000 }, + { 11025, SNDRV_PCM_RATE_11025 }, + { 16000, SNDRV_PCM_RATE_16000 }, + { 22050, SNDRV_PCM_RATE_22050 }, + { 32000, SNDRV_PCM_RATE_32000 }, + { 44100, SNDRV_PCM_RATE_44100 }, + { 48000, SNDRV_PCM_RATE_48000 }, + { 64000, SNDRV_PCM_RATE_64000 }, + { 88200, SNDRV_PCM_RATE_88200 }, + { 96000, SNDRV_PCM_RATE_96000 }, + { 176400, SNDRV_PCM_RATE_176400 }, + { 192000, SNDRV_PCM_RATE_192000 }, + }; + int i, res; + + if (!fclk_rate) + return 0; + + res = 0; + for (i = 0; i < ARRAY_SIZE(rate_table); ++i) { + int lo, hi; + + if (!mcasp_compute_clock_dividers(fclk_rate, + rate_table[i][0], + &lo, + &hi)) + res |= rate_table[i][1]; + } + + return res; +} + +static int mcasp_start_tx(struct omap_mcasp *mcasp) +{ + int i; + mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXHCLKRST); + mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXCLKRST); + mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXSERCLR); + + /* Wait until the DMA has loaded the first sample into TXBUF before we + * let the TX state machine and frame sync generator out of reset. */ + i = 0; + while (1) { + u32 reg = mcasp_get_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG); + if (!(reg & TXSTAT_XDATA)) + break; + + if (++i > 1000) { + printk(KERN_ERR "Timeout waiting for DMA to load first" + " sample of audio.\n"); + return -ETIMEDOUT; + } + + udelay(1); + } + + mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXSMRST); + mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXFSRST); + mcasp_clr_bits(mcasp->base + OMAP_MCASP_TXEVTCTL_REG, TXDATADMADIS); + + return 0; +} + +static int omap_mcasp_start(struct omap_mcasp *mcasp, int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + return mcasp_start_tx(mcasp); + + return -EINVAL; +} + +static void mcasp_stop_tx(struct omap_mcasp *mcasp) +{ + mcasp_set_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, 0); + mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG, + OMAP_MCASP_TXSTAT_MASK); +} + +static void omap_mcasp_stop(struct omap_mcasp *mcasp, int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + mcasp_stop_tx(mcasp); +} + +static int omap_mcasp_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev; + struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + + mcasp_clk_on(mcasp); + + pdev = to_platform_device(mcasp->dev); + + if (!mcasp->active++) + pm_runtime_get_sync(&pdev->dev); + + mcasp_set_reg(mcasp->base + OMAP_MCASP_SYSCONFIG_REG, 0x1); + + return 0; +} + +static void omap_mcasp_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev; + struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + + pdev = to_platform_device(mcasp->dev); + + mcasp_set_reg(mcasp->base + OMAP_MCASP_SYSCONFIG_REG, 0x2); + + if (!--mcasp->active) + pm_runtime_put_sync(&pdev->dev); + + mcasp_clk_off(mcasp); +} + +/* S/PDIF */ +static int omap_hw_dit_param(struct omap_mcasp *mcasp, unsigned int rate) +{ + u32 aclkxdiv, ahclkxdiv; + int res; + + /* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */ + mcasp_set_reg(mcasp->base + OMAP_MCASP_TXFMCTL_REG, + AFSXE | FSXMOD(0x180)); + + /* Set the TX clock controls : div = 1 and internal */ + mcasp_set_reg(mcasp->base + OMAP_MCASP_ACLKXCTL_REG, + ACLKXE | TX_ASYNC); + + /* Set the HS TX clock controls : div = 1 and internal */ + mcasp_set_reg(mcasp->base + OMAP_MCASP_AHCLKXCTL_REG, AHCLKXE); + + /* The SPDIF bit clock is derived from the McASP functional clock. + * The McASP has two programmable clock dividers (aclkxdiv and + * ahclkxdiv) that are configured via the registers MCASP_ACLKXCTL + * and MCASP_AHCLKXCTL. For SPDIF the bit clock frequency should be + * 128 * sample rate freq. The dividers are defined as part of + * platform data as they are dependent upon the functional clock + * setting. Lookup the appropriate dividers for the sampling + * frequency that we are playing. + */ + res = mcasp_compute_clock_dividers(clk_get_rate(mcasp->fclk), + rate, + &aclkxdiv, + &ahclkxdiv); + if (res) { + dev_err(mcasp->dev, + "%s: No valid McASP config for sampling rate (%d)!\n", + __func__, rate); + return res; + } + + mcasp_set_bits(mcasp->base + OMAP_MCASP_AHCLKXCTL_REG, + AHCLKXDIV(ahclkxdiv)); + mcasp_set_bits(mcasp->base + OMAP_MCASP_ACLKXCTL_REG, + AHCLKXDIV(aclkxdiv)); + + /* Configure McASP formatter */ + mcasp_mod_bits(mcasp->base + OMAP_MCASP_TXFMT_REG, + TXSSZ(SLOTSIZE_32), TXSSZ_MASK); + mcasp_mod_bits(mcasp->base + OMAP_MCASP_TXFMT_REG, TXROT(ROTATE_24), + TXROT_MASK); + mcasp_set_reg(mcasp->base + OMAP_MCASP_TXMASK_REG, 0xFFFF); + + /* Set the TX tdm : for all the slots */ + mcasp_set_reg(mcasp->base + OMAP_MCASP_TXTDM_REG, 0xFFFFFFFF); + + /* configure the serializer for transmit mode operation */ + mcasp_set_bits(mcasp->base + OMAP_MCASP_XRSRCTL0_REG, MODE(1)); + + /* All PINS as McASP */ + mcasp_set_reg(mcasp->base + OMAP_MCASP_PFUNC_REG, 0); + + mcasp_set_bits(mcasp->base + OMAP_MCASP_PDIR_REG, AXR0); + + /* Enable the DIT */ + mcasp_set_bits(mcasp->base + OMAP_MCASP_TXDITCTL_REG, DITEN); + + mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG, 0xFF); + + return 0; +} + +static int omap_mcasp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); + int stream = substream->stream; + + mcasp_stop_tx(mcasp); + + if ((params_format(params)) != SNDRV_PCM_FORMAT_S16_LE) { + printk(KERN_WARNING "omap-mcasp: unsupported PCM format"); + return -EINVAL; + } + + if (omap_hw_dit_param(mcasp, params_rate(params)) < 0) + return -EPERM; + + snd_soc_dai_set_dma_data(dai, substream, + &omap_mcasp_dai_dma_params[stream]); + + return 0; +} + +static int omap_mcasp_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = omap_mcasp_start(mcasp, substream->stream); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + omap_mcasp_stop(mcasp, substream->stream); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static struct snd_soc_dai_ops omap_mcasp_dai_ops = { + .startup = omap_mcasp_startup, + .shutdown = omap_mcasp_shutdown, + .trigger = omap_mcasp_trigger, + .hw_params = omap_mcasp_hw_params, + +}; + +static struct snd_soc_dai_driver omap_mcasp_dai[] = { + { + .name = "omap-mcasp-dai", + .playback = { + .channels_min = 1, + .channels_max = 384, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &omap_mcasp_dai_ops, + }, +}; + +static __devinit int omap_mcasp_probe(struct platform_device *pdev) +{ + struct omap_mcasp *mcasp; + struct omap_hwmod *oh; + long fclk_rate; + int ret = 0; + + oh = omap_hwmod_lookup("omap-mcasp-dai"); + if (oh == NULL) { + dev_err(&pdev->dev, "no hwmod device found\n"); + return -ENODEV; + } + + mcasp = kzalloc(sizeof(struct omap_mcasp), GFP_KERNEL); + if (!mcasp) + return -ENOMEM; + mcasp->oh = oh; + + mcasp->base = omap_hwmod_get_mpu_rt_va(oh); + if (!mcasp->base) { + ret = -ENODEV; + goto err; + } + + mcasp->fclk = clk_get(&pdev->dev, "mcasp_fck"); + if (!mcasp->fclk) { + ret = -ENODEV; + goto err; + } + mcasp_clk_on(mcasp); + fclk_rate = clk_get_rate(mcasp->fclk); + + platform_set_drvdata(pdev, mcasp); + mcasp->dev = &pdev->dev; + + omap_mcasp_dai[0].playback.rates = + mcasp_compute_playback_rates(fclk_rate); + if (!omap_mcasp_dai[0].playback.rates) { + dev_err(&pdev->dev, "no valid sample rates can be produce from" + " a %ld Hz fClk\n", fclk_rate); + ret = -ENODEV; + goto err; + } + + ret = snd_soc_register_dai(&pdev->dev, omap_mcasp_dai); + + if (ret < 0) + goto err; + + pm_runtime_enable(&pdev->dev); + mcasp_clk_off(mcasp); + + return 0; +err: + if (mcasp && mcasp->fclk) + mcasp_clk_off(mcasp); + kfree(mcasp); + + return ret; +} + +static __devexit int omap_mcasp_remove(struct platform_device *pdev) +{ + struct omap_mcasp *mcasp = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_dai(&pdev->dev); + mcasp_clk_off(mcasp); + clk_put(mcasp->fclk); + + kfree(mcasp); + + return 0; +} + +static struct platform_driver omap_mcasp_driver = { + .probe = omap_mcasp_probe, + .remove = omap_mcasp_remove, + .driver = { + .name = "omap-mcasp-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_mcasp_init(void) +{ + return platform_driver_register(&omap_mcasp_driver); +} +module_init(omap_mcasp_init); + +static void __exit omap_mcasp_exit(void) +{ + platform_driver_unregister(&omap_mcasp_driver); +} +module_exit(omap_mcasp_exit); + +MODULE_AUTHOR("Jon Hunter <jon-hunter@ti.com>"); +MODULE_DESCRIPTION("TI OMAP McASP SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/omap-mcasp.h b/sound/soc/omap/omap-mcasp.h new file mode 100644 index 0000000..25b1aa1 --- /dev/null +++ b/sound/soc/omap/omap-mcasp.h @@ -0,0 +1,36 @@ +/* + * ALSA SoC McASP Audio Layer for TI OMAP processor + * + * MCASP related definitions + * + * Author: Jon Hunter <jon-hunter@ti.com>, + * Dan Milea <dan.milea@ti.com>, + * + * Based upon McASP driver written for TI DaVinci + * + * Copyright: (C) 2011 Texas Instruments + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef OMAP_MCASP_H +#define OMAP_MCASP_H + +#include <linux/io.h> +#include <plat/mcasp.h> + +#define OMAP44XX_MCASP_CFG_BASE 0x49028000 +#define OMAP44XX_MCASP_DAT_BASE 0x4902A000 + +struct omap_mcasp { + struct device *dev; + void __iomem *base; + struct clk *fclk; + int clk_active; + int active; + struct omap_hwmod *oh; +}; + +#endif /* OMAP_MCASP_H */ diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c index c56f38a..ea2081f 100644 --- a/sound/soc/omap/omap-mcpdm.c +++ b/sound/soc/omap/omap-mcpdm.c @@ -64,7 +64,6 @@ struct omap_mcpdm { unsigned long phys_base; void __iomem *io_base; int irq; - struct delayed_work delayed_work; struct mutex mutex; struct omap_mcpdm_platform_data *pdata; @@ -77,7 +76,6 @@ struct omap_mcpdm { u32 dn_channels; u32 up_channels; int active; - int powered; int abe_mode; /* DC offset */ @@ -364,27 +362,19 @@ static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, dev_dbg(dai->dev, "%s: active %d\n", __func__, dai->active); - /* make sure we stop any pre-existing shutdown */ - cancel_delayed_work_sync(&mcpdm->delayed_work); - mutex_lock(&mcpdm->mutex); + /* nothing to do if already active */ + if (mcpdm->active++) + goto out; + if (dai->id >= MCPDM_ABE_DAI_DL1) mcpdm->abe_mode = 1; else mcpdm->abe_mode = 0; - /* nothing to do if already active */ - if (mcpdm->active++ || mcpdm->powered) - goto out; - pm_runtime_get_sync(mcpdm->dev); - if (mcpdm->abe_mode) - abe_dsp_pm_get(); - - mcpdm->powered = 1; - /* Enable McPDM watch dog for ES above ES 1.0 to avoid saturation */ if (omap_rev() != OMAP4430_REV_ES1_0) { ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); @@ -399,60 +389,38 @@ out: return err; } -/* work to delay McPDM shutdown */ -static void playback_work(struct work_struct *work) +static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { - struct omap_mcpdm *mcpdm = - container_of(work, struct omap_mcpdm, delayed_work.work); + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s: active %d\n", __func__, dai->active); mutex_lock(&mcpdm->mutex); - /* - * still active or another stream started while - * delayed work was waiting for execution - */ - if (!mcpdm->powered || mcpdm->active) + if (--mcpdm->active) goto out; - /* ABE playback stop handled by delayed work */ if (mcpdm->abe_mode) { if (omap_mcpdm_active(mcpdm)) { omap_abe_port_disable(mcpdm->abe, mcpdm->dl_port); omap_abe_port_disable(mcpdm->abe, mcpdm->ul_port); udelay(250); + abe_remove_opp_req(mcpdm->dev); omap_mcpdm_stop(mcpdm); } - omap_mcpdm_close(mcpdm); - abe_dsp_shutdown(); - abe_dsp_pm_put(); } else { omap_mcpdm_stop(mcpdm); - omap_mcpdm_close(mcpdm); } + omap_mcpdm_close(mcpdm); + pm_runtime_put_sync(mcpdm->dev); - mcpdm->powered = 0; out: mutex_unlock(&mcpdm->mutex); } -static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); - - dev_dbg(dai->dev, "%s: active %d\n", __func__, dai->active); - - mutex_lock(&mcpdm->mutex); - - if (!--mcpdm->active) - schedule_delayed_work(&mcpdm->delayed_work, - msecs_to_jiffies(1000)); /* TODO: pdata ? */ - - mutex_unlock(&mcpdm->mutex); -} - static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -519,6 +487,9 @@ static int omap_mcpdm_prepare(struct snd_pcm_substream *substream, omap_abe_port_is_enabled(mcpdm->abe, mcpdm->dl_port)) goto out; + /* PDM tasks require ABE OPP 50 */ + abe_add_opp_req(mcpdm->dev, ABE_OPP_50); + /* start ATC before McPDM IP */ omap_abe_port_enable(mcpdm->abe, mcpdm->dl_port); omap_abe_port_enable(mcpdm->abe, mcpdm->ul_port); @@ -728,8 +699,6 @@ static __devinit int asoc_mcpdm_probe(struct platform_device *pdev) if (err < 0) dev_err(mcpdm->dev,"failed to DL2 DC offset sysfs: %d\n", err); - INIT_DELAYED_WORK(&mcpdm->delayed_work, playback_work); - #if defined(CONFIG_SND_OMAP_SOC_ABE_DSP) ||\ defined(CONFIG_SND_OMAP_SOC_ABE_DSP_MODULE) @@ -776,8 +745,6 @@ static int __devexit asoc_mcpdm_remove(struct platform_device *pdev) struct omap_mcpdm *mcpdm = platform_get_drvdata(pdev); struct resource *res; - flush_delayed_work_sync(&mcpdm->delayed_work); - snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(omap_mcpdm_dai)); device_remove_file(&pdev->dev, &dev_attr_dl1); diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c index ad2cec2..8baa898 100644 --- a/sound/soc/omap/sdp4430.c +++ b/sound/soc/omap/sdp4430.c @@ -377,6 +377,26 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Digital Mic1 Bias", NULL, "Digital Mic 2"}, }; +static int sdp4430_mcpdm_twl6040_pre(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct twl6040 *twl6040 = codec->control_data; + + /* TWL6040 supplies McPDM PAD_CLKS */ + return twl6040_enable(twl6040); +} + +static void sdp4430_mcpdm_twl6040_post(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct twl6040 *twl6040 = codec->control_data; + + /* TWL6040 supplies McPDM PAD_CLKS */ + twl6040_disable(twl6040); +} + static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; @@ -686,6 +706,8 @@ static struct snd_soc_dai_link sdp4430_dai[] = { .codec_dai_name = "twl6040-dl1", .codec_name = "twl6040-codec", + .pre = sdp4430_mcpdm_twl6040_pre, + .post = sdp4430_mcpdm_twl6040_post, .ops = &sdp4430_mcpdm_ops, .ignore_suspend = 1, }, @@ -724,6 +746,8 @@ static struct snd_soc_dai_link sdp4430_dai[] = { .no_pcm = 1, /* don't create ALSA pcm for this */ .init = sdp4430_twl6040_init, + .pre = sdp4430_mcpdm_twl6040_pre, + .post = sdp4430_mcpdm_twl6040_post, .ops = &sdp4430_mcpdm_ops, .be_id = OMAP_ABE_DAI_PDM_DL1, }, @@ -757,6 +781,8 @@ static struct snd_soc_dai_link sdp4430_dai[] = { .no_pcm = 1, /* don't create ALSA pcm for this */ .init = sdp4430_twl6040_dl2_init, + .pre = sdp4430_mcpdm_twl6040_pre, + .post = sdp4430_mcpdm_twl6040_post, .ops = &sdp4430_mcpdm_ops, .be_id = OMAP_ABE_DAI_PDM_DL2, }, @@ -773,6 +799,8 @@ static struct snd_soc_dai_link sdp4430_dai[] = { .codec_name = "twl6040-codec", .no_pcm = 1, /* don't create ALSA pcm for this */ + .pre = sdp4430_mcpdm_twl6040_pre, + .post = sdp4430_mcpdm_twl6040_post, .ops = &sdp4430_mcpdm_ops, .be_id = OMAP_ABE_DAI_PDM_VIB, }, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 79a4135..b1398ee 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -561,13 +561,22 @@ int soc_pcm_open(struct snd_pcm_substream *substream) if (rtd->dai_link->no_host_mode == SND_SOC_DAI_LINK_NO_HOST) snd_soc_set_runtime_hwparams(substream, &no_host_hardware); + if (rtd->dai_link->pre) { + ret = rtd->dai_link->pre(substream); + if (ret < 0) { + printk(KERN_ERR "asoc: can't setup DAI link %s\n", + rtd->dai_link->name); + goto out; + } + } + /* startup the audio subsystem */ if (cpu_dai->driver->ops->startup) { ret = cpu_dai->driver->ops->startup(substream, cpu_dai); if (ret < 0) { printk(KERN_ERR "asoc: can't open interface %s\n", cpu_dai->name); - goto out; + goto cpu_err; } } @@ -712,6 +721,9 @@ codec_dai_err: platform_err: if (cpu_dai->driver->ops->shutdown) cpu_dai->driver->ops->shutdown(substream, cpu_dai); +cpu_err: + if (rtd->dai_link->post) + rtd->dai_link->post(substream); out: mutex_unlock(&rtd->pcm_mutex); return ret; @@ -790,6 +802,10 @@ int soc_pcm_close(struct snd_pcm_substream *substream) if (platform->driver->ops && platform->driver->ops->close) platform->driver->ops->close(substream); + + if (rtd->dai_link->post) + rtd->dai_link->post(substream); + cpu_dai->runtime = NULL; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7a58b48..a3ac2cb 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1498,7 +1498,8 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) trace_snd_soc_dapm_start(card); list_for_each_entry(d, &card->dapm_list, list) - if (d->n_widgets || d->codec == NULL) + if (d->n_widgets || d->codec == NULL || + strstr(d->codec->name, "null-codec")) d->dev_power = 0; /* Check which widgets we need to power and store them in @@ -2862,6 +2863,36 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm, dapm->stream_event(dapm); } +static void soc_dapm_platform_stream_event(struct snd_soc_platform *platform, + const char *stream, int event) +{ + soc_dapm_stream_event(&platform->dapm, stream, event); +} + +static void soc_dapm_codec_stream_event(struct snd_soc_codec *codec, + const char *stream, int event) +{ + soc_dapm_stream_event(&codec->dapm, stream, event); +} + +void snd_soc_dapm_platform_stream_event(struct snd_soc_platform *platform, + const char *stream, int event) +{ + mutex_lock(&platform->card->dapm_mutex); + soc_dapm_platform_stream_event(platform, stream, event); + mutex_unlock(&platform->card->dapm_mutex); +} +EXPORT_SYMBOL(snd_soc_dapm_platform_stream_event); + +void snd_soc_dapm_codec_stream_event(struct snd_soc_codec *codec, + const char *stream, int event) +{ + mutex_lock(&codec->card->dapm_mutex); + soc_dapm_codec_stream_event(codec, stream, event); + mutex_unlock(&codec->card->dapm_mutex); +} +EXPORT_SYMBOL(snd_soc_dapm_codec_stream_event); + /** * snd_soc_dapm_stream_event - send a stream event to the dapm core * @rtd: PCM runtime data @@ -2881,8 +2912,8 @@ int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd, mutex_lock(&rtd->card->dapm_mutex); - soc_dapm_stream_event(&rtd->platform->dapm, stream, event); - soc_dapm_stream_event(&rtd->codec->dapm, stream, event); + soc_dapm_platform_stream_event(rtd->platform, stream, event); + soc_dapm_codec_stream_event(rtd->codec, stream, event); mutex_unlock(&rtd->card->dapm_mutex); return 0; diff --git a/sound/soc/soc-dsp.c b/sound/soc/soc-dsp.c index 73d103c..9620fc4 100644 --- a/sound/soc/soc-dsp.c +++ b/sound/soc/soc-dsp.c @@ -37,6 +37,22 @@ int soc_pcm_prepare(struct snd_pcm_substream *); int soc_pcm_trigger(struct snd_pcm_substream *, int); int soc_pcm_bespoke_trigger(struct snd_pcm_substream *, int); +/* count the number of FE clients in a particular state */ +int soc_dsp_fe_state_count(struct snd_soc_pcm_runtime *be, int stream, + enum snd_soc_dsp_state state) +{ + struct snd_soc_dsp_params *dsp_params; + int count = 0; + + list_for_each_entry(dsp_params, &be->dsp[stream].fe_clients, list_fe) { + if (dsp_params->fe->dsp[stream].state == state) + count++; + } + + return count; +} +EXPORT_SYMBOL(soc_dsp_fe_state_count); + static inline int be_connect(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream) { @@ -71,15 +87,43 @@ static inline int be_connect(struct snd_soc_pcm_runtime *fe, return 1; } +static inline void be_reparent(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + struct snd_soc_dsp_params *dsp_params; + struct snd_pcm_substream *fe_substream, *be_substream; + + /* reparent if BE is connected to other FEs */ + if (!be->dsp[stream].users) + return; + + be_substream = snd_soc_dsp_get_substream(be, stream); + + list_for_each_entry(dsp_params, &be->dsp[stream].fe_clients, list_fe) { + if (dsp_params->fe != fe) { + fe_substream = snd_soc_dsp_get_substream(dsp_params->fe, + stream); + be_substream->runtime = fe_substream->runtime; + break; + } + } +} + static inline void be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dsp_params *dsp_params, *d; list_for_each_entry_safe(dsp_params, d, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; + if (dsp_params->state == SND_SOC_DSP_LINK_STATE_FREE) { dev_dbg(&fe->dev, " freed DSP %s path %s %s %s\n", - stream ? "capture" : "playback", fe->dai_link->name, - stream ? "<-" : "->", dsp_params->be->dai_link->name); + stream ? "capture" : "playback", + fe->dai_link->name, stream ? "<-" : "->", + be->dai_link->name); + + /* BEs still alive need new FE */ + be_reparent(fe, be, stream); #ifdef CONFIG_DEBUG_FS debugfs_remove(dsp_params->debugfs_state); @@ -287,32 +331,6 @@ out: } /* - * Update the state of all BE's with state old to state new. - */ -static void be_state_update(struct snd_soc_pcm_runtime *fe, int stream, - enum snd_soc_dsp_link_state old, enum snd_soc_dsp_link_state new) -{ - struct snd_soc_dsp_params *dsp_params; - - list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { - if (dsp_params->state == old) - dsp_params->state = new; - } -} - -/* - * Update the state of all BE's to new regardless of current state. - */ -static void fe_state_update(struct snd_soc_pcm_runtime *fe, int stream, - enum snd_soc_dsp_link_state new) -{ - struct snd_soc_dsp_params *dsp_params; - - list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) - dsp_params->state = new; -} - -/* * Clear the runtime pending state of all BE's. */ static void fe_clear_pending(struct snd_soc_pcm_runtime *fe, int stream) @@ -332,17 +350,20 @@ static void soc_dsp_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, int st /* disable any enabled and non active backends */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); - if (--dsp_params->be->dsp[stream].users != 0) + if (--be->dsp[stream].users != 0) continue; - if (dsp_params->state != SND_SOC_DSP_LINK_STATE_NEW) + if (be->dsp[stream].state != SND_SOC_DSP_STATE_OPEN) continue; soc_pcm_close(be_substream); be_substream->runtime = NULL; + + be->dsp[stream].state = SND_SOC_DSP_STATE_CLOSE; } } @@ -355,59 +376,57 @@ static int soc_dsp_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) /* only startup BE DAIs that are either sinks or sources to this FE DAI */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, stream)) + if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; /* first time the dsp_params is open ? */ - if (dsp_params->be->dsp[stream].users++ != 0) + if (be->dsp[stream].users++ != 0) continue; - /* only open and ref count new links */ - if (dsp_params->state != SND_SOC_DSP_LINK_STATE_NEW) + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_NEW) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_CLOSE)) continue; - dev_dbg(&dsp_params->be->dev, "dsp: open BE %s\n", - dsp_params->be->dai_link->name); + dev_dbg(&be->dev, "dsp: open BE %s\n", be->dai_link->name); - be_substream->runtime = dsp_params->be->dsp[stream].runtime; + be_substream->runtime = be->dsp[stream].runtime; err = soc_pcm_open(be_substream); if (err < 0) goto unwind; + + be->dsp[stream].state = SND_SOC_DSP_STATE_OPEN; count++; } - /* update BE state */ - be_state_update(fe, stream, - SND_SOC_DSP_LINK_STATE_NEW, SND_SOC_DSP_LINK_STATE_HW_PARAMS); return count; unwind: /* disable any enabled and non active backends */ list_for_each_entry_continue_reverse(dsp_params, &fe->dsp[stream].be_clients, list_be) { - + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, stream)) + if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - if (--dsp_params->be->dsp[stream].users != 0) + if (--be->dsp[stream].users != 0) continue; - if (dsp_params->state != SND_SOC_DSP_LINK_STATE_NEW) + if (be->dsp[stream].state != SND_SOC_DSP_STATE_OPEN) continue; soc_pcm_close(be_substream); be_substream->runtime = NULL; + + be->dsp[stream].state = SND_SOC_DSP_STATE_CLOSE; } - /* update BE state for disconnect */ - be_state_update(fe, stream, - SND_SOC_DSP_LINK_STATE_NEW, SND_SOC_DSP_LINK_STATE_FREE); return err; } @@ -459,6 +478,8 @@ static int soc_dsp_fe_dai_startup(struct snd_pcm_substream *fe_substream) goto unwind; } + fe->dsp[stream].state = SND_SOC_DSP_STATE_OPEN; + soc_dsp_set_dynamic_runtime(fe_substream); snd_pcm_limit_hw_rates(runtime); @@ -481,24 +502,28 @@ static int soc_dsp_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream) /* only shutdown backends that are either sinks or sources to this frontend DAI */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, stream)) + if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - if (--dsp_params->be->dsp[stream].users != 0) + if (--be->dsp[stream].users != 0) continue; - if (dsp_params->state != SND_SOC_DSP_LINK_STATE_FREE) + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_HW_FREE) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_OPEN)) continue; - dev_dbg(&dsp_params->be->dev, "dsp: close BE %s\n", + dev_dbg(&be->dev, "dsp: close BE %s\n", dsp_params->fe->dai_link->name); soc_pcm_close(be_substream); be_substream->runtime = NULL; + + be->dsp[stream].state = SND_SOC_DSP_STATE_CLOSE; } return 0; } @@ -532,6 +557,7 @@ static int soc_dsp_fe_dai_shutdown(struct snd_pcm_substream *substream) fe->cpu_dai->driver->capture.stream_name, SND_SOC_DAPM_STREAM_STOP); + fe->dsp[stream].state = SND_SOC_DSP_STATE_CLOSE; fe->dsp[stream].runtime_update = runtime_update; mutex_unlock(&fe->card->dsp_mutex); @@ -545,21 +571,23 @@ static int soc_dsp_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, stream)) + if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - if (dsp_params->state != SND_SOC_DSP_LINK_STATE_HW_PARAMS) + /* first time the dsp_params is open ? */ + if (be->dsp[stream].users != 1) continue; - /* first time the dsp_params is open ? */ - if (dsp_params->be->dsp[stream].users != 1) + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_OPEN) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_HW_FREE)) continue; - dev_dbg(&dsp_params->be->dev, "dsp: hw_params BE %s\n", + dev_dbg(&be->dev, "dsp: hw_params BE %s\n", dsp_params->fe->dai_link->name); /* copy params for each dsp_params */ @@ -567,12 +595,13 @@ static int soc_dsp_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) sizeof(struct snd_pcm_hw_params)); /* perform any hw_params fixups */ - if (dsp_params->be->dai_link->be_hw_params_fixup) { - ret = dsp_params->be->dai_link->be_hw_params_fixup(dsp_params->be, + if (be->dai_link->be_hw_params_fixup) { + ret = be->dai_link->be_hw_params_fixup(be, &dsp_params->params); if (ret < 0) { - dev_err(&dsp_params->be->dev, - "dsp: hw_params BE fixup failed %d\n", ret); + dev_err(&be->dev, + "dsp: hw_params BE fixup failed %d\n", + ret); return ret; } } @@ -582,6 +611,8 @@ static int soc_dsp_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) dev_err(&dsp_params->be->dev, "dsp: hw_params BE failed %d\n", ret); return ret; } + + be->dsp[stream].state = SND_SOC_DSP_STATE_HW_PARAMS; } return 0; } @@ -610,6 +641,8 @@ int soc_dsp_fe_dai_hw_params(struct snd_pcm_substream *substream, if (ret < 0) dev_err(&fe->dev,"dsp: hw_params FE failed %d\n", ret); + fe->dsp[stream].state = SND_SOC_DSP_STATE_HW_PARAMS; + out: fe->dsp[stream].runtime_update = runtime_update; mutex_unlock(&fe->card->dsp_mutex); @@ -638,52 +671,45 @@ int soc_dsp_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, int cmd) list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, stream)) + if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; switch (cmd) { case SNDRV_PCM_TRIGGER_START: - /* only start BEs that are not triggered */ - if (dsp_params->state == SND_SOC_DSP_LINK_STATE_PREPARE) { - ret = dsp_do_trigger(dsp_params, be_substream, cmd); - if (ret == 0) - dsp_params->state = SND_SOC_DSP_LINK_STATE_START; - } - break; - case SNDRV_PCM_TRIGGER_STOP: - /* only stop BEs that are being shutdown */ - if (dsp_params->state == SND_SOC_DSP_LINK_STATE_FREE && - dsp_params->be->dsp[stream].users == 1) - ret = dsp_do_trigger(dsp_params, be_substream, cmd); - break; - case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_RESUME: - /* suspend and resume all BEs */ - ret = dsp_do_trigger(dsp_params, be_substream, cmd); - break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - /* only release Paused BEs */ - if (dsp_params->state == SND_SOC_DSP_LINK_STATE_PAUSED) { - ret = dsp_do_trigger(dsp_params, be_substream, cmd); - if (ret == 0) - dsp_params->state = SND_SOC_DSP_LINK_STATE_START; - } + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_PREPARE) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_STOP)) + continue; + + ret = dsp_do_trigger(dsp_params, be_substream, cmd); + if (ret) + return ret; + + be->dsp[stream].state = SND_SOC_DSP_STATE_START; break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - /* only pause active BEs */ - if (dsp_params->state == SND_SOC_DSP_LINK_STATE_START) { - ret = dsp_do_trigger(dsp_params, be_substream, cmd); - if (ret == 0) - dsp_params->state = SND_SOC_DSP_LINK_STATE_PAUSED; - } + if (be->dsp[stream].state != SND_SOC_DSP_STATE_START) + continue; + + if (soc_dsp_fe_state_count(be, stream, + SND_SOC_DSP_STATE_START) > 1) + continue; + + ret = dsp_do_trigger(dsp_params, be_substream, cmd); + if (ret) + return ret; + + be->dsp[stream].state = SND_SOC_DSP_STATE_STOP; break; } - if (ret < 0) - return ret; } return ret; @@ -744,6 +770,19 @@ int soc_dsp_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) dev_err(&fe->dev, "dsp: invalid trigger cmd %d for %s\n", cmd, fe->dai_link->name); ret = -EINVAL; + goto out; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + fe->dsp[stream].state = SND_SOC_DSP_STATE_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fe->dsp[stream].state = SND_SOC_DSP_STATE_STOP; break; } @@ -759,31 +798,29 @@ static int soc_dsp_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, stream)) + if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - /* only prepare ACTIVE or READY BE's */ - if (dsp_params->state == SND_SOC_DSP_LINK_STATE_NEW || - dsp_params->state == SND_SOC_DSP_LINK_STATE_FREE) + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_HW_PARAMS) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_STOP)) continue; - dev_dbg(&dsp_params->be->dev, "dsp: prepare BE %s\n", + dev_dbg(&be->dev, "dsp: prepare BE %s\n", dsp_params->fe->dai_link->name); ret = soc_pcm_prepare(be_substream); if (ret < 0) { - dev_err(&dsp_params->be->dev,"dsp: backend prepare failed %d\n", - ret); + dev_err(&be->dev, "dsp: backend prepare failed %d\n", + ret); break; } - /* mark the BE as active */ - be_state_update(fe, stream, SND_SOC_DSP_LINK_STATE_HW_PARAMS, - SND_SOC_DSP_LINK_STATE_PREPARE); + be->dsp[stream].state = SND_SOC_DSP_STATE_PREPARE; } return ret; } @@ -812,9 +849,6 @@ int soc_dsp_fe_dai_prepare(struct snd_pcm_substream *substream) if (ret < 0) goto out; - /* mark the BE as active */ - fe_state_update(fe, stream, SND_SOC_DSP_LINK_STATE_PREPARE); - /* call prepare on the frontend */ ret = soc_pcm_prepare(substream); if (ret < 0) { @@ -832,6 +866,8 @@ int soc_dsp_fe_dai_prepare(struct snd_pcm_substream *substream) fe->cpu_dai->driver->capture.stream_name, SNDRV_PCM_TRIGGER_START); + fe->dsp[stream].state = SND_SOC_DSP_STATE_PREPARE; + out: fe->dsp[stream].runtime_update = runtime_update; mutex_unlock(&fe->card->dsp_mutex); @@ -846,24 +882,29 @@ static int soc_dsp_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) * to this frontend DAI */ list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { + struct snd_soc_pcm_runtime *be = dsp_params->be; struct snd_pcm_substream *be_substream = - snd_soc_dsp_get_substream(dsp_params->be, stream); + snd_soc_dsp_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, stream)) + if (!snd_soc_dsp_is_op_for_be(fe, be, stream)) continue; - if (dsp_params->state != SND_SOC_DSP_LINK_STATE_FREE) + /* only free hw when no longer used */ + if (be->dsp[stream].users != 1) continue; - /* only free hw when no longer used */ - if (dsp_params->be->dsp[stream].users != 1) + if ((be->dsp[stream].state != SND_SOC_DSP_STATE_HW_PARAMS) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_PREPARE) && + (be->dsp[stream].state != SND_SOC_DSP_STATE_STOP)) continue; - dev_dbg(&dsp_params->be->dev, "dsp: hw_free BE %s\n", + dev_dbg(&be->dev, "dsp: hw_free BE %s\n", dsp_params->fe->dai_link->name); soc_pcm_hw_free(be_substream); + + be->dsp[stream].state = SND_SOC_DSP_STATE_HW_FREE; } return 0; @@ -879,8 +920,6 @@ int soc_dsp_fe_dai_hw_free(struct snd_pcm_substream *substream) runtime_update = fe->dsp[stream].runtime_update; fe->dsp[stream].runtime_update = SND_SOC_DSP_UPDATE_FE; - fe_state_update(fe, stream, SND_SOC_DSP_LINK_STATE_FREE); - dev_dbg(&fe->dev, "dsp: hw_free FE %s\n", fe->dai_link->name); /* call hw_free on the frontend */ @@ -892,6 +931,7 @@ int soc_dsp_fe_dai_hw_free(struct snd_pcm_substream *substream) * to this frontend DAI */ ret = soc_dsp_be_dai_hw_free(fe, stream); + fe->dsp[stream].state = SND_SOC_DSP_STATE_HW_FREE; fe->dsp[stream].runtime_update = runtime_update; mutex_unlock(&fe->card->dsp_mutex); @@ -924,7 +964,6 @@ static int dsp_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dsp_link *dsp_link = fe->dai_link->dsp_link; struct snd_pcm_substream *substream = snd_soc_dsp_get_substream(fe, stream); - struct snd_soc_dsp_params *dsp_params; int ret; dev_dbg(&fe->dev, "runtime %s close on FE %s\n", @@ -941,16 +980,12 @@ static int dsp_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) return ret; } } else { + dev_dbg(&fe->dev, "dsp: trigger FE %s cmd stop\n", + fe->dai_link->name); - list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { - - dev_dbg(&fe->dev, "dsp: trigger FE %s cmd stop\n", - fe->dai_link->name); - - ret = soc_dsp_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); - if (ret < 0) - return ret; - } + ret = soc_dsp_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + return ret; } ret = soc_dsp_be_dai_hw_free(fe, stream); @@ -974,44 +1009,10 @@ static int dsp_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) return 0; } -/* check for running FEs */ -static int dsp_get_be_trigger_cmd(struct snd_soc_pcm_runtime *fe, int stream) -{ - struct snd_soc_dsp_params *dsp_be_params, *dsp_fe_params; - - /* get the FEs for each BE */ - list_for_each_entry(dsp_be_params, &fe->dsp[stream].be_clients, list_be) { - struct snd_soc_pcm_runtime *be = dsp_be_params->be; - - /* get the FEs for this BE */ - list_for_each_entry(dsp_fe_params, &be->dsp[stream].fe_clients, list_fe) { - - if (dsp_fe_params->state == SND_SOC_DSP_LINK_STATE_START) - return SND_SOC_DSP_LINK_STATE_START; - } - } - return SND_SOC_DSP_LINK_STATE_PAUSED; -} - -/* check for running BEs */ -static int dsp_get_fe_trigger_cmd(struct snd_soc_pcm_runtime *fe, int stream) -{ - struct snd_soc_dsp_params *dsp_be_params; - - /* get the FEs for each BE */ - list_for_each_entry(dsp_be_params, &fe->dsp[stream].be_clients, list_be) { - - if (dsp_be_params->state == SND_SOC_DSP_LINK_STATE_START) - return SND_SOC_DSP_LINK_STATE_START; - } - return SND_SOC_DSP_LINK_STATE_PAUSED; -} - static int dsp_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dsp_link *dsp_link = fe->dai_link->dsp_link; struct snd_pcm_substream *substream = snd_soc_dsp_get_substream(fe, stream); - struct snd_soc_dsp_params *dsp_params; int ret, cmd; dev_dbg(&fe->dev, "runtime %s open on FE %s\n", @@ -1039,11 +1040,13 @@ static int dsp_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) fe->cpu_dai->driver->capture.stream_name, SNDRV_PCM_TRIGGER_START); - if (dsp_link->trigger[stream] == SND_SOC_DSP_TRIGGER_BESPOKE) { - - /* there is no point in triggering START iff all BEs are PAUSED */ - cmd = dsp_get_fe_trigger_cmd(fe, stream); + /* determine trigger command */ + if (fe->dsp[stream].state == SND_SOC_DSP_STATE_START) + cmd = SNDRV_PCM_TRIGGER_START; + else + cmd = SNDRV_PCM_TRIGGER_STOP; + if (dsp_link->trigger[stream] == SND_SOC_DSP_TRIGGER_BESPOKE) { /* call trigger on the frontend - FE takes care of all BE triggers */ dev_dbg(&fe->dev, "dsp: bespoke trigger FE %s cmd start\n", fe->dai_link->name); @@ -1053,42 +1056,13 @@ static int dsp_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) dev_err(&fe->dev,"dsp: trigger FE failed %d\n", ret); return ret; } - - /* successful trigger so update BE trigger status */ - list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { - - /* is this op for this BE ? */ - if (!snd_soc_dsp_is_op_for_be(fe, dsp_params->be, - stream)) - continue; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: - dsp_params->state = SND_SOC_DSP_LINK_STATE_START; - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - dsp_params->state = SND_SOC_DSP_LINK_STATE_PAUSED; - break; - } - } } else { + dev_dbg(&fe->dev, "dsp: trigger FE %s cmd start\n", + fe->dai_link->name); - list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) { - - /* there is no point in triggering start iff all FEs are PAUSED */ - cmd = dsp_get_be_trigger_cmd(fe, stream); - - dev_dbg(&fe->dev, "dsp: trigger FE %s cmd start\n", - fe->dai_link->name); - - ret = soc_dsp_be_dai_trigger(fe, stream, cmd); - if (ret < 0) - return ret; - } + ret = soc_dsp_be_dai_trigger(fe, stream, cmd); + if (ret < 0) + return ret; } return 0; @@ -1547,13 +1521,18 @@ int soc_dsp_fe_dai_open(struct snd_pcm_substream *fe_substream) int soc_dsp_fe_dai_close(struct snd_pcm_substream *fe_substream) { struct snd_soc_pcm_runtime *fe = fe_substream->private_data; - int ret; + struct snd_soc_dsp_params *dsp_params; + int stream = fe_substream->stream, ret; ret = soc_dsp_fe_dai_shutdown(fe_substream); - be_disconnect(fe, fe_substream->stream); + /* mark FE's links ready to prune */ + list_for_each_entry(dsp_params, &fe->dsp[stream].be_clients, list_be) + dsp_params->state = SND_SOC_DSP_LINK_STATE_FREE; + + be_disconnect(fe, stream); - fe->dsp[fe_substream->stream].runtime = NULL; + fe->dsp[stream].runtime = NULL; return ret; } |