diff options
author | Rob Herring <r.herring@freescale.com> | 2008-02-14 14:44:21 -0600 |
---|---|---|
committer | Daniel Schaeffer <daniel.schaeffer@timesys.com> | 2008-08-25 15:18:55 -0400 |
commit | 3f8ed3afb9cee6648f9650d5daf950bb9347cca6 (patch) | |
tree | c9e28cdb34524d44c5dc4dc0778ebdcb858af20c /drivers/char | |
parent | 49914084e797530d9baaf51df9eda77babc98fa8 (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/Kconfig | 51 | ||||
-rw-r--r-- | drivers/char/Makefile | 3 | ||||
-rw-r--r-- | drivers/char/mxc_ipc.c | 2823 | ||||
-rw-r--r-- | drivers/char/mxc_mu.c | 1646 | ||||
-rw-r--r-- | drivers/char/mxc_mu_reg.h | 105 | ||||
-rw-r--r-- | drivers/char/mxc_sdma_tty.c | 744 | ||||
-rw-r--r-- | drivers/char/watchdog/mxc_wdt.c | 385 | ||||
-rw-r--r-- | drivers/char/watchdog/mxc_wdt.h | 37 |
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__ */ |