aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/omap_hsi/hsi_driver_dma.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/omap_hsi/hsi_driver_dma.c')
-rw-r--r--drivers/omap_hsi/hsi_driver_dma.c643
1 files changed, 643 insertions, 0 deletions
diff --git a/drivers/omap_hsi/hsi_driver_dma.c b/drivers/omap_hsi/hsi_driver_dma.c
new file mode 100644
index 0000000..ad819f5
--- /dev/null
+++ b/drivers/omap_hsi/hsi_driver_dma.c
@@ -0,0 +1,643 @@
+/*
+ * hsi_driver_dma.c
+ *
+ * Implements HSI low level interface driver functionality with DMA support.
+ *
+ * Copyright (C) 2007-2008 Nokia Corporation. All rights reserved.
+ * Copyright (C) 2009 Texas Instruments, Inc.
+ *
+ * Author: Carlos Chinea <carlos.chinea@nokia.com>
+ * Author: Sebastien JAN <s-jan@ti.com>
+ *
+ * This package 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <linux/dma-mapping.h>
+#include "hsi_driver.h"
+
+#define HSI_SYNC_WRITE 0
+#define HSI_SYNC_READ 1
+#define HSI_L3_TPUT 13428 /* 13428 KiB/s => ~110 Mbit/s */
+
+static unsigned char hsi_sync_table[2][2][8] = {
+ {
+ {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
+ {0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}
+ }, {
+ {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+ {0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
+ }
+};
+
+/**
+ * hsi_is_dma_read_int_pending - Indicates if a DMA read interrupt is pending
+ * @hsi_ctrl - HSI controller of the GDD.
+ *
+ * Needs to be called holding the hsi_controller lock
+ *
+ * Returns true if DMA read interrupt is pending, else false
+ */
+bool hsi_is_dma_read_int_pending(struct hsi_dev *hsi_ctrl)
+{
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int gdd_lch = 0;
+ u32 status_reg = 0;
+ int i, j;
+ status_reg = hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+ status_reg &= hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ if (!status_reg)
+ return false;
+
+ /* Scan all enabled DMA channels */
+ for (gdd_lch = 0; gdd_lch < hsi_ctrl->gdd_chan_count; gdd_lch++) {
+ if (!(status_reg & HSI_GDD_LCH(gdd_lch)))
+ continue;
+ for (i = 0; i < hsi_ctrl->max_p; i++)
+ for (j = 0; j < hsi_ctrl->hsi_port[i].max_ch; j++)
+ if (hsi_ctrl->hsi_port[i].
+ hsi_channel[j].read_data.lch == gdd_lch)
+ return true;
+ }
+ return false;
+}
+/**
+ * hsi_get_free_lch - Get a free GDD(DMA) logical channel
+ * @hsi_ctrl - HSI controller of the GDD.
+ *
+ * Needs to be called holding the hsi_controller lock
+ *
+ * Returns the logical channel number, or -EBUSY if none available
+ */
+static int hsi_get_free_lch(struct hsi_dev *hsi_ctrl)
+{
+ unsigned int enable_reg;
+ int i, lch;
+
+ enable_reg = hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ lch = hsi_ctrl->last_gdd_lch;
+ for (i = 0; i < hsi_ctrl->gdd_chan_count; i++) {
+ if (++lch >= hsi_ctrl->gdd_chan_count)
+ lch = 0;
+ if ((enable_reg & HSI_GDD_LCH(lch)) == 0) {
+ hsi_ctrl->last_gdd_lch = lch;
+ return lch;
+ }
+ }
+ return -EBUSY;
+}
+
+/**
+ * hsi_driver_write_dma - Program GDD [DMA] to write data from memory to
+ * the hsi channel buffer.
+ * @hsi_channel - pointer to the hsi_channel to write data to.
+ * @data - 32-bit word pointer to the data.
+ * @size - Number of 32bit words to be transfered.
+ *
+ * hsi_controller lock must be held before calling this function.
+ *
+ * Return 0 on success and < 0 on error.
+ */
+int hsi_driver_write_dma(struct hsi_channel *hsi_channel, u32 * data,
+ unsigned int size)
+{
+ struct hsi_dev *hsi_ctrl = hsi_channel->hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = hsi_channel->hsi_port->port_number;
+ unsigned int channel = hsi_channel->channel_number;
+ unsigned int sync;
+ int lch;
+ dma_addr_t src_addr;
+ dma_addr_t dest_addr;
+ u16 tmp;
+ int fifo;
+
+ if ((size < 1) || (data == NULL))
+ return -EINVAL;
+
+ lch = hsi_get_free_lch(hsi_ctrl);
+ if (lch < 0) {
+ dev_err(hsi_ctrl->dev, "No free DMA channels.\n");
+ return -EBUSY; /* No free GDD logical channels. */
+ } else {
+ dev_dbg(hsi_ctrl->dev, "Allocated DMA channel %d for write on"
+ " HSI channel %d.\n", lch,
+ hsi_channel->channel_number);
+ }
+
+ /* NOTE: Getting a free gdd logical channel and
+ * reserve it must be done atomicaly. */
+ hsi_channel->write_data.lch = lch;
+
+ /* Sync is required for SSI but not for HSI */
+ sync = hsi_sync_table[HSI_SYNC_WRITE][port - 1][channel];
+
+ src_addr = dma_map_single(hsi_ctrl->dev, data, size * 4, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(hsi_ctrl->dev, src_addr))) {
+ dev_err(hsi_ctrl->dev, "Failed to create DMA write mapping.\n");
+ return -ENOMEM;
+ }
+
+ tmp = HSI_SRC_SINGLE_ACCESS0 |
+ HSI_SRC_MEMORY_PORT |
+ HSI_DST_SINGLE_ACCESS0 |
+ HSI_DST_PERIPHERAL_PORT | HSI_DATA_TYPE_S32;
+ hsi_outw(tmp, base, HSI_GDD_CSDP_REG(lch));
+
+ tmp = HSI_SRC_AMODE_POSTINC | HSI_DST_AMODE_CONST | sync;
+ hsi_outw(tmp, base, HSI_GDD_CCR_REG(lch));
+
+ hsi_outw((HSI_BLOCK_IE | HSI_TOUT_IE), base, HSI_GDD_CCIR_REG(lch));
+
+ if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) {
+ fifo = hsi_fifo_get_id(hsi_ctrl, channel, port);
+ if (unlikely(fifo < 0)) {
+ dev_err(hsi_ctrl->dev, "No valid FIFO id for DMA "
+ "transfer to FIFO.\n");
+ return -EFAULT;
+ }
+ /* HSI CDSA register takes a FIFO ID when copying to FIFO */
+ hsi_outl(fifo, base, HSI_GDD_CDSA_REG(lch));
+ } else {
+ dest_addr = hsi_ctrl->phy_base + HSI_HST_BUFFER_CH_REG(port,
+ channel);
+ /* SSI CDSA register always takes a 32-bit address */
+ hsi_outl(dest_addr, base, HSI_GDD_CDSA_REG(lch));
+ }
+
+ /* HSI CSSA register takes a 32-bit address when copying from memory */
+ /* SSI CSSA register always takes a 32-bit address */
+ hsi_outl(src_addr, base, HSI_GDD_CSSA_REG(lch));
+ hsi_outw(size, base, HSI_GDD_CEN_REG(lch));
+
+ /* TODO : Need to clean interrupt status here to avoid spurious int */
+
+ hsi_outl_or(HSI_GDD_LCH(lch), base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outw_or(HSI_CCR_ENABLE, base, HSI_GDD_CCR_REG(lch));
+
+ return 0;
+}
+
+/**
+ * hsi_driver_read_dma - Program GDD [DMA] to write data to memory from
+ * the hsi channel buffer.
+ * @hsi_channel - pointer to the hsi_channel to read data from.
+ * @data - 32-bit word pointer where to store the incoming data.
+ * @size - Number of 32bit words to be transfered to the buffer.
+ *
+ * hsi_controller lock must be held before calling this function.
+ *
+ * Return 0 on success and < 0 on error.
+ */
+int hsi_driver_read_dma(struct hsi_channel *hsi_channel, u32 * data,
+ unsigned int count)
+{
+ struct hsi_dev *hsi_ctrl = hsi_channel->hsi_port->hsi_controller;
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int port = hsi_channel->hsi_port->port_number;
+ unsigned int channel = hsi_channel->channel_number;
+ unsigned int sync;
+ int lch;
+ dma_addr_t src_addr;
+ dma_addr_t dest_addr;
+ u16 tmp;
+ int fifo;
+
+ lch = hsi_get_free_lch(hsi_ctrl);
+ if (lch < 0) {
+ dev_err(hsi_ctrl->dev, "No free DMA channels.\n");
+ return -EBUSY; /* No free GDD logical channels. */
+ } else {
+ dev_dbg(hsi_ctrl->dev, "Allocated DMA channel %d for read on"
+ " HSI channel %d.\n", lch,
+ hsi_channel->channel_number);
+ }
+
+ /* When DMA is used for Rx, disable the Rx Interrupt.
+ * (else DATAAVAILLABLE event would get triggered on first
+ * received data word)
+ * (Rx interrupt might be active for polling feature)
+ */
+#if 0
+ if (omap_readl(0x4A05A810)) {
+ dev_err(hsi_ctrl->dev,
+ "READ INTERRUPT IS PENDING DMA() but still disabling %0x\n",
+ omap_readl(0x4A05A810));
+ }
+#endif
+ hsi_driver_disable_read_interrupt(hsi_channel);
+
+ /*
+ * NOTE: Gettting a free gdd logical channel and
+ * reserve it must be done atomicaly.
+ */
+ hsi_channel->read_data.lch = lch;
+
+ /* Sync is required for SSI but not for HSI */
+ sync = hsi_sync_table[HSI_SYNC_READ][port - 1][channel];
+
+ dest_addr = dma_map_single(hsi_ctrl->dev, data, count * 4,
+ DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(hsi_ctrl->dev, dest_addr))) {
+ dev_err(hsi_ctrl->dev, "Failed to create DMA read mapping.\n");
+ return -ENOMEM;
+ }
+
+ tmp = HSI_DST_SINGLE_ACCESS0 |
+ HSI_DST_MEMORY_PORT |
+ HSI_SRC_SINGLE_ACCESS0 |
+ HSI_SRC_PERIPHERAL_PORT | HSI_DATA_TYPE_S32;
+ hsi_outw(tmp, base, HSI_GDD_CSDP_REG(lch));
+
+ tmp = HSI_DST_AMODE_POSTINC | HSI_SRC_AMODE_CONST | sync;
+ hsi_outw(tmp, base, HSI_GDD_CCR_REG(lch));
+
+ hsi_outw((HSI_BLOCK_IE | HSI_TOUT_IE), base, HSI_GDD_CCIR_REG(lch));
+
+ if (hsi_driver_device_is_hsi(to_platform_device(hsi_ctrl->dev))) {
+ fifo = hsi_fifo_get_id(hsi_ctrl, channel, port);
+ if (unlikely(fifo < 0)) {
+ dev_err(hsi_ctrl->dev, "No valid FIFO id for DMA "
+ "transfer from FIFO.\n");
+ return -EFAULT;
+ }
+ /* HSI CSSA register takes a FIFO ID when copying from FIFO */
+ hsi_outl(fifo, base, HSI_GDD_CSSA_REG(lch));
+ } else{
+ src_addr = hsi_ctrl->phy_base + HSI_HSR_BUFFER_CH_REG(port,
+ channel);
+ /* SSI CSSA register always takes a 32-bit address */
+ hsi_outl(src_addr, base, HSI_GDD_CSSA_REG(lch));
+ }
+
+ /* HSI CDSA register takes a 32-bit address when copying to memory */
+ /* SSI CDSA register always takes a 32-bit address */
+ hsi_outl(dest_addr, base, HSI_GDD_CDSA_REG(lch));
+ hsi_outw(count, base, HSI_GDD_CEN_REG(lch));
+
+ /* TODO : Need to clean interrupt status here to avoid spurious int */
+
+ hsi_outl_or(HSI_GDD_LCH(lch), base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outw_or(HSI_CCR_ENABLE, base, HSI_GDD_CCR_REG(lch));
+
+ return 0;
+}
+
+/**
+ * hsi_driver_cancel_write_dma - Cancel an ongoing GDD [DMA] write for the
+ * specified hsi channel.
+ * @hsi_ch - pointer to the hsi_channel to cancel DMA write.
+ *
+ * hsi_controller lock must be held before calling this function.
+ *
+ * Return: -ENXIO : No DMA channel found for specified HSI channel
+ * -ECANCELED : DMA cancel success, data not transfered to TX FIFO
+ * 0 : DMA transfer is already over, data already transfered to TX FIFO
+ *
+ * Note: whatever returned value, write callback will not be called after
+ * write cancel.
+ */
+int hsi_driver_cancel_write_dma(struct hsi_channel *hsi_ch)
+{
+ int lch = hsi_ch->write_data.lch;
+ unsigned int port = hsi_ch->hsi_port->port_number;
+ unsigned int channel = hsi_ch->channel_number;
+ struct hsi_dev *hsi_ctrl = hsi_ch->hsi_port->hsi_controller;
+ u16 ccr, gdd_csr;
+ long buff_offset;
+ u32 status_reg;
+ dma_addr_t dma_h;
+ size_t size;
+
+ dev_err(&hsi_ch->dev->device, "hsi_driver_cancel_write_dma( "
+ "channel %d\n", hsi_ch->channel_number);
+
+ if (lch < 0) {
+ dev_err(&hsi_ch->dev->device, "No DMA channel found for HSI "
+ "channel %d\n", hsi_ch->channel_number);
+ return -ENXIO;
+ }
+ ccr = hsi_inw(hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+ if (!(ccr & HSI_CCR_ENABLE)) {
+ dev_dbg(&hsi_ch->dev->device, "Write cancel on not "
+ "enabled logical channel %d CCR REG 0x%04X\n",
+ lch, ccr);
+ }
+ status_reg = hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+ status_reg &= hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outw_and(~HSI_CCR_ENABLE, hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+
+ /* Clear CSR register by reading it, as it is cleared automaticaly */
+ /* by HW after SW read. */
+ gdd_csr = hsi_inw(hsi_ctrl->base, HSI_GDD_CSR_REG(lch));
+ hsi_outl_and(~HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outl(HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+
+ /* Unmap DMA region */
+ dma_h = hsi_inl(hsi_ctrl->base, HSI_GDD_CSSA_REG(lch));
+ size = hsi_inw(hsi_ctrl->base, HSI_GDD_CEN_REG(lch)) * 4;
+ dma_unmap_single(hsi_ctrl->dev, dma_h, size, DMA_TO_DEVICE);
+
+ buff_offset = hsi_hst_bufstate_f_reg(hsi_ctrl, port, channel);
+ if (buff_offset >= 0)
+ hsi_outl_and(~HSI_BUFSTATE_CHANNEL(channel), hsi_ctrl->base,
+ buff_offset);
+
+ hsi_reset_ch_write(hsi_ch);
+ return status_reg & HSI_GDD_LCH(lch) ? 0 : -ECANCELED;
+}
+
+/**
+ * hsi_driver_cancel_read_dma - Cancel an ongoing GDD [DMA] read for the
+ * specified hsi channel.
+ * @hsi_ch - pointer to the hsi_channel to cancel DMA read.
+ *
+ * hsi_controller lock must be held before calling this function.
+ *
+ * Return: -ENXIO : No DMA channel found for specified HSI channel
+ * -ECANCELED : DMA cancel success, data not available at expected
+ * address.
+ * 0 : DMA transfer is already over, data already available at
+ * expected address.
+ *
+ * Note: whatever returned value, read callback will not be called after cancel.
+ */
+int hsi_driver_cancel_read_dma(struct hsi_channel *hsi_ch)
+{
+ int lch = hsi_ch->read_data.lch;
+ struct hsi_dev *hsi_ctrl = hsi_ch->hsi_port->hsi_controller;
+ u16 ccr, gdd_csr;
+ u32 status_reg;
+ dma_addr_t dma_h;
+ size_t size;
+
+ dev_err(&hsi_ch->dev->device, "hsi_driver_cancel_read_dma "
+ "channel %d\n", hsi_ch->channel_number);
+
+ /* Re-enable interrupts for polling if needed */
+ if (hsi_ch->flags & HSI_CH_RX_POLL)
+ hsi_driver_enable_read_interrupt(hsi_ch, NULL);
+
+ if (lch < 0) {
+ dev_err(&hsi_ch->dev->device, "No DMA channel found for HSI "
+ "channel %d\n", hsi_ch->channel_number);
+ return -ENXIO;
+ }
+
+ ccr = hsi_inw(hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+ if (!(ccr & HSI_CCR_ENABLE)) {
+ dev_dbg(&hsi_ch->dev->device, "Read cancel on not "
+ "enabled logical channel %d CCR REG 0x%04X\n",
+ lch, ccr);
+ }
+
+ status_reg = hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+ status_reg &= hsi_inl(hsi_ctrl->base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outw_and(~HSI_CCR_ENABLE, hsi_ctrl->base, HSI_GDD_CCR_REG(lch));
+
+ /* Clear CSR register by reading it, as it is cleared automaticaly */
+ /* by HW after SW read */
+ gdd_csr = hsi_inw(hsi_ctrl->base, HSI_GDD_CSR_REG(lch));
+ hsi_outl_and(~HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ hsi_outl(HSI_GDD_LCH(lch), hsi_ctrl->base,
+ HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+
+ /* Unmap DMA region - Access to the buffer is now safe */
+ dma_h = hsi_inl(hsi_ctrl->base, HSI_GDD_CDSA_REG(lch));
+ size = hsi_inw(hsi_ctrl->base, HSI_GDD_CEN_REG(lch)) * 4;
+ dma_unmap_single(hsi_ctrl->dev, dma_h, size, DMA_FROM_DEVICE);
+
+ hsi_reset_ch_read(hsi_ch);
+ return status_reg & HSI_GDD_LCH(lch) ? 0 : -ECANCELED;
+}
+
+/**
+ * hsi_get_info_from_gdd_lch - Retrieve channels information from DMA channel
+ * @hsi_ctrl - HSI device control structure
+ * @lch - DMA logical channel
+ * @port - HSI port
+ * @channel - HSI channel
+ * @is_read_path - channel is used for reading
+ *
+ * Updates the port, channel and is_read_path parameters depending on the
+ * lch DMA channel status.
+ *
+ * Return 0 on success and < 0 on error.
+ */
+int hsi_get_info_from_gdd_lch(struct hsi_dev *hsi_ctrl, unsigned int lch,
+ unsigned int *port, unsigned int *channel,
+ unsigned int *is_read_path)
+{
+ int i_ports;
+ int i_chans;
+ int err = -1;
+
+ for (i_ports = 0; i_ports < HSI_MAX_PORTS; i_ports++)
+ for (i_chans = 0; i_chans < HSI_PORT_MAX_CH; i_chans++)
+ if (hsi_ctrl->hsi_port[i_ports].
+ hsi_channel[i_chans].read_data.lch == lch) {
+ *is_read_path = 1;
+ *port = i_ports + 1;
+ *channel = i_chans;
+ err = 0;
+ goto get_info_bk;
+ } else if (hsi_ctrl->hsi_port[i_ports].
+ hsi_channel[i_chans].write_data.lch == lch) {
+ *is_read_path = 0;
+ *port = i_ports + 1;
+ *channel = i_chans;
+ err = 0;
+ goto get_info_bk;
+ }
+get_info_bk:
+ return err;
+}
+
+static void do_hsi_gdd_lch(struct hsi_dev *hsi_ctrl, unsigned int gdd_lch)
+{
+ void __iomem *base = hsi_ctrl->base;
+ struct platform_device *pdev = to_platform_device(hsi_ctrl->dev);
+ struct hsi_channel *ch;
+ unsigned int port;
+ unsigned int channel;
+ unsigned int is_read_path;
+ u32 gdd_csr;
+ dma_addr_t dma_h;
+ size_t size;
+ int fifo, fifo_words_avail;
+
+ if (hsi_get_info_from_gdd_lch(hsi_ctrl, gdd_lch, &port, &channel,
+ &is_read_path) < 0) {
+ dev_err(hsi_ctrl->dev, "Unable to match the DMA channel %d with"
+ " an HSI channel\n", gdd_lch);
+ return;
+ } else {
+ dev_dbg(hsi_ctrl->dev, "DMA event on gdd_lch=%d => port=%d, "
+ "channel=%d, read=%d\n", gdd_lch, port, channel,
+ is_read_path);
+ }
+
+ hsi_outl_and(~HSI_GDD_LCH(gdd_lch), base,
+ HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ /* Warning : CSR register is cleared automaticaly by HW after SW read */
+ gdd_csr = hsi_inw(base, HSI_GDD_CSR_REG(gdd_lch));
+
+ if (!(gdd_csr & HSI_CSR_TOUT)) {
+ if (is_read_path) { /* Read path */
+ dma_h = hsi_inl(base, HSI_GDD_CDSA_REG(gdd_lch));
+ size = hsi_inw(base, HSI_GDD_CEN_REG(gdd_lch)) * 4;
+ dma_sync_single_for_cpu(hsi_ctrl->dev, dma_h, size,
+ DMA_FROM_DEVICE);
+ dma_unmap_single(hsi_ctrl->dev, dma_h, size,
+ DMA_FROM_DEVICE);
+ ch = hsi_ctrl_get_ch(hsi_ctrl, port, channel);
+ hsi_reset_ch_read(ch);
+
+ dev_dbg(hsi_ctrl->dev, "Calling ch %d read callback "
+ "(size %d).\n", channel, size/4);
+ spin_unlock(&hsi_ctrl->lock);
+ ch->read_done(ch->dev, size / 4);
+ spin_lock(&hsi_ctrl->lock);
+
+ /* Check if FIFO is correctly emptied */
+ if (hsi_driver_device_is_hsi(pdev)) {
+ fifo = hsi_fifo_get_id(hsi_ctrl, channel, port);
+ if (unlikely(fifo < 0)) {
+ dev_err(hsi_ctrl->dev, "No valid FIFO "
+ "id found for channel %d.\n",
+ channel);
+ return;
+ }
+ fifo_words_avail =
+ hsi_get_rx_fifo_occupancy(hsi_ctrl,
+ fifo);
+ if (fifo_words_avail)
+ dev_dbg(hsi_ctrl->dev,
+ "WARNING: FIFO %d not empty "
+ "after DMA copy, remaining "
+ "%d/%d frames\n",
+ fifo, fifo_words_avail,
+ HSI_HSR_FIFO_SIZE);
+ }
+ /* Re-enable interrupts for polling if needed */
+ if (ch->flags & HSI_CH_RX_POLL)
+ hsi_driver_enable_read_interrupt(ch, NULL);
+ } else { /* Write path */
+ dma_h = hsi_inl(base, HSI_GDD_CSSA_REG(gdd_lch));
+ size = hsi_inw(base, HSI_GDD_CEN_REG(gdd_lch)) * 4;
+ dma_unmap_single(hsi_ctrl->dev, dma_h, size,
+ DMA_TO_DEVICE);
+ ch = hsi_ctrl_get_ch(hsi_ctrl, port, channel);
+ hsi_reset_ch_write(ch);
+
+ dev_dbg(hsi_ctrl->dev, "Calling ch %d write callback "
+ "(size %d).\n", channel, size/4);
+ spin_unlock(&hsi_ctrl->lock);
+ ch->write_done(ch->dev, size / 4);
+ spin_lock(&hsi_ctrl->lock);
+ }
+ } else {
+ dev_err(hsi_ctrl->dev, "Time-out overflow Error on GDD transfer"
+ " on gdd channel %d\n", gdd_lch);
+ spin_unlock(&hsi_ctrl->lock);
+ /* TODO : need to perform a DMA soft reset */
+ hsi_port_event_handler(&hsi_ctrl->hsi_port[port - 1],
+ HSI_EVENT_ERROR, NULL);
+ spin_lock(&hsi_ctrl->lock);
+ }
+}
+
+static u32 hsi_process_dma_event(struct hsi_dev *hsi_ctrl)
+{
+ void __iomem *base = hsi_ctrl->base;
+ unsigned int gdd_lch = 0;
+ u32 status_reg = 0;
+ u32 lch_served = 0;
+ unsigned int gdd_max_count = hsi_ctrl->gdd_chan_count;
+
+ status_reg = hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+ status_reg &= hsi_inl(base, HSI_SYS_GDD_MPU_IRQ_ENABLE_REG);
+ if (!status_reg) {
+ dev_dbg(hsi_ctrl->dev, "DMA : no event, exit.\n");
+ return 0;
+ }
+
+ for (gdd_lch = 0; gdd_lch < gdd_max_count; gdd_lch++) {
+ if (status_reg & HSI_GDD_LCH(gdd_lch)) {
+ do_hsi_gdd_lch(hsi_ctrl, gdd_lch);
+ lch_served |= HSI_GDD_LCH(gdd_lch);
+ }
+ }
+
+ /* Acknowledge interrupt for DMA channel */
+ hsi_outl(lch_served, base, HSI_SYS_GDD_MPU_IRQ_STATUS_REG);
+
+
+ return status_reg;
+}
+
+static void do_hsi_gdd_tasklet(unsigned long device)
+{
+ struct hsi_dev *hsi_ctrl = (struct hsi_dev *)device;
+
+ dev_dbg(hsi_ctrl->dev, "DMA Tasklet : clock_enabled=%d\n",
+ hsi_ctrl->clock_enabled);
+
+ spin_lock(&hsi_ctrl->lock);
+ hsi_clocks_enable(hsi_ctrl->dev, __func__);
+ hsi_ctrl->in_dma_tasklet = true;
+
+ hsi_process_dma_event(hsi_ctrl);
+
+ hsi_ctrl->in_dma_tasklet = false;
+ hsi_clocks_disable(hsi_ctrl->dev, __func__);
+ spin_unlock(&hsi_ctrl->lock);
+
+ enable_irq(hsi_ctrl->gdd_irq);
+}
+
+static irqreturn_t hsi_gdd_mpu_handler(int irq, void *p)
+{
+ struct hsi_dev *hsi_ctrl = p;
+
+ tasklet_hi_schedule(&hsi_ctrl->hsi_gdd_tasklet);
+
+ /* Disable interrupt until Bottom Half has cleared the IRQ status */
+ /* register */
+ disable_irq_nosync(hsi_ctrl->gdd_irq);
+
+ return IRQ_HANDLED;
+}
+
+int __init hsi_gdd_init(struct hsi_dev *hsi_ctrl, const char *irq_name)
+{
+ tasklet_init(&hsi_ctrl->hsi_gdd_tasklet, do_hsi_gdd_tasklet,
+ (unsigned long)hsi_ctrl);
+
+ dev_info(hsi_ctrl->dev, "Registering IRQ %s (%d)\n",
+ irq_name, hsi_ctrl->gdd_irq);
+
+ if (request_irq(hsi_ctrl->gdd_irq, hsi_gdd_mpu_handler,
+ IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH,
+ irq_name, hsi_ctrl) < 0) {
+ dev_err(hsi_ctrl->dev, "FAILED to request GDD IRQ %d\n",
+ hsi_ctrl->gdd_irq);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+void hsi_gdd_exit(struct hsi_dev *hsi_ctrl)
+{
+ tasklet_kill(&hsi_ctrl->hsi_gdd_tasklet);
+ free_irq(hsi_ctrl->gdd_irq, hsi_ctrl);
+}