/*
* ALSA SoC McASP Audio Layer for TI DAVINCI processor
*
* Multi-channel Audio Serial Port Driver
*
* Author: Nirmal Pandey <n-pandey@ti.com>,
* Suresh Rajashekara <suresh.r@ti.com>
* Steve Chen <schen@.mvista.com>
*
* Copyright: (C) 2009 MontaVista Software, Inc., <source@mvista.com>
* Copyright: (C) 2009 Texas Instruments, India
*
* 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 <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_device.h>
#include <sound/asoundef.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <sound/omap-pcm.h>
#include "davinci-pcm.h"
#include "edma-pcm.h"
#include "davinci-mcasp.h"
#define MCASP_MAX_AFIFO_DEPTH 64
static u32 context_regs[] = {
DAVINCI_MCASP_TXFMCTL_REG,
DAVINCI_MCASP_RXFMCTL_REG,
DAVINCI_MCASP_TXFMT_REG,
DAVINCI_MCASP_RXFMT_REG,
DAVINCI_MCASP_ACLKXCTL_REG,
DAVINCI_MCASP_ACLKRCTL_REG,
DAVINCI_MCASP_AHCLKXCTL_REG,
DAVINCI_MCASP_AHCLKRCTL_REG,
DAVINCI_MCASP_PDIR_REG,
DAVINCI_MCASP_RXMASK_REG,
DAVINCI_MCASP_TXMASK_REG,
DAVINCI_MCASP_RXTDM_REG,
DAVINCI_MCASP_TXTDM_REG,
};
struct davinci_mcasp_context {
u32 config_regs[ARRAY_SIZE(context_regs)];
u32 afifo_regs[2]; /* for read/write fifo control registers */
u32 *xrsr_regs; /* for serializer configuration */
};
struct davinci_mcasp {
struct davinci_pcm_dma_params dma_params[2];
struct snd_dmaengine_dai_dma_data dma_data[2];
void __iomem *base;
u32 fifo_base;
struct device *dev;
struct snd_pcm_substream *substreams[2];
/* McASP specific data */
int tdm_slots;
u8 op_mode;
u8 num_serializer;
u8 *serial_dir;
u8 version;
u8 bclk_div;
u16 bclk_lrclk_ratio;
int streams;
u32 irq_request[2];
int sysclk_freq;
bool bclk_master;
/* McASP FIFO related */
u8 txnumevt;
u8 rxnumevt;
bool dat_port;
/* Used for comstraint setting on the second stream */
u32 channels;
#ifdef CONFIG_PM_SLEEP
struct davinci_mcasp_context context;
#endif
};
static inline void mcasp_set_bits(struct davinci_mcasp *mcasp, u32 offset,
u32 val)
{
void __iomem *reg = mcasp->base + offset;
__raw_writel(__raw_readl(reg) | val, reg);
}
static inline void mcasp_clr_bits(struct davinci_mcasp *mcasp, u32 offset,
u32 val)
{
void __iomem *reg = mcasp->base + offset;
__raw_writel((__raw_readl(reg) & ~(val)), reg);
}
static inline void mcasp_mod_bits(struct davinci_mcasp *mcasp, u32 offset,
u32 val, u32 mask)
{
void __iomem *reg = mcasp->base + offset;
__raw_writel((__raw_readl(reg) & ~mask) | val, reg);
}
static inline void mcasp_set_reg(struct davinci_mcasp *mcasp, u32 offset,
u32 val)
{
__raw_writel(val, mcasp->base + offset);
}
static inline u32 mcasp_get_reg(struct davinci_mcasp *mcasp, u32 offset)
{
return (u32)__raw_readl(mcasp->base + offset);
}
static void mcasp_set_ctl_reg(struct davinci_mcasp *mcasp, u32 ctl_reg, u32 val)
{
int i = 0;
mcasp_set_bits(mcasp, ctl_reg, 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(mcasp, ctl_reg) & val) == val)
break;
}
if (i == 1000 && ((mcasp_get_reg(mcasp, ctl_reg) & val) != val))
printk(KERN_ERR "GBLCTL write error\n");
}
static bool mcasp_is_synchronous(struct davinci_mcasp *mcasp)
{
u32 rxfmctl = mcasp_get_reg(mcasp, DAVINCI_MCASP_RXFMCTL_REG);
u32 aclkxctl = mcasp_get_reg(mcasp, DAVINCI_MCASP_ACLKXCTL_REG);
return !(aclkxctl & TX_ASYNC) && rxfmctl & AFSRE;
}
static void mcasp_start_rx(struct davinci_mcasp *mcasp)
{
if (mcasp->rxnumevt) { /* enable FIFO */
u32 reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET;
mcasp_clr_bits(mcasp, reg, FIFO_ENABLE);
mcasp_set_bits(mcasp, reg, FIFO_ENABLE);
}
/* Start clocks */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXHCLKRST);
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXCLKRST);
/*
* When ASYNC == 0 the transmit and receive sections operate
* synchronously from the transmit clock and frame sync. We need to make
* sure that the TX signlas are enabled when starting reception.
*/
if (mcasp_is_synchronous(mcasp)) {
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST);
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST);
}
/* Activate serializer(s) */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXSERCLR);
/* Release RX state machine */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXSMRST);
/* Release Frame Sync generator */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, RXFSRST);
if (mcasp_is_synchronous(mcasp))
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXFSRST);
/* enable receive IRQs */
mcasp_set_bits(mcasp, DAVINCI_MCASP_EVTCTLR_REG,
mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]);
}
static void mcasp_start_tx(struct davinci_mcasp *mcasp)
{
u32 cnt;
if (mcasp->txnumevt) { /* enable FIFO */
u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET;
mcasp_clr_bits(mcasp, reg, FIFO_ENABLE);
mcasp_set_bits(mcasp, reg, FIFO_ENABLE);
}
/* Start clocks */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST);
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST);
/* Activate serializer(s) */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR);
/* wait for XDATA to be cleared */
cnt = 0;
while (!(mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) &
~XRDATA) && (cnt < 100000))
cnt++;
/* Release TX state machine */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSMRST);
/* Release Frame Sync generator */
mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXFSRST);
/* enable transmit IRQs */
mcasp_set_bits(mcasp, DAVINCI_MCASP_EVTCTLX_REG,
mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]);
}
static void davinci_mcasp_start(struct davinci_mcasp *mcasp, int stream)
{
mcasp->streams++;
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
mcasp_start_tx(mcasp);
else
mcasp_start_rx(mcasp);
}
static void mcasp_stop_rx(struct davinci_mcasp *mcasp)
{
/* disable IRQ sources */
mcasp_clr_bits(mcasp, DAVINCI_MCASP_EVTCTLR_REG,
mcasp->irq_request[SNDRV_PCM_STREAM_CAPTURE]);
/*
* In synchronous mode stop the TX clocks if no other stream is
* running
*/
if (mcasp_is_synchronous(mcasp) && !mcasp->streams)
mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, 0);
mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLR_REG, 0);
mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF);
if (mcasp->rxnumevt) { /* disable FIFO */
u32 reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET;
mcasp_clr_bits(mcasp, reg, FIFO_ENABLE);
}
}
static void mcasp_stop_tx(struct davinci_mcasp *mcasp)
{
u32 val = 0;
/* disable IRQ sources */
mcasp_clr_bits(mcasp, DAVINCI_MCASP_EVTCTLX_REG,
mcasp->irq_request[SNDRV_PCM_STREAM_PLAYBACK]);
/*
* In synchronous mode keep TX clocks running if the capture stream is
* still running.
*/
if (mcasp_is_synchronous(mcasp) && mcasp->streams)
val = TXHCLKRST | TXCLKRST | TXFSRST;
mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, val);
mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF);
if (mcasp->txnumevt) { /* disable FIFO */
u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET;
mcasp_clr_bits(mcasp, reg, FIFO_ENABLE);
}
}
static void davinci_mcasp_stop(struct davinci_mcasp *mcasp, int stream)
{
mcasp->streams--;
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
mcasp_stop_tx(mcasp);
else
mcasp_stop_rx(mcasp);
}
static irqreturn_t davinci_mcasp_tx_irq_handler(int irq, void *data)
{
struct d