diff options
Diffstat (limited to 'sound/soc/omap/omap-mcpdm.c')
-rw-r--r-- | sound/soc/omap/omap-mcpdm.c | 832 |
1 files changed, 763 insertions, 69 deletions
diff --git a/sound/soc/omap/omap-mcpdm.c b/sound/soc/omap/omap-mcpdm.c index bed09c27e44..a68546d228a 100644 --- a/sound/soc/omap/omap-mcpdm.c +++ b/sound/soc/omap/omap-mcpdm.c @@ -25,7 +25,17 @@ #include <linux/init.h> #include <linux/module.h> -#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <linux/workqueue.h> + #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -33,35 +43,62 @@ #include <sound/soc.h> #include <plat/dma.h> +#include <plat/mcpdm.h> #include <plat/mcbsp.h> -#include "mcpdm.h" +#include <plat/omap_hwmod.h> +#include "omap-mcpdm.h" #include "omap-pcm.h" +#ifdef CONFIG_SND_OMAP_SOC_ABE_DSP +#include "omap-abe-dsp.h" +#include "abe/abe_main.h" +#endif struct omap_mcpdm_data { struct omap_mcpdm_link *links; - int active; +}; + +struct omap_mcpdm { + struct device *dev; + unsigned long phys_base; + void __iomem *io_base; + u8 free; + int irq; + struct delayed_work delayed_work; +#ifdef CONFIG_SND_OMAP_SOC_ABE_DSP + struct delayed_work delayed_abe_work; +#endif + + struct mutex mutex; + struct omap_mcpdm_platform_data *pdata; + struct omap_mcpdm_link *downlink; + struct omap_mcpdm_link *uplink; + struct completion irq_completion; + + int dn_channels; + int up_channels; + int dl_active; + int ul_active; + + /* DC offset */ + int dl1_offset; + int dl2_offset; }; static struct omap_mcpdm_link omap_mcpdm_links[] = { /* downlink */ { .irq_mask = MCPDM_DN_IRQ_EMPTY | MCPDM_DN_IRQ_FULL, - .threshold = 1, + .threshold = 2, .format = PDMOUTFORMAT_LJUST, }, /* uplink */ { .irq_mask = MCPDM_UP_IRQ_EMPTY | MCPDM_UP_IRQ_FULL, - .threshold = 1, + .threshold = 2, .format = PDMOUTFORMAT_LJUST, }, }; -static struct omap_mcpdm_data mcpdm_data = { - .links = omap_mcpdm_links, - .active = 0, -}; - /* * Stream DMA parameters */ @@ -84,13 +121,413 @@ static struct omap_pcm_dma_data omap_mcpdm_dai_dma_params[] = { }, }; +static inline void omap_mcpdm_write(struct omap_mcpdm *mcpdm, + u16 reg, u32 val) +{ + __raw_writel(val, mcpdm->io_base + reg); +} + +static inline int omap_mcpdm_read(struct omap_mcpdm *mcpdm, u16 reg) +{ + return __raw_readl(mcpdm->io_base + reg); +} + +#ifdef DEBUG +static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) +{ + dev_dbg(mcpdm->dev, "***********************\n"); + dev_dbg(mcpdm->dev, "IRQSTATUS_RAW: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_IRQSTATUS_RAW)); + dev_dbg(mcpdm->dev, "IRQSTATUS: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_IRQSTATUS)); + dev_dbg(mcpdm->dev, "IRQENABLE_SET: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_IRQENABLE_SET)); + dev_dbg(mcpdm->dev, "IRQENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_IRQENABLE_CLR)); + dev_dbg(mcpdm->dev, "IRQWAKE_EN: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_IRQWAKE_EN)); + dev_dbg(mcpdm->dev, "DMAENABLE_SET: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_DMAENABLE_SET)); + dev_dbg(mcpdm->dev, "DMAENABLE_CLR: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_DMAENABLE_CLR)); + dev_dbg(mcpdm->dev, "DMAWAKEEN: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_DMAWAKEEN)); + dev_dbg(mcpdm->dev, "CTRL: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_CTRL)); + dev_dbg(mcpdm->dev, "DN_DATA: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_DN_DATA)); + dev_dbg(mcpdm->dev, "UP_DATA: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_UP_DATA)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_DN: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_FIFO_CTRL_DN)); + dev_dbg(mcpdm->dev, "FIFO_CTRL_UP: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_FIFO_CTRL_UP)); + dev_dbg(mcpdm->dev, "DN_OFFSET: 0x%04x\n", + omap_mcpdm_read(mcpdm, MCPDM_DN_OFFSET)); + dev_dbg(mcpdm->dev, "***********************\n"); +} +#else +static void omap_mcpdm_reg_dump(struct omap_mcpdm *mcpdm) {} +#endif + +/* + * Takes the McPDM module in and out of reset state. + * Uplink and downlink can be reset individually. + */ +static void omap_mcpdm_reset_capture(struct omap_mcpdm * mcpdm, + int reset) +{ + int ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); + + if (reset) + ctrl |= SW_UP_RST; + else + ctrl &= ~SW_UP_RST; + + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); +} + +static void omap_mcpdm_reset_playback(struct omap_mcpdm * mcpdm, + int reset) +{ + int ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); + + if (reset) + ctrl |= SW_DN_RST; + else + ctrl &= ~SW_DN_RST; + + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); +} + +/* + * Enables the transfer through the PDM interface to/from the Phoenix + * codec by enabling the corresponding UP or DN channels. + */ +static void omap_mcpdm_start(struct omap_mcpdm *mcpdm, int stream) +{ + int ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); + + if (stream) { + ctrl |= SW_UP_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl |= mcpdm->up_channels; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl &= ~SW_UP_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + } else { + ctrl |= SW_DN_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl |= mcpdm->dn_channels; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl &= ~SW_DN_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + } +} + +/* + * Disables the transfer through the PDM interface to/from the Phoenix + * codec by disabling the corresponding UP or DN channels. + */ +static void omap_mcpdm_stop(struct omap_mcpdm *mcpdm, int stream) +{ + int ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); + + if (stream) { + ctrl |= SW_UP_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl &= ~mcpdm->up_channels; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl &= ~SW_UP_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + } else { + ctrl |= SW_DN_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl &= ~mcpdm->dn_channels; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + ctrl &= ~SW_DN_RST; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + } +} + +/* + * Configures McPDM uplink for audio recording. + * This function should be called before omap_mcpdm_start. + */ +static int omap_mcpdm_capture_open(struct omap_mcpdm *mcpdm, + struct omap_mcpdm_link *uplink) +{ + int irq_mask = 0; + int ctrl; + + /* Enable irq request generation */ + irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; + omap_mcpdm_write(mcpdm, MCPDM_IRQENABLE_SET, irq_mask); + + /* Configure uplink threshold */ + if (uplink->threshold > UP_THRES_MAX) + uplink->threshold = UP_THRES_MAX; + + omap_mcpdm_write(mcpdm, MCPDM_FIFO_CTRL_UP, uplink->threshold); + + /* Configure DMA controller */ + omap_mcpdm_write(mcpdm, MCPDM_DMAENABLE_SET, DMA_UP_ENABLE); + + /* Set pdm out format */ + ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); + ctrl &= ~PDMOUTFORMAT; + ctrl |= uplink->format & PDMOUTFORMAT; + + /* Uplink channels */ + mcpdm->up_channels = uplink->channels & (PDM_UP_MASK | PDM_STATUS_MASK); + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + + return 0; +} + +/* + * Configures McPDM downlink for audio playback. + * This function should be called before omap_mcpdm_start. + */ +static int omap_mcpdm_playback_open(struct omap_mcpdm *mcpdm, + struct omap_mcpdm_link *downlink) +{ + int irq_mask = 0; + int ctrl; + + /* Enable irq request generation */ + irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; + omap_mcpdm_write(mcpdm, MCPDM_IRQENABLE_SET, irq_mask); + + /* Configure uplink threshold */ + if (downlink->threshold > DN_THRES_MAX) + downlink->threshold = DN_THRES_MAX; + + omap_mcpdm_write(mcpdm, MCPDM_FIFO_CTRL_DN, downlink->threshold); + + /* Enable DMA request generation */ + omap_mcpdm_write(mcpdm, MCPDM_DMAENABLE_SET, DMA_DN_ENABLE); + + /* Set pdm out format */ + ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); + ctrl &= ~PDMOUTFORMAT; + ctrl |= downlink->format & PDMOUTFORMAT; + + /* Downlink channels */ + mcpdm->dn_channels = downlink->channels & (PDM_DN_MASK | PDM_CMD_MASK); + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + + return 0; +} + +/* + * Cleans McPDM uplink configuration. + * This function should be called when the stream is closed. + */ +static int omap_mcpdm_capture_close(struct omap_mcpdm *mcpdm, + struct omap_mcpdm_link *uplink) +{ + int irq_mask = 0; + + /* Disable irq request generation */ + irq_mask |= uplink->irq_mask & MCPDM_UPLINK_IRQ_MASK; + omap_mcpdm_write(mcpdm, MCPDM_IRQENABLE_CLR, irq_mask); + + /* Disable DMA request generation */ + omap_mcpdm_write(mcpdm, MCPDM_DMAENABLE_CLR, DMA_UP_ENABLE); + + /* Clear Downlink channels */ + mcpdm->up_channels = 0; + + return 0; +} + +/* + * Cleans McPDM downlink configuration. + * This function should be called when the stream is closed. + */ +static int omap_mcpdm_playback_close(struct omap_mcpdm *mcpdm, + struct omap_mcpdm_link *downlink) +{ + int irq_mask = 0; + + /* Disable irq request generation */ + irq_mask |= downlink->irq_mask & MCPDM_DOWNLINK_IRQ_MASK; + omap_mcpdm_write(mcpdm, MCPDM_IRQENABLE_CLR, irq_mask); + + /* Disable DMA request generation */ + omap_mcpdm_write(mcpdm, MCPDM_DMAENABLE_CLR, DMA_DN_ENABLE); + + /* clear Downlink channels */ + mcpdm->dn_channels = 0; + + return 0; +} + +static irqreturn_t omap_mcpdm_irq_handler(int irq, void *dev_id) +{ + struct omap_mcpdm *mcpdm = dev_id; + int irq_status; + + irq_status = omap_mcpdm_read(mcpdm, MCPDM_IRQSTATUS); + + /* Acknowledge irq event */ + omap_mcpdm_write(mcpdm, MCPDM_IRQSTATUS, irq_status); + + if (irq & MCPDM_DN_IRQ_FULL) { + dev_err(mcpdm->dev, "DN FIFO error %x\n", irq_status); + omap_mcpdm_reset_playback(mcpdm, 1); + omap_mcpdm_playback_open(mcpdm, mcpdm->downlink); + omap_mcpdm_reset_playback(mcpdm, 0); + } + + if (irq & MCPDM_DN_IRQ_EMPTY) { + dev_err(mcpdm->dev, "DN FIFO error %x\n", irq_status); + omap_mcpdm_reset_playback(mcpdm, 1); + omap_mcpdm_playback_open(mcpdm, mcpdm->downlink); + omap_mcpdm_reset_playback(mcpdm, 0); + } + + if (irq & MCPDM_DN_IRQ) { + dev_dbg(mcpdm->dev, "DN write request\n"); + } + + if (irq & MCPDM_UP_IRQ_FULL) { + dev_err(mcpdm->dev, "UP FIFO error %x\n", irq_status); + omap_mcpdm_reset_capture(mcpdm, 1); + omap_mcpdm_capture_open(mcpdm, mcpdm->uplink); + omap_mcpdm_reset_capture(mcpdm, 0); + } + + if (irq & MCPDM_UP_IRQ_EMPTY) { + dev_err(mcpdm->dev, "UP FIFO error %x\n", irq_status); + omap_mcpdm_reset_capture(mcpdm, 1); + omap_mcpdm_capture_open(mcpdm, mcpdm->uplink); + omap_mcpdm_reset_capture(mcpdm, 0); + } + + if (irq & MCPDM_UP_IRQ) { + dev_dbg(mcpdm->dev, "UP write request\n"); + } + + return IRQ_HANDLED; +} + +static int omap_mcpdm_request(struct omap_mcpdm *mcpdm) +{ + struct platform_device *pdev; + struct omap_mcpdm_platform_data *pdata; + int ctrl; + int attemps = 0; + + pdev = to_platform_device(mcpdm->dev); + pdata = pdev->dev.platform_data; + + if (!mcpdm->free) { + dev_err(mcpdm->dev, "McPDM interface is in use\n"); + return -EBUSY; + } + mcpdm->free = 0; + + pm_runtime_get_sync(&pdev->dev); + + /* Perform SW RESET of McPDM IP */ + ctrl = omap_mcpdm_read(mcpdm, MCPDM_SYSCONFIG); + ctrl |= MCPDM_SOFTRESET; + omap_mcpdm_write(mcpdm, MCPDM_SYSCONFIG, ctrl); + /* Wait completion of SW RESET */ + while ((omap_mcpdm_read(mcpdm, MCPDM_SYSCONFIG) & MCPDM_SOFTRESET)) { + if (attemps++ > 10000) { + udelay(10); + dev_err(mcpdm->dev, "Could not RESET McPDM\n"); + } + } + + /* Disable lines while request is ongoing */ + omap_mcpdm_write(mcpdm, MCPDM_CTRL, 0x00); + + if (omap_rev() != OMAP4430_REV_ES1_0) { + /* Enable McPDM watch dog for ES above ES 1.0 to avoid saturation */ + ctrl = omap_mcpdm_read(mcpdm, MCPDM_CTRL); + ctrl |= WD_EN; + omap_mcpdm_write(mcpdm, MCPDM_CTRL, ctrl); + } + + return 0; +} + +static void omap_mcpdm_free(struct omap_mcpdm *mcpdm) +{ + struct platform_device *pdev; + struct omap_mcpdm_platform_data *pdata; + + pdev = to_platform_device(mcpdm->dev); + pdata = pdev->dev.platform_data; + + if (mcpdm->free) { + dev_err(mcpdm->dev, "McPDM interface is already free\n"); + return; + } + mcpdm->free = 1; + + pm_runtime_put_sync(&pdev->dev); +} + +/* Enable/disable DC offset cancelation for the analog + * headset path (PDM channels 1 and 2). + */ +static int omap_mcpdm_set_offset(struct omap_mcpdm *mcpdm) +{ + int offset; + + if ((mcpdm->dl1_offset > DN_OFST_MAX) || + (mcpdm->dl2_offset > DN_OFST_MAX)) + return -EINVAL; + + offset = (mcpdm->dl1_offset << DN_OFST_RX1) | + (mcpdm->dl2_offset << DN_OFST_RX2); + + /* offset cancellation for channel 1 */ + if (mcpdm->dl1_offset) + offset |= DN_OFST_RX1_EN; + else + offset &= ~DN_OFST_RX1_EN; + + /* offset cancellation for channel 2 */ + if (mcpdm->dl2_offset) + offset |= DN_OFST_RX2_EN; + else + offset &= ~DN_OFST_RX2_EN; + + omap_mcpdm_write(mcpdm, MCPDM_DN_OFFSET, offset); + + return 0; +} + static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); int err = 0; - if (!dai->active) - err = omap_mcpdm_request(); + dev_dbg(dai->dev, "%s: active %d\n", __func__, dai->active); + + mutex_lock(&mcpdm->mutex); + + /* make sure we stop any pre-existing shutdown */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cancel_delayed_work(&mcpdm->delayed_work); + + if (!dai->active && mcpdm->free) { + err = omap_mcpdm_request(mcpdm); + if (err) { + mutex_unlock(&mcpdm->mutex); + return err; + } + omap_mcpdm_set_offset(mcpdm); + } + + mutex_unlock(&mcpdm->mutex); return err; } @@ -98,44 +535,45 @@ static int omap_mcpdm_dai_startup(struct snd_pcm_substream *substream, static void omap_mcpdm_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - if (!dai->active) - omap_mcpdm_free(); + 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 (!dai->active) { + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + omap_mcpdm_capture_close(mcpdm, mcpdm->uplink); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + schedule_delayed_work(&mcpdm->delayed_work, + msecs_to_jiffies(1000)); /* TODO: pdata ? */ + } + + mutex_unlock(&mcpdm->mutex); } -static int omap_mcpdm_dai_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) +/* work to delay McPDM shutdown */ +static void playback_work(struct work_struct *work) { - struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai); - int stream = substream->stream; - int err = 0; + struct omap_mcpdm *mcpdm = + container_of(work, struct omap_mcpdm, delayed_work.work); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (!mcpdm_priv->active++) - omap_mcpdm_start(stream); - break; + mutex_lock(&mcpdm->mutex); - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (!--mcpdm_priv->active) - omap_mcpdm_stop(stream); - break; - default: - err = -EINVAL; - } + if (!mcpdm->dl_active) + omap_mcpdm_playback_close(mcpdm, mcpdm->downlink); - return err; + if (!mcpdm->free && !mcpdm->dl_active && !mcpdm->ul_active) + omap_mcpdm_free(mcpdm); + + 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) { - struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai); - struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links; + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); int stream = substream->stream; int channels, err, link_mask = 0; @@ -165,57 +603,250 @@ static int omap_mcpdm_dai_hw_params(struct snd_pcm_substream *substream, } if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - mcpdm_links[stream].channels = link_mask << 3; - err = omap_mcpdm_playback_open(&mcpdm_links[stream]); + mcpdm->downlink->channels = link_mask << 3; + err = omap_mcpdm_playback_open(mcpdm, &omap_mcpdm_links[0]); } else { - mcpdm_links[stream].channels = link_mask << 0; - err = omap_mcpdm_capture_open(&mcpdm_links[stream]); + mcpdm->uplink->channels = link_mask << 0; + err = omap_mcpdm_capture_open(mcpdm, &omap_mcpdm_links[1]); } return err; } -static int omap_mcpdm_dai_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int omap_mcpdm_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) { - struct omap_mcpdm_data *mcpdm_priv = snd_soc_dai_get_drvdata(dai); - struct omap_mcpdm_link *mcpdm_links = mcpdm_priv->links; + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); int stream = substream->stream; - int err; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - err = omap_mcpdm_playback_close(&mcpdm_links[stream]); - else - err = omap_mcpdm_capture_close(&mcpdm_links[stream]); + dev_dbg(dai->dev, "cmd %d\n", cmd); + omap_mcpdm_reg_dump(mcpdm); - return err; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + omap_mcpdm_start(mcpdm, stream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + case SNDRV_PCM_TRIGGER_STOP: + omap_mcpdm_stop(mcpdm, stream); + break; + default: + break; + } + return 0; } static struct snd_soc_dai_ops omap_mcpdm_dai_ops = { .startup = omap_mcpdm_dai_startup, .shutdown = omap_mcpdm_dai_shutdown, - .trigger = omap_mcpdm_dai_trigger, .hw_params = omap_mcpdm_dai_hw_params, - .hw_free = omap_mcpdm_dai_hw_free, + .trigger = omap_mcpdm_dai_trigger, }; -#define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) -#define OMAP_MCPDM_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +#ifdef CONFIG_SND_OMAP_SOC_ABE_DSP +static int omap_mcpdm_abe_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + mutex_lock(&mcpdm->mutex); + + /* make sure we stop any pre-existing shutdown */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cancel_delayed_work(&mcpdm->delayed_abe_work); + } + + if (!dai->active && mcpdm->free) { + ret = omap_mcpdm_request(mcpdm); + if (ret) { + mutex_unlock(&mcpdm->mutex); + return ret; + } + omap_mcpdm_set_offset(mcpdm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mcpdm->dl_active++; + else + mcpdm->ul_active++; + + mutex_unlock(&mcpdm->mutex); + + return ret; +} + +static void omap_mcpdm_abe_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 (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mcpdm->dl_active--; + else + mcpdm->ul_active--; + + if (!dai->active) { + if (!mcpdm->ul_active && substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + omap_mcpdm_capture_close(mcpdm, mcpdm->uplink); + if (!mcpdm->free && !mcpdm->dn_channels && + !mcpdm->dl_active) + omap_mcpdm_free(mcpdm); + } + if (!mcpdm->dl_active && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + schedule_delayed_work(&mcpdm->delayed_abe_work, + msecs_to_jiffies(1000)); /* TODO: pdata ? */ + } + + mutex_unlock(&mcpdm->mutex); + +} + +/* work to delay McPDM shutdown */ +static void playback_abe_work(struct work_struct *work) +{ + struct omap_mcpdm *mcpdm = + container_of(work, struct omap_mcpdm, delayed_abe_work.work); + + mutex_lock(&mcpdm->mutex); + if (!mcpdm->dl_active && mcpdm->dn_channels) { + abe_disable_data_transfer(PDM_DL_PORT); + udelay(250); + omap_mcpdm_stop(mcpdm, SNDRV_PCM_STREAM_PLAYBACK); + omap_mcpdm_playback_close(mcpdm, mcpdm->downlink); + abe_dsp_mcpdm_shutdown(); + abe_dsp_pm_put(); + } + mutex_unlock(&mcpdm->mutex); + + if (!mcpdm->free && !mcpdm->ul_active && !mcpdm->dl_active) + omap_mcpdm_free(mcpdm); -static int omap_mcpdm_dai_probe(struct snd_soc_dai *dai) +} + +static int omap_mcpdm_abe_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { - snd_soc_dai_set_drvdata(dai, &mcpdm_data); + struct omap_mcpdm *mcpdm = snd_soc_dai_get_drvdata(dai); + int stream = substream->stream; + int ret = 0; + + snd_soc_dai_set_dma_data(dai, substream, + &omap_mcpdm_dai_dma_params[stream]); + + mutex_lock(&mcpdm->mutex); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Check if McPDM is already started */ + if (!mcpdm->dn_channels) { + abe_dsp_pm_get(); + /* start ATC before McPDM IP */ + abe_enable_data_transfer(PDM_DL_PORT); + udelay(250); + mcpdm->downlink->channels = (PDM_DN_MASK | PDM_CMD_MASK); + ret = omap_mcpdm_playback_open(mcpdm, &omap_mcpdm_links[0]); + if (ret < 0) { + mutex_unlock(&mcpdm->mutex); + return ret; + } + + omap_mcpdm_start(mcpdm, stream); + } + } else { + mcpdm->uplink->channels = PDM_UP1_EN | PDM_UP2_EN; + ret = omap_mcpdm_capture_open(mcpdm, &omap_mcpdm_links[1]); + } + + mutex_unlock(&mcpdm->mutex); + + return ret; +} + + +static int omap_mcpdm_abe_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + omap_mcpdm_dai_trigger(substream, cmd, dai); + } + return 0; } -static struct snd_soc_dai_driver omap_mcpdm_dai = { - .probe = omap_mcpdm_dai_probe, +static struct snd_soc_dai_ops omap_mcpdm_abe_dai_ops = { + .startup = omap_mcpdm_abe_dai_startup, + .shutdown = omap_mcpdm_abe_dai_shutdown, + .hw_params = omap_mcpdm_abe_dai_hw_params, + .trigger = omap_mcpdm_abe_dai_trigger, +}; +#endif + +#define OMAP_MCPDM_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define OMAP_MCPDM_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver omap_mcpdm_dai[] = { +#ifdef CONFIG_SND_OMAP_SOC_ABE_DSP +{ + .name = "mcpdm-dl1", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = OMAP_MCPDM_RATES, + .formats = OMAP_MCPDM_FORMATS, + }, + .ops = &omap_mcpdm_abe_dai_ops, +}, +{ + .name = "mcpdm-dl2", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = OMAP_MCPDM_RATES, + .formats = OMAP_MCPDM_FORMATS, + }, + .ops = &omap_mcpdm_abe_dai_ops, +}, +{ + .name = "mcpdm-vib", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = OMAP_MCPDM_RATES, + .formats = OMAP_MCPDM_FORMATS, + }, + .ops = &omap_mcpdm_abe_dai_ops, +}, +{ + .name = "mcpdm-ul1", + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = OMAP_MCPDM_RATES, + .formats = OMAP_MCPDM_FORMATS, + }, + .ops = &omap_mcpdm_abe_dai_ops, +}, +#endif +{ + .name = "mcpdm-dl", .playback = { .channels_min = 1, .channels_max = 4, .rates = OMAP_MCPDM_RATES, .formats = OMAP_MCPDM_FORMATS, }, + .ops = &omap_mcpdm_dai_ops, +}, +{ + .name = "mcpdm-ul", .capture = { .channels_min = 1, .channels_max = 2, @@ -223,25 +854,88 @@ static struct snd_soc_dai_driver omap_mcpdm_dai = { .formats = OMAP_MCPDM_FORMATS, }, .ops = &omap_mcpdm_dai_ops, -}; +}, }; static __devinit int asoc_mcpdm_probe(struct platform_device *pdev) { - int ret; + struct omap_mcpdm *mcpdm; + struct resource *res; + int ret = 0; + + mcpdm = kzalloc(sizeof(struct omap_mcpdm), GFP_KERNEL); + if (!mcpdm) + return -ENOMEM; + + platform_set_drvdata(pdev, mcpdm); + mcpdm->downlink = &omap_mcpdm_links[0]; + mcpdm->uplink = &omap_mcpdm_links[1]; + + mutex_init(&mcpdm->mutex); + mcpdm->free = 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no resource\n"); + ret = -EINVAL; + goto err; + } - ret = omap_mcpdm_probe(pdev); - if (ret < 0) - return ret; - ret = snd_soc_register_dai(&pdev->dev, &omap_mcpdm_dai); + mcpdm->io_base = ioremap(res->start, resource_size(res)); + if (!mcpdm->io_base) { + ret = -ENOMEM; + goto err; + } + + mcpdm->irq = platform_get_irq(pdev, 0); + if (mcpdm->irq < 0) { + ret = mcpdm->irq; + goto err_irq; + } + + ret = request_irq(mcpdm->irq, omap_mcpdm_irq_handler, + 0, "McPDM", mcpdm); + if (ret) { + dev_err(mcpdm->dev, "Request for McPDM IRQ failed: %d\n", ret); + goto err_irq; + } + + pm_runtime_enable(&pdev->dev); + + mcpdm->dev = &pdev->dev; + + /* TODO: values will be different per device, read from FS */ + mcpdm->dl1_offset = 0x1F; + mcpdm->dl2_offset = 0x1F; + + INIT_DELAYED_WORK(&mcpdm->delayed_work, playback_work); +#ifdef CONFIG_SND_OMAP_SOC_ABE_DSP + INIT_DELAYED_WORK(&mcpdm->delayed_abe_work, playback_abe_work); +#endif + + ret = snd_soc_register_dais(&pdev->dev, omap_mcpdm_dai, + ARRAY_SIZE(omap_mcpdm_dai)); if (ret < 0) - omap_mcpdm_remove(pdev); + goto err_dai; + + return 0; + +err_dai: + free_irq(mcpdm->irq, mcpdm); +err_irq: + iounmap(mcpdm->io_base); +err: + kfree(mcpdm); return ret; } static int __devexit asoc_mcpdm_remove(struct platform_device *pdev) { - snd_soc_unregister_dai(&pdev->dev); - omap_mcpdm_remove(pdev); + struct omap_mcpdm *mcpdm = platform_get_drvdata(pdev); + + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(omap_mcpdm_dai)); + iounmap(mcpdm->io_base); + free_irq(mcpdm->irq, mcpdm); + kfree(mcpdm); return 0; } |