summaryrefslogtreecommitdiff
path: root/drivers/char/mxc_mu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/mxc_mu.c')
-rw-r--r--drivers/char/mxc_mu.c1646
1 files changed, 1646 insertions, 0 deletions
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");