summaryrefslogtreecommitdiff
path: root/drivers/char
diff options
context:
space:
mode:
authorRob Herring <r.herring@freescale.com>2008-02-14 14:44:21 -0600
committerDaniel Schaeffer <daniel.schaeffer@timesys.com>2008-08-25 15:18:55 -0400
commit3f8ed3afb9cee6648f9650d5daf950bb9347cca6 (patch)
treec9e28cdb34524d44c5dc4dc0778ebdcb858af20c /drivers/char
parent49914084e797530d9baaf51df9eda77babc98fa8 (diff)
ENGR00065563 Merge L2622-01 to 2.6.24
Merge L2622-01 release to 2.6.24 kernel.
Diffstat (limited to 'drivers/char')
-rw-r--r--drivers/char/Kconfig51
-rw-r--r--drivers/char/Makefile3
-rw-r--r--drivers/char/mxc_ipc.c2823
-rw-r--r--drivers/char/mxc_mu.c1646
-rw-r--r--drivers/char/mxc_mu_reg.h105
-rw-r--r--drivers/char/mxc_sdma_tty.c744
-rw-r--r--drivers/char/watchdog/mxc_wdt.c385
-rw-r--r--drivers/char/watchdog/mxc_wdt.h37
8 files changed, 5794 insertions, 0 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 2e3a0d4bc4c2..b77901481d37 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -411,6 +411,57 @@ config SGI_MBCS
If you have an SGI Altix with an attached SABrick
say Y or M here, otherwise say N.
+config MXC_MU
+ bool "MXC Messaging Unit Driver"
+ depends on ARCH_MXC
+ depends on !ARCH_MX3
+ depends on !ARCH_MX33
+ depends on !ARCH_MX27
+ depends on !ARCH_MX21
+ select INPUT
+ default y
+ ---help---
+ This module is called from other modules for
+ message transfers between MCU and DSP core.
+
+config MXC_IPC
+ bool "MXC IPC driver"
+ depends on MXC_MU && MXC_SDMA_API
+ depends on ARCH_MXC
+ depends on !ARCH_MX3
+ depends on !ARCH_MX33
+ depends on !ARCH_MX27
+ depends on !ARCH_MX21
+ default y
+ ---help---
+ This module is called from other modules for
+ message and data transfers between MCU and DSP core.
+
+config MXC_SUPER_GEM
+ bool "MXC Super GEM"
+ depends on MXC_SDMA_API
+ depends on ARCH_MXC
+ depends on !ARCH_MX3
+ depends on !ARCH_MX33
+ depends on !ARCH_MX27
+ depends on !ARCH_MX21
+ default n
+ ---help---
+ Super GEM module called for transfers between MCU and DSP core.
+
+config MXC_SDMA_TTY
+ tristate "MXC SDMA TTY Driver"
+ depends on ARCH_MXC && MXC_SDMA_API
+ depends on !ARCH_MX3
+ depends on !ARCH_MX33
+ depends on !ARCH_MX21
+ depends on !ARCH_MX27
+ select INPUT
+ default y
+ ---help---
+ This module is called from other modules for
+ data transfers between MCU and DSP core.
+
source "drivers/serial/Kconfig"
config UNIX98_PTYS
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 07304d50e0cb..72ead21d2fc5 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -96,6 +96,9 @@ obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o
obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
obj-$(CONFIG_GPIO_TB0219) += tb0219.o
obj-$(CONFIG_TELCLOCK) += tlclk.o
+obj-$(CONFIG_MXC_MU) += mxc_mu.o
+obj-$(CONFIG_MXC_IPC) += mxc_ipc.o
+obj-$(CONFIG_MXC_SDMA_TTY) += mxc_sdma_tty.o
obj-$(CONFIG_MWAVE) += mwave/
obj-$(CONFIG_AGP) += agp/
diff --git a/drivers/char/mxc_ipc.c b/drivers/char/mxc_ipc.c
new file mode 100644
index 000000000000..a89f14c1ec20
--- /dev/null
+++ b/drivers/char/mxc_ipc.c
@@ -0,0 +1,2823 @@
+/*
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file mxc_ipc.c
+ *
+ * @brief This file provides all the kernel level and user level API
+ * definitions for all kind of transfers between MCU and DSP core.
+ *
+ * The Interfaces are with respect to the MCU core. Any driver or user-space
+ * application on the MCU side can transfer messages or data to the DSP side
+ * using the interfaces implemented here.
+ *
+ * @ingroup IPC
+ */
+
+/*
+ * Include Files
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/kdev_t.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/platform_device.h>
+#include <linux/kthread.h>
+#include <linux/circ_buf.h>
+#include <linux/uio.h>
+#include <linux/poll.h>
+#include <linux/dma-mapping.h>
+#include <asm/arch/mxc_mu.h>
+#include <asm/arch/mxc_ipc.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/semaphore.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+
+#include "mxc_mu_reg.h"
+
+/*!
+ * Automatic major assignment
+ */
+#define MXC_IPC_MAJOR 0x0
+
+/*! SDMA total receive buffer size, used by kernel space API */
+#define MAX_SDMA_RX_BUF_SIZE 8192
+/*! SDMA transmit buffer maximum size */
+#define MAX_SDMA_TX_BUF_SIZE 8192
+/*!
+ * Number of buffers used for SDMA receive, used by user API. This should
+ * be set to a power of 2.
+ */
+#define NUM_RX_SDMA_BUF 16
+/*! Size of each SDMA receive buffer */
+#define SDMA_RX_BUF_SIZE 512
+/*! MU buffer maximum size */
+#define MAX_MU_BUF_SIZE 512
+
+/*!
+ * Max virtual channels available for this implementation
+ */
+#define IPC_MAX_VIRTUAL_CHANNELS 32
+
+/*!
+ * Max ipc channels available for this implementation
+ */
+#define IPC_MAX_IPC_CHANNEL 10
+
+/*!
+ * This defines an unused virtual channel
+ */
+#define IPC_DUMMY_CHANNEL IPC_MAX_VIRTUAL_CHANNELS + 1
+
+/*!
+ * Max config index allowed for IPC channels. There are three types:
+ * read/write channel for messages
+ * read/write channel for data
+ * read only for data
+ */
+#define IPC_MAX_MU_CHANNEL_INDEX 3
+#define IPC_MAX_SDMA_BIDI_CHANNEL_INDEX 1
+#define IPC_MAX_SDMA_MONO_CHANNEL_INDEX 3
+
+/*!
+ * States allowed for an IPC channel
+ */
+#define CHANNEL_CLOSED 0x0
+#define CHANNEL_OPEN 0x1
+#define CHANNEL_READ_ONGOING 0x2
+#define CHANNEL_WRITE_ONGOING 0x4
+
+/*!
+ * Physical channels used to implement IPC channels.
+ * This defines depends on channels used on DSP side
+ * i.e. if the DSP uses SDMA channel 1 to do writes,
+ * we must use SDMA channel 1 to receive data.
+ *
+ * Currently we use 8 physical channels to implement the
+ * IPC abstraction.
+ *
+ * - MU channel 0 --> IPC channel 0
+ * - MU channel 1 --> IPC channel 1
+ * - MU channel 2 --> IPC channel 2
+ * - MU channel 3 --> IPC channel 3
+ * - SDMA Log channel 0 --> IPC channel 4
+ * - SDMA Log channel 1 --> IPC channel 5
+ * - SDMA Log channel 2 --> IPC channel 6
+ * - SDMA Log channel 3 --> IPC channel 7
+ * - SDMA Data channel 0 --> IPC channel 8
+ * - SDMA Data channel 1 --> IPC channel 9
+ *
+ * Note that IPC channels 8 and 9 uses two physical channels. This
+ * is because SDMA channels are unidirectional and IPC
+ * links must be bidirectional.
+ *
+ */
+#define SHORT_MESSAGE_CHANNEL0 0
+#define SHORT_MESSAGE_CHANNEL1 1
+#define SHORT_MESSAGE_CHANNEL2 2
+#define SHORT_MESSAGE_CHANNEL3 3
+#define LOGGING_CHANNEL0 4
+#define LOGGING_CHANNEL1 5
+#define LOGGING_CHANNEL2 6
+#define LOGGING_CHANNEL3 7
+#define PACKET_DATA_CHANNEL0 8
+#define PACKET_DATA_CHANNEL1 9
+
+/*!
+ * SDMA physical channels used for the different IPC channels
+ */
+#define PACKET_DATA0_SDMA_RD_CHNL 1
+#define PACKET_DATA0_SDMA_WR_CHNL 2
+#define PACKET_DATA1_SDMA_RD_CHNL 3
+#define PACKET_DATA1_SDMA_WR_CHNL 4
+#define LOG0_SDMA_RD_CHNL 5
+#define LOG1_SDMA_RD_CHNL 6
+#define LOG2_SDMA_RD_CHNL 7
+#define LOG3_SDMA_RD_CHNL 8
+
+/*!
+ * Operations allowed on IPC channels. These macros are
+ * used to tell the IPC driver-interface level which
+ * operation to execute.
+*/
+#define IPC_READ 0x1
+#define IPC_WRITE 0x2
+#define IPC_RDWR IPC_READ | IPC_WRITE
+
+#define IPC_WRITE_BYTE_INIT_VAL -4
+
+#define IPC_DEFAULT_MAX_CTRL_STRUCT_NUM 50
+
+typedef void (*read_callback_t) (HW_CTRL_IPC_READ_STATUS_T *);
+typedef void (*write_callback_t) (HW_CTRL_IPC_WRITE_STATUS_T *);
+typedef void (*notify_callback_t) (HW_CTRL_IPC_NOTIFY_STATUS_T *);
+
+/*!
+ * This structure represents the IPC channels SDMA receive buffer.
+ */
+struct sdma_rbuf {
+ /*! Virtual addres of the read buffer */
+ char *buf;
+
+ /*! Physical address of the SDMA read buffers */
+ dma_addr_t rpaddr;
+
+ /*! Amount of data available to read */
+ int count;
+
+ /*! Offset within read buffer */
+ int offset;
+};
+
+/*!
+ * This structure represents the SDMA channels used
+ * by the IPC abstraction.
+ */
+struct sdma_channel {
+ /*! SDMA read channel number */
+ int read_channel;
+
+ /*! SDMA write channel number */
+ int write_channel;
+
+ /*! SDMA write buffer */
+ char *wbuf;
+
+ /*! Physical address of the SDMA write buffer */
+ dma_addr_t wpaddr;
+
+ /*! DMA read buffers, uses muti-buffering scheme */
+ struct sdma_rbuf rbuf[NUM_RX_SDMA_BUF];
+
+ /*! Head to keep track of DMA read buffers */
+ int rbuf_head;
+
+ /*! Tail to keep track of DMA read buffers */
+ int rbuf_tail;
+
+ /*! Head to keep track of DMA write buffers */
+ int wbuf_head;
+
+ /*! Tail to keep track of DMA write buffers */
+ int wbuf_tail;
+
+ /*! Tasklet to call SDMA read callback */
+ struct tasklet_struct read_tasklet;
+
+ /*! Tasklet to call SDMA write callback */
+ struct tasklet_struct write_tasklet;
+};
+
+/*!
+ * This structure represents the MU channels used
+ * by the IPC abstraction.
+ */
+struct mu_channel {
+ /*! MU channel number */
+ int index;
+
+ /*! MU read buffer */
+ struct circ_buf *rbuf;
+
+ /*! MU write buffer */
+ struct circ_buf *wbuf;
+
+ /*! Number of bytes writen */
+ int bytes_written;
+};
+
+/*!
+ * This structure represents an IPC channel. It is represented
+ * by one or even two physical channels, plus a control structure
+ */
+struct ipc_channel {
+ /*! Physical channel(s) used to implement an IPC channel */
+ union {
+ struct sdma_channel sdma;
+ struct mu_channel mu;
+ } ch;
+
+ /*!
+ * Control structure. It handles access and transfers on this
+ * IPC channel
+ */
+ struct ipc_priv_data *priv;
+
+ /*! Lock to protect the channel buffers */
+ spinlock_t channel_lock;
+};
+
+/*!
+ * This structure represents a virtual channel. Multiple virtual channels
+ * could be connected to an IPC channel. IPC abstraction supports up to 32
+ * virtual channels.
+ */
+struct virtual_channel {
+ /*! Controls access to the handle of this channel */
+ struct semaphore sem;
+
+ /*! Current state of the channel */
+ atomic_t state;
+ /*!Added state variables for IPCv2 Read-while -write functionality */
+ atomic_t read_state_ipcv2;
+
+ atomic_t write_state_ipcv2;
+
+ /*!
+ *
+ */
+ char *data;
+
+ /*!
+ * Pointer to the control structure of the IPC channel mapped by
+ * this virtual channel
+ */
+ struct ipc_channel *ipc;
+};
+
+/*!
+ * Private data. Each time an IPC channel is opened, a private
+ * structure is allocated and stored in the priv
+ * field of the file descriptor. This data is
+ * used for further operations on the recently opened
+ * channel
+ */
+struct ipc_priv_data {
+ /*! Blocking mode (non-blocking, blocking) */
+ atomic_t blockmode;
+
+ /*! Physical channel to which the virtual channel is mapped to */
+ unsigned short pchannel;
+
+ /*! Index of virtual channel */
+ unsigned short vchannel;
+
+ /*! This stores number of bytes received */
+ unsigned int read_count;
+
+ /*! This stores number of bytes transmitted */
+ unsigned int write_count;
+
+ /*! Write waitqueue */
+ wait_queue_head_t wq;
+
+ /*! Read waitqueue */
+ wait_queue_head_t rq;
+
+ /*! Store the task_struct structure of the calling process. */
+ struct task_struct *owner;
+
+ /*!
+ * Virtual channel assigned to the IPC channel storing
+ * this structure.
+ */
+ struct virtual_channel *vc;
+
+ /*!
+ * Callbacks used to signal the end of a operation
+ * on an IPC channel. Only for kernel modules.
+ */
+ struct kernel_callbacks *k_callbacks;
+};
+
+/*!
+ * This struct stores the callbacks pointers
+ * used to communicate the end of an operation
+ * on an IPC channel. These callbacks must be
+ * provided by the caller. This feature is available
+ * only for kernel modules.
+ */
+struct kernel_callbacks {
+ read_callback_t read;
+ write_callback_t write;
+ notify_callback_t notify;
+};
+
+/*!
+ * Array used to store the physical channels.
+ */
+static struct ipc_channel ipc_channels[IPC_MAX_IPC_CHANNEL];
+
+/*!
+ * Array used to store the pointers to the virtual channels.
+ * These pointers will be allocated when trying to open a virtual channel.
+ */
+static struct virtual_channel virtual_channels[IPC_MAX_VIRTUAL_CHANNELS];
+
+/*!
+ * Array used to store the channel handlers, correlated to the virtual_channels array.
+ * In future implementations, the handler could have been directly a
+ * virtual_channel pointer, but then it would need an harmonization between AP and BP
+ * structures to properly define what is a handler to a virtual channel on both sides.
+ */
+static HW_CTRL_IPC_CHANNEL_T channel_handlers[IPC_MAX_VIRTUAL_CHANNELS];
+
+/*!
+ * Status registers of the MU channels
+ */
+static unsigned int wbits[4] = { AS_MUMSR_MTE0, AS_MUMSR_MTE1, AS_MUMSR_MTE2,
+ AS_MUMSR_MTE3
+};
+static unsigned int rbits[4] = { AS_MUMSR_MRF0, AS_MUMSR_MRF1, AS_MUMSR_MRF2,
+ AS_MUMSR_MRF3
+};
+
+/*!
+ * Major number
+ */
+static int major_num = 0;
+
+static struct class *mxc_ipc_class;
+
+static void sdma_user_readcb(void *args);
+static void sdma_user_writecb(void *args);
+
+/*!
+ * Locks the virtual channel located on the index channel.
+ * @param channel index of the virtual channel to lock
+ */
+static inline int lock_virtual_channel(unsigned short channel)
+{
+ return (down_interruptible(&virtual_channels[channel].sem));
+}
+
+/*!
+ * Try to locks the virtual channel located on the index channel.
+ * @param channel index of the virtual channel to (try)lock
+ */
+static inline int trylock_virtual_channel(unsigned short channel)
+{
+ return down_trylock(&virtual_channels[channel].sem);
+}
+
+/*!
+ * Unlocks the virtual channel located on the index channel
+ * @param channel index of the virtual channel to unlock
+ */
+static inline void unlock_virtual_channel(unsigned short channel)
+{
+ up(&virtual_channels[channel].sem);
+}
+
+/*!
+ * Finds an unused channel into the virtual channel array
+ *
+ * @return The function returns teh index of the first unused
+ * channel found, IPC_DUMMY_CHANNEL otherwise.
+ */
+inline int get_free_virtual_channel(void)
+{
+ struct virtual_channel *vch = NULL;
+ int i;
+
+ for (i = 0; i < IPC_MAX_VIRTUAL_CHANNELS; i++) {
+ if (trylock_virtual_channel(i) == 1) {
+ continue;
+ }
+
+ vch = &virtual_channels[i];
+
+ if (atomic_read(&vch->state) == CHANNEL_CLOSED) {
+ return i;
+ }
+
+ unlock_virtual_channel(i);
+ }
+
+ return IPC_DUMMY_CHANNEL;
+}
+
+/*!
+ * The MU interrupt handler schedules this tasklet for
+ * execution to start processing a write request.
+ * This function writes data to chnum MU register and signals
+ * the end of the transfer to the servicing thread attached
+ * to the IPC channel.
+ *
+ * @param chnum MU channel number where the operation will occur
+ */
+static void mu_write_tasklet(int chnum)
+{
+ struct mu_channel *mu = NULL;
+ struct ipc_priv_data *priv = NULL;
+ char *p = NULL;
+ int bytes, error = 0;
+
+ priv = ipc_channels[chnum].priv;
+ mu = &ipc_channels[chnum].ch.mu;
+
+ /* Check for bytes in the IPC's MU channel write buffer */
+ bytes = CIRC_CNT(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE);
+
+ while ((bytes != 0) && (readl(AS_MUMSR) & wbits[chnum])) {
+
+ pr_debug("remaining bytes = %d\n", bytes);
+
+ p = &mu->wbuf->buf[mu->wbuf->tail];
+
+ if (mxc_mu_mcuwrite(mu->index, p)) {
+ pr_debug("Mu MCU write Failed\n");
+ error = 1;
+ break;
+ }
+ mu->wbuf->tail = (mu->wbuf->tail + 4) & (MAX_MU_BUF_SIZE - 1);
+ bytes = CIRC_CNT(mu->wbuf->head, mu->wbuf->tail,
+ MAX_MU_BUF_SIZE);
+ }
+
+ spin_lock(&ipc_channels[chnum].channel_lock);
+ bytes = CIRC_CNT(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE);
+ /* Disable interrupts if buffer is empty */
+ if ((bytes == 0) && (error == 0)) {
+ pr_debug("(2)remaining bytes = %d\n", bytes);
+ mxc_mu_intdisable(mu->index, TX);
+ }
+ spin_unlock(&ipc_channels[chnum].channel_lock);
+
+ if (waitqueue_active(&priv->wq)) {
+ wake_up_interruptible(&priv->wq);
+ }
+}
+
+/*!
+ * The MU interrupt handler schedules this tasklet for
+ * execution to start processing a read request.
+ * This function reads data from chnum MU register and signals
+ * the end of the transfer to the servicing thread attached
+ * to the IPC channel.
+ *
+ * @param chnum MU channel number where the operation will occur
+ */
+static void mu_read_tasklet(int chnum)
+{
+ struct mu_channel *mu = NULL;
+ struct ipc_priv_data *priv = NULL;
+ int bytes = 0;
+ char p[4];
+ int error = 0;
+
+ priv = ipc_channels[chnum].priv;
+ mu = &ipc_channels[chnum].ch.mu;
+
+ bytes = CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE);
+
+ while ((bytes >= 4) && (readl(AS_MUMSR) & rbits[chnum])) {
+
+ pr_debug("space available = %d\n", bytes);
+
+ if (mxc_mu_mcuread(mu->index, p)) {
+ pr_debug("MU MCU Read Failed\n");
+ error = 1;
+ break;
+ }
+
+ *((int *)(&(mu->rbuf->buf[mu->rbuf->head]))) = *((int *)p);
+ mu->rbuf->head = (mu->rbuf->head + 4) & (MAX_MU_BUF_SIZE - 1);
+
+ bytes = CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail,
+ MAX_MU_BUF_SIZE);
+ }
+
+ /* Check if space available in read buffer */
+ spin_lock(&ipc_channels[chnum].channel_lock);
+ bytes = CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE);
+ if ((bytes < 4) && (error == 0)) {
+ mxc_mu_intdisable(mu->index, RX);
+ } else {
+ pr_debug("(2)space available = %d\n", bytes);
+ mxc_mu_intenable(mu->index, RX);
+ }
+ spin_unlock(&ipc_channels[chnum].channel_lock);
+ if (waitqueue_active(&priv->rq)) {
+ wake_up_interruptible(&priv->rq);
+ }
+}
+
+/*!
+ * The MU interrupt handler schedules this tasklet for
+ * execution to start processing a read request.
+ * This function reads data from chnum MU register and signals
+ * the end of the transfer to the servicing thread attached
+ * to the IPC channel.
+ *
+ * This function is used when the caller is a kernel module
+ *
+ * @param chnum MU channel number where the operation will occur
+ */
+static void mu_read_tasklet_kernel(int chnum)
+{
+ struct ipc_priv_data *priv = NULL;
+ struct mu_channel *mu = NULL;
+ char p[4];
+ int bytes = 0;
+ int error = 0;
+
+ priv = ipc_channels[chnum].priv;
+ mu = &ipc_channels[chnum].ch.mu;
+
+ while ((bytes < priv->read_count) && (readl(AS_MUMSR) & rbits[chnum])) {
+ pr_debug("space available = %d\n", bytes);
+ if (mxc_mu_mcuread(mu->index, p)) {
+ pr_debug("MU MCU Read Failed");
+ error = 1;
+ break;
+ }
+
+ *((int *)(&(priv->vc->data[bytes]))) = *((int *)p);
+ bytes += 4;
+ }
+
+ if (error != 0) {
+ mxc_mu_intenable(mu->index, RX);
+ } else {
+ mxc_mu_intdisable(mu->index, RX);
+ mxc_mu_unbind(mu->index, RX);
+ atomic_set(&priv->vc->state, CHANNEL_OPEN);
+
+ if (priv->k_callbacks->read != NULL) {
+ HW_CTRL_IPC_READ_STATUS_T status;
+
+ status.channel = &channel_handlers[priv->vchannel];
+ status.nb_bytes = bytes;
+ priv->k_callbacks->read(&status);
+ }
+ }
+}
+
+/*!
+ * The MU interrupt handler schedules this tasklet for
+ * execution to start processing a write request.
+ * This function write data to chnum MU register and signals
+ * the end of the transfer to the servicing thread attached
+ * to the IPC channel.
+ *
+ * This function is used when the caller is a kernel module
+ *
+ * @param chnum MU channel number where the operation will occur
+ */
+static void mu_write_tasklet_kernel(int chnum)
+{
+ struct ipc_priv_data *priv = NULL;
+ struct mu_channel *mu = NULL;
+ char *p = NULL;
+ int error = 0;
+
+ priv = ipc_channels[chnum].priv;
+ mu = &ipc_channels[chnum].ch.mu;
+
+ /* Indication that previous write was successful */
+ mu->bytes_written += 4;
+
+ while (mu->bytes_written < priv->write_count) {
+ pr_debug("remaining bytes = %d\n", mu->bytes_written);
+ p = &priv->vc->data[mu->bytes_written];
+ if (mxc_mu_mcuwrite(mu->index, p) != 0) {
+ error = 1;
+ break;
+ }
+ if ((readl(AS_MUMSR) & wbits[chnum]) == 1) {
+ mu->bytes_written += 4;
+ } else {
+ /*
+ * Data not yet read by DSP side, enable interrupts
+ * and exit
+ */
+ break;
+ }
+ }
+
+ /* Enable interrupts if there is data pending to be transmitted */
+ if ((error == 0) && (mu->bytes_written < priv->write_count)) {
+ mxc_mu_intenable(mu->index, TX);
+ } else {
+ mxc_mu_unbind(mu->index, TX);
+ atomic_set(&priv->vc->state, CHANNEL_OPEN);
+
+ if (priv->k_callbacks->write != NULL) {
+ HW_CTRL_IPC_WRITE_STATUS_T status;
+
+ status.channel = &channel_handlers[priv->vchannel];
+ status.nb_bytes = mu->bytes_written;
+ priv->k_callbacks->write(&status);
+ }
+ }
+}
+
+/*!
+ * Enable MU channel interrupts
+ *
+ * @param chnum MU channel to enable
+ * @return returns 0 on success, negative value otherwise
+ */
+static int mxc_ipc_enable_muints(int chnum)
+{
+ int status = 0;
+ struct mu_channel *mu = NULL;
+
+ mu = &ipc_channels[chnum].ch.mu;
+ /* Bind the default callback functions */
+ status = mxc_mu_bind(mu->index, &mu_read_tasklet, RX);
+ if (status == 0) {
+ status = mxc_mu_bind(mu->index, &mu_write_tasklet, TX);
+ }
+ if (status == 0) {
+ status = mxc_mu_intdisable(mu->index, TX);
+ }
+ /* Enable the RX interrupt to start receiving data */
+ if (status == 0) {
+ status = mxc_mu_intenable(mu->index, RX);
+ }
+
+ /* Cleanup if we had an error at any step */
+ if (status != 0) {
+ pr_debug("Error enabling MU channel %d interrupts \n", chnum);
+ mxc_mu_unbind(mu->index, RX);
+ mxc_mu_unbind(mu->index, TX);
+ }
+
+ return status;
+}
+
+/*!
+ * Allocate a MU channel
+ *
+ * @param chnum MU channel to allocate
+ * @return returns 0 on success, negative value otherwise
+ */
+static int allocate_mu_channel(int chnum)
+{
+ struct mu_channel *mu = NULL;
+ int num = 0, status = 0;
+
+ num = mxc_mu_alloc_channel(chnum);
+ if (num < 0) {
+ pr_debug("Allocation of MU channel %d failed \n", chnum);
+ return num;
+ }
+ mu = &ipc_channels[chnum].ch.mu;
+ mu->index = num;
+
+ status = mxc_mu_intdisable(mu->index, TX);
+ if (status == 0) {
+ status = mxc_mu_intdisable(mu->index, RX);
+ }
+
+ if (status != 0) {
+ mxc_mu_dealloc_channel(mu->index);
+ return status;
+ }
+ /* Allocate the read and write buffers */
+ mu->rbuf = kmalloc(sizeof(struct circ_buf), GFP_KERNEL);
+ if (mu->rbuf == NULL) {
+ goto err_alloc_rbuf;
+ }
+ mu->rbuf->buf = kmalloc(MAX_MU_BUF_SIZE, GFP_KERNEL);
+ if (mu->rbuf->buf == NULL) {
+ goto err_alloc_rbuf_buf;
+ }
+ mu->wbuf = kmalloc(sizeof(struct circ_buf), GFP_KERNEL);
+ if (mu->wbuf == NULL) {
+ goto err_alloc_wbuf;
+ }
+ mu->wbuf->buf = kmalloc(MAX_MU_BUF_SIZE, GFP_KERNEL);
+ if (mu->wbuf->buf == NULL) {
+ goto err_alloc_wbuf_buf;
+ }
+ spin_lock_init(&ipc_channels[chnum].channel_lock);
+ mu->wbuf->head = mu->wbuf->tail = 0;
+ mu->rbuf->head = mu->rbuf->tail = 0;
+
+ return 0;
+
+ err_alloc_wbuf_buf:
+ kfree(mu->wbuf);
+ err_alloc_wbuf:
+ kfree(mu->rbuf->buf);
+ err_alloc_rbuf_buf:
+ kfree(mu->rbuf);
+ err_alloc_rbuf:
+ mxc_mu_dealloc_channel(mu->index);
+ return -ENOMEM;
+}
+
+/*! Deallocate a MU channel
+ *
+ * @param chnum MU channel to allocate
+ * @return returns 0 on success, negative value otherwise
+ */
+static int deallocate_mu_channel(int chnum)
+{
+ struct mu_channel *mu = NULL;
+ int ret = 0;
+
+ mu = &ipc_channels[chnum].ch.mu;
+
+ ret = mxc_mu_intdisable(mu->index, TX);
+ ret += mxc_mu_intdisable(mu->index, RX);
+ ret += mxc_mu_unbind(mu->index, TX);
+ ret += mxc_mu_unbind(mu->index, RX);
+ ret += mxc_mu_dealloc_channel(mu->index);
+
+ mu->index = -1;
+
+ /* Free the read and write buffers */
+ kfree(mu->rbuf->buf);
+ kfree(mu->rbuf);
+ kfree(mu->wbuf->buf);
+ kfree(mu->wbuf);
+
+ return ret;
+}
+
+/*!
+ * Start the SDMA read channel accessed from kernel mode
+ *
+ * @param ipc_chnl SDMA channel to start
+ * @param num_od_bd number of BD's allocated for the channel
+ *
+ * @return 0 on succes, negative number if channel does not start
+ */
+static int initialize_sdma_read_channel_kernel(struct ipc_channel *ipc_chnl,
+ int num_of_bd)
+{
+ struct sdma_channel *read_chnl = NULL;
+ dma_channel_params sdma_params;
+ int result = 0;
+
+ read_chnl = &ipc_chnl->ch.sdma;
+
+ memset(&sdma_params, 0, sizeof(dma_channel_params));
+ sdma_params.peripheral_type = DSP;
+ sdma_params.transfer_type = dsp_2_emi;
+ sdma_params.event_id = 0;
+ sdma_params.callback = NULL;
+ sdma_params.arg = 0;
+ sdma_params.bd_number = num_of_bd;
+
+ result = mxc_dma_setup_channel(read_chnl->read_channel, &sdma_params);
+ if (result < 0) {
+ return result;
+ }
+
+ return result;
+}
+
+/*!
+ * Start the SDMA read channel accessed from user mode
+ *
+ * @param ipc_chnl SDMA channel to start
+ *
+ * @return 0 on succes, negative number if channel does not start
+ */
+static int initialize_sdma_read_channel_user(int ipc_chnl)
+{
+ dma_request_t sdma_request;
+ struct ipc_channel ipc_num;
+ struct sdma_channel *read_chnl = NULL;
+ dma_channel_params sdma_params;
+ int result = 0;
+ int i = 0;
+
+ ipc_num = ipc_channels[ipc_chnl];
+ read_chnl = &ipc_channels[ipc_chnl].ch.sdma;
+
+ memset(&sdma_params, 0, sizeof(dma_channel_params));
+ sdma_params.peripheral_type = DSP;
+ sdma_params.transfer_type = dsp_2_emi;
+ sdma_params.event_id = 0;
+ sdma_params.callback = NULL;
+ sdma_params.arg = 0;
+ sdma_params.bd_number = NUM_RX_SDMA_BUF;
+
+ result = mxc_dma_setup_channel(read_chnl->read_channel, &sdma_params);
+ if (result < 0) {
+ return result;
+ } else {
+ for (i = 0; i < NUM_RX_SDMA_BUF; i++) {
+ /* Allocate the DMA'able buffers filled by SDMA engine */
+ read_chnl->rbuf[i].buf =
+ dma_alloc_coherent(NULL, SDMA_RX_BUF_SIZE,
+ &read_chnl->rbuf[i].rpaddr,
+ GFP_DMA);
+ if (read_chnl->rbuf[i].buf == NULL) {
+ result = -1;
+ break;
+ }
+ read_chnl->rbuf[i].count = 0;
+ read_chnl->rbuf[i].offset = 0;
+ memset(&sdma_request, 0, sizeof(dma_request_t));
+ sdma_request.count = SDMA_RX_BUF_SIZE;
+ sdma_request.destAddr =
+ (__u8 *) read_chnl->rbuf[i].rpaddr;
+ sdma_request.bd_cont = 1;
+ result = mxc_dma_set_config(read_chnl->read_channel,
+ &sdma_request, i);
+ if (result != 0) {
+ break;
+ }
+ }
+ }
+
+ if (result != 0) {
+ return result;
+ }
+
+ read_chnl->rbuf_head = 0;
+ read_chnl->rbuf_tail = 0;
+
+ mxc_dma_set_callback(read_chnl->read_channel, sdma_user_readcb,
+ ipc_num.priv);
+ mxc_dma_start(read_chnl->read_channel);
+ return 0;
+}
+
+/*!
+ * Setup the write channel for user mode and kernel mode
+ *
+ * @param ipc_chnl SDMA channel to start
+ * @param kernel_access flag indicates the access type, 1=kernel mode access.
+ * @param num_of_bd number of BD's allocated for the channel
+ *
+ * @return 0 on succes, negative number if channel does not start
+ */
+static int initialize_sdma_write_channel(struct ipc_channel *ipc_chnl,
+ int kernel_access, int num_of_bd)
+{
+ struct sdma_channel *write_chnl = NULL;
+ dma_channel_params sdma_params;
+ int result = 0;
+
+ write_chnl = &ipc_chnl->ch.sdma;
+ memset(&sdma_params, 0, sizeof(dma_channel_params));
+ sdma_params.peripheral_type = DSP;
+ sdma_params.transfer_type = emi_2_dsp;
+ sdma_params.event_id = 0;
+ sdma_params.callback = NULL;
+ sdma_params.arg = 0;
+ sdma_params.bd_number = num_of_bd;
+
+ result = mxc_dma_setup_channel(write_chnl->write_channel, &sdma_params);
+ if (result < 0) {
+ pr_debug("Failed Setting up SDMA channel %d\n",
+ write_chnl->write_channel);
+ mxc_free_dma(write_chnl->write_channel);
+ write_chnl->write_channel = -1;
+ return result;
+ }
+
+ /*
+ * Allocate the write buffer for user space, used to copy user-space
+ * data for DMA.
+ */
+ if (kernel_access == 0) {
+ write_chnl->wbuf =
+ dma_alloc_coherent(NULL, MAX_SDMA_TX_BUF_SIZE,
+ &write_chnl->wpaddr, GFP_DMA);
+ if (write_chnl->wbuf == NULL) {
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ * Allocate a SDMA channel
+ *
+ * @param ipc_chnl IPC channel to allocate
+ * @param chnum SDMA channel to allocate
+ * @param mode open mode for this channel (read, write, read/write)
+ *
+ * @return returns 0 on success, negative number otherwise.
+ */
+static int allocate_sdma_channel(int ipc_chnl, int chnum, int mode)
+{
+ struct ipc_channel ipc_num;
+ struct sdma_channel *sdma = NULL;
+ int result = 0;
+
+ ipc_num = ipc_channels[ipc_chnl];
+ sdma = &ipc_channels[ipc_chnl].ch.sdma;
+ if (mode == (IPC_RDWR)) {
+ sdma->write_channel = chnum;
+ sdma->read_channel = chnum - 1;
+ } else {
+ sdma->write_channel = -1;
+ sdma->read_channel = chnum;
+ }
+
+ if (mode & IPC_WRITE) {
+ result =
+ mxc_request_dma(&sdma->write_channel, "IPC WRITE SDMA");
+ if (result < 0) {
+ pr_debug("Failed Opening SDMA channel %d\n",
+ sdma->write_channel);
+ sdma->write_channel = -1;
+ return result;
+ }
+ }
+
+ if (mode & IPC_READ) {
+ result = mxc_request_dma(&sdma->read_channel, "IPC READ SDMA");
+ if (result < 0) {
+ pr_debug("Failed Opening SDMA channel %d\n",
+ sdma->read_channel);
+ sdma->read_channel = -1;
+ if (mode & IPC_WRITE) {
+ mxc_free_dma(sdma->write_channel);
+ sdma->write_channel = -1;
+ }
+ return result;
+ }
+ }
+
+ spin_lock_init(&ipc_channels[chnum].channel_lock);
+ return result;
+}
+
+static void mxc_ipc_free_readbuf_user(int chnum)
+{
+ int i = 0;
+ struct sdma_channel *sdma;
+
+ sdma = &ipc_channels[chnum].ch.sdma;
+
+ /* Free the DMA'able receive buffers */
+ for (i = 0; i < NUM_RX_SDMA_BUF; i++) {
+ if (sdma->rbuf[i].buf != NULL) {
+ dma_free_coherent(NULL, SDMA_RX_BUF_SIZE,
+ sdma->rbuf[i].buf,
+ sdma->rbuf[i].rpaddr);
+ sdma->rbuf[i].buf = NULL;
+ }
+ }
+}
+
+/*!
+ * Deallocate a SDMA channel
+ *
+ * @param chnum SDMA channel to allocate
+ */
+static void deallocate_sdma_channel(int chnum)
+{
+ struct sdma_channel *sdma;
+
+ sdma = &ipc_channels[chnum].ch.sdma;
+ if (sdma->write_channel != -1) {
+ mxc_free_dma(sdma->write_channel);
+ }
+ if (sdma->read_channel != -1) {
+ mxc_free_dma(sdma->read_channel);
+ }
+ sdma->write_channel = -1;
+ sdma->read_channel = -1;
+
+ /* Free the DMA'able transmit buffer */
+ if (sdma->wbuf != NULL) {
+ dma_free_coherent(NULL, MAX_SDMA_TX_BUF_SIZE,
+ sdma->wbuf, sdma->wpaddr);
+ sdma->wbuf = NULL;
+ }
+}
+
+/*!
+ * Copy bytes from the circular buffer to the user buffer
+ *
+ * @param buf buffer to store data
+ * @param count number of bytes to copy
+ * @param cbuf circular_buffer to copy the data from
+ * @param buf_size size of the circular buffer
+ *
+ * @return number of bytes copied into the user buffer
+ */
+static int mxc_ipc_copy_from_mubuf(char *buf, int count, struct circ_buf *cbuf,
+ int buf_size)
+{
+ int n = 0, ret = 0, result = 0;
+
+ while (1) {
+ n = CIRC_CNT_TO_END(cbuf->head, cbuf->tail, buf_size);
+ pr_debug("bytes available in circular buffer = %d\n", n);
+ if (count < n) {
+ n = count;
+ }
+ if (n <= 0) {
+ break;
+ }
+ pr_debug("copying %d bytes into user's buffer\n", n);
+ result = copy_to_user(buf, cbuf->buf + cbuf->tail, n);
+ n -= result;
+ cbuf->tail = (cbuf->tail + n) & (buf_size - 1);
+ buf += n;
+ count -= n;
+ ret += n;
+ /* Check if there is problem copying data */
+ if (result != 0) {
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/*!
+ * Write bytes to the circular buffer from the user buffer
+ *
+ *
+ * @param buf buffer to copy data from
+ * @param count number of bytes to copy
+ * @param cbuf circular_buffer to copy the data to
+ *
+ * @return number of bytes copied into the circular buffer
+ */
+static int mxc_ipc_copy_to_buf(char **buf, int *count, struct circ_buf *cbuf)
+{
+ int n = 0;
+ int result;
+ int ret = 0;
+
+ while (1) {
+ n = CIRC_SPACE_TO_END(cbuf->head, cbuf->tail, MAX_MU_BUF_SIZE);
+ if (*count < n) {
+ n = *count;
+ }
+ if (n < 4) {
+ break;
+ }
+ result = copy_from_user(cbuf->buf + cbuf->head, *buf, n);
+ if (result != 0) {
+ pr_debug("EINVAL error\n");
+ ret = -EFAULT;
+ break;
+ }
+ n -= result;
+ cbuf->head = (cbuf->head + n) & (MAX_MU_BUF_SIZE - 1);
+ *buf += n;
+ *count -= n;
+ }
+
+ return ret;
+}
+
+/*!
+ * This function copies data from the MU buffer into the user read buffer.
+ *
+ * @param priv Private data. Used to synchronize the top half
+ * and the botton half as well as to keep track of
+ * the transfer
+ * @param count number of bytes to be transferred
+ * @param buf buffer where received bytes are saved
+ *
+ * @return number of transferred bytes
+ */
+static int mu_start_dsp_transfer(struct ipc_priv_data *priv,
+ int count, char *buf)
+{
+ int bytes = 0;
+ struct mu_channel *mu = &priv->vc->ipc->ch.mu;
+
+ bytes = mxc_ipc_copy_from_mubuf(buf, count, mu->rbuf, MAX_MU_BUF_SIZE);
+ pr_debug("bytes read = %d\n", bytes);
+
+ spin_lock(&priv->vc->ipc->channel_lock);
+ /* Enable intrs if space in receive buffer */
+ if (CIRC_SPACE(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE)) {
+ mxc_mu_intenable(mu->index, RX);
+ } else {
+ mxc_mu_intdisable(mu->index, RX);
+ }
+ spin_unlock(&priv->vc->ipc->channel_lock);
+
+ if (bytes == 0) {
+ pr_debug("Could not copy to user buffer error\n");
+ return -EFAULT;
+ }
+
+ return bytes;
+}
+
+/*!
+ * This function starts a copies data into MU buffer and starts the transfer.
+ *
+ * @param priv Private data. Used to synchronize the top half
+ * and the botton half as well as to keep track of
+ * the transfer
+ * @param count number of bytes to be transferred
+ * @param buf buffer containing the bytes to be transferred
+ *
+ * @return number of transferred bytes.
+ */
+static int mu_start_mcu_transfer(struct ipc_priv_data *priv,
+ int count, const char *buf)
+{
+ struct mu_channel *mu = &priv->vc->ipc->ch.mu;
+ int retval = 0, orig_cnt;
+
+ orig_cnt = count;
+
+ /* Copy all the data into write buffer */
+ while (count > 0) {
+ retval = mxc_ipc_copy_to_buf((char **)&buf, &count, mu->wbuf);
+ if (retval < 0) {
+ break;
+ }
+
+ /* Enable TX intr, data available in buffer */
+ spin_lock(&priv->vc->ipc->channel_lock);
+ mxc_mu_intenable(mu->index, TX);
+ spin_unlock(&priv->vc->ipc->channel_lock);
+ /* Check if non-blocking mode requested */
+ if ((atomic_read(&priv->blockmode)) == 0) {
+ break;
+ }
+ if (count == 0) {
+ break;
+ }
+ if (wait_event_interruptible(priv->wq,
+ CIRC_SPACE(mu->wbuf->head,
+ mu->wbuf->tail,
+ MAX_MU_BUF_SIZE))) {
+ break;
+ }
+ }
+
+ pr_debug("bytes written = %d\n", count);
+
+ return orig_cnt - count;
+}
+
+/*!
+ * This function starts a MCU->DSP transfer requested by a kernel module.
+ * This transfer uses a MU channel.
+ *
+ * @param mu the MU channel structure
+ *
+ * @return Zero
+ */
+static int mu_start_mcu_transfer_kernel(struct mu_channel *mu)
+{
+ pr_debug("writing bytes to DSP\n");
+ mxc_mu_bind(mu->index, &mu_write_tasklet_kernel, TX);
+ mxc_mu_intenable(mu->index, TX);
+
+ return 0;
+}
+
+/*!
+ * This function starts a DSP->MCU transfer requested by a kernel module.
+ * This transfer uses a MU channel.
+ *
+ * @param priv Private data. Used to synchronize and to keep track of
+ * the transfer
+ * @param count number of bytes to be transferred
+ * @param buf buffer containing the bytes to be transferred
+ *
+ * @return Zero
+ */
+static int mu_start_dsp_transfer_kernel(struct ipc_priv_data *priv,
+ int count, char *buf)
+{
+ struct mu_channel *mu = &priv->vc->ipc->ch.mu;
+
+ if (count > MAX_MU_BUF_SIZE) {
+ count = MAX_MU_BUF_SIZE;
+ }
+
+ priv->read_count = count;
+ priv->vc->data = buf;
+
+ mxc_mu_bind(mu->index, &mu_read_tasklet_kernel, RX);
+ mxc_mu_intenable(mu->index, RX);
+
+ return 0;
+}
+
+/*!
+ * This function is called by the SDMA's ISR whenever a MCU->DSP
+ * transfer has finished. This callback is used whenever a
+ * kernel module requests a transfer.
+ *
+ * @param args points to a priv structure. This structure contains
+ * fields to synchronize the IPC channel and the top half
+ *
+ * @return None
+ */
+static void sdma_kernel_writecb(void *args)
+{
+ dma_request_t sdma_request;
+ struct ipc_priv_data *priv = NULL;
+ struct sdma_channel *sdma = NULL;
+
+ priv = (struct ipc_priv_data *)args;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ mxc_dma_get_config(sdma->write_channel, &sdma_request, 0);
+
+ priv->write_count = sdma_request.count;
+
+ atomic_set(&priv->vc->state, CHANNEL_OPEN);
+
+ tasklet_schedule(&sdma->write_tasklet);
+ pr_debug("Called SDMA write callback %d\n", priv->write_count);
+}
+
+/*!
+ * This function is called by the SDMA's ISR whenever a MCU->DSP
+ * transfer has finished. This callback is used whenever a
+ * kernel module requests a transfer.
+ *
+ * @param args points to a priv structure. This structure contains
+ * fields to synchronize the IPC channel and the top half
+ *
+ * @return None
+ */
+static void sdma_kernel_writecb_ipcv2(void *args)
+{
+ struct ipc_priv_data *priv = NULL;
+ struct sdma_channel *sdma = NULL;
+
+ priv = (struct ipc_priv_data *)args;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ priv->write_count = 0;
+
+ atomic_set(&priv->vc->write_state_ipcv2, CHANNEL_OPEN);
+
+ tasklet_schedule(&sdma->write_tasklet);
+ pr_debug("Called SDMA write callback\n");
+}
+
+/*!
+ * This function starts a MCU->DSP transfer requested by a kernel module.
+ * This transfer uses a SDMA channel.
+ *
+ * @param ipc_chn IPC channel data structure
+ * @param count number of bytes to be transferred
+ *
+ * @return Zero
+ */
+static int sdma_start_mcu_transfer_kernel(struct ipc_channel *ipc_chn,
+ int count)
+{
+ dma_request_t sdma_request;
+ struct sdma_channel *sdma = NULL;
+
+ sdma = &ipc_chn->ch.sdma;
+ memset(&sdma_request, 0, sizeof(dma_request_t));
+
+ pr_debug("writing %d bytes to DSP\n", count);
+
+ sdma_request.sourceAddr = (__u8 *) sdma->wpaddr;
+ sdma_request.count = count;
+ sdma_request.bd_cont = 1;
+ mxc_dma_set_config(sdma->write_channel, &sdma_request, sdma->wbuf_head);
+ sdma->wbuf_head++;
+ if (sdma->wbuf_head >= sdma->wbuf_tail) {
+ sdma->wbuf_head = 0;
+ }
+
+ return 0;
+}
+
+/*!
+ * This function is called by the SDMA's ISR whenever a MCU->DSP
+ * transfer has finished. This callback is used whenever a
+ * user space program requests a transfer.
+ *
+ * @param args points to a priv structure. This structure contains
+ * fields to synchronize the IPC channel and the top half
+ *
+ * @return None
+ */
+static void sdma_user_writecb(void *args)
+{
+ dma_request_t sdma_request;
+ struct ipc_priv_data *priv = NULL;
+ struct sdma_channel *sdma = NULL;
+
+ priv = (struct ipc_priv_data *)args;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ mxc_dma_get_config(sdma->write_channel, &sdma_request, 0);
+
+ pr_debug("Called SDMA write callback %d\n", sdma_request.count);
+ atomic_set(&priv->vc->state, CHANNEL_OPEN);
+ if (waitqueue_active(&priv->wq)) {
+ wake_up_interruptible(&priv->wq);
+ }
+}
+
+/*!
+ * This function starts a MCU->DSP transfer requested by a user space
+ * program. This transfer uses a SDMA channel.
+ *
+ * @param priv Private data. Useful to synchronize and to keep track of
+ * the transfer
+ * @param count number of bytes to be transferred
+ * @param buf buffer containing the bytes to be transferred
+ *
+ * @return Zero
+ */
+static int sdma_start_mcu_transfer(struct ipc_priv_data *priv,
+ int count, const char *buf)
+{
+ dma_request_t sdma_request;
+ struct sdma_channel *sdma = NULL;
+ int result = 0;
+
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ /* Check if the Channel is in use */
+ while ((atomic_read(&priv->vc->state)) == CHANNEL_WRITE_ONGOING) {
+ pr_debug("Write Block till channel is available\n");
+ if (wait_event_interruptible(priv->wq,
+ (atomic_read(&priv->vc->state) !=
+ CHANNEL_WRITE_ONGOING))) {
+ return -ERESTARTSYS;
+ }
+ }
+
+ result = copy_from_user(sdma->wbuf, buf, count);
+ if (result != 0) {
+ kfree(sdma->wbuf);
+ return -EFAULT;
+ }
+
+ atomic_set(&priv->vc->state, CHANNEL_WRITE_ONGOING);
+ memset(&sdma_request, 0, sizeof(dma_request_t));
+
+ pr_debug("writing %d bytes to SDMA channel %d\n", count,
+ sdma->write_channel);
+
+ sdma_request.sourceAddr = (__u8 *) sdma->wpaddr;
+ sdma_request.count = count;
+
+ mxc_dma_set_callback(sdma->write_channel, sdma_user_writecb, priv);
+ mxc_dma_set_config(sdma->write_channel, &sdma_request, 0);
+ mxc_dma_start(sdma->write_channel);
+
+ return count;
+}
+
+/*!
+ * This function is called by the SDMA ISR whenever a DSP->MCU
+ * transfer has finished. This callback is used whenever a
+ * kernel module requests a transfer.
+ *
+ * @param args points to a priv structure. This structure contains
+ * fields to synchronize the IPC channel and the top half
+ *
+ * @return None
+ */
+static void sdma_kernel_readcb(void *args)
+{
+ dma_request_t sdma_request;
+ struct ipc_priv_data *priv;
+ struct sdma_channel *sdma = NULL;
+
+ priv = (struct ipc_priv_data *)args;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ mxc_dma_get_config(sdma->read_channel, &sdma_request, 0);
+
+ priv->read_count = sdma_request.count;
+
+ atomic_set(&priv->vc->state, CHANNEL_OPEN);
+
+ tasklet_schedule(&sdma->read_tasklet);
+ pr_debug("Called SDMA read callback %d\n", priv->read_count);
+}
+
+/*!
+ * This function is called by the SDMA ISR whenever a DSP->MCU
+ * transfer has finished. This callback is used whenever a
+ * kernel module requests a transfer.
+ *
+ * @param args points to a priv structure. This structure contains
+ * fields to synchronize the IPC channel and the top half
+ *
+ * @return None
+ */
+static void sdma_kernel_readcb_ipcv2(void *args)
+{
+ struct ipc_priv_data *priv;
+ struct sdma_channel *sdma = NULL;
+
+ priv = (struct ipc_priv_data *)args;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ priv->read_count = 0;
+
+ atomic_set(&priv->vc->read_state_ipcv2, CHANNEL_OPEN);
+
+ tasklet_schedule(&sdma->read_tasklet);
+ pr_debug("Called SDMA read callback\n");
+}
+
+/*!
+ * This function starts a DSP->MCU transfer requested by a kernel module.
+ * This transfer uses a SDMA channel.
+ *
+ * @param priv Private data. Used to synchronize and to keep track of
+ * the transfer
+ * @param count number of bytes to be transferred
+ * @param buf buffer containing the bytes to be transferred
+ *
+ * @return Zero
+ */
+static int sdma_start_dsp_transfer_kernel(struct ipc_priv_data *priv,
+ int count, char *buf)
+{
+ dma_request_t sdma_request;
+ struct sdma_channel *sdma = NULL;
+
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ priv->vc->data = buf;
+
+ memset(&sdma_request, 0, sizeof(dma_request_t));
+
+ pr_debug("writing %d bytes to DSP\n", count);
+
+ sdma_request.destAddr = buf;
+ sdma_request.count = count;
+
+ mxc_dma_set_callback(sdma->read_channel, sdma_kernel_readcb, priv);
+ mxc_dma_set_config(sdma->read_channel, &sdma_request, sdma->rbuf_head);
+ sdma->rbuf_head++;
+ if (sdma->rbuf_head >= sdma->rbuf_tail) {
+ sdma->rbuf_head = 0;
+ }
+ mxc_dma_start(sdma->read_channel);
+
+ return 0;
+}
+
+/*!
+ * This function is called by the SDMA's ISR whenever a DSP->MCU
+ * transfer has finished. This callback fills the SDMA channels read buffer
+ *
+ * @param args points to a priv structure. This structure contains
+ * fields to synchronize the IPC channel and the top half
+ *
+ * @return None
+ */
+static void sdma_user_readcb(void *args)
+{
+ dma_request_t sdma_request;
+ struct ipc_priv_data *priv = NULL;
+ struct sdma_channel *sdma = NULL;
+
+ priv = (struct ipc_priv_data *)args;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ memset(&sdma_request, 0, sizeof(dma_request_t));
+ mxc_dma_get_config(sdma->read_channel, &sdma_request, sdma->rbuf_head);
+
+ while ((sdma_request.bd_done == 0)
+ && (sdma->rbuf[sdma->rbuf_head].count == 0)) {
+ pr_debug("buf head=%d, cnt=%d\n", sdma->rbuf_head,
+ sdma_request.count);
+ sdma->rbuf[sdma->rbuf_head].count = sdma_request.count;
+ sdma->rbuf_head = (sdma->rbuf_head + 1) & (NUM_RX_SDMA_BUF - 1);
+ memset(&sdma_request, 0, sizeof(dma_request_t));
+ mxc_dma_get_config(sdma->read_channel, &sdma_request,
+ sdma->rbuf_head);
+ }
+
+ /* Data arrived in read chnl buffers, wake up any pending processes */
+ if (waitqueue_active(&priv->rq)) {
+ wake_up_interruptible(&priv->rq);
+ }
+}
+
+/*!
+ * Copy bytes from the SDMA read buffers to the user buffer
+ *
+ * @param sdma pointer to the sdma structure
+ * @param buf buffer to store data
+ * @param count number of bytes to copy
+ *
+ * @return number of bytes copied into the user buffer
+ */
+static int mxc_ipc_copy_from_sdmabuf(struct sdma_channel *sdma, char *buf,
+ int count)
+{
+ int n = 0;
+ int amt_cpy = 0;
+ int offset = 0;
+ int ret = 0;
+ char *tmp_buf = NULL;
+ dma_request_t sdma_request;
+ int result;
+
+ while ((sdma->rbuf[sdma->rbuf_tail].count > 0) && (count > 0)) {
+ n = sdma->rbuf[sdma->rbuf_tail].count;
+ offset = sdma->rbuf[sdma->rbuf_tail].offset;
+ tmp_buf = sdma->rbuf[sdma->rbuf_tail].buf + offset;
+
+ /* Use the minimum of the 2 counts to copy data */
+ amt_cpy = (count < n) ? count : n;
+
+ result = copy_to_user(buf, tmp_buf, amt_cpy);
+
+ /* Check if there was problem copying data */
+ if (result != 0) {
+ pr_debug("problem copying into user's buffer\n");
+ break;
+ }
+ pr_debug("copied %d bytes into user's buffer\n", amt_cpy);
+ pr_debug("Buf Tail=%d\n", sdma->rbuf_tail);
+ /* Check if we emptied the SDMA read buffer */
+ if (count < n) {
+ sdma->rbuf[sdma->rbuf_tail].count -= amt_cpy;
+ sdma->rbuf[sdma->rbuf_tail].offset += amt_cpy;
+ } else {
+ sdma->rbuf[sdma->rbuf_tail].count = 0;
+ sdma->rbuf[sdma->rbuf_tail].offset = 0;
+ memset(&sdma_request, 0, sizeof(dma_request_t));
+ sdma_request.count = SDMA_RX_BUF_SIZE;
+ sdma_request.destAddr =
+ (__u8 *) sdma->rbuf[sdma->rbuf_tail].rpaddr;
+ sdma_request.bd_cont = 1;
+ result = mxc_dma_set_config(sdma->read_channel,
+ &sdma_request,
+ sdma->rbuf_tail);
+ sdma->rbuf_tail =
+ (sdma->rbuf_tail + 1) & (NUM_RX_SDMA_BUF - 1);
+
+ /* Restart Sdma channel after data copy */
+
+ mxc_dma_start(sdma->read_channel);
+ }
+ buf += amt_cpy;
+ count -= amt_cpy;
+ ret += amt_cpy;
+ }
+
+ return ret;
+}
+
+/*!
+ * This function allocates a virtual channel.
+ *
+ * @param index index of virtual channel to be open
+ * @param config pointer to a structure containing the type
+ * of channel to open.
+ *
+ * @return 0 on success, negative value otherwise
+ */
+static int allocate_virtual_channel(unsigned short index,
+ const HW_CTRL_IPC_OPEN_T * config)
+{
+ struct kernel_callbacks *k_callbacks;
+ write_callback_t wcallback = NULL;
+ struct ipc_priv_data *priv;
+ unsigned short pchannel;
+ int ret = 0, sdma_chnl = -1;
+
+ priv = (struct ipc_priv_data *)
+ kmalloc(sizeof(struct ipc_priv_data), GFP_KERNEL);
+ if (priv == NULL) {
+ return -1;
+ }
+
+ k_callbacks = (struct kernel_callbacks *)
+ kmalloc(sizeof(struct kernel_callbacks), GFP_KERNEL);
+ if (k_callbacks == NULL) {
+ kfree(priv);
+ return -1;
+ }
+
+ wcallback = config->write_callback;
+ priv->vc = &virtual_channels[index];
+
+ switch (config->type) {
+ case HW_CTRL_IPC_SHORT_MSG:
+ if (config->index > IPC_MAX_MU_CHANNEL_INDEX) {
+ kfree(priv);
+ kfree(k_callbacks);
+ return -1;
+ }
+
+ pchannel = config->index;
+ priv->vc->ipc = &ipc_channels[pchannel];
+ ret = allocate_mu_channel(pchannel);
+ if (ret < 0) {
+ kfree(priv);
+ kfree(k_callbacks);
+ return ret;
+ }
+ break;
+ case HW_CTRL_IPC_PACKET_DATA:
+ if (config->index > IPC_MAX_SDMA_BIDI_CHANNEL_INDEX) {
+ kfree(priv);
+ kfree(k_callbacks);
+ return -1;
+ }
+ if (config->index == 0) {
+ pchannel = PACKET_DATA_CHANNEL0;
+ sdma_chnl = PACKET_DATA0_SDMA_WR_CHNL;
+ } else {
+ pchannel = PACKET_DATA_CHANNEL1;
+ sdma_chnl = PACKET_DATA1_SDMA_WR_CHNL;
+ }
+ priv->vc->ipc = &ipc_channels[pchannel];
+ ret = allocate_sdma_channel(pchannel, sdma_chnl, IPC_RDWR);
+ if (ret == 0) {
+ ret =
+ initialize_sdma_write_channel(priv->vc->ipc, 1,
+ IPC_DEFAULT_MAX_CTRL_STRUCT_NUM);
+ }
+ if (ret == 0) {
+ ret =
+ initialize_sdma_read_channel_kernel(priv->vc->ipc,
+ IPC_DEFAULT_MAX_CTRL_STRUCT_NUM);
+ }
+ if (ret < 0) {
+ deallocate_sdma_channel(pchannel);
+ kfree(priv);
+ kfree(k_callbacks);
+ return ret;
+ }
+ break;
+ case HW_CTRL_IPC_CHANNEL_LOG:
+ if (config->index > IPC_MAX_SDMA_MONO_CHANNEL_INDEX) {
+ kfree(priv);
+ kfree(k_callbacks);
+ return -1;
+ }
+
+ if (config->index == 0) {
+ pchannel = LOGGING_CHANNEL0;
+ sdma_chnl = LOG0_SDMA_RD_CHNL;
+ } else if (config->index == 1) {
+ pchannel = LOGGING_CHANNEL1;
+ sdma_chnl = LOG1_SDMA_RD_CHNL;
+ } else if (config->index == 2) {
+ pchannel = LOGGING_CHANNEL2;
+ sdma_chnl = LOG2_SDMA_RD_CHNL;
+ } else {
+ pchannel = LOGGING_CHANNEL3;
+ sdma_chnl = LOG3_SDMA_RD_CHNL;
+ }
+ priv->vc->ipc = &ipc_channels[pchannel];
+ ret = allocate_sdma_channel(pchannel, sdma_chnl, IPC_READ);
+ if (ret == 0) {
+ ret =
+ initialize_sdma_read_channel_kernel(priv->vc->ipc,
+ IPC_DEFAULT_MAX_CTRL_STRUCT_NUM);
+ }
+ if (ret < 0) {
+ deallocate_sdma_channel(pchannel);
+ kfree(priv);
+ kfree(k_callbacks);
+ return ret;
+ }
+ wcallback = NULL;
+ break;
+ default:
+ kfree(priv);
+ kfree(k_callbacks);
+ return -1;
+ }
+
+ k_callbacks->write = wcallback;
+ k_callbacks->read = config->read_callback;
+ k_callbacks->notify = config->notify_callback;
+
+ /* always non-blocking mode for kernel modules */
+ atomic_set(&priv->blockmode, 0);
+ priv->vchannel = index;
+ priv->pchannel = pchannel;
+ priv->read_count = 0;
+ priv->write_count = 0;
+ priv->vc->ipc->priv = (void *)priv;
+ priv->k_callbacks = k_callbacks;
+
+ return 0;
+}
+
+/*!
+ * Verify that the handler points to the correct handler in the handler array
+ * if not, that mean either the passed handler is NULL, either it is not a correct handler
+ *
+ * @param channel handler to the virtual channel
+ *
+ * @return 1 when handler valid, 0 otherwise
+ */
+
+static int is_channel_handler_valid(HW_CTRL_IPC_CHANNEL_T * channel)
+{
+ if (channel != NULL) {
+ if (channel != &channel_handlers[channel->channel_nb]) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+static void mxc_ipc_sdma_readtasklet(unsigned long arg)
+{
+ struct ipc_priv_data *priv = (struct ipc_priv_data *)arg;
+
+ if (priv->k_callbacks->read != NULL) {
+ HW_CTRL_IPC_READ_STATUS_T status;
+
+ status.channel = &channel_handlers[priv->vchannel];
+ status.nb_bytes = priv->read_count;
+
+ priv->k_callbacks->read(&status);
+ }
+}
+
+static void mxc_ipc_sdma_writetasklet(unsigned long arg)
+{
+ struct ipc_priv_data *priv = (struct ipc_priv_data *)arg;
+
+ if (priv->k_callbacks->write != NULL) {
+ HW_CTRL_IPC_WRITE_STATUS_T status;
+
+ status.channel = &channel_handlers[priv->vchannel];
+ status.nb_bytes = priv->write_count;
+
+ priv->k_callbacks->write(&status);
+ }
+}
+
+/*!
+ * Opens an IPC link. This functions can be called directly by kernel
+ * modules. POSIX implementation of the IPC Driver also calls it.
+ *
+ * @param config Pointer to a struct containing configuration para
+ * meters for the channel to open (type of channel,
+ * callbacks, etc)
+ *
+ * @return returns a virtual channel handler on success, a NULL
+ * pointer otherwise.
+ */
+HW_CTRL_IPC_CHANNEL_T *hw_ctrl_ipc_open(const HW_CTRL_IPC_OPEN_T * config)
+{
+ struct virtual_channel *v;
+ int channel_nb = 0;
+ int status = 0;
+ HW_CTRL_IPC_CHANNEL_T *channel;
+
+ /* return a NULL handler if something goes wrong */
+ channel = NULL;
+
+ if (config == NULL) {
+ return NULL;
+ }
+
+ /* Look for an empty virtual channel, if found, lock it */
+ channel_nb = get_free_virtual_channel();
+
+ /* if free virtual channel not found, return NULL handler */
+ if (channel_nb == IPC_DUMMY_CHANNEL) {
+ return NULL;
+ }
+
+ status = allocate_virtual_channel(channel_nb, config);
+ if (status < 0) {
+ goto cleanup;
+ }
+
+ v = &virtual_channels[channel_nb];
+ atomic_set(&v->state, CHANNEL_OPEN);
+ /*! set state to open for read-while-write IPcv2 functionality */
+ atomic_set(&v->read_state_ipcv2, CHANNEL_OPEN);
+ atomic_set(&v->write_state_ipcv2, CHANNEL_OPEN);
+
+ channel = &channel_handlers[channel_nb];
+
+ /*
+ * set the channel_nb in the handler struct. This allows to
+ * find back the corresponding index
+ * in both channel_handlers and virtual_channels arrays
+ */
+ channel->channel_nb = channel_nb;
+ if (v->ipc->priv->pchannel > 3) {
+ struct sdma_channel *sdma_chnl;
+ sdma_chnl = &v->ipc->ch.sdma;
+
+ sdma_chnl->rbuf_head = 0;
+ sdma_chnl->rbuf_tail = IPC_DEFAULT_MAX_CTRL_STRUCT_NUM;
+ sdma_chnl->wbuf_head = 0;
+ sdma_chnl->wbuf_tail = IPC_DEFAULT_MAX_CTRL_STRUCT_NUM;
+
+ tasklet_init(&sdma_chnl->read_tasklet,
+ mxc_ipc_sdma_readtasklet,
+ (unsigned long)v->ipc->priv);
+
+ tasklet_init(&sdma_chnl->write_tasklet,
+ mxc_ipc_sdma_writetasklet,
+ (unsigned long)v->ipc->priv);
+ }
+
+ pr_debug("Virtual channel %d opened\n", channel_nb);
+
+ cleanup:
+ unlock_virtual_channel(channel_nb);
+
+ return channel;
+}
+
+/*!
+ * Close an IPC link. This functions can be called directly by kernel
+ * modules.
+ *
+ * @param channel handler to the virtual channel to close.
+ *
+ * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code
+ * otherwise.
+ */
+HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_close(HW_CTRL_IPC_CHANNEL_T * channel)
+{
+ struct virtual_channel *v;
+ int channel_nb;
+ if (is_channel_handler_valid(channel) == 0) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ channel_nb = channel->channel_nb;
+ v = &virtual_channels[channel_nb];
+ if (atomic_read(&v->state) == CHANNEL_CLOSED ||
+ atomic_read(&v->read_state_ipcv2) == CHANNEL_CLOSED ||
+ atomic_read(&v->write_state_ipcv2) == CHANNEL_CLOSED) {
+ return HW_CTRL_IPC_STATUS_OK;
+ }
+ if (lock_virtual_channel(channel_nb)) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+ kfree(v->ipc->priv->k_callbacks);
+ v->ipc->priv->k_callbacks = NULL;
+
+ if (v->ipc->priv->pchannel > 3) {
+ deallocate_sdma_channel(v->ipc->priv->pchannel);
+ } else {
+ deallocate_mu_channel(v->ipc->priv->pchannel);
+ }
+ kfree(v->ipc->priv);
+ v->ipc->priv = NULL;
+ v->ipc = NULL;
+ unlock_virtual_channel(channel_nb);
+ atomic_set(&v->state, CHANNEL_CLOSED);
+ atomic_set(&v->read_state_ipcv2, CHANNEL_CLOSED);
+ atomic_set(&v->write_state_ipcv2, CHANNEL_CLOSED);
+
+ pr_debug("Virtual channel %d closed\n", channel_nb);
+
+ return HW_CTRL_IPC_STATUS_OK;
+}
+
+/*!
+ * Reads data from an IPC link. This functions can be called directly by kernel
+ * modules. POSIX implementation of the IPC Driver also calls it.
+ *
+ * @param channel handler to the virtual channel where read has been requested
+ * @param buf physical address of DMA'able read buffer to store data read from
+ * the channel.
+ * @param nb_bytes size of the buffer
+ *
+ * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code
+ * otherwise.
+ */
+HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_read(HW_CTRL_IPC_CHANNEL_T * channel,
+ unsigned char *buf,
+ unsigned short nb_bytes)
+{
+ struct virtual_channel *v;
+ int status = 0;
+ int channel_nb;
+
+ if (is_channel_handler_valid(channel) == 0) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ if (buf == NULL) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ channel_nb = channel->channel_nb;
+
+ v = &virtual_channels[channel_nb];
+
+ if (lock_virtual_channel(channel_nb)) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ status = atomic_read(&v->state);
+ if (status == CHANNEL_READ_ONGOING) {
+ unlock_virtual_channel(channel_nb);
+ return HW_CTRL_IPC_STATUS_READ_ON_GOING;
+ }
+
+ if (status == CHANNEL_CLOSED) {
+ unlock_virtual_channel(channel_nb);
+ return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE;
+ }
+
+ atomic_set(&v->state, CHANNEL_READ_ONGOING);
+
+ v->ipc->priv->read_count = 0;
+ if (v->ipc->priv->pchannel > 3) {
+ struct sdma_channel *sdma_chnl;
+ sdma_chnl = &v->ipc->ch.sdma;
+ status =
+ sdma_start_dsp_transfer_kernel(v->ipc->priv, nb_bytes, buf);
+ } else {
+ status =
+ mu_start_dsp_transfer_kernel(v->ipc->priv, nb_bytes, buf);
+ }
+
+ unlock_virtual_channel(channel_nb);
+ return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR;
+}
+
+/*!
+ * Writes data to an IPC link. This functions can be called directly by kernel
+ * modules. POSIX implementation of the IPC Driver also calls it.
+ *
+ * @param channel handler to the virtual channel where read has been requested.
+ * @param buf physical address of DMA'able write buffer containing data to
+ * be written on the channel.
+ * @param nb_bytes size of the buffer
+ *
+ * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code
+ * otherwise.
+ */
+HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_write(HW_CTRL_IPC_CHANNEL_T * channel,
+ unsigned char *buf,
+ unsigned short nb_bytes)
+{
+ struct virtual_channel *v;
+ struct sdma_channel *sdma_chnl;
+ struct mu_channel *mu_chnl;
+ int status = 0;
+ int channel_nb;
+
+ if (is_channel_handler_valid(channel) == 0) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ if (buf == NULL) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ channel_nb = channel->channel_nb;
+ v = &virtual_channels[channel_nb];
+
+ if (lock_virtual_channel(channel_nb)) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ status = atomic_read(&v->state);
+
+ if (status == CHANNEL_CLOSED) {
+ unlock_virtual_channel(channel_nb);
+ return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE;
+ }
+
+ if (status == CHANNEL_WRITE_ONGOING) {
+ unlock_virtual_channel(channel_nb);
+ return HW_CTRL_IPC_STATUS_WRITE_ON_GOING;
+ }
+
+ atomic_set(&v->state, CHANNEL_WRITE_ONGOING);
+
+ v->ipc->priv->write_count = 0;
+
+ if (v->ipc->priv->pchannel > 3) {
+ sdma_chnl = &v->ipc->ch.sdma;
+ sdma_chnl->wpaddr = (dma_addr_t) buf;
+ status = sdma_start_mcu_transfer_kernel(v->ipc, nb_bytes);
+ mxc_dma_set_callback(sdma_chnl->write_channel,
+ sdma_kernel_writecb, v->ipc->priv);
+ mxc_dma_start(sdma_chnl->write_channel);
+ } else {
+ mu_chnl = &v->ipc->ch.mu;
+ v->ipc->priv->vc->data = buf;
+ mu_chnl->bytes_written = IPC_WRITE_BYTE_INIT_VAL;
+ v->ipc->priv->write_count = nb_bytes;
+ status = mu_start_mcu_transfer_kernel(mu_chnl);
+ }
+
+ pr_debug("transferred %d bytes on virtual channel %d\n", nb_bytes,
+ channel_nb);
+
+ unlock_virtual_channel(channel_nb);
+ return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR;
+}
+
+/*!
+ * Writes data to an IPC link. This function can be called directly by kernel
+ * modules. It accepts a linked list or contiguous data.
+ *
+ * @param channel handler to the virtual channel where read has
+ * been requested.
+ * @param mem_ptr pointer of type HW_CTRL_IPC_WRITE_PARAMS_T. Each element
+ * points to the physical address of a DMA'able buffer
+ *
+ * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code
+ * otherwise.
+ */
+HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_write_ex(HW_CTRL_IPC_CHANNEL_T * channel,
+ HW_CTRL_IPC_WRITE_PARAMS_T * mem_ptr)
+{
+ struct virtual_channel *v;
+ struct sdma_channel *sdma_chnl = NULL;
+ int status = 0;
+ int channel_nb;
+ HW_CTRL_IPC_LINKED_LIST_T *elt_ptr;
+
+ if (is_channel_handler_valid(channel) == 0) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ channel_nb = channel->channel_nb;
+
+ v = &virtual_channels[channel_nb];
+ if (lock_virtual_channel(channel_nb)) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ status = atomic_read(&v->state);
+ if (status == CHANNEL_CLOSED) {
+ unlock_virtual_channel(channel_nb);
+ return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE;
+ }
+ if (status == CHANNEL_WRITE_ONGOING) {
+ unlock_virtual_channel(channel_nb);
+ return HW_CTRL_IPC_STATUS_WRITE_ON_GOING;
+ }
+ atomic_set(&v->state, CHANNEL_WRITE_ONGOING);
+
+ sdma_chnl = &v->ipc->ch.sdma;
+ v->ipc->priv->write_count = 0;
+
+ if (mem_ptr->ipc_memory_read_mode == HW_CTRL_IPC_MODE_CONTIGUOUS) {
+ sdma_chnl->wpaddr =
+ (dma_addr_t) mem_ptr->read.cont_ptr->data_ptr;
+ status =
+ sdma_start_mcu_transfer_kernel(v->ipc,
+ mem_ptr->read.cont_ptr->
+ length);
+ } else {
+ elt_ptr = mem_ptr->read.list_ptr;
+ while (elt_ptr != NULL) {
+ sdma_chnl->wpaddr = (dma_addr_t) elt_ptr->data_ptr;
+ status =
+ sdma_start_mcu_transfer_kernel(v->ipc,
+ elt_ptr->length);
+ elt_ptr = elt_ptr->next;
+ }
+ }
+ mxc_dma_set_callback(sdma_chnl->write_channel, sdma_kernel_writecb,
+ v->ipc->priv);
+ mxc_dma_start(sdma_chnl->write_channel);
+ pr_debug("transferred bytes on virtual channel %d\n", channel_nb);
+
+ unlock_virtual_channel(channel_nb);
+ return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR;
+}
+
+/*!
+ * Used to set various channel parameters
+ *
+ * @param channel handler to the virtual channel where read has
+ * been requested.
+ * @param action IPC driver control action to perform.
+ * @param param parameters required to complete the requested action
+ */
+HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_ioctl(HW_CTRL_IPC_CHANNEL_T * channel,
+ HW_CTRL_IPC_IOCTL_ACTION_T action,
+ void *param)
+{
+ struct virtual_channel *v;
+ struct sdma_channel *sdma_chnl = NULL;
+
+ if (is_channel_handler_valid(channel) == 0) {
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ v = &virtual_channels[channel->channel_nb];
+ switch (action) {
+ case HW_CTRL_IPC_SET_READ_CALLBACK:
+ v->ipc->priv->k_callbacks->read = param;
+ break;
+ case HW_CTRL_IPC_SET_WRITE_CALLBACK:
+ v->ipc->priv->k_callbacks->write = param;
+ break;
+ case HW_CTRL_IPC_SET_NOTIFY_CALLBACK:
+ v->ipc->priv->k_callbacks->notify = param;
+ break;
+ case HW_CTRL_IPC_SET_MAX_CTRL_STRUCT_NB:
+ sdma_chnl = &v->ipc->ch.sdma;
+ initialize_sdma_read_channel_kernel(v->ipc, (int)param);
+ /* Reinitialize the buff pointers to start at zero BD */
+ sdma_chnl->rbuf_tail = (int)param;
+ sdma_chnl->rbuf_head = 0;
+ if (sdma_chnl->write_channel != -1) {
+ initialize_sdma_write_channel(v->ipc, 1, (int)param);
+ sdma_chnl->wbuf_tail = (int)param;
+ sdma_chnl->wbuf_head = 0;
+ }
+ break;
+ default:
+ return HW_CTRL_IPC_STATUS_ERROR;
+ }
+
+ return HW_CTRL_IPC_STATUS_OK;
+}
+
+/*!
+ * This function is a variant on the write() function, and is used to send a
+ * group of frames made of various pieces each to the IPC driver.
+ * It is mandatory to allow high throughput on IPC while minimizing the time
+ * spent in the drivers / interrupts.
+ *
+ * @param channel handler to the virtual channel where read has
+ * been requested.
+ * @param ctrl_ptr Pointer on the control structure.
+ *
+ * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code
+ * otherwise.
+ */
+HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_write_ex2(HW_CTRL_IPC_CHANNEL_T * channel,
+ HW_CTRL_IPC_DATA_NODE_DESCRIPTOR_T *
+ ctrl_ptr)
+{
+ int status = 0;
+ struct virtual_channel *v;
+ struct sdma_channel *sdma_chnl = NULL;
+
+ int channel_nb;
+
+ channel_nb = channel->channel_nb;
+
+ v = &virtual_channels[channel_nb];
+
+ status = atomic_read(&v->write_state_ipcv2);
+ if (status == CHANNEL_WRITE_ONGOING) {
+
+ return HW_CTRL_IPC_STATUS_WRITE_ON_GOING;
+ }
+
+ if (status == CHANNEL_CLOSED) {
+
+ return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE;
+ }
+
+ atomic_set(&v->write_state_ipcv2, CHANNEL_WRITE_ONGOING);
+
+ v = &virtual_channels[channel->channel_nb];
+ sdma_chnl = &v->ipc->ch.sdma;
+ mxc_dma_set_callback(sdma_chnl->write_channel,
+ sdma_kernel_writecb_ipcv2, v->ipc->priv);
+ status = mxc_sdma_write_ipcv2(sdma_chnl->write_channel, ctrl_ptr);
+ return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR;
+}
+
+/*!
+ * This function is used to give a set of buffers to the IPC and enable data
+ * transfers.
+ *
+ * @param channel handler to the virtual channel where read has
+ * been requested.
+ * @param ctrl_ptr Pointer on the control structure.
+ *
+ * @return returns HW_CTRL_IPC_STATUS_OK on success, an error code
+ * otherwise.
+ */
+HW_CTRL_IPC_STATUS_T hw_ctrl_ipc_read_ex2(HW_CTRL_IPC_CHANNEL_T * channel,
+ HW_CTRL_IPC_DATA_NODE_DESCRIPTOR_T *
+ ctrl_ptr)
+{
+ int status = 0;
+ struct virtual_channel *v;
+ struct sdma_channel *sdma_chnl = NULL;
+
+ int channel_nb;
+
+ channel_nb = channel->channel_nb;
+
+ v = &virtual_channels[channel_nb];
+
+ status = atomic_read(&v->read_state_ipcv2);
+ if (status == CHANNEL_READ_ONGOING) {
+
+ return HW_CTRL_IPC_STATUS_READ_ON_GOING;
+ }
+
+ if (status == CHANNEL_CLOSED) {
+ return HW_CTRL_IPC_STATUS_CHANNEL_UNAVAILABLE;
+ }
+
+ atomic_set(&v->read_state_ipcv2, CHANNEL_READ_ONGOING);
+ v = &virtual_channels[channel->channel_nb];
+ sdma_chnl = &v->ipc->ch.sdma;
+ mxc_dma_set_callback(sdma_chnl->read_channel, sdma_kernel_readcb_ipcv2,
+ v->ipc->priv);
+ status = mxc_sdma_read_ipcv2(sdma_chnl->read_channel, ctrl_ptr);
+ return (status == 0) ? HW_CTRL_IPC_STATUS_OK : HW_CTRL_IPC_STATUS_ERROR;
+}
+
+/*!
+ * This function is called when an IPC channel is opened. This function does
+ * the initialization of the device corresponding to the channel user want to
+ * open.
+ *
+ * @param inode Pointer to device inode
+ * @param file Pointer to device file structure
+ *
+ * @return The function returns 0 on success, -1 otherwise
+ */
+static int mxc_ipc_open(struct inode *inode, struct file *file)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ struct ipc_priv_data *priv = NULL;
+ unsigned short vch_index;
+ int ret = 0, mode = IPC_READ, sdma_chnl = -1, ipc_chnl = 0;
+
+ if (((minor == 4) || (minor == 5)) &&
+ (file->f_flags & (O_WRONLY | O_RDWR))) {
+ return -ENXIO;
+ }
+
+ /*look for an empty virtual channel, if found, lock it */
+ vch_index = get_free_virtual_channel();
+ /*if free virtual channel not found, return ENODEV */
+ if (vch_index == IPC_DUMMY_CHANNEL) {
+ return -ENODEV;
+ }
+
+ priv = (struct ipc_priv_data *)
+ kmalloc(sizeof(struct ipc_priv_data), GFP_KERNEL);
+ if (priv == NULL) {
+ unlock_virtual_channel(vch_index);
+ return -ENOMEM;
+ }
+
+ priv->vc = &virtual_channels[vch_index];
+ if (minor < 3) {
+ /* Allocate MU channel */
+ ipc_chnl = minor;
+ priv->vc->ipc = &ipc_channels[ipc_chnl];
+ ret = allocate_mu_channel(ipc_chnl);
+ if (ret < 0) {
+ deallocate_mu_channel(ipc_chnl);
+ kfree(priv);
+ unlock_virtual_channel(vch_index);
+ return ret;
+ }
+ } else {
+ /* Allocate SDMA channel */
+ if (minor == 3) {
+ mode = IPC_RDWR;
+ sdma_chnl = PACKET_DATA1_SDMA_WR_CHNL;
+ ipc_chnl = PACKET_DATA_CHANNEL1;
+ } else if (minor == 4) {
+ mode = IPC_READ;
+ sdma_chnl = LOG0_SDMA_RD_CHNL;
+ ipc_chnl = LOGGING_CHANNEL0;
+ } else if (minor == 5) {
+ mode = IPC_READ;
+ sdma_chnl = LOG1_SDMA_RD_CHNL;
+ ipc_chnl = LOGGING_CHANNEL1;
+ }
+ ret = allocate_sdma_channel(ipc_chnl, sdma_chnl, mode);
+ if (ret < 0) {
+ deallocate_sdma_channel(ipc_chnl);
+ kfree(priv);
+ unlock_virtual_channel(vch_index);
+ return ret;
+ }
+ priv->vc->ipc = &ipc_channels[ipc_chnl];
+ if (mode & IPC_WRITE) {
+ ret =
+ initialize_sdma_write_channel(priv->vc->ipc, 0, 1);
+ if (ret < 0) {
+ deallocate_sdma_channel(ipc_chnl);
+ kfree(priv);
+ unlock_virtual_channel(vch_index);
+ return ret;
+ }
+ }
+ }
+
+ atomic_set(&priv->blockmode, ((file->f_flags & (O_NONBLOCK)) ? 0 : 1));
+ priv->vchannel = vch_index;
+ priv->pchannel = ipc_chnl;
+ priv->owner = current;
+
+ init_waitqueue_head(&priv->wq);
+ init_waitqueue_head(&priv->rq);
+
+ priv->vc->ipc->priv = (void *)priv;
+
+ /* Start the SDMA read channel */
+ if (minor > 2) {
+ ret = initialize_sdma_read_channel_user(ipc_chnl);
+ if (ret < 0) {
+ deallocate_sdma_channel(ipc_chnl);
+ mxc_ipc_free_readbuf_user(ipc_chnl);
+ kfree(priv);
+ unlock_virtual_channel(vch_index);
+ return ret;
+ }
+ } else {
+ /* Start the MU read channel */
+ ret = mxc_ipc_enable_muints(ipc_chnl);
+ if (ret < 0) {
+ deallocate_mu_channel(ipc_chnl);
+ kfree(priv);
+ unlock_virtual_channel(vch_index);
+ return ret;
+ }
+ }
+ atomic_set(&priv->vc->state, CHANNEL_OPEN);
+
+ file->private_data = (void *)priv;
+
+ unlock_virtual_channel(priv->vchannel);
+
+ pr_debug("Channel successfully opened\n");
+
+ return 0;
+}
+
+/*!
+ * This function is called to close an IPC channel.
+ * Any allocated buffers are freed
+ *
+ * @param inode Pointer to device inode
+ * @param file Pointer to device file structure
+ *
+ * @return The function returns 0 on success or
+ * returns NO_ACCESS if incorrect channel is accessed.
+ */
+static int mxc_ipc_close(struct inode *inode, struct file *file)
+{
+ struct ipc_priv_data *priv = NULL;
+ unsigned int minor;
+ int do_sleep = 0;
+
+ minor = MINOR(file->f_dentry->d_inode->i_rdev);
+ priv = (struct ipc_priv_data *)file->private_data;
+
+ if (lock_virtual_channel(priv->vchannel)) {
+ return -EINTR;
+ }
+
+ if (minor < 3) {
+ struct mu_channel *mu = NULL;
+ mu = &priv->vc->ipc->ch.mu;
+
+ /* Wait to complete any pending writes */
+ /* Block until the write buffer is empty */
+ while (CIRC_CNT(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE)
+ != 0) {
+ if (wait_event_interruptible
+ (priv->wq,
+ (CIRC_CNT
+ (mu->wbuf->head, mu->wbuf->tail,
+ MAX_MU_BUF_SIZE) == 0))) {
+ return -ERESTARTSYS;
+ }
+ }
+ deallocate_mu_channel(priv->pchannel);
+ } else {
+ struct sdma_channel *sdma = NULL;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ /* Wait to complete any pending writes */
+ /* Block until the write buffer is empty */
+ while (atomic_read(&priv->vc->state) == CHANNEL_WRITE_ONGOING) {
+ if (wait_event_interruptible(priv->wq,
+ (atomic_read
+ (&priv->vc->state) !=
+ CHANNEL_WRITE_ONGOING))) {
+ return -ERESTARTSYS;
+ }
+ }
+ deallocate_sdma_channel(priv->pchannel);
+ mxc_ipc_free_readbuf_user(priv->pchannel);
+ }
+
+ /* Wake up all waiting processes on the wait queue */
+ while (1) {
+ do_sleep = 0;
+
+ if (waitqueue_active(&priv->wq)) {
+ wake_up(&priv->wq);
+ do_sleep++;
+ }
+ if (waitqueue_active(&priv->rq)) {
+ wake_up(&priv->rq);
+ do_sleep++;
+ }
+ if (!do_sleep) {
+ break;
+ }
+ schedule();
+ }
+
+ priv->vc->ipc = NULL;
+ atomic_set(&priv->vc->state, CHANNEL_CLOSED);
+ priv->vc = NULL;
+
+ unlock_virtual_channel(priv->vchannel);
+
+ kfree(file->private_data);
+ file->private_data = NULL;
+
+ pr_debug("Channel %d closed\n", minor);
+
+ return 0;
+}
+
+/*!
+ * The write function is available to the user-space to perform
+ * a write operation
+ *
+ * @param file Pointer to device file structure
+ * @param buf User buffer, where write data is placed.
+ * @param bytes Size of the requested data transfer
+ * @param off File position where the user is accessing.
+ *
+ * @return Returns number of bytes written or
+ * Returns COUNT_ESIZE if the number of bytes
+ * requested is not a multiple of channel
+ * receive or transmit size or
+ * Returns -EAGAIN for a non-block write when there is
+ * no data in the buffer or
+ * Returns -EFAULT if copy_to_user failed
+ */
+static ssize_t mxc_ipc_write(struct file *file, const char *buf,
+ size_t bytes, loff_t * off)
+{
+ struct ipc_priv_data *priv;
+ unsigned int minor;
+ int count = 0;
+
+ minor = MINOR(file->f_dentry->d_inode->i_rdev);
+
+ if ((minor <= 2) && ((bytes % 4) != 0)) {
+ pr_debug("EINVAL error\n");
+ return -EINVAL;
+ }
+
+ if (((minor == 4) || (minor == 5)) &&
+ (file->f_flags & (O_WRONLY | O_RDWR))) {
+ pr_debug("ENXIO error\n");
+ return -ENXIO; /*log channel is read-only */
+ }
+
+ priv = (struct ipc_priv_data *)file->private_data;
+
+ if (lock_virtual_channel(priv->vchannel)) {
+ return -EINTR;
+ }
+
+ pr_debug("bytes to write = %d\n", bytes);
+
+ if (minor > 2) {
+ if (atomic_read(&priv->vc->state) == CHANNEL_WRITE_ONGOING) {
+ /* If non-blocking mode return */
+ if (atomic_read(&priv->blockmode) == 0) {
+ unlock_virtual_channel(priv->vchannel);
+ return -EAGAIN;
+ }
+ }
+ if (bytes > MAX_SDMA_TX_BUF_SIZE) {
+ bytes = MAX_SDMA_TX_BUF_SIZE;
+ }
+ count = sdma_start_mcu_transfer(priv, bytes, buf);
+ } else {
+ count = mu_start_mcu_transfer(priv, bytes, buf);
+ }
+
+ pr_debug("bytes written = %d\n", count);
+
+ unlock_virtual_channel(priv->vchannel);
+
+ return count;
+}
+
+/*!
+ * Check to see if there is data available in the internal buffers
+ *
+ * @param minor minor number used to differentiate between MU and
+ * SDMA buffers
+ * @param priv private data. Useful to synchronize and to keep track of
+ * the transfer
+ * @return returns 0 if no data available or a positive value when data is
+ * available
+ */
+static int mxc_ipc_data_available(int minor, struct ipc_priv_data *priv)
+{
+ int cnt = 0;
+
+ if (minor > 2) {
+ struct sdma_channel *sdma = NULL;
+
+ sdma = &priv->vc->ipc->ch.sdma;
+ cnt = sdma->rbuf[sdma->rbuf_tail].count;
+ pr_debug("bytes in SDMA read buf=%d\n", cnt);
+ } else {
+ struct mu_channel *mu = NULL;
+ mu = &priv->vc->ipc->ch.mu;
+ cnt = CIRC_CNT(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE);
+ pr_debug("bytes in MU read buf = %d\n", cnt);
+ }
+
+ return cnt;
+}
+
+/*!
+ * The read function is available to the user-space to perform
+ * a read operation
+ *
+ * @param file Pointer to device file structure
+ * @param buf User buffer, where read data to be placed.
+ * @param bytes Size of the requested data transfer
+ * @param off File position where the user is accessing.
+ *
+ * @return Returns number of bytes read or
+ * Returns COUNT_ESIZE if the number of bytes
+ * requested is not a multiple of channel
+ * receive or transmit size or
+ * Returns -EAGAIN for a non-block read when no data
+ * is ready in the buffer or in the register or
+ * Returns -EFAULT if copy_to_user failed
+ */
+static ssize_t mxc_ipc_read(struct file *file, char *buf,
+ size_t bytes, loff_t * off)
+{
+ struct ipc_priv_data *priv;
+ unsigned int minor;
+ int blockmode;
+ int status;
+ int count = 0;
+
+ minor = MINOR(file->f_dentry->d_inode->i_rdev);
+
+ if ((minor <= 2) && ((bytes % 4) != 0)) {
+ return -EINVAL;
+ }
+
+ priv = (struct ipc_priv_data *)file->private_data;
+
+ status = atomic_read(&priv->vc->state);
+ blockmode = atomic_read(&priv->blockmode);
+
+ /*
+ * If non-blocking mode has been requested and no data is available
+ * read channel buffers, return -EAGAIN.
+ */
+ if ((blockmode == 0) && (!mxc_ipc_data_available(minor, priv))) {
+ return -EAGAIN;
+ }
+
+ if (lock_virtual_channel(priv->vchannel)) {
+ return -EINTR;
+ }
+
+ pr_debug("bytes to read = %d\n", bytes);
+
+ /* Block till data is available */
+ while ((mxc_ipc_data_available(minor, priv)) == 0) {
+ pr_debug("No data to read, BLOCK\n");
+ if (wait_event_interruptible(priv->rq,
+ (mxc_ipc_data_available
+ (minor, priv) > 0))) {
+ unlock_virtual_channel(priv->vchannel);
+ return -ERESTARTSYS;
+ }
+ }
+
+ if (minor > 2) {
+ struct sdma_channel *sdma = NULL;
+ sdma = &priv->vc->ipc->ch.sdma;
+
+ count = mxc_ipc_copy_from_sdmabuf(sdma, buf, bytes);
+ } else {
+ count = mu_start_dsp_transfer(priv, bytes, buf);
+ }
+
+ pr_debug("bytes read = %d\n", count);
+
+ atomic_set(&priv->vc->state, CHANNEL_OPEN);
+ unlock_virtual_channel(priv->vchannel);
+
+ return count;
+}
+
+/*!
+ * This function allows the caller to determine whether it can
+ * read from or write to one or more open files without blocking.
+ *
+ * @param filp Pointer to device file structure
+ * @param wait used by poll_wait to indicate a change in the
+ * poll status.
+ *
+ * @return the bit mask describing which operations could
+ * be completed immediately.
+ */
+unsigned int mxc_ipc_poll(struct file *filp, poll_table * wait)
+{
+ struct ipc_priv_data *priv;
+ unsigned int minor;
+ unsigned int mask = 0;
+ int state = 0;
+
+ minor = MINOR(filp->f_dentry->d_inode->i_rdev);
+ priv = (struct ipc_priv_data *)filp->private_data;
+
+ poll_wait(filp, &priv->wq, wait);
+ poll_wait(filp, &priv->rq, wait);
+
+ if (minor > 2) {
+ struct sdma_channel *sdma;
+ sdma = &priv->vc->ipc->ch.sdma;
+ state = atomic_read(&priv->vc->state);
+ /* Check if SDMA Write channel is available */
+ if (state == CHANNEL_OPEN) {
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ /* Check if data is available in SDMA channel read buffer */
+ if ((mxc_ipc_data_available(minor, priv)) > 0) {
+ mask |= POLLIN | POLLRDNORM;
+ }
+ } else {
+ struct mu_channel *mu;
+ mu = &priv->vc->ipc->ch.mu;
+ /* Check for space in MU channel write buffer */
+ if (CIRC_SPACE(mu->wbuf->head, mu->wbuf->tail, MAX_MU_BUF_SIZE)) {
+ mask |= POLLOUT | POLLWRNORM;
+ }
+
+ /* Check if data is available in MU channel read buffer */
+ if (CIRC_CNT(mu->rbuf->head, mu->rbuf->tail, MAX_MU_BUF_SIZE)) {
+ mask |= POLLIN | POLLRDNORM;
+ }
+ }
+ pr_debug("Poll mask = %d\n", mask);
+ return mask;
+}
+
+static struct file_operations mxc_ipc_fops = {
+ .owner = THIS_MODULE,
+ .read = mxc_ipc_read,
+ .write = mxc_ipc_write,
+// .writev = mxc_ipc_writev,
+ .poll = mxc_ipc_poll,
+ .open = mxc_ipc_open,
+ .release = mxc_ipc_close,
+};
+
+/*!
+ * This function is used to unload the module.
+ */
+static void ipc_cleanup_module(void)
+{
+ int i;
+ for (i = 0; i <= 5; i++) {
+ class_device_destroy(mxc_ipc_class, MKDEV(major_num, i));
+ }
+ class_destroy(mxc_ipc_class);
+ unregister_chrdev(major_num, "mxc_ipc");
+
+ pr_debug("IPC Driver Module Unloaded\n");
+}
+
+/*!
+ * This function is used to load the module. All initializations and
+ * resources requesting is done here
+ *
+ * @return Returns 0 on success, -1 otherwise
+ */
+int __init ipc_init_module(void)
+{
+ int res = 0;
+ int i;
+ struct class_device *temp_class;
+
+ res = register_chrdev(MXC_IPC_MAJOR, "mxc_ipc", &mxc_ipc_fops);
+ if (res < 0) {
+ pr_debug("IPC Driver Module was not Loaded successfully\n");
+ return res;
+ }
+
+ major_num = res;
+
+ mxc_ipc_class = class_create(THIS_MODULE, "mxc_ipc");
+ if (IS_ERR(mxc_ipc_class)) {
+ printk(KERN_ERR "Error creating mxc_ipc class.\n");
+ unregister_chrdev(major_num, "mxc_ipc");
+ return PTR_ERR(mxc_ipc_class);
+ }
+
+ for (i = 0; i <= 5; i++) {
+ temp_class =
+ class_device_create(mxc_ipc_class, NULL,
+ MKDEV(major_num, i),
+ NULL, "mxc_ipc%u", i);
+
+ if (IS_ERR(temp_class))
+ goto err_out;
+ }
+
+ for (i = 0; i < IPC_MAX_VIRTUAL_CHANNELS; i++) {
+ virtual_channels[i].ipc = NULL;
+ sema_init(&virtual_channels[i].sem, 1);
+ atomic_set(&virtual_channels[i].state, CHANNEL_CLOSED);
+ /* Initialize the channel_handlers array to map the virtual channel indexes */
+ channel_handlers[i].channel_nb = i;
+ }
+
+ printk(KERN_INFO "IPC driver successfully loaded.\n");
+ return major_num;
+
+ err_out:
+ printk(KERN_ERR "Error creating mxc_ipc class device.\n");
+ for (i = 0; i <= 5; i++) {
+ class_device_destroy(mxc_ipc_class, MKDEV(major_num, i));
+ }
+ class_destroy(mxc_ipc_class);
+ unregister_chrdev(major_num, "mxc_ipc");
+ return -1;
+}
+
+module_init(ipc_init_module);
+module_exit(ipc_cleanup_module);
+EXPORT_SYMBOL(hw_ctrl_ipc_open);
+EXPORT_SYMBOL(hw_ctrl_ipc_close);
+EXPORT_SYMBOL(hw_ctrl_ipc_read);
+EXPORT_SYMBOL(hw_ctrl_ipc_write);
+EXPORT_SYMBOL(hw_ctrl_ipc_write_ex);
+EXPORT_SYMBOL(hw_ctrl_ipc_write_ex2);
+EXPORT_SYMBOL(hw_ctrl_ipc_read_ex2);
+EXPORT_SYMBOL(hw_ctrl_ipc_ioctl);
diff --git a/drivers/char/mxc_mu.c b/drivers/char/mxc_mu.c
new file mode 100644
index 000000000000..5d16f62a6246
--- /dev/null
+++ b/drivers/char/mxc_mu.c
@@ -0,0 +1,1646 @@
+/*
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file mxc_mu.c
+ *
+ * @brief This file provides all the kernel level and user level API
+ * definitions for the message transfer between MCU and DSP core.
+ *
+ * The Interfaces are with respect to the MCU core. Any driver on the MCU side
+ * can transfer messages to the DSP side using the interfaces implemented here.
+ *
+ * @ingroup MU
+ */
+
+/*
+ * Include Files
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/circ_buf.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/clk.h>
+#include <asm/arch/mxc_mu.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include "mxc_mu_reg.h"
+
+#define DVR_VER "2.0"
+static struct class *mxc_mu_class;
+static int mu_major;
+
+static struct clk *mu_clkp;
+
+/*
+ * Used to calculate number of bytes in the read buffer
+ */
+#define GET_READBYTES(a) CIRC_CNT(mu_rbuf[a].cbuf.head, \
+ mu_rbuf[a].cbuf.tail, MAX_BUF_SIZE)
+
+/*
+ * Used to calculate number of bytes in the write buffer
+ */
+#define GET_WRITEBYTES(a) CIRC_CNT(mu_wbuf[a].cbuf.head, \
+ mu_wbuf[a].cbuf.tail, MAX_BUF_SIZE)
+
+/*
+ * Used to calculate empty space in the read buffer
+ */
+#define GET_READSPACE(a) CIRC_SPACE(mu_rbuf[a].cbuf.head, \
+ mu_rbuf[a].cbuf.tail, MAX_BUF_SIZE)
+
+/*
+ * Used to calculate empty space in the write buffer
+ */
+#define GET_WRITESPACE(a) CIRC_SPACE(mu_wbuf[a].cbuf.head, \
+ mu_wbuf[a].cbuf.tail, MAX_BUF_SIZE)
+
+/*!
+ * There are 4 callback functions, one for each receive
+ * interrupt. When 'bind' function is called the callback
+ * functions are stored in an array and retrieved in the
+ * tasklets when a receive interrupt arrives
+ */
+static callbackfn rxcallback[NUM_MU_CHANNELS];
+
+/*!
+ * There are 4 callback functions, one for each transmit
+ * interrupt. When 'bind' function is called the callback
+ * functions are stored in an array and retrieved in the
+ * tasklets when a transmit interrupt arrives
+ */
+static callbackfn txcallback[NUM_MU_CHANNELS];
+
+/*!
+ * There are 4 callback functions, one for each general purpose
+ * interrupt. When 'bind' function is called the callback
+ * functions are stored in an array and retrieved in the
+ * tasklets when a general purpose interrupt arrives
+ */
+static callbackfn gpcallback[NUM_MU_CHANNELS];
+
+/*!
+ * Declaring lock for mutual exclusion
+ */
+static DEFINE_SPINLOCK(mu_lock);
+
+/*!
+ * Declaring lock to prevent access to
+ * allocated registers
+ */
+static DEFINE_SPINLOCK(alloc_lock);
+
+/*!
+ * Number of users waiting in suspendq
+ */
+static int swait = 0;
+
+/*!
+ * To indicate whether any of the MU devices are suspending
+ */
+static int suspend_flag = 0;
+
+/*!
+ * To restore the state of control register while resuming the device
+ * to the state it was before suspending it.
+ */
+static unsigned int mcr_suspend_state = 0;
+
+/*!
+ * Indicate block mode - 0 and non block mode - 1. Default is non-block mode
+ */
+static int blockmode[NUM_MU_CHANNELS] = { 1, 1, 1, 1 };
+
+/*!
+ * Device Kernel Buffers. Each channel has one read and write buffer.
+ */
+typedef struct {
+ /*!
+ * Buffer used to store data that was read or the data that is
+ * to be written
+ */
+ struct circ_buf cbuf;
+ /*!
+ * to indicate number of users waiting on blocking mode of the device
+ */
+ int pwait;
+} ring_buffer;
+
+ring_buffer mu_rbuf[NUM_MU_CHANNELS], mu_wbuf[NUM_MU_CHANNELS];
+
+/*!
+ * reg_allocated[] stores channel handlers for the allocated registers.
+ */
+static int reg_allocated[NUM_MU_CHANNELS] = { -1, -1, -1, -1 };
+
+static unsigned int wbits[NUM_MU_CHANNELS] = { AS_MUMSR_MTE0, AS_MUMSR_MTE1,
+ AS_MUMSR_MTE2, AS_MUMSR_MTE3
+};
+
+static unsigned int rbits[NUM_MU_CHANNELS] = { AS_MUMSR_MRF0, AS_MUMSR_MRF1,
+ AS_MUMSR_MRF2, AS_MUMSR_MRF3
+};
+
+static unsigned int read_en[NUM_MU_CHANNELS] = { AS_MUMCR_MRIE0, AS_MUMCR_MRIE1,
+ AS_MUMCR_MRIE2, AS_MUMCR_MRIE3
+};
+
+static unsigned int write_en[NUM_MU_CHANNELS] =
+ { AS_MUMCR_MTIE0, AS_MUMCR_MTIE1,
+ AS_MUMCR_MTIE2, AS_MUMCR_MTIE3
+};
+
+static unsigned int genp_en[NUM_MU_CHANNELS] = { AS_MUMCR_MGIE0, AS_MUMCR_MGIE1,
+ AS_MUMCR_MGIE2, AS_MUMCR_MGIE3
+};
+
+static unsigned int genp_pend[NUM_MU_CHANNELS] =
+ { AS_MUMSR_MGIP0, AS_MUMSR_MGIP1,
+ AS_MUMSR_MGIP2, AS_MUMSR_MGIP3
+};
+
+static unsigned int genp_req[NUM_MU_CHANNELS] =
+ { AS_MUMCR_MGIR0, AS_MUMCR_MGIR1,
+ AS_MUMCR_MGIR2, AS_MUMCR_MGIR3
+};
+
+static unsigned int read_reg[NUM_MU_CHANNELS] = { AS_MUMRR0, AS_MUMRR1,
+ AS_MUMRR2, AS_MUMRR3
+};
+
+static unsigned int write_reg[NUM_MU_CHANNELS] = { AS_MUMTR0, AS_MUMTR1,
+ AS_MUMTR2, AS_MUMTR3
+};
+
+/*!
+ * The readq and waitq are used by blocking read/write
+ */
+static wait_queue_head_t readq, writeq, suspendq, wait_before_closeq;
+
+/*!
+ * To enable or disable byte swapping
+ */
+static bool byte_swapping_rd[NUM_MU_CHANNELS] = { false, false, false, false };
+static bool byte_swapping_wr[NUM_MU_CHANNELS] = { false, false, false, false };
+
+/*
+ * Declaring Bottom-half handlers - one for GP, RX and TX interrupt lines
+ */
+static void mxc_mu_rxhandler(unsigned long someval);
+static void mxc_mu_txhandler(unsigned long someval);
+static void mxc_mu_gphandler(unsigned long someval);
+
+/*
+ * Tasklets Declarations
+ */
+/*
+ * Declaring tasklet to handle receive interrupts
+ */
+DECLARE_TASKLET(rxch0tasklet, (void *)mxc_mu_rxhandler, (unsigned long)0);
+DECLARE_TASKLET(rxch1tasklet, (void *)mxc_mu_rxhandler, (unsigned long)1);
+DECLARE_TASKLET(rxch2tasklet, (void *)mxc_mu_rxhandler, (unsigned long)2);
+DECLARE_TASKLET(rxch3tasklet, (void *)mxc_mu_rxhandler, (unsigned long)3);
+
+/*
+ * Declaring tasklet to handle transmit interrupts
+ */
+DECLARE_TASKLET(txch0tasklet, (void *)mxc_mu_txhandler, (unsigned long)0);
+DECLARE_TASKLET(txch1tasklet, (void *)mxc_mu_txhandler, (unsigned long)1);
+DECLARE_TASKLET(txch2tasklet, (void *)mxc_mu_txhandler, (unsigned long)2);
+DECLARE_TASKLET(txch3tasklet, (void *)mxc_mu_txhandler, (unsigned long)3);
+
+/*
+ * Declaring tasklet to handle general purpose interrupts
+ */
+DECLARE_TASKLET(gpch0tasklet, (void *)mxc_mu_gphandler, (unsigned long)0);
+DECLARE_TASKLET(gpch1tasklet, (void *)mxc_mu_gphandler, (unsigned long)1);
+DECLARE_TASKLET(gpch2tasklet, (void *)mxc_mu_gphandler, (unsigned long)2);
+DECLARE_TASKLET(gpch3tasklet, (void *)mxc_mu_gphandler, (unsigned long)3);
+
+/*!
+ * Allocating Resources:
+ * Allocating channels required to the caller if they are free.
+ * 'reg_allocated' variable holds all the registers that have been allocated
+ *
+ * @param chnum The channel number required is passed
+ *
+ * @return Returns unique channel handler associated with channel. This
+ * channel handler is used to validate the user accessing the
+ * channel.
+ * Returns channel handler - on success
+ * Returns negative value - on error
+ * If invalid channel is passed, it returns -ENODEV error.
+ * If no channel is available, returns -EBUSY.
+ */
+int mxc_mu_alloc_channel(int chnum)
+{
+ unsigned long flags;
+ int ret_val = -EBUSY;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ /* Acquiring Lock */
+ spin_lock_irqsave(&alloc_lock, flags);
+ if (reg_allocated[chnum] == -1) {
+ /*
+ * If channel requested is available, create
+ * channel handler and store in the array
+ */
+ reg_allocated[chnum] = BASE_NUM + chnum;
+ ret_val = BASE_NUM + chnum;
+ }
+ spin_unlock_irqrestore(&alloc_lock, flags);
+ return ret_val;
+}
+
+/*!
+ * Deallocating Resources:
+ *
+ * @param chand The channel handler associated with the channel
+ * that is to be freed is passed.
+ *
+ * @return Returns 0 on success and -1 if channel was not
+ * already allocated or ENODEV error if invalid
+ * channel is obtained from the channel handler
+ */
+int mxc_mu_dealloc_channel(int chand)
+{
+ unsigned long flags;
+ int retval = -1;
+ int chnum = chand - BASE_NUM;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ spin_lock_irqsave(&alloc_lock, flags);
+ if (reg_allocated[chnum] == chand) {
+ /* deallocating */
+ reg_allocated[chnum] = -1;
+ /* clearing callback arrays */
+ rxcallback[chnum] = NULL;
+ txcallback[chnum] = NULL;
+ gpcallback[chnum] = NULL;
+ retval = 0;
+ }
+ spin_unlock_irqrestore(&alloc_lock, flags);
+ return retval;
+}
+
+/* NON BLOCKING READ/WRITE */
+
+/*!
+ * This function is called by other modules to read from one of the
+ * receive registers on the MCU side.
+ *
+ * @param chand The channel handler associated with the channel
+ * to be accessed is passed
+ * @param mu_msg Buffer where the read data has to be stored
+ * is passed by pointer as an argument
+ *
+ * @return Returns 0 on success or
+ * Returns NO_DATA error if there is no data to read or
+ * Returns NO_ACCESS error if the incorrect channel
+ * is accessed or ENODEV error if invalid
+ * channel is obtained from the channel handler
+ * Returns -1 if the buffer passed is not allocated
+ */
+int mxc_mu_mcuread(int chand, char *mu_msg)
+{
+ /* mu_msg should be 4 bytes long */
+ int chnum = chand - BASE_NUM;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ if (mu_msg == NULL) {
+ return -1;
+ }
+ if ((readl(AS_MUMSR) & rbits[chnum]) == 0) {
+ return NO_DATA;
+ }
+ if (reg_allocated[chnum] == chand) {
+ *(unsigned int *)mu_msg = readl(read_reg[chnum]);
+ } else {
+ return NO_ACCESS;
+ }
+ if (byte_swapping_rd[chnum] == true) {
+ *(unsigned int *)mu_msg = swab32(*(unsigned int *)mu_msg);
+ }
+ return 0;
+}
+
+/*!
+ * This function is called by other modules to write to one of the
+ * transmit registers on the MCU side
+ *
+ * @param chand The channel handler associated with the channel
+ * to be accessed is passed
+ * @param mu_msg Buffer where the write data has to be stored
+ * is passed by pointer as an argument
+ *
+ * @return Returns 0 on success or
+ * Returns NO_DATA error if the register not empty or
+ * Returns NO_ACCESS error if the incorrect channel
+ * is accessed or ENODEV error if invalid
+ * channel is obtained from the channel handler
+ * Returns -1 if the buffer passed is not allocated
+ */
+int mxc_mu_mcuwrite(int chand, char *mu_msg)
+{
+ int chnum = chand - BASE_NUM;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ if (mu_msg == NULL) {
+ return -1;
+ }
+ if (byte_swapping_wr[chnum] == true) {
+ /* Swap the bytes */
+ *(unsigned int *)mu_msg = swab32(*(unsigned int *)mu_msg);
+ }
+ if ((readl(AS_MUMSR) & wbits[chnum]) == 0) {
+ return NO_DATA;
+ }
+ /* Check whether the register is allocated */
+ if (reg_allocated[chnum] == chand) {
+ writel(*(unsigned int *)mu_msg, write_reg[chnum]);
+ } else {
+ return NO_ACCESS;
+ }
+ return 0;
+}
+
+/*!
+ * Tasklet used by the Interrupt service routine to handle the muxed
+ * receive interrupts. When an interrupt occurs, control from the top main
+ * interrupt service routine is transferred to this tasklet which sends the
+ * channel number to the call back function
+ *
+ * @param chnum index value indicating channel number
+ *
+ */
+static void mxc_mu_rxhandler(unsigned long chnum)
+{
+ if (rxcallback[chnum] != NULL) {
+ rxcallback[chnum] (chnum);
+ }
+}
+
+/*!
+ * Tasklet used by the Interrupt service routine to handle the muxed
+ * transmit interrupts. When an interrupt occurs, control from the top main
+ * interrupt service routine is transferred to this tasklet which sends the
+ * channel number to the call back function
+ *
+ * @param chnum index value indicating channel number
+ *
+ */
+static void mxc_mu_txhandler(unsigned long chnum)
+{
+ if (txcallback[chnum] != NULL) {
+ txcallback[chnum] (chnum);
+ }
+}
+
+/*!
+ * Tasklet used by the Interrupt service routine to handle the general
+ * purpose interrupts. When an interrupt occurs, control from the top main
+ * interrupt service routine is transferred to this tasklet which sends the
+ * channel number to the call back function
+ *
+ * @param chnum index value indicating channel number
+ *
+ */
+static void mxc_mu_gphandler(unsigned long chnum)
+{
+ if (gpcallback[chnum] != NULL) {
+ gpcallback[chnum] (chnum);
+ }
+}
+
+/*!
+ * Interrupt service routine registered to handle the individual general purpose
+ * interrupts or muxed. Interrupts are cleared in ISR before scheduling tasklet
+ *
+ * @param irq the interrupt number
+ * @param dev_id driver private data
+ *
+ * @return The function returns \b IRQ_RETVAL(1) if interrupt was
+ * handled, returns \b IRQ_RETVAL(0) if the interrupt was
+ * not handled.
+ * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h.
+ */
+static irqreturn_t mxc_mu_mcugphand(int irq, void *dev_id)
+{
+ int handled = 0;
+ unsigned int sreg1, sreg2, status = 0;
+
+ sreg1 = readl(AS_MUMSR);
+ sreg2 = readl(AS_MUMCR);
+ /* When General Purpose Interrupt occurs */
+ if ((sreg1 & AS_MUMSR_MGIP0) && (sreg2 & AS_MUMCR_MGIE0)) {
+ status |= (AS_MUMSR_MGIP0);
+ tasklet_schedule(&gpch0tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MGIP1) && (sreg2 & AS_MUMCR_MGIE1)) {
+ status |= (AS_MUMSR_MGIP1);
+ tasklet_schedule(&gpch1tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MGIP2) && (sreg2 & AS_MUMCR_MGIE2)) {
+ status |= (AS_MUMSR_MGIP2);
+ tasklet_schedule(&gpch2tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MGIP3) && (sreg2 & AS_MUMCR_MGIE3)) {
+ status |= (AS_MUMSR_MGIP3);
+ tasklet_schedule(&gpch3tasklet);
+ handled = 1;
+ }
+ writel(status, AS_MUMSR);
+ return IRQ_RETVAL(handled);
+}
+
+/*!
+ * Interrupt service routine registered to handle the muxed receive
+ * interrupts. Interrupts disabled inside ISR before scheduling tasklet
+ *
+ * @param irq the interrupt number
+ * @param dev_id driver private data
+ *
+ * @return The function returns \b IRQ_RETVAL(1) if interrupt was
+ * handled, returns \b IRQ_RETVAL(0) if the interrupt was
+ * not handled.
+ * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h.
+ */
+static irqreturn_t mxc_mu_mcurxhand(int irq, void *dev_id)
+{
+ int handled = 0;
+ unsigned int sreg1, sreg2, control;
+
+ sreg1 = readl(AS_MUMSR);
+ sreg2 = readl(AS_MUMCR);
+ control = sreg2;
+ /* When Receive Interrupt occurs */
+ if ((sreg1 & AS_MUMSR_MRF0) && (sreg2 & AS_MUMCR_MRIE0)) {
+ control &= ~(AS_MUMCR_MRIE0);
+ tasklet_schedule(&rxch0tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MRF1) && (sreg2 & AS_MUMCR_MRIE1)) {
+ control &= ~(AS_MUMCR_MRIE1);
+ tasklet_schedule(&rxch1tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MRF2) && (sreg2 & AS_MUMCR_MRIE2)) {
+ control &= ~(AS_MUMCR_MRIE2);
+ tasklet_schedule(&rxch2tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MRF3) && (sreg2 & AS_MUMCR_MRIE3)) {
+ control &= ~(AS_MUMCR_MRIE3);
+ tasklet_schedule(&rxch3tasklet);
+ handled = 1;
+ }
+ writel(control, AS_MUMCR);
+ return IRQ_RETVAL(handled);
+}
+
+/*!
+ * Interrupt service routine registered to handle the muxed transmit interrupts
+ * Interrupts disabled inside ISR before scheduling tasklet
+ *
+ * @param irq the interrupt number
+ * @param dev_id driver private data
+ *
+ * @return The function returns \b IRQ_RETVAL(1) if interrupt was
+ * handled, returns \b IRQ_RETVAL(0) if the interrupt was
+ * not handled.
+ * \b IRQ_RETVAL is defined in \b include/linux/interrupt.h.
+ */
+static irqreturn_t mxc_mu_mcutxhand(int irq, void *dev_id)
+{
+ int handled = 0;
+ unsigned int sreg1, sreg2, control;
+
+ sreg1 = readl(AS_MUMSR);
+ sreg2 = readl(AS_MUMCR);
+ control = sreg2;
+ /* When Transmit Interrupt occurs */
+ if ((sreg1 & AS_MUMSR_MTE0) && (sreg2 & AS_MUMCR_MTIE0)) {
+ control &= ~(AS_MUMCR_MTIE0);
+ tasklet_schedule(&txch0tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MTE1) && (sreg2 & AS_MUMCR_MTIE1)) {
+ control &= ~(AS_MUMCR_MTIE1);
+ tasklet_schedule(&txch1tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MTE2) && (sreg2 & AS_MUMCR_MTIE2)) {
+ control &= ~(AS_MUMCR_MTIE2);
+ tasklet_schedule(&txch2tasklet);
+ handled = 1;
+ }
+ if ((sreg1 & AS_MUMSR_MTE3) && (sreg2 & AS_MUMCR_MTIE3)) {
+ control &= ~(AS_MUMCR_MTIE3);
+ tasklet_schedule(&txch3tasklet);
+ handled = 1;
+ }
+ writel(control, AS_MUMCR);
+ return IRQ_RETVAL(handled);
+}
+
+/*!
+ * This function is used by other modules to issue DSP hardware reset.
+ *
+ */
+int mxc_mu_dsp_reset(void)
+{
+ unsigned long flags;
+
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ spin_lock_irqsave(&mu_lock, flags);
+ writel((readl(AS_MUMCR) | AS_MUMCR_DHR), AS_MUMCR);
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return 0;
+}
+
+/*!
+ * This function is used by other modules to deassert DSP hardware reset
+ */
+int mxc_mu_dsp_deassert(void)
+{
+ unsigned long flags;
+
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ spin_lock_irqsave(&mu_lock, flags);
+ writel((readl(AS_MUMCR) & (~AS_MUMCR_DHR)), AS_MUMCR);
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return 0;
+}
+
+/*!
+ * This function is used by other modules to issue DSP Non-Maskable
+ * Interrupt to the DSP
+ */
+int mxc_mu_dsp_nmi(void)
+{
+ unsigned long flags;
+
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ spin_lock_irqsave(&mu_lock, flags);
+ writel((readl(AS_MUMCR) | AS_MUMCR_DNMI), AS_MUMCR);
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return 0;
+}
+
+/*!
+ * This function is used by other modules to retrieve the DSP reset state.
+ *
+ * @return Returns 1 if DSP in reset state and 0 otherwise
+ */
+int mxc_mu_dsp_reset_status(void)
+{
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ if ((readl(AS_MUMSR) & AS_MUMSR_DRS) != 0) {
+ /* 1 implies the DSP side of MU is in reset state */
+ return 1;
+ } else {
+ /* 0 implies the DSP side of MU is not in reset state */
+ return 0;
+ }
+}
+
+/*!
+ * This function is used by other modules to retrieve the DSP Power Mode.
+ *
+ * @return Returns a value from which power mode of the DSP side of
+ * of MU unit can be inferred
+ * 0 - Run mode, 1 - Wait mode
+ * 2 - Stop mode, 3 - DSM mode
+ */
+unsigned int mxc_mu_dsp_pmode_status(void)
+{
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ return (readl(AS_MUMSR) & (AS_MUMSR_DPM1 | AS_MUMSR_DPM0) >> 5);
+}
+
+/*!
+ * This function is used by other modules to reset the MU Unit. This would reset
+ * both the MCU side and the DSP side
+ *
+ */
+int mxc_mu_reset(void)
+{
+ unsigned long flags;
+
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ spin_lock_irqsave(&mu_lock, flags);
+ writel((readl(AS_MUMCR) | AS_MUMCR_MMUR), AS_MUMCR);
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return 0;
+}
+
+/*!
+ * This function is called by unbind function of this module and
+ * can also be called from other modules to enable desired
+ * receive Interrupt
+ *
+ * @param chand The channel handler associated with the channel
+ * whose interrupt to be disabled is passed
+ * @param muoper The value passed is TX, RX, GP
+ *
+ * @return Returns 0 on success or ENODEV error if invalid
+ * channel is obtained from the channel handler
+ * Returns -1 if muoper is other than RX, TX or GP
+ */
+int mxc_mu_intdisable(int chand, enum mu_oper muoper)
+{
+ unsigned int status;
+ unsigned long flags;
+ int chnum = chand - BASE_NUM;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ spin_lock_irqsave(&mu_lock, flags);
+ status = readl(AS_MUMCR);
+ switch (muoper) {
+ case RX:
+ status &= ~(read_en[chnum]);
+ break;
+ case TX:
+ status &= ~(write_en[chnum]);
+ break;
+ case GP:
+ status &= ~(genp_en[chnum]);
+ break;
+ default:
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return -1;
+ }
+ writel(status, AS_MUMCR);
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return 0;
+}
+
+/*!
+ * This function is called by other modules to unbind their
+ * call back functions
+ *
+ * @param chand The channel handler associated with the channel
+ * to be accessed is passed
+ * @param muoper The value passed is TX, RX, GP
+ *
+ * @return Returns 0 on success or ENODEV error if invalid
+ * channel is obtained from the channel handler
+ * Returns -1 if muoper is other than RX, TX or GP, or
+ * if disabling interrupt failed
+ */
+int mxc_mu_unbind(int chand, enum mu_oper muoper)
+{
+ int result = 0;
+ int chnum = chand - BASE_NUM;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ if (reg_allocated[chnum] == chand) {
+ if (mxc_mu_intdisable(chand, muoper) != 0) {
+ return -1;
+ }
+ switch (muoper) {
+ case RX:
+ rxcallback[chnum] = NULL;
+ break;
+ case TX:
+ txcallback[chnum] = NULL;
+ break;
+ case GP:
+ gpcallback[chnum] = NULL;
+ break;
+ default:
+ result = -1;
+ }
+ }
+ return result;
+}
+
+/*!
+ * This function is called by the user and other modules to enable desired
+ * interrupt
+ *
+ * @param chand The channel handler associated with the channel
+ * to be accessed is passed
+ * @param muoper The value passed is TX, RX, GP
+ *
+ * @return Returns 0 on success or ENODEV error if invalid
+ * channel is obtained from the channel handler
+ * Returns -1 if muoper is other than RX, TX or GP
+ */
+int mxc_mu_intenable(int chand, enum mu_oper muoper)
+{
+ unsigned long status = 0;
+ unsigned long flags;
+ int chnum = chand - BASE_NUM;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ spin_lock_irqsave(&mu_lock, flags);
+ status = readl(AS_MUMCR);
+ switch (muoper) {
+ case RX:
+ status |= (read_en[chnum]);
+ break;
+ case TX:
+ status |= (write_en[chnum]);
+ break;
+ case GP:
+ status |= (genp_en[chnum]);
+ break;
+ default:
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return -1;
+ }
+ writel(status, AS_MUMCR);
+ spin_unlock_irqrestore(&mu_lock, flags);
+ return 0;
+}
+
+/*!
+ * This function is called by other modules to bind their
+ * call back functions.
+ *
+ * @param chand The channel handler associated with the channel
+ * to be accessed is passed
+ * @param callback the caller's callback function
+ * @param muoper The value passed is TX, RX, GP
+ *
+ * @return Returns 0 on success or ENODEV error if invalid
+ * channel is obtained from the channel handler
+ * Returns -1 if muoper is other than RX, TX or GP
+ */
+int mxc_mu_bind(int chand, callbackfn callback, enum mu_oper muoper)
+{
+
+ int chnum = chand - BASE_NUM;
+
+ if (chnum < 0 || chnum > 3) {
+ return -ENODEV;
+ }
+ if (suspend_flag == 1) {
+ return -EBUSY;
+ }
+ if (reg_allocated[chnum] == chand) {
+ switch (muoper) {
+ case RX:
+ rxcallback[chnum] = callback;
+ break;
+ case TX:
+ txcallback[chnum] = callback;
+ break;
+ case GP:
+ gpcallback[chnum] = callback;
+ break;
+ default:
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * This function is used to copy data from kernel space buffer
+ * to user space buffer
+ */
+static int mcu_copytouser(int count, int minor, char *userbuff)
+{
+ int bytes_available = 0, actual_bytes = 0, checkval = 0;
+
+ while (1) {
+ bytes_available = CIRC_CNT_TO_END(mu_rbuf[minor].cbuf.head,
+ mu_rbuf[minor].cbuf.tail,
+ MAX_BUF_SIZE);
+ /*
+ * If the number of bytes already in the buffer is greater than
+ * count then read count from buffer else read what is in the buffer
+ */
+ if (count < bytes_available) {
+ bytes_available = count;
+ }
+ if (bytes_available <= 0) {
+ break;
+ }
+ checkval = copy_to_user(userbuff,
+ &mu_rbuf[minor].cbuf.buf[mu_rbuf[minor].
+ cbuf.tail],
+ bytes_available);
+ if (checkval) {
+ break;
+ }
+ mu_rbuf[minor].cbuf.tail = (mu_rbuf[minor].cbuf.tail +
+ bytes_available) & (MAX_BUF_SIZE -
+ 1);
+
+ userbuff += bytes_available;
+ count -= bytes_available;
+ actual_bytes += bytes_available;
+ }
+ /*
+ * Return the number of bytes copied
+ */
+ return actual_bytes;
+}
+
+/*
+ * This function is used to copy data from user space buffer
+ * to kernel space buffer
+ */
+static int mcu_copyfromuser(int count, int minor, const char *userbuff)
+{
+ int space_available = 0, bytes_copied = 0, checkval = 0;
+
+ while (1) {
+ space_available = CIRC_SPACE_TO_END(mu_wbuf[minor].cbuf.head,
+ mu_wbuf[minor].cbuf.tail,
+ MAX_BUF_SIZE);
+ /*
+ * If the space available in the kernel space buffer
+ * is more than required i.e., count then number of bytes
+ * copied from the user space buffer is count else available empty
+ * space for transmission
+ */
+ if (space_available >= count) {
+ space_available = count;
+ }
+ if ((space_available % CH_SIZE) != 0) {
+ space_available = space_available -
+ (space_available % CH_SIZE);
+ }
+ if (space_available < CH_SIZE) {
+ break;
+ }
+ checkval =
+ copy_from_user(&mu_wbuf[minor].cbuf.
+ buf[mu_wbuf[minor].cbuf.head], userbuff,
+ space_available);
+ if (checkval) {
+ break;
+ }
+ mu_wbuf[minor].cbuf.head =
+ (mu_wbuf[minor].cbuf.head +
+ space_available) & (MAX_BUF_SIZE - 1);
+
+ userbuff += space_available;
+ count -= space_available;
+ bytes_copied += space_available;
+ }
+
+ return bytes_copied;
+}
+
+/*!
+ * This function allows the caller to determine whether it can
+ * read from or write to one or more open files without blocking.
+ *
+ * @param filp Pointer to device file structure
+ * @param wait used by poll_wait to indicate a change in the
+ * poll status.
+ *
+ * @return the bit mask describing which operations could
+ * be completed immediately.
+ */
+static unsigned int mxc_mu_poll(struct file *filep, poll_table * wait)
+{
+ unsigned int minor;
+ unsigned int mask = 0;
+
+ minor = MINOR(filep->f_dentry->d_inode->i_rdev);
+
+ poll_wait(filep, &writeq, wait);
+ poll_wait(filep, &readq, wait);
+
+ if (GET_READBYTES(minor)) {
+ mask |= POLLIN | POLLRDNORM;
+ }
+ if (GET_WRITESPACE(minor) >= CH_SIZE) {
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ return mask;
+}
+
+/*!
+ * The read function is available to the user-space to perform
+ * a read operation
+ *
+ * @param filp Pointer to device file structure
+ * @param userbuff User buffer, where read data to be placed.
+ * @param count Size of the requested data transfer
+ * @param offp File position where the user is accessing.
+ *
+ * @return Returns number of bytes read or
+ * Returns -EINVAL if the number of bytes
+ * requested is not a multiple of channel
+ * receive or transmit size or
+ * Returns -EAGAIN for a non-block read when no data
+ * is ready in the buffer or in the register or
+ * Returns -EFAULT if copy_to_user failed
+ */
+static ssize_t mxc_mu_read(struct file *filp,
+ char *userbuff, size_t count, loff_t * offp)
+{
+ int total_bytestocopy = 0;
+ struct inode *inode = filp->f_dentry->d_inode;
+ unsigned int minor = MINOR(inode->i_rdev);
+
+ while (suspend_flag == 1) {
+ swait++;
+ /* Block if the device is suspended */
+ if (wait_event_interruptible(suspendq, (suspend_flag == 0))) {
+ return -ERESTARTSYS;
+ }
+ }
+ /*
+ * If the count value requested is not multiple of channel size then
+ * return error
+ */
+ if (((count % CH_SIZE) != 0) || (count == 0)) {
+ return -EINVAL; /* error */
+ }
+ total_bytestocopy = mcu_copytouser(count, minor, userbuff);
+ /*
+ * Enable the interrupt to read from the register
+ */
+ mxc_mu_intenable(reg_allocated[minor], RX);
+ if (total_bytestocopy != 0) {
+ return total_bytestocopy;
+ }
+ /*
+ * If there is no data in buffer and number of bytes
+ * copied to user space buffer is 0, then block until at least
+ * 4 bytes are read incase of block read else, return immediately
+ * incase of nonblock read
+ */
+ if (!(filp->f_flags & O_NONBLOCK)) {
+ while (GET_READBYTES(minor) == 0) {
+ mu_rbuf[minor].pwait++;
+ /* Block */
+ if (wait_event_interruptible(readq,
+ (GET_READBYTES(minor) !=
+ 0))) {
+ return -ERESTARTSYS;
+ }
+ }
+ } else {
+ /* Return error for Non-Block read */
+ return -EAGAIN;
+ }
+ /*
+ * Some data is now available in the register,
+ * read the data and copy to the user
+ */
+ return (mcu_copytouser(count, minor, userbuff));
+}
+
+/*!
+ * The write function is available to the user-space to perform
+ * a write operation
+ *
+ * @param filp Pointer to device file structure
+ * @param userbuff User buffer, where read data to be placed.
+ * @param count Size of the requested data transfer
+ * @param offp File position where the user is accessing.
+ *
+ * @return Returns number of bytes read or
+ * Returns -EINVAL if the number of bytes
+ * requested is not a multiple of channel
+ * receive or transmit size or
+ * Returns -EAGAIN for a non-block read when no data
+ * is ready in the buffer or in the register or
+ * Returns -EFAULT if copy_to_user failed
+ */
+static ssize_t mxc_mu_write(struct file *filp, const char *userbuff, size_t
+ count, loff_t * offp)
+{
+ int totalbytes_copied = 0;
+ struct inode *inode = filp->f_dentry->d_inode;
+ unsigned int minor = MINOR(inode->i_rdev);
+
+ while (suspend_flag == 1) {
+ swait++;
+ /* Block if the device is suspended */
+ if (wait_event_interruptible(suspendq, (suspend_flag == 0))) {
+ return -ERESTARTSYS;
+ }
+ }
+ /*
+ * If the count value requested is not multiple of channel size then
+ * return error
+ */
+ if (((count % CH_SIZE) != 0) || (count == 0)) {
+ return -EINVAL; /* error */
+ }
+ totalbytes_copied = mcu_copyfromuser(count, minor, userbuff);
+ /* Enable the interrupt before return so that data is transmitted */
+ mxc_mu_intenable(reg_allocated[minor], TX);
+ /*
+ * If number of bytes copied to the kernel space buffer is greater than
+ * 0, return immediately
+ */
+ if (totalbytes_copied != 0) {
+ return totalbytes_copied;
+ }
+ /* If there is no space in buffer and number of bytes
+ * copied from user space buffer is 0, then block until at least
+ * 4 bytes are written incase of block write else, return
+ * immediately incase of nonblock write
+ */
+ if (!(filp->f_flags & O_NONBLOCK)) {
+ while (GET_WRITESPACE(minor) < CH_SIZE) {
+ mu_wbuf[minor].pwait++;
+ if (wait_event_interruptible(writeq,
+ (GET_WRITESPACE(minor) >=
+ CH_SIZE))) {
+ return -ERESTARTSYS;
+ }
+ }
+ } else {
+ /* Return error incase of non-block write */
+ return -EAGAIN;
+ }
+ /*
+ * Some space is now available since some
+ * data has been transmitted. So, copy data from
+ * the user space buffer to kernel space buffer,
+ */
+ return (mcu_copyfromuser(count, minor, userbuff));
+}
+
+/*!
+ * The write callback function is used by this module to perform any
+ * write requests from user-space
+ *
+ * @param chnum channel number whose register has to be accessed
+ */
+static void mxc_mu_writecb(int chnum)
+{
+ char *message;
+
+ /*
+ * While there are more bytes to transfer and
+ * register is empty, continue transmitting
+ */
+ while ((GET_WRITEBYTES(chnum) != 0) && (readl(AS_MUMSR) & wbits[chnum])) {
+ /* Maximum of 4 bytes everytime */
+ message = &mu_wbuf[chnum].cbuf.buf[mu_wbuf[chnum].cbuf.tail];
+ mu_wbuf[chnum].cbuf.tail =
+ (mu_wbuf[chnum].cbuf.tail + CH_SIZE) & (MAX_BUF_SIZE - 1);
+ mxc_mu_mcuwrite(reg_allocated[chnum], message);
+ /* Emptying write buffer */
+ }
+ /*
+ * Enable the interrupt if more data has to be transmitted.
+ * Since interrupts are disabled inside ISR.
+ */
+ if (GET_WRITEBYTES(chnum) != 0) {
+ mxc_mu_intenable(reg_allocated[chnum], TX);
+ }
+ /* Wake up the sleeping process if the buffer gets emptied */
+ if (blockmode[chnum] == 0 && mu_wbuf[chnum].pwait > 0 &&
+ GET_WRITESPACE(chnum) >= CH_SIZE) {
+ mu_wbuf[chnum].pwait--;
+ /* Wake up */
+ wake_up_interruptible(&writeq);
+ }
+}
+
+/*!
+ * The read callback function is used by this module to perform any
+ * read requests from user-space
+ *
+ * @param chnum channel number whose register has to be accessed
+ */
+static void mxc_mu_readcb(int chnum)
+{
+ char message[CH_SIZE];
+ int index;
+
+ /*
+ * While there more bytes can be read and if
+ * buffer is empty
+ */
+ while ((GET_READSPACE(chnum) >= CH_SIZE)
+ && (readl(AS_MUMSR) & rbits[chnum])) {
+ mxc_mu_mcuread(reg_allocated[chnum], message);
+ index = mu_rbuf[chnum].cbuf.head;
+ *(int *)(&(mu_rbuf[chnum].cbuf.buf[index])) = *(int *)message;
+ mu_rbuf[chnum].cbuf.head =
+ (mu_rbuf[chnum].cbuf.head + CH_SIZE) & (MAX_BUF_SIZE - 1);
+ }
+ /*
+ * If no empty space in buffer to store the data that
+ * has been read, then disable the interrupt else
+ * enable the interrupt
+ */
+ (GET_READSPACE(chnum) <
+ CH_SIZE) ? mxc_mu_intdisable(reg_allocated[chnum],
+ RX) :
+mxc_mu_intenable(reg_allocated[chnum], RX);
+ /* Wake up the sleeping process if data is available */
+ if (blockmode[chnum] == 0 && mu_rbuf[chnum].pwait > 0 &&
+ GET_READBYTES(chnum) != 0) {
+ mu_rbuf[chnum].pwait--;
+ /* Wake up */
+ wake_up_interruptible(&readq);
+ }
+}
+
+/*!
+ * The general purpose callback function is used by this module to
+ * perform any general purpose interrupt requests from user-space
+ *
+ * @param chnum channel number whose register has to be accessed
+ */
+static void mxc_mu_genpurposecb(int chnum)
+{
+ writel((readl(AS_MUMSR) | (genp_pend[chnum])), AS_MUMSR);
+}
+
+/*!
+ * This function is called when an ioctl call is made from user space.
+ *
+ * @param inode Pointer to device inode
+ * @param file Pointer to device file structure
+ * @param cmd Ioctl command
+ * @param arg Ioctl argument
+ *
+ * @return The function returns 0 on success and -EINVAL on
+ * failure when cmd is other than what is specified
+ */
+int mxc_mu_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct inode *i_node = file->f_dentry->d_inode;
+ unsigned int minor = MINOR(i_node->i_rdev);
+ int i = 0;
+
+ while (suspend_flag == 1) {
+ swait++;
+ /* Block if the device is suspended */
+ if (wait_event_interruptible(suspendq, (suspend_flag == 0))) {
+ return -ERESTARTSYS;
+ }
+ }
+ switch (cmd) {
+ case SENDINT:
+ writel(genp_req[minor], AS_MUMCR);
+ break;
+ case SENDNMI:
+ mxc_mu_dsp_nmi();
+ break;
+ case SENDDSPRESET:
+ mxc_mu_dsp_reset();
+ for (i = 0; i < 20; i++) ;
+ mxc_mu_dsp_deassert();
+ while (mxc_mu_dsp_reset_status() != 0) ;
+ break;
+ case SENDMURESET:
+ mxc_mu_reset();
+ break;
+ case RXENABLE:
+ mxc_mu_intenable(reg_allocated[minor], RX);
+ break;
+ case TXENABLE:
+ mxc_mu_intenable(reg_allocated[minor], TX);
+ break;
+ case GPENABLE:
+ mxc_mu_intenable(reg_allocated[minor], GP);
+ break;
+ case BYTESWAP_RD:
+ byte_swapping_rd[minor] = true;
+ break;
+ case BYTESWAP_WR:
+ byte_swapping_wr[minor] = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*!
+ * This function is called to close the device. The MCU side of MU interrupts
+ * are disabled. Any allocated buffers are freed
+ *
+ * @param inode Pointer to device inode
+ * @param filp Pointer to device file structure
+ *
+ * @return The function returns 0 on success or
+ * returns -ENODEV if incorrect channel is accessed
+ *
+ */
+static int mxc_mu_close(struct inode *inode, struct file *filp)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+
+ while (suspend_flag == 1) {
+ swait++;
+ /* Block if the device is suspended */
+ if (wait_event_interruptible(suspendq, (suspend_flag == 0))) {
+ return -ERESTARTSYS;
+ }
+ }
+ /* Wait to complete any pending writes */
+ /* Block until the write buffer is empty */
+ while (GET_WRITEBYTES(minor) != 0) {
+ if (wait_event_interruptible_timeout
+ (wait_before_closeq, (GET_WRITEBYTES(minor) == 0),
+ MAX_SCHEDULE_TIMEOUT)) {
+ return -ERESTARTSYS;
+ }
+ }
+ if (mxc_mu_dealloc_channel(reg_allocated[minor]) != 0) {
+ return -ENODEV;
+ }
+ kfree(mu_rbuf[minor].cbuf.buf);
+ return 0;
+}
+
+/*!
+ * This function is called when the MU driver is opened. This function does
+ * the initialization of the device.
+ *
+ * @param inode Pointer to device inode
+ * @param filp Pointer to device file structure
+ *
+ * @return The function returns 0 on success or a negative value if
+ * channel was not allocated or if kmalloc failed
+ */
+static int mxc_mu_open(struct inode *inode, struct file *filp)
+{
+ int chand;
+ unsigned int minor = MINOR(inode->i_rdev);
+
+ while (suspend_flag == 1) {
+ swait++;
+ /* Block if the device is suspended */
+ if (wait_event_interruptible(suspendq, (suspend_flag == 0))) {
+ return -ERESTARTSYS;
+ }
+ }
+ chand = mxc_mu_alloc_channel(minor);
+ if (chand < 0) {
+ return chand;
+ }
+ mxc_mu_bind(reg_allocated[minor], &mxc_mu_readcb, RX);
+ mxc_mu_bind(reg_allocated[minor], &mxc_mu_writecb, TX);
+ mxc_mu_bind(reg_allocated[minor], &mxc_mu_genpurposecb, GP);
+ mxc_mu_intdisable(reg_allocated[minor], TX);
+ /*
+ * Read buffer from 0 to MAX_BUF_SIZE
+ * Write buffer from MAX_BUF_SIZE to 2*MAX_BUF_SIZE
+ */
+ mu_rbuf[minor].cbuf.buf = kmalloc((2 * MAX_BUF_SIZE), GFP_KERNEL);
+ mu_wbuf[minor].cbuf.buf = mu_rbuf[minor].cbuf.buf + MAX_BUF_SIZE;
+ if (mu_rbuf[minor].cbuf.buf == NULL) {
+ reg_allocated[minor] = -1;
+ return -1;
+ }
+ mu_rbuf[minor].cbuf.tail = mu_rbuf[minor].cbuf.head = 0;
+ mu_wbuf[minor].cbuf.tail = mu_wbuf[minor].cbuf.head = 0;
+
+ byte_swapping_rd[minor] = false;
+ byte_swapping_wr[minor] = false;
+
+ mu_rbuf[minor].pwait = mu_rbuf[minor].pwait = 0;
+ if (filp->f_flags & O_NONBLOCK) {
+ blockmode[minor] = 1;
+ } else {
+ blockmode[minor] = 0;
+ }
+ mxc_mu_intenable(reg_allocated[minor], RX);
+ return 0;
+}
+
+/*!
+ * This function is called to put the MU in a low power state.
+ *
+ * @param pdev the device structure used to give information on which MU
+ * device (0 through 3 channels) to suspend
+ * @param state the power state the device is entering
+ *
+ * @return The function always returns 0.
+ */
+static int mxc_mu_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ unsigned long flags;
+
+ suspend_flag = 1;
+ spin_lock_irqsave(&mu_lock, flags);
+ mcr_suspend_state = readl(AS_MUMCR);
+ /* Disabling all receive, transmit, gen. purpose interrupts */
+ writel((mcr_suspend_state & 0x0000FFFF), AS_MUMCR);
+ spin_unlock_irqrestore(&mu_lock, flags);
+
+ /* Turn off clock */
+ clk_disable(mu_clkp);
+
+ return 0;
+}
+
+/*!
+ * This function is called to resume the MU from a low power state.
+ *
+ * @param dev the device structure used to give information on which MU
+ * device (0 through 3 channels) to suspend
+ * @param level the stage in device suspension process that we want the
+ * device to be put in
+ *
+ * @return The function always returns 0.
+ */
+static int mxc_mu_resume(struct platform_device *pdev)
+{
+ unsigned long flags;
+
+ /* Turn on clock */
+ clk_enable(mu_clkp);
+
+ spin_lock_irqsave(&mu_lock, flags);
+ /* Re-enable interrupts or restore state of interrupts */
+ writel(mcr_suspend_state, AS_MUMCR);
+ while (swait > 0) {
+ swait--;
+ wake_up_interruptible(&suspendq);
+ }
+ spin_unlock_irqrestore(&mu_lock, flags);
+ suspend_flag = 0;
+
+ return 0;
+}
+
+static struct file_operations mu_fops = {
+ .owner = THIS_MODULE,
+ .read = mxc_mu_read,
+ .write = mxc_mu_write,
+ .ioctl = mxc_mu_ioctl,
+ .open = mxc_mu_open,
+ .release = mxc_mu_close,
+ .poll = mxc_mu_poll,
+};
+
+/*!
+ * This function is called while loading the module to initiate message
+ * transfers. This function initializes all the registers.
+ */
+static void mxc_mu_load_mod(void)
+{
+ /* Initializing wait queues */
+ init_waitqueue_head(&readq);
+ init_waitqueue_head(&writeq);
+ init_waitqueue_head(&suspendq);
+ init_waitqueue_head(&wait_before_closeq);
+ /* Setting the status and control registers to default values */
+ writel(0xffff, AS_MUMSR);
+ writel(0, AS_MUMCR);
+}
+
+/*!
+ * This function is called while unloading driver.
+ * This resets all the registers and disables
+ * interrupts.
+ */
+static void mxc_mu_unload_mod(void)
+{
+ /* setting the status and control registers to default values */
+ writel(0xffff, AS_MUMSR);
+ writel(0, AS_MUMCR);
+}
+
+/*!
+ * This function is used to load the module and all the interrupt lines
+ * are requested.
+ *
+ * @return Returns an Integer on success
+ */
+static int __init mxc_mu_probe(struct platform_device *pdev)
+{
+ /* Initializing resources allocated */
+ int rx_irq, tx_irq, irq;
+ int max_res;
+ int i;
+ struct class_device *temp_class;
+
+ max_res = pdev->num_resources;
+ rx_irq = platform_get_irq(pdev, 0);
+ tx_irq = platform_get_irq(pdev, 1);
+ if (rx_irq == NO_IRQ || tx_irq == NO_IRQ)
+ return -ENXIO;
+
+ mu_clkp = clk_get(&pdev->dev, "mu_clk");
+ if (IS_ERR(mu_clkp))
+ return -ENXIO;
+ clk_enable(mu_clkp);
+
+ if ((mu_major = register_chrdev(0, "mxc_mu", &mu_fops)) < 0) {
+ printk(KERN_NOTICE
+ "Can't allocate major number for MU Devices.\n");
+ return -EAGAIN;
+ }
+
+ mxc_mu_class = class_create(THIS_MODULE, "mxc_mu");
+
+ if (IS_ERR(mxc_mu_class)) {
+ printk(KERN_ERR "Error creating mu class.\n");
+ unregister_chrdev(mu_major, "mxc_mu");
+ return PTR_ERR(mxc_mu_class);
+ }
+
+ mxc_mu_load_mod();
+
+ for (i = 0; i <= 3; i++) {
+ temp_class =
+ class_device_create(mxc_mu_class, NULL, MKDEV(mu_major, i),
+ NULL, "mxc_mu%u", i);
+
+ if (IS_ERR(temp_class))
+ goto err_out1;
+ }
+
+ if (request_irq(rx_irq, mxc_mu_mcurxhand, 0, "mxc_mu_rx", NULL) != 0)
+ goto err_out1;
+
+ if (request_irq(tx_irq, mxc_mu_mcutxhand, 0, "mxc_mu_tx", NULL) != 0)
+ goto err_out2;
+
+ for (i = 2; i < max_res; i++) {
+ irq = platform_get_irq(pdev, i);
+ if (irq == NO_IRQ)
+ goto err_out3;
+
+ if (request_irq(irq, mxc_mu_mcugphand, 0, "mxc_mu_mcug", NULL)
+ != 0) {
+ printk(KERN_ERR "mxc_mu: request_irq for %d failed\n",
+ irq);
+ goto err_out3;
+ }
+ }
+
+ return 0;
+
+ err_out3:
+ for (--i; i >= 2; i--) {
+ irq = platform_get_irq(pdev, i);
+ free_irq(irq, NULL);
+ }
+ free_irq(tx_irq, NULL);
+
+ err_out2:
+ free_irq(rx_irq, NULL);
+
+ err_out1:
+ for (i = 0; i <= 3; i++) {
+ class_device_destroy(mxc_mu_class, MKDEV(mu_major, i));
+ }
+ mxc_mu_unload_mod();
+ class_destroy(mxc_mu_class);
+ unregister_chrdev(mu_major, "mxc_mu");
+
+ return -1;
+}
+
+/*!
+ * This function is used to unload the module and all interrupt lines are freed
+ */
+static void __exit mxc_mu_remove(struct platform_device *pdev)
+{
+ int irq, max_res, i;
+ max_res = pdev->num_resources;
+
+ /* Freeing the resources allocated */
+ mxc_mu_unload_mod();
+ clk_disable(mu_clkp);
+ clk_put(mu_clkp);
+ for (i = 0; i < max_res; i++) {
+ irq = platform_get_irq(pdev, i);
+ free_irq(irq, NULL);
+ }
+ for (i = 0; i <= 3; i++) {
+ class_device_destroy(mxc_mu_class, MKDEV(mu_major, i));
+ }
+ class_destroy(mxc_mu_class);
+ unregister_chrdev(mu_major, "mxc_mu");
+
+}
+
+/*!
+ * This structure contains pointers to the power management callback functions.
+ */
+static struct platform_driver mxc_mu_driver = {
+ .driver = {
+ .name = "mxc_mu",
+ },
+ .probe = mxc_mu_probe,
+ .remove = __exit_p(mxc_mu_remove),
+ .suspend = mxc_mu_suspend,
+ .resume = mxc_mu_resume,
+};
+
+/*
+ * Main initialization routine
+ */
+static int __init mxc_mu_init(void)
+{
+ /* Register the device driver structure. */
+ pr_info("MXC MU Driver %s\n", DVR_VER);
+ if (platform_driver_register(&mxc_mu_driver) != 0) {
+ printk(KERN_ERR "Driver register failed for mxc_mu_driver\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+/*
+ * Clean up routine
+ */
+static void __exit mxc_mu_cleanup(void)
+{
+ /* Unregister the device structure */
+ platform_driver_unregister(&mxc_mu_driver);
+ printk(KERN_INFO "MU Driver Module Unloaded\n");
+}
+
+module_init(mxc_mu_init);
+module_exit(mxc_mu_cleanup);
+
+EXPORT_SYMBOL(mxc_mu_bind);
+EXPORT_SYMBOL(mxc_mu_unbind);
+EXPORT_SYMBOL(mxc_mu_alloc_channel);
+EXPORT_SYMBOL(mxc_mu_dealloc_channel);
+EXPORT_SYMBOL(mxc_mu_mcuread);
+EXPORT_SYMBOL(mxc_mu_mcuwrite);
+EXPORT_SYMBOL(mxc_mu_intenable);
+EXPORT_SYMBOL(mxc_mu_intdisable);
+EXPORT_SYMBOL(mxc_mu_reset);
+EXPORT_SYMBOL(mxc_mu_dsp_reset);
+EXPORT_SYMBOL(mxc_mu_dsp_deassert);
+EXPORT_SYMBOL(mxc_mu_dsp_nmi);
+EXPORT_SYMBOL(mxc_mu_dsp_reset_status);
+EXPORT_SYMBOL(mxc_mu_dsp_pmode_status);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Messaging Unit Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/mxc_mu_reg.h b/drivers/char/mxc_mu_reg.h
new file mode 100644
index 000000000000..2723e8707ed4
--- /dev/null
+++ b/drivers/char/mxc_mu_reg.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+/*!
+ * @file mxc_mu_reg.h
+ *
+ * @brief This file provides all the Register Addresses, Bit definitions.
+ *
+ * @ingroup MU
+ */
+#ifndef __MXC_MU_REG_H__
+
+#define __MXC_MU_REG_H__
+
+#include <asm/hardware.h>
+
+/*
+ * Address offsets of the MU MCU side transmit registers
+ */
+#define AS_MUMTR0 IO_ADDRESS(MU_BASE_ADDR + 0x000) /* Transmit register 0 */
+#define AS_MUMTR1 IO_ADDRESS(MU_BASE_ADDR + 0x004) /* Transmit register 1 */
+#define AS_MUMTR2 IO_ADDRESS(MU_BASE_ADDR + 0x008) /* Transmit register 2 */
+#define AS_MUMTR3 IO_ADDRESS(MU_BASE_ADDR + 0x00c) /* Transmit register 3 */
+
+/*
+ * Address offsets of the MU MCU side receive registers
+ */
+#define AS_MUMRR0 IO_ADDRESS(MU_BASE_ADDR + 0x010) /* Receive register 0 */
+#define AS_MUMRR1 IO_ADDRESS(MU_BASE_ADDR + 0x014) /* Receive register 1 */
+#define AS_MUMRR2 IO_ADDRESS(MU_BASE_ADDR + 0x018) /* Receive register 2 */
+#define AS_MUMRR3 IO_ADDRESS(MU_BASE_ADDR + 0x01c) /* Receive register 3 */
+
+/*
+ * Address offset of the MU MCU side status register
+ */
+#define AS_MUMSR IO_ADDRESS(MU_BASE_ADDR + 0x020) /* Status register */
+
+/*
+ * Address offset of the MU MCU side control register
+ */
+#define AS_MUMCR IO_ADDRESS(MU_BASE_ADDR + 0x024) /* Control register */
+
+/*
+ * Bit definitions of MSR
+ */
+#define AS_MUMSR_MGIP0 0x80000000
+#define AS_MUMSR_MGIP1 0x40000000
+#define AS_MUMSR_MGIP2 0x20000000
+#define AS_MUMSR_MGIP3 0x10000000
+#define AS_MUMSR_MRF0 0x08000000
+#define AS_MUMSR_MRF1 0x04000000
+#define AS_MUMSR_MRF2 0x02000000
+#define AS_MUMSR_MRF3 0x01000000
+#define AS_MUMSR_MTE0 0x00800000
+#define AS_MUMSR_MTE1 0x00400000
+#define AS_MUMSR_MTE2 0x00200000
+#define AS_MUMSR_MTE3 0x00100000
+#define AS_MUMSR_MFUP 0x00000100
+#define AS_MUMSR_DRS 0x00000080
+#define AS_MUMSR_DPM1 0x00000040
+#define AS_MUMSR_DPM0 0x00000020
+#define AS_MUMSR_MEP 0x00000010
+#define AS_MUMSR_MNMIC 0x00000008
+#define AS_MUMSR_MF2 0x00000004
+#define AS_MUMSR_MF1 0x00000002
+#define AS_MUMSR_MF0 0x00000001
+
+/*
+ * Bit definitions of MCR
+ */
+#define AS_MUMCR_MGIE0 0x80000000
+#define AS_MUMCR_MGIE1 0x40000000
+#define AS_MUMCR_MGIE2 0x20000000
+#define AS_MUMCR_MGIE3 0x10000000
+#define AS_MUMCR_MRIE0 0x08000000
+#define AS_MUMCR_MRIE1 0x04000000
+#define AS_MUMCR_MRIE2 0x02000000
+#define AS_MUMCR_MRIE3 0x01000000
+#define AS_MUMCR_MTIE0 0x00800000
+#define AS_MUMCR_MTIE1 0x00400000
+#define AS_MUMCR_MTIE2 0x00200000
+#define AS_MUMCR_MTIE3 0x00100000
+#define AS_MUMCR_MGIR0 0x00080000
+#define AS_MUMCR_MGIR1 0x00040000
+#define AS_MUMCR_MGIR2 0x00020000
+#define AS_MUMCR_MGIR3 0x00010000
+#define AS_MUMCR_MMUR 0x00000020
+#define AS_MUMCR_DHR 0x00000010
+#define AS_MUMCR_DNMI 0x00000008
+#define AS_MUMCR_MDF2 0x00000004
+#define AS_MUMCR_MDF1 0x00000002
+#define AS_MUMCR_MDF0 0x00000001
+
+#define BASE_NUM 1222
+
+#endif /* __MXC_MU_REG_H__ */
diff --git a/drivers/char/mxc_sdma_tty.c b/drivers/char/mxc_sdma_tty.c
new file mode 100644
index 000000000000..d0b59e4ff69e
--- /dev/null
+++ b/drivers/char/mxc_sdma_tty.c
@@ -0,0 +1,744 @@
+/*
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+/*!
+ * @file mxc_sdma_tty.c
+ * @brief This file contains functions for SDMA TTY driver
+ *
+ * SDMA TTY driver is used for moving data between MCU and DSP using line discipline API
+ *
+ * @ingroup IPC
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <asm/dma.h>
+#include <asm/mach/dma.h>
+#include <linux/interrupt.h>
+#include <asm/irq.h>
+#include <asm/hardware.h>
+#include <asm/semaphore.h>
+#include <asm/system.h> /* save/local_irq_restore */
+#include <asm/uaccess.h> /* For copy_from_user */
+#include <linux/sched.h> /* For schedule */
+#include <linux/device.h>
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/circ_buf.h>
+
+#include <asm/arch/mxc_sdma_tty.h>
+
+#define DEBUG 0
+
+#if DEBUG
+#define DPRINTK(fmt, args...) printk("%s: " fmt, __FUNCTION__ , ## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+#define SDMA_TTY_NORMAL_MAJOR 0
+
+#define WRITE_ROOM 512
+
+/*!
+ * This define returns the number of read SDMA channel
+ */
+#define READ_CHANNEL(line) (line < IPC_NB_CH_BIDIR) ? 2 * line + 1 : \
+ (line < (IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU)) ? \
+ (line + 3) : -1
+
+/*!
+ * This define returns the number of write SDMA channel
+ */
+#define WRITE_CHANNEL(line) (line < IPC_NB_CH_BIDIR) ? 2 * line + 2 : -1
+
+/*!
+ * This define returns 1 if the device is unidirectional
+ * DSP to MCU
+ */
+#define DSPMCU_DEVICE(line) line < (IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU) && \
+ line >= IPC_NB_CH_BIDIR
+
+/*!
+ * This holds tty driver definitions
+ */
+static struct tty_driver *sdma_tty_driver;
+
+/*!
+ * This holds the transmit tasklet
+ */
+static struct tasklet_struct sdma_tty_tasklet;
+
+static struct class *sdma_tty_class;
+
+/*!
+ * Structure containing sdma tty line status
+ */
+typedef struct {
+ struct tty_struct *tty; /*!< pointer to tty struct of the line */
+ int loopback_mode; /*!< Loopback mode: 0 - disable, 1 - enable */
+ struct circ_buf write_buf; /*!< Write buffer */
+ char *write_buf_phys; /*!< Write buffer dma pointer */
+ char *read_buf; /*!< Read buffer */
+ char *read_buf_phys; /*!< Read buffer dma pointer */
+ int chars_in_buffer; /*!< Number of characters in write buffer */
+ int sending; /*!< Sending flag */
+ int sending_count; /*!< Number of characters sent
+ in last write operation */
+ int f_mode; /*!< File mode (read/write) */
+} sdma_tty_struct;
+
+static sdma_tty_struct sdma_tty_data[IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU];
+
+static void sdma_tty_write_tasklet(unsigned long arg)
+{
+ int line, tx_num;
+ struct tty_struct *tty;
+ dma_request_t writechnl_request;
+ struct circ_buf *circ;
+
+ tty = (struct tty_struct *)arg;
+ line = tty->index;
+ DPRINTK("SDMA %s on line %d\n", __FUNCTION__, line);
+
+ if (sdma_tty_data[line].chars_in_buffer > 0) {
+ circ = &sdma_tty_data[line].write_buf;
+ tx_num = CIRC_CNT_TO_END(circ->head, circ->tail, WRITE_ROOM);
+ writechnl_request.sourceAddr =
+ sdma_tty_data[line].write_buf_phys + circ->tail;
+ writechnl_request.count = tx_num;
+ mxc_dma_set_config(WRITE_CHANNEL(line), &writechnl_request, 0);
+ mxc_dma_start(WRITE_CHANNEL(line));
+ } else {
+ sdma_tty_data[line].sending = 0;
+ }
+}
+
+/*!
+ * This function called on SDMA write channel interrupt.
+ *
+ * @param arg number of SDMA channel
+ */
+static void sdma_tty_write_callback(void *arg)
+{
+ dma_request_t sdma_read_request, sdma_write_request;
+ int line, count = 0;
+ struct tty_struct *tty;
+ struct circ_buf *circ;
+
+ tty = (struct tty_struct *)arg;
+ line = tty->index;
+
+ mxc_dma_get_config(WRITE_CHANNEL(line), &sdma_write_request, 0);
+
+ count = sdma_write_request.count;
+ circ = &sdma_tty_data[line].write_buf;
+
+ DPRINTK("SDMA %s on line %d count %d\n", __FUNCTION__, line, count);
+
+ if (sdma_tty_data[line].loopback_mode) {
+ sdma_tty_data[line].sending_count = count;
+
+ mxc_dma_get_config(READ_CHANNEL(line), &sdma_read_request, 0);
+ sdma_read_request.count = count;
+ sdma_read_request.destAddr = sdma_tty_data[line].read_buf_phys;
+
+ if (count <= tty_buffer_request_room(tty, count)) {
+ tty_flip_buffer_push(tty);
+ }
+
+ mxc_dma_set_config(READ_CHANNEL(line), &sdma_read_request, 0);
+ mxc_dma_start(READ_CHANNEL(line));
+ }
+ sdma_tty_data[line].chars_in_buffer -= count;
+ circ->tail = (circ->tail + count) & (WRITE_ROOM - 1);
+ DPRINTK("SDMA %s on head %x tail %x\n", __FUNCTION__, circ->head,
+ circ->tail);
+ wake_up_interruptible(&tty->write_wait);
+
+ if (sdma_tty_data[line].chars_in_buffer > 0) {
+ tasklet_schedule(&sdma_tty_tasklet);
+ } else {
+ sdma_tty_data[line].sending = 0;
+ }
+}
+
+/*!
+ * This function called on SDMA read channel interrupt.
+ *
+ * @param arg number of SDMA channel
+ */
+static void sdma_tty_read_callback(void *arg)
+{
+ dma_request_t read_request;
+ struct tty_struct *tty;
+ int line;
+ int count;
+
+ tty = (struct tty_struct *)arg;
+ line = tty->index;
+
+ if (sdma_tty_data[line].loopback_mode &&
+ sdma_tty_data[line].sending_count <= 0) {
+ return;
+ }
+
+ mxc_dma_get_config(READ_CHANNEL(line), &read_request, 0);
+
+ if (read_request.bd_done == 1) {
+ /* BD is not done yet */
+ return;
+ }
+
+ count = read_request.count;
+ /* Check for space availability in the TTY Flip buffer */
+ count = tty_buffer_request_room(tty, count);
+ if (!count) {
+ goto drop_data;
+ }
+
+ /* Set the real_raw flag to prevent processing on the received chars */
+ tty->real_raw = 1;
+ tty_insert_flip_string(tty, sdma_tty_data[line].read_buf, count);
+ tty_flip_buffer_push(tty);
+
+ wake_up_interruptible(&tty->read_wait);
+
+ drop_data:
+ if (!sdma_tty_data[line].loopback_mode) {
+ read_request.count = WRITE_ROOM;
+ read_request.destAddr = sdma_tty_data[line].read_buf_phys;
+
+ mxc_dma_set_config(READ_CHANNEL(line), &read_request, 0);
+ mxc_dma_start(READ_CHANNEL(line));
+ } else {
+ sdma_tty_data[line].sending_count = 0;
+ }
+
+ DPRINTK("Exit\n");
+}
+
+/*!
+ * This function changes loopback mode
+ *
+ * @param tty pointer to current tty line structure
+ * @param mode loopback mode: 0 - disable, 1 enable
+ * @param line tty line
+ * @return 0 on success, error code on fail
+ */
+static int sdma_tty_change_mode(struct tty_struct *tty, int mode, int line)
+{
+ dma_channel_params read_sdma_params, write_sdma_params;
+ dma_request_t sdma_read_request;
+ int res = 0;
+
+ if (!(mode == 0 || mode == 1)) {
+ printk(KERN_WARNING "Illegal loopback mode value\n");
+ return -EINVAL;
+ }
+
+ if ((mode == 1) && (!((sdma_tty_data[line].f_mode & FMODE_READ) &&
+ (sdma_tty_data[line].f_mode & FMODE_WRITE)))) {
+ printk(KERN_WARNING
+ "Loopback mode requires channel to be have read and write modes\n");
+ return -EINVAL;
+ }
+
+ /* Mode not changed */
+ if (sdma_tty_data[line].loopback_mode == mode) {
+ return 0;
+ }
+
+ sdma_tty_data[line].loopback_mode = mode;
+
+ if (sdma_tty_data[line].f_mode & FMODE_READ) {
+ mxc_dma_stop(READ_CHANNEL(line));
+ }
+ if (sdma_tty_data[line].f_mode & FMODE_WRITE) {
+ mxc_dma_stop(WRITE_CHANNEL(line));
+ }
+
+ if (sdma_tty_data[line].f_mode & FMODE_READ) {
+ /* SDMA read channel setup */
+ memset(&read_sdma_params, 0, sizeof(dma_channel_params));
+ read_sdma_params.peripheral_type = DSP;
+ read_sdma_params.transfer_type =
+ (mode == 0) ? dsp_2_emi : dsp_2_emi_loop;
+ read_sdma_params.event_id = 0;
+ read_sdma_params.callback = sdma_tty_read_callback;
+ read_sdma_params.arg = tty;
+ res =
+ mxc_dma_setup_channel(READ_CHANNEL(line),
+ &read_sdma_params);
+ if (res < 0) {
+ return res;
+ }
+ tty_flip_buffer_push(tty);
+
+ /* SDMA read request setup */
+ memset(&sdma_read_request, 0, sizeof(dma_request_t));
+ sdma_read_request.destAddr = sdma_tty_data[line].read_buf_phys;
+
+ mxc_dma_set_config(READ_CHANNEL(line), &sdma_read_request, 0);
+
+ if (!sdma_tty_data[line].loopback_mode) {
+ mxc_dma_start(READ_CHANNEL(line));
+ }
+
+ }
+
+ if (sdma_tty_data[line].f_mode & FMODE_WRITE) {
+ /* Write SDMA channel setup */
+ memset(&write_sdma_params, 0, sizeof(dma_channel_params));
+ write_sdma_params.peripheral_type = DSP;
+ write_sdma_params.transfer_type =
+ (mode == 0) ? emi_2_dsp : emi_2_dsp_loop;
+ write_sdma_params.event_id = 0;
+ write_sdma_params.callback = sdma_tty_write_callback;
+ write_sdma_params.arg = tty;
+ res =
+ mxc_dma_setup_channel(WRITE_CHANNEL(line),
+ &write_sdma_params);
+ if (res < 0) {
+ return res;
+ }
+ }
+
+ if (mode) {
+ /* Echo off to avoid infinite loop */
+ tty->termios->c_lflag = 0;
+ }
+
+ return 0;
+}
+
+/*!
+ * This function opens tty line
+ *
+ * @param tty pointer to current tty line structure
+ * @param filp pointer to file structure
+ * @return 0 on success, error code on fail
+ */
+static int sdma_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ int channels[2];
+ int line;
+ int res;
+
+ line = tty->index;
+
+ if (DSPMCU_DEVICE(line) && (filp->f_mode & FMODE_WRITE)) {
+ printk(KERN_WARNING
+ "Error: Cannot open this channel for writing.\n");
+ return -EINVAL;
+ }
+
+ memset(&sdma_tty_data[line], 0, sizeof(sdma_tty_struct));
+
+ channels[0] = READ_CHANNEL(line);
+ channels[1] = WRITE_CHANNEL(line);
+
+ sdma_tty_data[line].tty = tty;
+ sdma_tty_data[line].f_mode = filp->f_mode;
+
+ if (sdma_tty_data[line].f_mode & FMODE_READ) {
+ res = mxc_request_dma(channels, "SDMA TTY");
+ if (res < 0) { // For read
+ printk(KERN_WARNING
+ "Error: SDMA DSP read channel busy\n");
+ goto read_dma_req_failed;
+ }
+ }
+ if (sdma_tty_data[line].f_mode & FMODE_WRITE) {
+ res = mxc_request_dma(channels + 1, "SDMA TTY");
+ if (res < 0) { // For write
+ printk(KERN_WARNING
+ "Error: SDMA DSP write channel busy\n");
+ goto write_dma_req_failed;
+ }
+ }
+
+ sdma_tty_data[line].chars_in_buffer = 0;
+ sdma_tty_data[line].sending = 0;
+ sdma_tty_data[line].loopback_mode = -1;
+
+ if (sdma_tty_data[line].f_mode & FMODE_WRITE) {
+ sdma_tty_data[line].write_buf.buf = sdma_malloc(WRITE_ROOM);
+ sdma_tty_data[line].write_buf.head = 0;
+ sdma_tty_data[line].write_buf.tail = 0;
+ sdma_tty_data[line].write_buf_phys =
+ (char *)sdma_virt_to_phys(sdma_tty_data[line].write_buf.
+ buf);
+ }
+ if (sdma_tty_data[line].f_mode & FMODE_READ) {
+ sdma_tty_data[line].read_buf = sdma_malloc(WRITE_ROOM);
+ sdma_tty_data[line].read_buf_phys =
+ (char *)sdma_virt_to_phys(sdma_tty_data[line].read_buf);
+ }
+
+ tty->low_latency = 1; // High rate
+
+ res = sdma_tty_change_mode(tty, DEFAULT_LOOPBACK_MODE, line);
+ if (res < 0) {
+ goto change_mode_failed;
+ }
+ tasklet_init(&sdma_tty_tasklet, sdma_tty_write_tasklet,
+ (unsigned long)tty);
+ DPRINTK("SDMA %s raw = %d real_raw = %d\n", __FUNCTION__, tty->raw,
+ tty->real_raw);
+
+ return 0;
+
+ change_mode_failed:
+ if (sdma_tty_data[line].f_mode & FMODE_WRITE) {
+ sdma_free(sdma_tty_data[line].write_buf.buf);
+ }
+ if (sdma_tty_data[line].f_mode & FMODE_READ) {
+ sdma_free(sdma_tty_data[line].read_buf);
+ }
+ write_dma_req_failed:
+ if (sdma_tty_data[line].f_mode & FMODE_READ) {
+ mxc_free_dma(READ_CHANNEL(line));
+ }
+ read_dma_req_failed:
+ return res;
+}
+
+/*!
+ * This function closes tty line
+ *
+ * @param tty pointer to current tty line structure
+ * @param filp pointer to file structure
+ */
+static void sdma_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ int line;
+
+ line = tty->index;
+
+ if (sdma_tty_data[line].f_mode & FMODE_READ) {
+ mxc_free_dma(READ_CHANNEL(line));
+ sdma_free(sdma_tty_data[line].read_buf);
+ }
+
+ if (sdma_tty_data[line].f_mode & FMODE_WRITE) {
+ mxc_free_dma(WRITE_CHANNEL(line));
+ sdma_free(sdma_tty_data[line].write_buf.buf);
+ sdma_tty_data[line].write_buf.buf = NULL;
+ }
+}
+
+/*!
+ * This function performs ioctl commands
+ *
+ * The driver supports 2 ioctls: TIOCLPBACK and TCGETS
+ * Other ioctls are supported by upper tty layers.
+ *
+ * @param tty pointer to current tty line structure
+ * @param file pointer to file structure
+ * @param cmd command
+ * @param arg pointer to argument
+ * @return 0 on success, error code on fail
+ */
+static int sdma_tty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int res;
+ int line;
+
+ line = tty->index;
+
+ switch (cmd) {
+ case TIOCLPBACK:
+ res = sdma_tty_change_mode(tty, *((int *)arg), line);
+
+ return res;
+ break;
+ case TCGETS:
+ if (copy_to_user((struct termios *)arg,
+ tty->termios, sizeof(struct termios))) {
+ return -EFAULT;
+ }
+ return (0);
+ break;
+ default:
+ /* printk(KERN_WARNING "Unsupported %d ioctl\n", cmd); */
+ break;
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+/*!
+ * This function starts SDMA for sending data from write buffer
+ *
+ * @param tty pointer to current tty line structure
+ * @param count number of characters
+ * @return number of characters copied to write buffer
+ */
+static ssize_t sdma_tty_write_to_device(struct tty_struct *tty, int count)
+{
+ dma_request_t sdma_write_request;
+ int line;
+
+ line = tty->index;
+
+ DPRINTK("SDMA %s on line %d %d\n", __FUNCTION__, line, count);
+
+ memset(&sdma_write_request, 0, sizeof(dma_request_t));
+
+ sdma_tty_data[line].sending_count = count;
+
+ sdma_write_request.sourceAddr = sdma_tty_data[line].write_buf_phys +
+ sdma_tty_data[line].write_buf.tail;
+ sdma_write_request.count = count;
+
+ mxc_dma_set_config(WRITE_CHANNEL(line), &sdma_write_request, 0);
+
+ mxc_dma_start(WRITE_CHANNEL(line));
+
+ return count;
+}
+
+/*!
+ * This function flushes chars from write buffer
+ *
+ * @param tty pointer to current tty line structure
+ */
+static void sdma_tty_flush_chars(struct tty_struct *tty)
+{
+ int line, count = 0;
+ struct circ_buf *circ;
+
+ line = tty->index;
+ circ = &sdma_tty_data[line].write_buf;
+
+ if (sdma_tty_data[line].sending == 1) {
+ return;
+ }
+
+ if (sdma_tty_data[line].chars_in_buffer == 0) {
+ return;
+ }
+
+ sdma_tty_data[line].sending = 1;
+ count = CIRC_CNT_TO_END(circ->head, circ->tail, WRITE_ROOM);
+ sdma_tty_write_to_device(tty, count);
+}
+
+/*!
+ * This function writes characters to write buffer
+ *
+ * @param tty pointer to current tty line structure
+ * @param buf pointer to buffer
+ * @param count number of characters
+ * @return number of characters copied to write buffer
+ */
+static ssize_t sdma_tty_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ int line;
+ int write_room, ret = 0;
+ struct circ_buf *circ;
+ line = tty->index;
+
+ DPRINTK("SDMA %s on line %d %d\n", __FUNCTION__, line, count);
+ circ = &sdma_tty_data[line].write_buf;
+
+ if (circ->head == circ->tail) {
+ circ->head = 0;
+ circ->tail = 0;
+ }
+
+ while (1) {
+ write_room =
+ CIRC_SPACE_TO_END(circ->head, circ->tail, WRITE_ROOM);
+ if (count < write_room) {
+ write_room = count;
+ }
+ if (write_room <= 0) {
+ break;
+ }
+
+ memcpy(circ->buf + circ->head, buf, write_room);
+ circ->head = (circ->head + write_room) & (WRITE_ROOM - 1);
+ buf += write_room;
+ count -= write_room;
+ ret += write_room;
+ }
+ DPRINTK("SDMA %s on head %x tail %x\n", __FUNCTION__, circ->head,
+ circ->tail);
+ sdma_tty_data[line].chars_in_buffer += ret;
+ sdma_tty_flush_chars(tty);
+
+ return ret;
+}
+
+/*!
+ * This function returns the number of characters in write buffer
+ *
+ * @param tty pointer to current tty line structure
+ * @return number of characters in write buffer
+ */
+static int sdma_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ int res;
+ int line;
+
+ line = tty->index;
+
+ res = sdma_tty_data[line].chars_in_buffer;
+
+ return res;
+}
+
+/*!
+ * This function returns how much room the tty driver has available in the write buffer
+ *
+ * @param tty pointer to current tty line structure
+ * @return how much room the tty driver has available in the write buffer
+ */
+static int sdma_tty_write_room(struct tty_struct *tty)
+{
+ int res;
+ int line;
+ struct circ_buf *circ;
+
+ line = tty->index;
+ circ = &sdma_tty_data[line].write_buf;
+
+ res = CIRC_SPACE(circ->head, circ->tail, WRITE_ROOM);
+
+ return res;
+}
+
+/*!
+ * TTY operation structure
+ */
+static struct tty_operations sdma_tty_ops = {
+ .open = sdma_tty_open,
+ .close = sdma_tty_close,
+ .write_room = sdma_tty_write_room,
+ .write = sdma_tty_write,
+ .flush_chars = sdma_tty_flush_chars,
+ .ioctl = sdma_tty_ioctl,
+ .chars_in_buffer = sdma_tty_chars_in_buffer,
+};
+
+/*!
+ * This function registers the tty driver
+ */
+static int __init sdma_tty_init(void)
+{
+ int error;
+ int dev_id, dev_number;
+ int dev_mode;
+ struct class_device *temp_class;
+
+ dev_number = IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU;
+
+ sdma_tty_driver = alloc_tty_driver(dev_number);
+
+ if (!sdma_tty_driver) {
+ return -ENOMEM;
+ }
+
+ sdma_tty_driver->owner = THIS_MODULE;
+ sdma_tty_driver->name = "mxc_sdma_tty";
+ sdma_tty_driver->name_base = 0;
+ sdma_tty_driver->major = SDMA_TTY_NORMAL_MAJOR;
+ sdma_tty_driver->minor_start = 0;
+ sdma_tty_driver->num = dev_number;
+ sdma_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ sdma_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ sdma_tty_driver->init_termios = tty_std_termios;
+ sdma_tty_driver->flags = TTY_DRIVER_DYNAMIC_DEV;
+ sdma_tty_driver->flags |= TTY_DRIVER_REAL_RAW; /* lpj */
+ tty_set_operations(sdma_tty_driver, &sdma_tty_ops);
+
+ if ((error = tty_register_driver(sdma_tty_driver))) {
+ printk(KERN_ERR "SDMA_TTY: Couldn't register SDMA_TTY driver,\
+ error = %d\n", error);
+ put_tty_driver(sdma_tty_driver);
+
+ return error;
+ }
+
+ sdma_tty_class = class_create(THIS_MODULE, "sdma");
+ if (IS_ERR(sdma_tty_class)) {
+ printk(KERN_ERR "Error creating sdma tty class.\n");
+ return PTR_ERR(sdma_tty_class);
+ }
+
+ dev_mode = S_IFCHR | S_IRUGO | S_IWUGO;
+ for (dev_id = 0; dev_id < dev_number; dev_id++) {
+ temp_class =
+ class_device_create(sdma_tty_class, NULL,
+ MKDEV(sdma_tty_driver->major,
+ dev_id), NULL, "sdma%u", dev_id);
+ if (IS_ERR(temp_class))
+ goto err_out;
+
+ if (dev_id == IPC_NB_CH_BIDIR) {
+ dev_mode = S_IFCHR | S_IRUGO;
+ }
+ if (dev_id == IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU) {
+ dev_mode = S_IFCHR | S_IWUGO;
+ }
+ }
+
+ printk("SDMA TTY Driver initialized\n");
+ return error;
+
+ err_out:
+ printk(KERN_ERR "Error creating sdma class or class device.\n");
+ for (dev_id = 0; dev_id < dev_number; dev_id++) {
+ class_device_destroy(sdma_tty_class, MKDEV
+ (sdma_tty_driver->major, dev_id));
+ }
+ class_destroy(sdma_tty_class);
+ return -1;
+}
+
+/*!
+ * This function unregisters the tty driver
+ */
+static void __exit sdma_tty_exit(void)
+{
+
+ int dev_id, dev_number;
+
+ dev_number = IPC_NB_CH_BIDIR + IPC_NB_CH_DSPMCU;
+
+ if (!sdma_tty_driver) {
+ for (dev_id = 0; dev_id < dev_number; dev_id++) {
+ class_device_destroy(sdma_tty_class,
+ MKDEV(sdma_tty_driver->major,
+ dev_id));
+ }
+ class_destroy(sdma_tty_class);
+ }
+
+ tty_unregister_driver(sdma_tty_driver);
+ put_tty_driver(sdma_tty_driver);
+}
+
+module_init(sdma_tty_init);
+module_exit(sdma_tty_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC SDMA TTY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/watchdog/mxc_wdt.c b/drivers/char/watchdog/mxc_wdt.c
new file mode 100644
index 000000000000..126b14357d5a
--- /dev/null
+++ b/drivers/char/watchdog/mxc_wdt.c
@@ -0,0 +1,385 @@
+/*
+ * linux/drivers/char/watchdog/mxc_wdt.c
+ *
+ * Watchdog driver for FSL MXC. It is based on omap1610_wdt.c
+ *
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ * 2005 (c) MontaVista Software, Inc. All Rights Reserved.
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History:
+ *
+ * 20051207: <AKuster@mvista.com>
+ * Full rewrite based on
+ * linux-2.6.15-rc5/drivers/char/watchdog/omap_wdt.c
+ * Add platform resource support
+ *
+ */
+
+/*!
+ * @defgroup WDOG Watchdog Timer (WDOG) Driver
+ */
+/*!
+ * @file mxc_wdt.c
+ *
+ * @brief Watchdog timer driver
+ *
+ * @ingroup WDOG
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/smp_lock.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/clk.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/hardware.h>
+#include <asm/irq.h>
+#include <asm/bitops.h>
+
+#include <asm/hardware.h>
+#include "mxc_wdt.h"
+#define DVR_VER "2.0"
+
+#define WDOG_SEC_TO_COUNT(s) ((s * 2) << 8)
+#define WDOG_COUNT_TO_SEC(c) ((c >> 8) / 2)
+
+static u32 wdt_base_reg;
+static int mxc_wdt_users;
+static struct clk *mxc_wdt_clk;
+
+static unsigned timer_margin = TIMER_MARGIN_DEFAULT;
+module_param(timer_margin, uint, 0);
+MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)");
+
+static unsigned dev_num = 0;
+
+static void mxc_wdt_ping(u32 base)
+{
+ /* issue the service sequence instructions */
+ __raw_writew(WDT_MAGIC_1, base + MXC_WDT_WSR);
+ __raw_writew(WDT_MAGIC_2, base + MXC_WDT_WSR);
+}
+
+static void mxc_wdt_config(u32 base)
+{
+ u16 val;
+
+ val = __raw_readw(base + MXC_WDT_WCR);
+ val |= 0xFF00 | WCR_WOE_BIT | WCR_WDA_BIT | WCR_SRS_BIT;
+ /* enable suspend WDT */
+ val |= WCR_WDZST_BIT | WCR_WDBG_BIT;
+ /* generate reset if wdog times out */
+ val &= ~WCR_WRE_BIT;
+
+ __raw_writew(val, base + MXC_WDT_WCR);
+}
+
+static void mxc_wdt_enable(u32 base)
+{
+ u16 val;
+
+ val = __raw_readw(base + MXC_WDT_WCR);
+ val |= WCR_WDE_BIT;
+ __raw_writew(val, base + MXC_WDT_WCR);
+}
+
+static void mxc_wdt_disable(u32 base)
+{
+ /* disable not supported by this chip */
+}
+
+static void mxc_wdt_adjust_timeout(unsigned new_timeout)
+{
+ if (new_timeout < TIMER_MARGIN_MIN)
+ new_timeout = TIMER_MARGIN_DEFAULT;
+ if (new_timeout > TIMER_MARGIN_MAX)
+ new_timeout = TIMER_MARGIN_MAX;
+ timer_margin = new_timeout;
+}
+
+static u16 mxc_wdt_get_timeout(u32 base)
+{
+ u16 val;
+
+ val = __raw_readw(base + MXC_WDT_WCR);
+ return WDOG_COUNT_TO_SEC(val);
+}
+
+static u16 mxc_wdt_get_bootreason(u32 base)
+{
+ u16 val;
+
+ val = __raw_readw(base + MXC_WDT_WRSR);
+ return val;
+}
+
+static void mxc_wdt_set_timeout(u32 base)
+{
+ u16 val;
+ val = __raw_readw(base + MXC_WDT_WCR);
+ val = (val & 0x00FF) | WDOG_SEC_TO_COUNT(timer_margin);
+ __raw_writew(val, base + MXC_WDT_WCR);
+ val = __raw_readw(base + MXC_WDT_WCR);
+ timer_margin = WDOG_COUNT_TO_SEC(val);
+}
+
+/*
+ * Allow only one task to hold it open
+ */
+
+static int mxc_wdt_open(struct inode *inode, struct file *file)
+{
+
+ if (test_and_set_bit(1, (unsigned long *)&mxc_wdt_users))
+ return -EBUSY;
+
+ mxc_wdt_config(wdt_base_reg);
+ mxc_wdt_set_timeout(wdt_base_reg);
+ mxc_wdt_enable(wdt_base_reg);
+ mxc_wdt_ping(wdt_base_reg);
+
+ return 0;
+}
+
+static int mxc_wdt_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer unless NOWAYOUT is defined.
+ */
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+ mxc_wdt_disable(wdt_base_reg);
+
+#else
+ printk(KERN_CRIT "mxc_wdt: Unexpected close, not stopping!\n");
+#endif
+ mxc_wdt_users = 0;
+ return 0;
+}
+
+static ssize_t
+mxc_wdt_write(struct file *file, const char __user * data,
+ size_t len, loff_t * ppos)
+{
+ /* Refresh LOAD_TIME. */
+ if (len)
+ mxc_wdt_ping(wdt_base_reg);
+ return len;
+}
+
+static int
+mxc_wdt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int new_margin;
+ int bootr;
+
+ static struct watchdog_info ident = {
+ .identity = "MXC Watchdog",
+ .options = WDIOF_SETTIMEOUT,
+ .firmware_version = 0,
+ };
+
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ return copy_to_user((struct watchdog_info __user *)arg, &ident,
+ sizeof(ident));
+ case WDIOC_GETSTATUS:
+ return put_user(0, (int __user *)arg);
+ case WDIOC_GETBOOTSTATUS:
+ bootr = mxc_wdt_get_bootreason(wdt_base_reg);
+ return put_user(bootr, (int __user *)arg);
+ case WDIOC_KEEPALIVE:
+ mxc_wdt_ping(wdt_base_reg);
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, (int __user *)arg))
+ return -EFAULT;
+
+ mxc_wdt_adjust_timeout(new_margin);
+ mxc_wdt_disable(wdt_base_reg);
+ mxc_wdt_set_timeout(wdt_base_reg);
+ mxc_wdt_enable(wdt_base_reg);
+ mxc_wdt_ping(wdt_base_reg);
+ return 0;
+
+ case WDIOC_GETTIMEOUT:
+ mxc_wdt_ping(wdt_base_reg);
+ new_margin = mxc_wdt_get_timeout(wdt_base_reg);
+ return put_user(new_margin, (int __user *)arg);
+ }
+}
+
+static struct file_operations mxc_wdt_fops = {
+ .owner = THIS_MODULE,
+ .write = mxc_wdt_write,
+ .ioctl = mxc_wdt_ioctl,
+ .open = mxc_wdt_open,
+ .release = mxc_wdt_release,
+};
+
+static struct miscdevice mxc_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &mxc_wdt_fops
+};
+
+static int __init mxc_wdt_probe(struct platform_device *pdev)
+{
+ struct resource *res, *mem;
+ int ret;
+
+ /* reserve static register mappings */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, dev_num);
+ if (!res)
+ return -ENOENT;
+
+ mem = request_mem_region(res->start, res->end - res->start + 1,
+ pdev->name);
+ if (mem == NULL)
+ return -EBUSY;
+
+ platform_set_drvdata(pdev, mem);
+
+ wdt_base_reg = IO_ADDRESS(res->start);
+ mxc_wdt_disable(wdt_base_reg);
+ mxc_wdt_adjust_timeout(timer_margin);
+
+ mxc_wdt_users = 0;
+
+ mxc_wdt_miscdev.this_device = &pdev->dev;
+
+ mxc_wdt_clk = clk_get(NULL, "wdog_clk");
+ clk_enable(mxc_wdt_clk);
+
+ ret = misc_register(&mxc_wdt_miscdev);
+ if (ret)
+ goto fail;
+
+ pr_info("MXC Watchdog # %d Timer: initial timeout %d sec\n", dev_num,
+ timer_margin);
+
+ return 0;
+
+ fail:
+ release_resource(mem);
+ pr_info("MXC Watchdog Probe failed\n");
+ return ret;
+}
+
+static void mxc_wdt_shutdown(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_drvdata(pdev);
+ mxc_wdt_disable(res->start);
+ pr_info("MXC Watchdog # %d shutdown\n", dev_num);
+}
+
+static int __exit mxc_wdt_remove(struct platform_device *pdev)
+{
+ struct resource *mem = platform_get_drvdata(pdev);
+ misc_deregister(&mxc_wdt_miscdev);
+ release_resource(mem);
+ pr_info("MXC Watchdog # %d removed\n", dev_num);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+/* REVISIT ... not clear this is the best way to handle system suspend; and
+ * it's very inappropriate for selective device suspend (e.g. suspending this
+ * through sysfs rather than by stopping the watchdog daemon). Also, this
+ * may not play well enough with NOWAYOUT...
+ */
+
+static int mxc_wdt_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct resource *res = platform_get_drvdata(pdev);
+
+ if (mxc_wdt_users) {
+ mxc_wdt_disable(res->start);
+ }
+ return 0;
+}
+
+static int mxc_wdt_resume(struct platform_device *pdev)
+{
+ struct resource *res = platform_get_drvdata(pdev);
+ if (mxc_wdt_users) {
+ mxc_wdt_enable(res->start);
+ mxc_wdt_ping(res->start);
+ }
+ return 0;
+}
+
+#else
+#define mxc_wdt_suspend NULL
+#define mxc_wdt_resume NULL
+#endif
+
+static struct platform_driver mxc_wdt_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "mxc_wdt",
+ },
+ .probe = mxc_wdt_probe,
+ .shutdown = mxc_wdt_shutdown,
+ .remove = __exit_p(mxc_wdt_remove),
+ .suspend = mxc_wdt_suspend,
+ .resume = mxc_wdt_resume,
+};
+
+static int __init mxc_wdt_init(void)
+{
+ pr_info("MXC WatchDog Driver %s\n", DVR_VER);
+
+ if ((timer_margin < TIMER_MARGIN_MIN) ||
+ (timer_margin > TIMER_MARGIN_MAX)) {
+ pr_info("MXC watchdog error. wrong timer_margin %d\n",
+ timer_margin);
+ pr_info(" Range: %d to %d seconds\n", TIMER_MARGIN_MIN,
+ TIMER_MARGIN_MAX);
+ return -EINVAL;
+ }
+
+ return platform_driver_register(&mxc_wdt_driver);
+}
+
+static void __exit mxc_wdt_exit(void)
+{
+ platform_driver_unregister(&mxc_wdt_driver);
+ pr_info("MXC WatchDog Driver removed\n");
+}
+
+module_init(mxc_wdt_init);
+module_exit(mxc_wdt_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/mxc_wdt.h b/drivers/char/watchdog/mxc_wdt.h
new file mode 100644
index 000000000000..cd09b9acf99f
--- /dev/null
+++ b/drivers/char/watchdog/mxc_wdt.h
@@ -0,0 +1,37 @@
+/*
+ * linux/drivers/char/watchdog/mxc_wdt.h
+ *
+ * BRIEF MODULE DESCRIPTION
+ * MXC Watchdog timer register definitions
+ *
+ * Author: MontaVista Software, Inc.
+ * <AKuster@mvista.com> or <source@mvista.com>
+ *
+ * 2005 (c) MontaVista Software, Inc.
+ * Copyright 2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef __MXC_WDT_H__
+#define __MXC_WDT_H__
+
+#define MXC_WDT_WCR 0x00
+#define MXC_WDT_WSR 0x02
+#define MXC_WDT_WRSR 0x04
+#define WCR_WOE_BIT (1 << 6)
+#define WCR_WDA_BIT (1 << 5)
+#define WCR_SRS_BIT (1 << 4)
+#define WCR_WRE_BIT (1 << 3)
+#define WCR_WDE_BIT (1 << 2)
+#define WCR_WDBG_BIT (1 << 1)
+#define WCR_WDZST_BIT (1 << 0)
+#define WDT_MAGIC_1 0x5555
+#define WDT_MAGIC_2 0xAAAA
+
+#define TIMER_MARGIN_MAX 127
+#define TIMER_MARGIN_DEFAULT 60 /* 60 secs */
+#define TIMER_MARGIN_MIN 1
+
+#endif /* __MXC_WDT_H__ */