summaryrefslogtreecommitdiff
path: root/sound/soc/mvf/mvf-sai-ac97.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/mvf/mvf-sai-ac97.c')
-rw-r--r--sound/soc/mvf/mvf-sai-ac97.c838
1 files changed, 838 insertions, 0 deletions
diff --git a/sound/soc/mvf/mvf-sai-ac97.c b/sound/soc/mvf/mvf-sai-ac97.c
new file mode 100644
index 000000000000..df387bc5a69b
--- /dev/null
+++ b/sound/soc/mvf/mvf-sai-ac97.c
@@ -0,0 +1,838 @@
+/*
+ * sound/soc/mvf/mvf-ac97.c
+ *
+ * Copyright (C) 2013 Toradex, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <mach/gpio.h>
+#include <mach/iomux-mvf.h>
+#include <mach/mcf_edma.h>
+
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+
+#include "mvf-sai.h"
+
+#define DRIVER_NAME "mvf-sai-ac97"
+#define EDMA_PRIO_HIGH 6
+#define MVF_SAI_AC97_DMABUF_SIZE (13 * 4)
+
+/* read states */
+enum read_state {
+ RD_IDLE = 0,
+ RD_INIT, /* command initiated via mvf_sai_ac97_read() call,
+ rd_address valid */
+ RD_WAIT, /* command sent, waiting for reply,
+ rd_address still valid */
+ RD_REP_RCV, /* reply received */
+};
+
+/* write states */
+enum write_state {
+ WR_IDLE = 0,
+ WR_INIT, /* command initiated via mvf_sai_ac97_write() call,
+ rd_address and rd_data valid */
+ WR_CMD_SENT, /* command sent,
+ slot subsequently to be cleared again */
+ WR_CMD_CLR, /* slot cleared */
+};
+
+/* AC97 controller */
+struct mvf_sai_ac97_info {
+ struct platform_device *pdev;
+ struct tegra_audio_platform_data *pdata;
+ phys_addr_t ac97_phys;
+ void __iomem *ac97_base;
+ void __iomem *sai0_base;
+ struct clk *clk;
+ struct snd_card *card;
+ struct imx_pcm_dma_params dma_params_rx;
+ struct imx_pcm_dma_params dma_params_tx;
+ struct snd_dma_buffer rx_buf;
+ struct snd_dma_buffer tx_buf;
+ int rx_tcd_chan;
+ int tx_tcd_chan;
+ struct mutex lock;
+ enum read_state rd_state;
+ unsigned int rd_address;
+ unsigned int rd_data;
+ enum write_state wr_state;
+ unsigned int wr_address;
+ unsigned int wr_data;
+};
+
+static u64 mvf_pcm_dmamask = DMA_BIT_MASK(32);
+
+/* TODO: not just global */
+static struct mvf_sai_ac97_info *info;
+
+/* software AC97 register read access */
+static unsigned short mvf_sai_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ u32 val;
+ int timeout = 100;
+
+ mutex_lock(&info->lock);
+
+ if (info->rd_state != RD_IDLE)
+ dev_warn(&info->pdev->dev, "read state machine was %d instead "
+ "of RD_IDLE\n", info->rd_state);
+ if (info->wr_state != WR_IDLE)
+ dev_warn(&info->pdev->dev, "write state machine was %d instead "
+ "of WR_IDLE\n", info->wr_state);
+
+ /* Slot 1: Command Address Port
+ Bit(19) Read/Write command (1=read, 0=write)
+ Bit(18:12) Control Register Index (64 16-bit locations,
+ addressed on even byte boundaries)
+ Bit(11:0) Reserved (Stuffed with 0’s) */
+ info->rd_address = (1 << 19) | /* read */
+ (reg << 12);
+ info->wr_address = info->rd_address;
+
+ info->rd_state = RD_INIT;
+ info->wr_state = WR_INIT;
+
+ /* Slot 2: Status Data Port
+ The status data port delivers 16-bit control register read data.
+ Bit(19:4) Control Register Read Data (Completed with 0’s if tagged
+ “invalid” by AC‘97) */
+ do {
+ mdelay(1);
+ val = (info->rd_data & 0xfffff) >> 4;
+ } while ((info->rd_state != RD_REP_RCV) && --timeout);
+
+ info->rd_state = RD_IDLE;
+
+ if (!timeout) {
+ dev_warn(&info->pdev->dev,
+ "timeout reading register %x\n", reg);
+ mutex_unlock(&info->lock);
+ return -ETIMEDOUT;
+ }
+
+ mutex_unlock(&info->lock);
+
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ return val;
+}
+
+/* software AC97 register write access */
+static void mvf_sai_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ int timeout = 100;
+
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ if (info->wr_state != WR_IDLE)
+ dev_warn(&info->pdev->dev, "write state machine was %d instead "
+ "of WR_IDLE\n", info->wr_state);
+
+ mutex_lock(&info->lock);
+
+ /* Slot 1: Command Address Port
+ Bit(19) Read/Write command (1=read, 0=write)
+ Bit(18:12) Control Register Index (64 16-bit locations,
+ addressed on even byte boundaries)
+ Bit(11:0) Reserved (Stuffed with 0’s) */
+ info->wr_address = reg << 12;
+
+ /* Slot 2: Command Data Port
+ The command data port is used to deliver 16-bit control register write
+ data in the event that the current command
+ port operation is a write cycle. (as indicated by Slot 1, bit 19)
+ Bit(19:4) Control Register Write Data (Completed with 0’s if current
+ operation is a read)
+ Bit(3:0) Reserved (Completed with 0’s) */
+ info->wr_data = (val << 4);
+
+ info->wr_state = WR_INIT;
+
+ while ((info->wr_state != WR_CMD_CLR) && --timeout)
+ mdelay(1);
+
+ info->wr_state = WR_IDLE;
+
+ if (!timeout)
+ dev_warn(&info->pdev->dev, "timeout writing register %x\n", reg);
+
+ mutex_unlock(&info->lock);
+}
+
+struct snd_ac97_bus_ops soc_ac97_ops = {
+ .read = mvf_sai_ac97_read,
+ .write = mvf_sai_ac97_write,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);
+
+static struct snd_ac97_bus_ops mvf_sai_ac97_ops = {
+ .read = mvf_sai_ac97_read,
+ .write = mvf_sai_ac97_write,
+};
+
+static struct snd_ac97 *mvf_sai_ac97_ac97;
+
+static __initdata iomux_v3_cfg_t ac97_pinmux[] = {
+ /* SAI2: AC97/Touchscreen */
+ MVF600_PAD4_PTA11_WM9715L_PENDOWN, /* carefull also used for JTAG
+ JTMS/SWDIO */
+ MVF600_PAD6_PTA16_SAI2_TX_BCLK, /* AC97_BIT_CLK */
+ MVF600_PAD8_PTA18_SAI2_TX_DATA, /* AC97_SDATA_OUT */
+ MVF600_PAD9_PTA19_SAI2_TX_SYNC, /* AC97_SYNC */
+ MVF600_PAD12_PTA22_SAI2_RX_DATA, /* AC97_SDATA_IN */
+ MVF600_PAD13_PTA23_WM9715L_RESET,
+ MVF600_PAD24_PTB2_WM9715L_GENIRQ,
+ MVF600_PAD40_PTB18_CKO1, /* AC97_MCLK fed back in from
+ camera clock pin */
+ MVF600_PAD93_PTB23_SAI0_TX_BCLK, /* AC97_MCLK */
+};
+
+static irqreturn_t mvf_sai_ac97_dma_irq(int channel, void *data)
+{
+ if (channel == info->rx_tcd_chan) {
+ if ((info->rd_state == RD_WAIT) &&
+ ((*((unsigned int *)(info->rx_buf.area + 4)) & (3 << (13 +
+ 4))) == (3 << (13 + 4))) && /* valid slot 1 & 2 */
+ ((*((unsigned int *)(info->rx_buf.area + 8)) | (1 << 19)) ==
+ info->rd_address)) {
+ info->rd_data = *((unsigned int *)(info->rx_buf.area
+ + 12));
+ info->rd_state = RD_REP_RCV;
+ }
+
+ mcf_edma_confirm_interrupt_handled(DMA_MUX_SAI2_RX);
+ } else if (channel == info->tx_tcd_chan) {
+ if (info->wr_state == WR_INIT) {
+ /* Slot 0: TAG
+ Bit 15 Codec Ready
+ Bit 14:3 Slot Valid (Which of slot 1 to slot 12 contain
+ valid data)
+ Bit 2:0 Zero */
+ *((unsigned int *)(info->tx_buf.area + 0)) =
+ (1 << (15 + 4)) | /* valid frame */
+ (1 << (14 + 4)) | /* slot 1 valid */
+ (((info->wr_address & (1 << 19))?0:1) <<
+ (13 + 4)); /* slot 2 valid */
+
+ /* Slot 1: Command Address Port
+ Bit(19) Read/Write command (1=read, 0=write)
+ Bit(18:12) Control Register Index (64 16-bit locations,
+ addressed on even byte boundaries)
+ Bit(11:0) Reserved (Stuffed with 0’s) */
+ *((unsigned int *)(info->tx_buf.area + 4)) =
+ info->wr_address;
+
+ if (!(info->wr_address & (1 << 19))) {
+ /* Slot 2: Command Data Port
+ The command data port is used to deliver 16-bit
+ control register write data in the event that
+ the current command port operation is a write
+ cycle. (as indicated by Slot 1, bit 19)
+ Bit(19:4) Control Register Write Data (Completed
+ with 0’s if current operation is
+ a read)
+ Bit(3:0) Reserved (Completed with 0’s) */
+ *((unsigned int *)(info->tx_buf.area + 8)) =
+ info->wr_data;
+ info->wr_state = WR_CMD_CLR;
+ } else {
+ info->rd_state = RD_WAIT;
+ info->wr_state = WR_IDLE;
+ }
+ }
+
+ mcf_edma_confirm_interrupt_handled(DMA_MUX_SAI2_TX);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit mvf_sai_ac97_dev_probe(struct platform_device *pdev)
+{
+ struct snd_ac97_bus *ac97_bus;
+ struct snd_ac97_template ac97_template;
+ int err = 0;
+ int gpio_status;
+ int i;
+ unsigned int rcsr;
+ unsigned int reg;
+ struct resource *res;
+ unsigned int tcsr;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ err = -ENOMEM;
+ goto failed_alloc;
+ }
+ dev_set_drvdata(&pdev->dev, info);
+ info->pdev = pdev;
+
+ mutex_init(&info->lock);
+
+/*
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ ret = -ENODEV;
+ goto failed_irq;
+ }
+ info->irq = res->start;
+*/
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "no mem resource!\n");
+ err = -ENODEV;
+ goto failed_mem;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
+ dev_err(&pdev->dev, "request_mem_region failed\n");
+ err = -EBUSY;
+ goto failed_region;
+ }
+
+ info->ac97_phys = res->start;
+ info->ac97_base = ioremap(res->start, resource_size(res));
+ if (!info->ac97_base) {
+ dev_err(&pdev->dev, "cannot remap iomem!\n");
+ err = -ENOMEM;
+ goto failed_map;
+ }
+
+ /* SAI0_TX_BCLK used as AC97 master clock */
+ info->sai0_base = ioremap(MVF_SAI0_BASE_ADDR, resource_size(res));
+ if (!info->sai0_base) {
+ dev_err(&pdev->dev, "cannot remap iomem!\n");
+ err = -ENOMEM;
+ goto failed_map2;
+ }
+
+ /* configure AC97 master clock */
+ reg = readl(info->sai0_base + SAI_TCR2);
+ reg &= ~SAI_TCR2_SYNC_MASK; /* asynchronous aka independent
+ operation */
+ reg &= ~SAI_TCR2_BCS; /* bit clock not swapped */
+ reg &= ~SAI_TCR2_MSEL_MASK;
+ reg |= SAI_TCR2_MSEL_MCLK1; /* Clock selected by
+ CCM_CSCMR1[SAIn_CLK_SEL] */
+ reg |= SAI_TCR2_BCD_MSTR; /* Bitclock is generated internally
+ (master mode) */
+ reg &= ~SAI_TCR2_DIV_MASK; /* Divides down the audio master */
+ reg |= SAI_TCR2_DIV(2); /* clock by 6 to generate the bitclock
+ when configured for an internal
+ bitclock (master). */
+ writel(reg, info->sai0_base + SAI_TCR2);
+
+ /* enable AC97 master clock */
+ reg = readl(info->sai0_base + SAI_TCSR);
+ writel(reg | SAI_TCSR_BCE, info->sai0_base + SAI_TCSR);
+
+ info->clk = clk_get(&pdev->dev, "sai_clk");
+ if (IS_ERR(info->clk)) {
+ err = PTR_ERR(info->clk);
+ dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+ err);
+ goto failed_clk;
+ }
+ clk_enable(info->clk);
+
+ err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, 0, &info->card);
+ if (err < 0)
+ goto failed_create;
+
+ info->card->dev = &pdev->dev;
+ strncpy(info->card->driver, pdev->dev.driver->name, sizeof(info->card->driver));
+
+ /* AC'97 controller required to drive and keep SYNC and SDATA_OUT low
+ to avoid wolfson entering test mode */
+#define GPIO_AC97_SDATAOUT 8
+ gpio_status = gpio_request(GPIO_AC97_SDATAOUT, "WOLFSON_SDATAOUT");
+ if (gpio_status < 0) {
+ pr_info("WOLFSON_SDATAOUT request GPIO FAILED\n");
+ WARN_ON(1);
+ }
+ gpio_status = gpio_direction_output(GPIO_AC97_SDATAOUT, 0);
+ if (gpio_status < 0) {
+ pr_info("WOLFSON_SDATAOU request GPIO DIRECTION FAILED\n");
+ WARN_ON(1);
+ }
+ udelay(2);
+
+ /* do wolfson hard reset */
+#define GPIO_AC97_nRESET 13
+ gpio_status = gpio_request(GPIO_AC97_nRESET, "WOLFSON_RESET");
+ if (gpio_status < 0) {
+ pr_info("WOLFSON_RESET request GPIO FAILED\n");
+ WARN_ON(1);
+ }
+
+ /* Wolfson initially powered up disabled due to ADCIRQ aka PWRUP
+ strapping pin being held high.
+ WM9715L awakes from sleep mode on warm reset of AC-Link
+ (according to the AC’97 specification). */
+
+ /* do wolfson warm reset by toggling SYNC */
+#define GPIO_AC97_SYNC 9
+ gpio_status = gpio_request(GPIO_AC97_SYNC, "WOLFSON_SYNC");
+ if (gpio_status < 0) {
+ pr_info("WOLFSON_SYNC request GPIO FAILED\n");
+ WARN_ON(1);
+ }
+ gpio_status = gpio_direction_output(GPIO_AC97_SYNC, 0);
+ if (gpio_status < 0) {
+ pr_info("WOLFSON_SYNC request GPIO DIRECTION FAILED\n");
+ WARN_ON(1);
+ }
+ udelay(2);
+ gpio_status = gpio_direction_output(GPIO_AC97_nRESET, 0);
+ if (gpio_status < 0) {
+ pr_info("WOLFSON_RESET request GPIO DIRECTION FAILED\n");
+ WARN_ON(1);
+ }
+ udelay(2);
+ gpio_set_value(GPIO_AC97_nRESET, 1);
+ udelay(2);
+ gpio_set_value(GPIO_AC97_SYNC, 1);
+ udelay(2);
+ gpio_set_value(GPIO_AC97_SYNC, 0);
+ udelay(2);
+ gpio_free(GPIO_AC97_SYNC);
+
+ mxc_iomux_v3_setup_multiple_pads(ac97_pinmux, ARRAY_SIZE(ac97_pinmux));
+
+ /* clear transmit/receive configuration/status registers */
+ writel(0x0, info->ac97_base + SAI_TCSR);
+ writel(0x0, info->ac97_base + SAI_RCSR);
+
+ info->dma_params_tx.dma_addr = res->start + SAI_TDR;
+ info->dma_params_rx.dma_addr = res->start + SAI_RDR;
+
+ /* 32 deep FIFOs */
+ info->dma_params_tx.burstsize = 16;
+ info->dma_params_rx.burstsize = 16;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
+ if (res)
+ info->dma_params_tx.dma = res->start;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
+ if (res)
+ info->dma_params_rx.dma = res->start;
+
+ platform_set_drvdata(pdev, info);
+
+ if (!pdev->dev.dma_mask)
+ pdev->dev.dma_mask = &mvf_pcm_dmamask;
+ if (!pdev->dev.coherent_dma_mask)
+ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+ /* pre allocate DMA buffers */
+ size_t size = MVF_SAI_AC97_DMABUF_SIZE;
+
+ info->tx_buf.dev.type = SNDRV_DMA_TYPE_DEV;
+ info->tx_buf.dev.dev = &pdev->dev;
+ info->tx_buf.private_data = NULL;
+ info->tx_buf.area = dma_alloc_writecombine(&pdev->dev, size,
+ &info->tx_buf.addr, GFP_KERNEL);
+ if (!info->tx_buf.area) {
+ err = -ENOMEM;
+ goto failed_tx_buf;
+ }
+ info->tx_buf.bytes = size;
+
+ info->rx_buf.dev.type = SNDRV_DMA_TYPE_DEV;
+ info->rx_buf.dev.dev = &pdev->dev;
+ info->rx_buf.private_data = NULL;
+ info->rx_buf.area = dma_alloc_writecombine(&pdev->dev, size,
+ &info->rx_buf.addr, GFP_KERNEL);
+ if (!info->rx_buf.area) {
+ err = -ENOMEM;
+ goto failed_rx_buf;
+ }
+ info->rx_buf.bytes = size;
+
+ memset(info->tx_buf.area, 0, MVF_SAI_AC97_DMABUF_SIZE);
+ memset(info->rx_buf.area, 0, MVF_SAI_AC97_DMABUF_SIZE);
+
+ /* 1. Configuration of SAI clock mode */
+
+ /* Issue software reset and FIFO reset for Transmitter and Receiver
+ sections before starting configuration. */
+ reg = readl(info->ac97_base + SAI_TCSR);
+ writel(reg | SAI_TCSR_SR, info->ac97_base + SAI_TCSR); /* Issue
+ software reset */
+ udelay(2);
+ writel(reg & ~SAI_TCSR_SR, info->ac97_base + SAI_TCSR); /* Release
+ software reset */
+ writel(reg | SAI_TCSR_FR, info->ac97_base + SAI_TCSR); /* FIFO reset */
+ reg = readl(info->ac97_base + SAI_RCSR);
+ writel(reg | SAI_RCSR_SR, info->ac97_base + SAI_RCSR); /* Issue
+ software reset */
+ udelay(2);
+ writel(reg & ~SAI_RCSR_SR, info->ac97_base + SAI_RCSR); /* Release
+ software reset */
+ writel(reg | SAI_RCSR_FR, info->ac97_base + SAI_RCSR); /* FIFO reset */
+
+ /* Configure FIFO watermark. FIFO watermark is used as an indicator for
+ DMA trigger when read or write data from/to FIFOs. */
+ /* Watermark level for all enabled transmit channels of one SAI module.
+ */
+ writel(info->dma_params_tx.burstsize, info->ac97_base + SAI_TCR1);
+ writel(info->dma_params_rx.burstsize, info->ac97_base + SAI_RCR1);
+
+ /* Configure the clocking mode, bitclock polarity, direction, and
+ divider. Clocking mode defines synchronous or asynchronous operation
+ for SAI module. Bitclock polarity configures polarity of the
+ bitclock. Bitclock direction configures direction of the bitclock.
+ Bus master has bitclock generated externally, slave has bitclock
+ generated internally */
+ reg = readl(info->ac97_base + SAI_TCR2);
+ reg &= ~SAI_TCR2_SYNC_MASK; /* The transmitter must be configured
+ for asynchronous operation and the receiver for
+ synchronous operation. */
+ reg &= ~SAI_TCR2_BCS; /* bit clock not swapped */
+ reg &= ~SAI_TCR2_BCP; /* Bitclock is active high (drive outputs on
+ rising edge and sample inputs on falling edge */
+ reg &= ~SAI_TCR2_BCD_MSTR; /* Bitclock is generated externally
+ (Slave mode) */
+ writel(reg, info->ac97_base + SAI_TCR2);
+ reg = readl(info->ac97_base + SAI_RCR2);
+ reg &= ~SAI_RCR2_SYNC_MASK; /* The transmitter must be configured
+ for asynchronous operation and the receiver */
+ reg |= SAI_RCR2_SYNC; /* for synchronous operation. */
+ reg &= ~SAI_RCR2_BCS; /* bit clock not swapped */
+ reg &= ~SAI_RCR2_BCP; /* Bitclock is active high (drive outputs on
+ rising edge and sample inputs on falling edge */
+ reg &= ~SAI_RCR2_BCD_MSTR; /* Bitclock is generated externally
+ (Slave mode) */
+ writel(reg, info->ac97_base + SAI_RCR2);
+
+ /* Configure frame size, frame sync width, MSB first, frame sync early,
+ polarity, and direction
+ Frame size – configures the number of words in each frame. AC97
+ requires 13 words per frame.
+ Frame sync width – configures the length of the frame sync in number
+ of bitclock. The sync width cannot be longer than the first word of
+ the frame. AC97 requires frame sync asserted for first word. */
+ reg = readl(info->ac97_base + SAI_TCR4);
+ reg &= ~SAI_TCR4_FRSZ_MASK; /* Configures number of words in each */
+ reg |= SAI_TCR4_FRSZ(12); /* frame. The value written should be
+ one less than the number of words in the frame. */
+ reg &= ~SAI_TCR4_SYWD_MASK; /* Configures length of the frame */
+ reg |= SAI_TCR4_SYWD(15); /* sync. The value written should be one
+ less than the number of bitclocks.
+ AC97 - 16 bits transmitted in first word. */
+ reg |= SAI_TCR4_MF; /* MSB is transmitted first */
+ reg |= SAI_TCR4_FSE; /* Frame sync asserted one bit before the first
+ bit of the frame */
+ reg &= ~SAI_TCR4_FSP; /* A new AC-link input frame begins with a low
+ to high transition of SYNC.
+ Frame sync is active high */
+ reg |= SAI_TCR4_FSD_MSTR; /* Frame sync is generated internally
+ (Master mode) */
+ writel(reg, info->ac97_base + SAI_TCR4);
+ reg = readl(info->ac97_base + SAI_RCR4);
+ reg &= ~SAI_RCR4_FRSZ_MASK; /* Configures number of words in each */
+ reg |= SAI_RCR4_FRSZ(12); /* frame. The value written should be
+ one less than the number of words in the frame. */
+ reg &= ~SAI_RCR4_SYWD_MASK; /* Configures length of the frame */
+ reg |= SAI_RCR4_SYWD(15); /* sync. The value written should be one
+ less than the number of bitclocks.
+ AC97 - 16 bits transmitted in first word. */
+ reg |= SAI_RCR4_MF; /* MSB is transmitted first */
+ reg |= SAI_RCR4_FSE; /* Frame sync asserted one bit before the first
+ bit of the frame */
+ reg &= ~SAI_RCR4_FSP; /* A new AC-link input frame begins with a low
+ to high transition of SYNC.
+ Frame sync is active high */
+ reg |= SAI_RCR4_FSD_MSTR; /* Frame sync is generated internally
+ (Master mode) */
+ writel(reg, info->ac97_base + SAI_RCR4);
+
+ /* Configure the Word 0 and next word sizes.
+ W0W – defines number of bits in the first word in each frame.
+ WNW – defines number of bits in each word for each word except the
+ first in the frame. */
+ reg = readl(info->ac97_base + SAI_TCR5);
+ reg &= ~SAI_TCR5_W0W_MASK; /* Number of bits in first word in */
+ reg |= SAI_TCR5_W0W(15); /* each frame. AC97 – 16-bit word is
+ transmitted. */
+ reg &= ~SAI_TCR5_WNW_MASK; /* Number of bits in each word in */
+ reg |= SAI_TCR5_WNW(19); /* each frame. AC97 – 20-bit word is
+ transmitted. */
+ reg &= ~SAI_TCR5_FBT_MASK; /* Configures the bit index for the
+ first bit transmitted for each word
+ in the frame. */
+ reg |= SAI_TCR5_FBT(19); /* The value written must be greater
+ than or equal to the word width when
+ configured for MSB First. */
+ writel(reg, info->ac97_base + SAI_TCR5);
+ reg = readl(info->ac97_base + SAI_RCR5);
+ reg &= ~SAI_RCR5_W0W_MASK; /* Number of bits in first word in */
+ reg |= SAI_RCR5_W0W(15); /* each frame. AC97 – 16-bit word is
+ transmitted. */
+ reg &= ~SAI_RCR5_WNW_MASK; /* Number of bits in each word in */
+ reg |= SAI_RCR5_WNW(19); /* each frame. AC97 – 20-bit word is
+ transmitted. */
+ reg &= ~SAI_RCR5_FBT_MASK; /* Configures the bit index for the
+ first bit transmitted for each word
+ in the frame. */
+ reg |= SAI_RCR5_FBT(19); /* The value written must be greater
+ than or equal to the word width when
+ configured for MSB First. */
+ writel(reg, info->ac97_base + SAI_RCR5);
+
+ /* Clear the Transmit and Receive Mask registers. */
+ writel(0, info->ac97_base + SAI_TMR); /* Enable or mask word N in the
+ frame. */
+ writel(0, info->ac97_base + SAI_RMR); /* Enable or mask word N in the
+ frame. */
+
+ /**
+ * mcf_edma_request_channel - Request an eDMA channel
+ * @channel: channel number. In case it is equal to EDMA_CHANNEL_ANY
+ * it will be allocated a first free eDMA channel.
+ * @handler: dma handler
+ * @error_handler: dma error handler
+ * @irq_level: irq level for the dma handler
+ * @arg: argument to pass back
+ * @lock: optional spinlock to hold over interrupt
+ * @device_id: device id
+ *
+ * Returns allocatedd channel number if success or
+ * a negative value if failure.
+ */
+ err = mcf_edma_request_channel(DMA_MUX_SAI2_TX, mvf_sai_ac97_dma_irq,
+ NULL, EDMA_PRIO_HIGH, NULL, NULL, DRIVER_NAME);
+ if (err < 0)
+ goto failed_request_tx_dma;
+ info->tx_tcd_chan = err;
+
+ err = mcf_edma_request_channel(DMA_MUX_SAI2_RX, mvf_sai_ac97_dma_irq,
+ NULL, EDMA_PRIO_HIGH, NULL, NULL, DRIVER_NAME);
+ if (err < 0)
+ goto failed_request_rx_dma;
+ info->rx_tcd_chan = err;
+
+ /* Setup transfer control descriptor (TCD)
+ * channel - descriptor number
+ * source - source address
+ * dest - destination address
+ * attr - attributes
+ * soff - source offset
+ * nbytes - number of bytes to be transfered in minor loop
+ * slast - last source address adjustment
+ * citer - major loop count
+ * biter - begining minor loop count
+ * doff - destination offset
+ * dlast_sga - last destination address adjustment
+ * major_int - generate interrupt after each major loop
+ * disable_req - disable DMA request after major loop
+ * enable_sg - enable scatter/gather processing
+ */
+ mcf_edma_set_tcd_params(info->tx_tcd_chan, info->tx_buf.addr,
+ info->dma_params_tx.dma_addr,
+ MCF_EDMA_TCD_ATTR_SSIZE_32BIT |
+ MCF_EDMA_TCD_ATTR_DSIZE_32BIT, 4, 4, -13 * 4,
+ 13, 13, 0, 0, 1, 0, 0);
+ mcf_edma_set_tcd_params(info->rx_tcd_chan, info->dma_params_rx.dma_addr,
+ info->rx_buf.addr,
+ MCF_EDMA_TCD_ATTR_SSIZE_32BIT |
+ MCF_EDMA_TCD_ATTR_DSIZE_32BIT, 0, 4, 0,
+ 13, 13, 4, -13 * 4, 1, 0, 0);
+
+ reg = readl(info->ac97_base + SAI_TCR3);
+ reg |= SAI_TCR3_TCE; /* Enables a data channel for a transmit
+ operation. */
+ writel(reg, info->ac97_base + SAI_TCR3);
+ reg = readl(info->ac97_base + SAI_RCR3);
+ reg |= SAI_RCR3_RCE; /* Enables a data channel for a receive
+ operation. */
+ writel(reg, info->ac97_base + SAI_RCR3);
+
+ mcf_edma_start_transfer(info->tx_tcd_chan);
+ mcf_edma_start_transfer(info->rx_tcd_chan);
+
+ tcsr = readl(info->ac97_base + SAI_TCSR);
+ rcsr = readl(info->ac97_base + SAI_RCSR);
+
+ /* enable transmit DMA */
+ tcsr |= SAI_TCSR_FRDE;
+
+ /* enable receive DMA */
+ rcsr |= SAI_RCSR_FRDE;
+
+ /* enable transmit/receive */
+ tcsr |= SAI_TCSR_TE;
+ rcsr |= SAI_RCSR_RE;
+
+ info->rd_state = RD_IDLE;
+ info->wr_state = WR_IDLE;
+
+ /* In synchronous mode, receiver is enabled only when both transmitter
+ and receiver are enabled. It is recommended that transmitter is
+ enabled last and disabled first. */
+ writel(rcsr, info->ac97_base + SAI_RCSR);
+ writel(tcsr, info->ac97_base + SAI_TCSR);
+
+ err = snd_ac97_bus(info->card, 0, &mvf_sai_ac97_ops, NULL, &ac97_bus);
+ if (err)
+ goto failed_bus;
+ memset(&ac97_template, 0, sizeof(ac97_template));
+ err = snd_ac97_mixer(ac97_bus, &ac97_template, &mvf_sai_ac97_ac97);
+ if (err)
+ goto failed_mixer;
+
+ snprintf(info->card->shortname, sizeof(info->card->shortname), "%s",
+ snd_ac97_get_short_name(mvf_sai_ac97_ac97));
+ snprintf(info->card->longname, sizeof(info->card->longname), "%s (%s)",
+ pdev->dev.driver->name, info->card->mixername);
+
+ snd_card_set_dev(info->card, &pdev->dev);
+ err = snd_card_register(info->card);
+ if (err == 0) {
+ platform_set_drvdata(pdev, info->card);
+ return 0;
+ }
+
+failed:
+ if (info->card)
+ snd_card_disconnect(info->card);
+failed_mixer:
+failed_bus:
+ /* disable transmit/receive and respective DMAs */
+ tcsr = readl(info->ac97_base + SAI_TCSR);
+ rcsr = readl(info->ac97_base + SAI_RCSR);
+ tcsr &= ~SAI_TCSR_FRDE;
+ rcsr &= ~SAI_RCSR_FRDE;
+ tcsr &= ~SAI_TCSR_TE;
+ rcsr &= ~SAI_RCSR_RE;
+ writel(rcsr, info->ac97_base + SAI_RCSR);
+ writel(tcsr, info->ac97_base + SAI_TCSR);
+
+ mcf_edma_free_channel(DMA_MUX_SAI2_RX, NULL);
+failed_request_rx_dma:
+ mcf_edma_free_channel(DMA_MUX_SAI2_TX, NULL);
+failed_request_tx_dma:
+ dma_free_writecombine(&pdev->dev, MVF_SAI_AC97_DMABUF_SIZE,
+ info->rx_buf.area, info->rx_buf.addr);
+failed_rx_buf:
+ dma_free_writecombine(&pdev->dev, MVF_SAI_AC97_DMABUF_SIZE,
+ info->tx_buf.area, info->tx_buf.addr);
+failed_tx_buf:
+//iomux
+ snd_card_free(info->card);
+failed_create:
+ clk_disable(info->clk);
+ clk_put(info->clk);
+
+ /* disable AC97 master clock */
+ reg = readl(info->sai0_base + SAI_TCSR);
+ writel(reg & ~SAI_TCSR_BCE, info->sai0_base + SAI_TCSR);
+failed_clk:
+ iounmap((volatile void *)MVF_SAI0_BASE_ADDR);
+failed_map2:
+ iounmap(info->ac97_base);
+failed_map:
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res) release_mem_region(res->start, resource_size(res));
+failed_region:
+failed_mem:
+// free_irq(info->irq, NULL);
+failed_irq:
+ kfree(info);
+failed_alloc:
+ return err;
+}
+
+static int __devexit mvf_sai_ac97_dev_remove(struct platform_device *pdev)
+{
+ unsigned int reg;
+ unsigned int rcsr;
+ struct resource *res;
+ unsigned int tcsr;
+
+ snd_card_disconnect(info->card);
+
+ /* disable transmit/receive and respective DMAs */
+ tcsr = readl(info->ac97_base + SAI_TCSR);
+ rcsr = readl(info->ac97_base + SAI_RCSR);
+ tcsr &= ~SAI_TCSR_FRDE;
+ rcsr &= ~SAI_RCSR_FRDE;
+ tcsr &= ~SAI_TCSR_TE;
+ rcsr &= ~SAI_RCSR_RE;
+ writel(rcsr, info->ac97_base + SAI_RCSR);
+ writel(tcsr, info->ac97_base + SAI_TCSR);
+
+ mcf_edma_free_channel(DMA_MUX_SAI2_RX, NULL);
+ mcf_edma_free_channel(DMA_MUX_SAI2_TX, NULL);
+ dma_free_writecombine(&pdev->dev, MVF_SAI_AC97_DMABUF_SIZE, info->rx_buf.area, info->rx_buf.addr);
+ dma_free_writecombine(&pdev->dev, MVF_SAI_AC97_DMABUF_SIZE, info->tx_buf.area, info->tx_buf.addr);
+//iomux
+ snd_card_free(info->card);
+ clk_disable(info->clk);
+ clk_put(info->clk);
+
+ /* disable AC97 master clock */
+ reg = readl(info->sai0_base + SAI_TCSR);
+ writel(reg & ~SAI_TCSR_BCE, info->sai0_base + SAI_TCSR);
+
+ iounmap((volatile void *)MVF_SAI0_BASE_ADDR);
+ iounmap(info->ac97_base);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res) release_mem_region(res->start, resource_size(res));
+// free_irq(info->irq, NULL);
+ kfree(info);
+
+ return 0;
+}
+
+static struct platform_driver mvf_sai_ac97_driver = {
+ .probe = mvf_sai_ac97_dev_probe,
+ .remove = __devexit_p(mvf_sai_ac97_dev_remove),
+ .driver = {
+ .name = "mvf-sai",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mvf_sai_ac97_init(void)
+{
+ return platform_driver_register(&mvf_sai_ac97_driver);
+}
+module_init(mvf_sai_ac97_init);
+
+static void __exit mvf_sai_ac97_exit(void)
+{
+ platform_driver_unregister(&mvf_sai_ac97_driver);
+}
+module_exit(mvf_sai_ac97_exit);
+
+MODULE_AUTHOR("Marcel Ziswiler");
+MODULE_DESCRIPTION("Software AC97 driver for the SAI of the Vybrid");
+MODULE_LICENSE("GPL");