From: Greg Tucker <greg.b.tucker@xxxxxxxxx> The interprocessor messaging unit supports mailbox style communication between the two Xscale cores on iop342. Signed-off-by: Greg Tucker <greg.b.tucker@xxxxxxxxx> Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- arch/arm/mach-iop13xx/Kconfig | 2 arch/arm/mach-iop13xx/Makefile | 1 arch/arm/mach-iop13xx/imu/Kconfig | 19 + arch/arm/mach-iop13xx/imu/Makefile | 3 arch/arm/mach-iop13xx/imu/common.c | 486 ++++++++++++++++++++++++++++ arch/arm/mach-iop13xx/imu/dev.c | 438 +++++++++++++++++++++++++ arch/arm/mach-iop13xx/imu/imu.c | 151 +++++++++ include/asm-arm/arch-iop13xx/iop13xx-imu.h | 207 ++++++++++++ 8 files changed, 1307 insertions(+), 0 deletions(-) diff --git a/arch/arm/mach-iop13xx/Kconfig b/arch/arm/mach-iop13xx/Kconfig index 40c2d68..27c1c2c 100644 --- a/arch/arm/mach-iop13xx/Kconfig +++ b/arch/arm/mach-iop13xx/Kconfig @@ -16,5 +16,7 @@ config MACH_IQ81340MC Say Y here if you want to support running on the Intel IQ81340MC evaluation kit. +source "arch/arm/mach-iop13xx/imu/Kconfig" + endmenu endif diff --git a/arch/arm/mach-iop13xx/Makefile b/arch/arm/mach-iop13xx/Makefile index 4185e05..7937d73 100644 --- a/arch/arm/mach-iop13xx/Makefile +++ b/arch/arm/mach-iop13xx/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_ARCH_IOP13XX) += pci.o obj-$(CONFIG_ARCH_IOP13XX) += io.o obj-$(CONFIG_MACH_IQ81340SC) += iq81340sc.o obj-$(CONFIG_MACH_IQ81340MC) += iq81340mc.o +obj-$(CONFIG_IOP_IMU) += imu/ diff --git a/arch/arm/mach-iop13xx/imu/Kconfig b/arch/arm/mach-iop13xx/imu/Kconfig new file mode 100644 index 0000000..ee49b37 --- /dev/null +++ b/arch/arm/mach-iop13xx/imu/Kconfig @@ -0,0 +1,19 @@ +# +# IOP13xx IMU Support +# + +menu "IOP13XX IMU Support" + +config IOP_IMU + tristate "IOP IMU support" + depends on EXPERIMENTAL + ---help--- + This includes support functions for the IMU. + +config IOP_IMU_DEV + tristate "IOP IMU char driver" + depends on IOP_IMU + ---help--- + This is a char driver that passes messages throught the IMU. + +endmenu diff --git a/arch/arm/mach-iop13xx/imu/Makefile b/arch/arm/mach-iop13xx/imu/Makefile new file mode 100644 index 0000000..f89fb53 --- /dev/null +++ b/arch/arm/mach-iop13xx/imu/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_IOP_IMU) += common.o imu.o +obj-$(CONFIG_IOP_IMU_DEV) += dev.o + diff --git a/arch/arm/mach-iop13xx/imu/common.c b/arch/arm/mach-iop13xx/imu/common.c new file mode 100644 index 0000000..2d9c311 --- /dev/null +++ b/arch/arm/mach-iop13xx/imu/common.c @@ -0,0 +1,486 @@ +/* + * arch/arm/mach-iop13xx/imu/iop1340-imu-common.c + * + * Interface functions for comunication using IMU hardware on the IOP1342 + * + * Copyright (C) 2005, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * Author: Greg Tucker <greg.b.tucker@xxxxxxxxx> + * + */ + +#ifdef linux +# include <linux/module.h> +# include <linux/kernel.h> +# include <asm/arch/iop13xx-imu.h> +#else +# include "iop1340-imu-common.h" +#endif + +imu_handler *imu_irq_table[NR_IMU_DOORBELLS + NR_IMU_QUEUE_IRQS]; +struct imu_queue_params imu_queue[NR_IMU_QUEUES]; + +/** + **************************************************************************** + * @iop_doorbell_ring + * @brief + * Ring specified hw doorbell. + * + * Will cause an interrupt on the other processor if enabled. + * + * @param IN: int doorbell - doorbell number + * @return NONE + *****************************************************************************/ + +/* #define iop_doorbell_ring(doorbell) do {*((volatile int*)IMU_DBAR) = (1<<doorbell);} while(0) */ + +inline void iop_doorbell_ring(int doorbell) +{ + *((volatile int *)IMU_DBAR) = (1 << doorbell); +} + +/** + **************************************************************************** + * @iop_wait_on_doorbell + * @brief + * Spin until doorbell asserted. + * @param IN: int doorbell - doorbell number + * @return NONE + *****************************************************************************/ + +/*#define iop_wait_on_doorbell(doorbell) do {while ( 0 == (*((volatile int*)IMU_DBCR) & (1<<doorbell)) ) ;} while (0) */ + +inline void iop_wait_on_doorbell(int doorbell) +{ + while (0 == (*((volatile int *)IMU_DBCR) & (1 << doorbell))) ; +} + +/** + ****************************************************************************** + * @iop_doorbell_clear_status + * @brief + * Clear doorbell status bit so future doorbells can interrupt. + * @param IN: int doorbell - doorbell number + * @return NONE + *****************************************************************************/ + +/* #define iop_doorbell_clear_status(doorbell) do {*((volatile int*)IMU_DBCR) = (1<<doorbell) & ((1<<16)-1);} while(0) */ + +inline void iop_doorbell_clear_status(int doorbell) +{ + *((volatile int *)IMU_DBCR) = (1 << doorbell) & ((1 << 16) - 1); +} + +/** + ****************************************************************************** + * @iop_doorbell_check_status + * @brief + * Check if doorbell is active. + * @param IN: int doorbell - doorbell number + * @return + * 0: doorbell not active + * other: doorbell active + *****************************************************************************/ +inline int iop_doorbell_check_status(int doorbell) +{ + return *((volatile int *)IMU_DBCR) & (1 << doorbell); +} + +/** + ****************************************************************************** + * @iop_doorbell_enable + * @brief + * Enable this doorbell to interrupt processor. + * @param IN: int doorbell - doorbell number + * @return NONE + *****************************************************************************/ +void iop_doorbell_enable(int doorbell) +{ + *((volatile int *)IMU_DBER) |= (1 << doorbell); +} + +/** + ****************************************************************************** + * @iop_doorbell_disable + * @brief + * Disable particular doorbell. + * @param IN: int doorbell - doorbell number + * @return NONE + *****************************************************************************/ +void iop_doorbell_disable(int doorbell) +{ + *((volatile int *)IMU_DBER) &= ~(1 << doorbell); +} + +/** + ****************************************************************************** + * @iop_doorbell_mask + * @brief + * Mask/Disable doorbells corresponding to mask. + * @param IN: int mask - vector of doorbells to disable + * @return NONE + *****************************************************************************/ +void iop_doorbell_mask(int mask) +{ + *((volatile int *)IMU_DBER) &= ~(mask); +} + +/** + ****************************************************************************** + * @iop_doorbell_unmask + * @brief + * Unmask/Enable doorbells corresponding to mask. + * @param IN: int mask - vector of doorbells to enable + * @return NONE + *****************************************************************************/ +void iop_doorbell_unmask(int mask) +{ + *((volatile int *)IMU_DBER) &= mask; +} + +/** + **************************************************************************** + * @iop_doorbell_reg_callback + * @brief + * Register the callback function for a particular doorbell. + * @param IN: int doorbell - doorbell number + * @param IN: void * callback(void) - pointer to callback function + * @return NONE + *****************************************************************************/ +int iop_doorbell_reg_callback(int doorbell, void (*callback) (int)) +{ + if ((doorbell >= 0) && (doorbell <= IMU_DB_RQ3NE)) { + imu_irq_table[doorbell] = (imu_handler *) callback; + return 0; + } else + return 1; +} + +/** + ****************************************************************************** + * @iop_mutex_lock + * @brief + * Block until mutex is granted. + * + * Checks if current core has been granted access to the hw mutex. + * Does not check if it's the current thread that has access or + * another thread on the same processor. Only binary mutex is + * supported. + * + * @param IN: mutex - mutex number + * @return NONE + *****************************************************************************/ +void iop_mutex_lock(int mutex) +{ + int trymutex = *IMU_TSR(mutex); + int corenum = (1 << CIDR_READ()); + + while (trymutex != 0 && trymutex != corenum) { + trymutex = *IMU_TSR(mutex); + } +} + +/** + **************************************************************************** + * @iop_mutex_trylock + * @brief + * Non-blocking attempt to get mutex. + * + * Checks if current core has been granted access to the hw mutex. + * Does not check if it's the current thread that has access or + * another thread on the same processor. Only binary mutex is + * supported. + * + * @param IN: int mutex - mutex number + * @return + * 0: success + * 1: other core has lock + *****************************************************************************/ +int iop_mutex_trylock(int mutex) +{ + int trymutex = *IMU_TSR(mutex); + int coreid = CIDR_READ(); + + if (trymutex == 0 || (trymutex ^ (1 << coreid)) == 0) + return 0; + else + return 1; +} + +/** + **************************************************************************** + * @iop_mutex_unlock + * @brief + * Unlock/free the given mutex. + * + * Does not first check if the mutex is locked. Assumes that the + * calling thread ownes the mutex. Currently only binary mutex is + * supported so does not keep a lock count. + * + * @param IN: int mutex - mutex number + * @return NONE + *****************************************************************************/ +void iop_mutex_unlock(int mutex) +{ + /* assert(iop_mutex_trylock(mutex)); */ + *IMU_TSR(mutex) = 0x0; +} + +/** + **************************************************************************** + * @iop_queue_init + * @brief + * Initialize circular queue. + * + * Sets up a circular queue with memory for the queue buffers and + * callback function for received messages. Caller must first + * allocate memory of appropriate size and type for the queue. The + * queue is of fixed size (msg_size * num_items) bytes. + * + * @todo List restrictions on the memory type (dma-able, etc.) + * + * @param IN: int queueid - queue identifier + * @param IN: void * phys_base - physical base pointer to pre-allocated memory + * @param IN: void * virt_base - virtual base pointer to pre-allocated memory + * @param IN: int msg_size - size of fixed messages in bytes + * @param IN: int num_items - max number of items in the queue + * @param IN: void * callback(int) - pointer to callback function + * @param IN: void * error_callback(int) - pointer to error function + * @return 0: success + *****************************************************************************/ +int +iop_queue_init(int queueid, void *phys_base, void *virt_base, int msg_size, + int num_items, void (*rcd_callback) (int), + void (*error_callback) (int)) +{ + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + if (queueid > 3 || num_items > ((1 << 16) - 1)) + return 1; + + /* init send queue pointers */ + queue_hw->sqcr = (1 << 31) /* reset send queue get/put pointers */ + |num_items; /* set size */ + queue_hw->rqcr = (1 << 31); /* reset receive queue get/put pointers */ + queue_hw->sqlbar = (int)phys_base; + queue_hw->squbar = 0; /* assuming 32 bit address space */ + + imu_queue[queueid].txbase = virt_base; + imu_queue[queueid].msg_size = msg_size; + imu_queue[queueid].items = num_items; + imu_queue[queueid].alloc = queue_hw->sqpg >> 16; /* alloc=get */ + + /* Register callback */ + imu_irq_table[NR_IMU_DOORBELLS - 1 + + (IMU_DB_RQ0NE - IMU_DB_QUEUE_IRQ_OFF) + (queueid * 2)] = + (imu_handler *) rcd_callback; + /* Enable receive interrupt */ + iop_doorbell_enable(IMU_DB_RQ0NE + (queueid * 2)); + + return 0; +} + +/** + **************************************************************************** + * @iop_queue_allocate + * @brief + * Return pointer to next free buffer queue. + * @param IN: int queueid - queue identifier + * @return buffer pointer to fill with message + *****************************************************************************/ +void *iop_queue_allocate(int queueid) +{ + void *ret = NULL; + int alloc, get, items_m1, next_alloc; + + struct imu_queue_params *queue = &imu_queue[queueid]; + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + if (queueid > 3) + return NULL; + + /* todo: put mutex around alloc update */ + alloc = queue->alloc; + get = queue_hw->sqpg >> 16; + items_m1 = queue->items - 1; + next_alloc = (alloc == items_m1) ? 0 : alloc + 1; + + if (next_alloc != get) { /* not full */ + ret = (void *)(queue->txbase + (queue->msg_size * alloc)); + queue->alloc = next_alloc; + } + /* todo: unlock mutex on alloc */ + + return ret; +} + +/** + **************************************************************************** + * @iop_queue_postmsg + * @brief + * Post (send) all buffers up to message identified by message pointer. + * @param IN: int queueid - queue identifier + * @param IN: void * msg_adr - pointer to last message to post + * @return NONE + *****************************************************************************/ +int iop_queue_postmsg(int queueid, void *msg_adr) +{ + int offset, items, items_m1, put, alloc, index; + + struct imu_queue_params *queue = &imu_queue[queueid]; + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + offset = (int)(msg_adr - queue->txbase); + + items = queue->items; + items_m1 = items - 1; + + /* Check if it is in range for this queue */ + if (offset < 0 || offset > (queue->msg_size * items)) + return 1; + + put = queue_hw->sqpg & 0xffff; + alloc = queue->alloc; + index = offset / queue->msg_size; + + /* todo: Check if allocated? */ + /* don't post something already posted */ + + if (alloc >= put) { + if (index < put || index >= alloc) + return 1; /* already posted or not allocated */ + } else { + if (index < put && index >= alloc) + return 1; /* already posted or not allocated */ + } + + queue_hw->sqpg = (index == items_m1) ? 0 : index + 1; + + return 0; +} + +/** + **************************************************************************** + * @iop_queue_rx_not_empty + * @brief + * Check rx queue for not empty status. + * @param IN: int queueid - queue identifier + * @return + * 0: rx queue empty + * other: rx queue not empty + *****************************************************************************/ +int iop_queue_rx_not_empty(int queueid) +{ + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + int rqpg = queue_hw->rqpg; + int put = rqpg & 0xffff; + int get = rqpg >> 16; + return get - put; +} + +/** + **************************************************************************** + * @iop_queue_tx_not_full + * @brief + * Check tx queue for full status + * @param IN: int queueid - queue identifier + * @return + * 0: tx queue full + * other: rx queue not full + *****************************************************************************/ +int iop_queue_tx_not_full(int queueid) +{ + struct imu_queue_params *queue = &imu_queue[queueid]; + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + int get = queue_hw->sqpg >> 16; + int next_alloc = queue->alloc + 1; + next_alloc = (next_alloc == queue->items) ? 0 : next_alloc; + + return next_alloc - get; +} + +/** + **************************************************************************** + * @iop_queue_getmsg + * @brief + * Get next available message. + * + * Get a pointer to the next available message in the recieve queue. + * Subsequent calls will return the same value until the message is freed. + * + * @param IN: int queueid - queue identifier + * @return buffer pointer to next available message + *****************************************************************************/ +void *iop_queue_getmsg(int queueid) +{ + + struct imu_queue_params *queue = &imu_queue[queueid]; + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + int rqpg = queue_hw->rqpg; + void *base = queue->rxbase; + int put = rqpg & 0xffff; + int get = rqpg >> 16; + + if (!base || put == get) + return 0; + + return base + (get * queue->msg_size); +} + +/** + **************************************************************************** + * @iop_queue_rxfree + * @brief + * Free a buffer in the receive queue that has already been processed. + * + * Free the buffer that includes msg_addr. Also frees all prior + * messages in the queue. + * + * @param IN: int queueid - queue identifier + * @param IN: void * msg_addr - pointer to data within message to free + * @return buffer pointer to next available message + *****************************************************************************/ +int iop_queue_rxfree(int queueid, void *msg_adr) +{ + int rq_items, offset, index; + + struct imu_queue_params *queue = &imu_queue[queueid]; + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + rq_items = queue_hw->rqcr & 0xffff; + offset = (int)(msg_adr - queue->rxbase); + + /* Check if it is in range for this queue */ + if (offset < 0 || offset > (queue->msg_size * rq_items)) { + return 1; + } + + index = (offset / queue->msg_size) + 1; + index = (index == rq_items) ? 0 : index; + queue_hw->rqpg = index << 16; /* update get pointer */ + + return 0; +} diff --git a/arch/arm/mach-iop13xx/imu/dev.c b/arch/arm/mach-iop13xx/imu/dev.c new file mode 100644 index 0000000..b73af6b --- /dev/null +++ b/arch/arm/mach-iop13xx/imu/dev.c @@ -0,0 +1,438 @@ +/* + * arch/arm/mach-iop13xx/imu/iop1340-imu-dev.c + * + * Char driver for user-space access to IMU inter-core messaging. + * + * Copyright (C) 2005, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * Author: Greg Tucker <greg.b.tucker@xxxxxxxxx> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/poll.h> +#include <linux/cdev.h> +#include <linux/jiffies.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/arch/iop13xx-imu.h> + +#define MODULE_VERS "1.0" +#define MODULE_NAME "IMUdev" +//#define IMU_MAJOR 245 +#define IMU_MAJOR 0 +#define IMU_MAX_MINORS 4 +#define IMU_FIRST_MINOR 1 + +#define Q_MSG_SIZE 64 +#define Q_MSG_ITEMS 16 +#define Q_PHYS_BASE (128*1024*1024) +#define Q_SIZE (Q_MSG_ITEMS*Q_MSG_SIZE) +#define MSG_HEADER_SIZE 4 + +#define IMU_WRITE_TIMEOUT 1000 + +struct imu_dev { + struct cdev cdev; + wait_queue_head_t rq; + wait_queue_head_t wq; + size_t rq_leftover; + char *rq_leftover_ptr; + atomic_t read_available; + atomic_t write_available; +}; + +static struct imu_dev imu[IMU_MAX_MINORS]; +static int imu_major = IMU_MAJOR; + +void queue_rq_callback(int queueid) +{ + struct imu_dev *imui = &imu[queueid]; + + pr_debug("queue_rq_callback %d\n", queueid); + wake_up_interruptible(&imui->rq); + iop_doorbell_disable(IMU_DB_RQ0NE + (queueid * 2)); +} + +void queue_wq_callback(int queueid) +{ + struct imu_dev *imui = &imu[queueid]; + + pr_debug("wq callback on not full q=%d\n", queueid); + wake_up_interruptible(&imui->wq); + iop_doorbell_disable(IMU_DB_SQ0NF + (queueid * 2)); +} + +extern struct imu_queue_params imu_queue[]; + +/* + * init_callback only called on the first rx to map the base + */ + +#define has_overlap(a,b,c,d) (((c<=a) && (d>a))||((c<b) && (d>=b))||((c>=a) && (d<=b))) + +void init_callback(int queueid) +{ + int i, qi_rxitems, err = 0; + struct imu_queue_params *qi; + struct imu_queue_params *queue = &imu_queue[queueid]; + struct imu_queue *qi_hw; + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * sizeof(struct imu_queue))); + + int phy_rxbase = queue_hw->rqlbar; + int rq_items = queue_hw->rqcr & 0xffff; + + // check if overlap + + for (i = 0; i < 4; i++) { + qi = &imu_queue[i]; + qi_hw = (struct imu_queue *) + (IMU_Q0_BASE + (i * sizeof(struct imu_queue))); + qi_rxitems = qi_hw->rqcr & 0xffff; + + if (i != queueid && // check overlap with other registered rx + qi->rxbase && qi->msg_size && + has_overlap(phy_rxbase, + phy_rxbase + (rq_items * qi->msg_size), + (int)qi->rxbase, + (int)qi->rxbase + + (qi_rxitems * qi->msg_size))) { + err = 1; + } + + if (qi->txbase && qi->msg_size && // check overlap with tx queues + has_overlap(phy_rxbase, + phy_rxbase + (rq_items * qi->msg_size), + (int)qi->txbase, + (int)qi->txbase + (qi->items * qi->msg_size))) { + err = 1; + } + } + + if (err) { + printk(KERN_WARNING + "overlap found in IMU rx queue request 0x%x\n", + (int)phy_rxbase); + } + + queue->rxbase = ioremap(phy_rxbase, rq_items * Q_MSG_SIZE); + + // switch to regular callback and call + iop_doorbell_reg_callback(NR_IMU_DOORBELLS - 1 + + (IMU_DB_RQ0NE - IMU_DB_QUEUE_IRQ_OFF) + + (queueid * 2), queue_rq_callback); + + printk + ("init_callback registerd q=%d rxbase=0x%x rxphy=0x%x size=0x%x\n", + queueid, (int)queue->rxbase, phy_rxbase, rq_items * Q_MSG_SIZE); + queue_rq_callback(queueid); +} + +void error_callback(int queueid) +{ +} + +static int imu_open(struct inode *inode, struct file *file) +{ + struct imu_dev *imui; + int queueid = iminor(file->f_dentry->d_inode); + + imui = &imu[queueid]; + + switch (file->f_flags & O_ACCMODE) { + case O_RDWR: + if (!atomic_dec_and_test(&imui->read_available)) { + atomic_inc(&imui->read_available); + return -EBUSY; + } + if (!atomic_dec_and_test(&imui->write_available)) { + atomic_inc(&imui->write_available); + atomic_inc(&imui->read_available); + return -EBUSY; + } + break; + case O_WRONLY: + if (!atomic_dec_and_test(&imui->write_available)) { + atomic_inc(&imui->write_available); + return -EBUSY; + } + break; + case O_RDONLY: + if (!atomic_dec_and_test(&imui->read_available)) { + atomic_inc(&imui->read_available); + return -EBUSY; + } + break; + + } + return 0; +} + +static int imu_release(struct inode *inode, struct file *file) +{ + struct imu_dev *imui; + int queueid = iminor(file->f_dentry->d_inode); + + imui = &imu[queueid]; + + switch (file->f_flags & O_ACCMODE) { + case O_RDWR: + atomic_inc(&imui->read_available); // fall through + case O_WRONLY: + atomic_inc(&imui->write_available); + break; + case O_RDONLY: + atomic_inc(&imui->read_available); + break; + } + return 0; +} + +static ssize_t +imu_read(struct file *file, char __user * buf, size_t count, loff_t * f_pos) +{ + struct imu_dev *imui; + char *dat; + int queueid = iminor(file->f_dentry->d_inode); + + imui = &imu[queueid]; + + pr_debug("imu_read count=%d ", count); + + while (1) { + if (imui->rq_leftover) { + pr_debug("%d leftover ", imui->rq_leftover); + count = min(count, imui->rq_leftover); + if (copy_to_user(buf, imui->rq_leftover_ptr, count)) + return -EFAULT; + imui->rq_leftover -= count; + pr_debug(" %d left \n", imui->rq_leftover); + if (imui->rq_leftover == 0) + iop_queue_rxfree(queueid, + imui->rq_leftover_ptr); + imui->rq_leftover_ptr += count; + return count; + } + + while (!iop_queue_rx_not_empty(queueid)) { + DECLARE_WAITQUEUE(wait, current); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + pr_debug("imu_read: empty going to sleep\n"); + + add_wait_queue(&imui->rq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + iop_doorbell_enable(IMU_DB_RQ0NE + (queueid * 2)); + schedule_timeout(IMU_WRITE_TIMEOUT); + + remove_wait_queue(&imui->rq, &wait); + if (signal_pending(current)) { + return -ERESTARTSYS; + } + } + + // new message + dat = iop_queue_getmsg(queueid); + + pr_debug("imu_read: got a new message at 0x%x\n", (int)dat); + if (NULL == dat) + return -EFAULT; + +#if MSG_HEADER_SIZE > 0 + imui->rq_leftover = *((int *)dat); +#else + imui->rq_leftover = 4; +#endif + if (imui->rq_leftover > Q_MSG_SIZE) + imui->rq_leftover = 0; + + imui->rq_leftover_ptr = dat + MSG_HEADER_SIZE; + + pr_debug("imu_read: msg size=%d\n", imui->rq_leftover); + if (!imui->rq_leftover) + iop_queue_rxfree(queueid, imui->rq_leftover_ptr); + } + +} +static ssize_t +imu_write(struct file *file, const char __user * buf, size_t count, + loff_t * f_pos) +{ + void *msg; + struct imu_dev *imui; + int queueid = iminor(file->f_dentry->d_inode); + + imui = &imu[queueid]; + + count = min(count, (size_t) (Q_MSG_SIZE - MSG_HEADER_SIZE)); + + while (NULL == (msg = iop_queue_allocate(queueid))) { + DECLARE_WAITQUEUE(wait, current); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + pr_debug("imu_write sleeping\n"); + + add_wait_queue(&imui->wq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + iop_doorbell_enable(IMU_DB_SQ0NF + (queueid * 2)); + schedule_timeout(IMU_WRITE_TIMEOUT); + + remove_wait_queue(&imui->wq, &wait); + if (signal_pending(current)) { + return -ERESTARTSYS; + } + + } + + if (copy_from_user(msg + MSG_HEADER_SIZE, buf, count)) + return -EFAULT; +#if MSG_HEADER_SIZE > 0 + *((int *)msg) = count; +#endif + + iop_queue_postmsg(queueid, msg); + + pr_debug("imu_write sent q=%d count=%d msg=0x%x\n", queueid, count, + (int)msg); + return count; +} + +static unsigned int imu_poll(struct file *file, poll_table * wait) +{ + struct imu_dev *imui; + unsigned int mask = 0; + int queueid = iminor(file->f_dentry->d_inode); + + imui = &imu[queueid]; + poll_wait(file, &imui->rq, wait); + poll_wait(file, &imui->wq, wait); + + if (iop_queue_rx_not_empty(queueid)) + mask |= POLLIN | POLLRDNORM; + if (iop_queue_tx_not_full(queueid)) + mask |= POLLOUT | POLLWRNORM; + + return mask; +} + +static struct file_operations imu_fops = { + .owner = THIS_MODULE, + .read = imu_read, + .write = imu_write, + .poll = imu_poll, + .open = imu_open, + .release = imu_release, +}; + +static int __init imu_dev_init(void) +{ + dev_t dev; + // struct class_simple *imu_class; + int err, i; + char *queue_base; + + if (imu_major) { + dev = MKDEV(imu_major, 0); + err = register_chrdev_region(dev, IMU_MAX_MINORS, "imu"); + } else { + err = alloc_chrdev_region(&dev, IMU_FIRST_MINOR, + IMU_MAX_MINORS, "imu"); + imu_major = MAJOR(dev); + } + if (err < 0) { + printk(KERN_WARNING "imu: can't get major %d\n", imu_major); + return err; + } + //todo: update imu_class = class_simple_create(THIS_MODULE, "imu"); + + for (i = IMU_FIRST_MINOR; i < IMU_MAX_MINORS; i++) { + cdev_init(&imu[i].cdev, &imu_fops); + if (cdev_add(&imu[i].cdev, MKDEV(imu_major, i), 1)) { + printk(KERN_WARNING "Error cdev_add imu%i\n", i); + continue; + } + + imu[i].rq_leftover = 0; + atomic_set(&imu[i].read_available, 1); + atomic_set(&imu[i].write_available, 1); + init_waitqueue_head(&imu[i].rq); + init_waitqueue_head(&imu[i].wq); + imu_queue[i].rxbase = 0; + + queue_base = ioremap(Q_PHYS_BASE + (i * Q_SIZE), Q_SIZE); + // todo: see about changing to one of following + // non-shared device tex.cb = 0x010.00 + // shared device tex.cb = 0x001.01 + + err = iop_queue_init(i, + (void *)Q_PHYS_BASE + (i * Q_SIZE), + queue_base, + Q_MSG_SIZE, + Q_MSG_ITEMS, + init_callback, error_callback); + if (err) { + printk(KERN_WARNING "could not init imu queue %d\n", i); + continue; + } + iop_doorbell_reg_callback(NR_IMU_DOORBELLS - 1 + + (IMU_DB_SQ0NF - + IMU_DB_QUEUE_IRQ_OFF) + (i * 2), + queue_wq_callback); + printk(KERN_INFO + "IMU Queue %d initialized major=%d minor=%d base=0x%x\n", + i, imu_major, i, (int)queue_base); + + } + + return 0; +} + +static void __exit imu_dev_cleanup(void) +{ + int i; + + for (i = IMU_FIRST_MINOR; i < IMU_MAX_MINORS; i++) { + cdev_del(&imu[i].cdev); + iop_doorbell_disable(IMU_DB_RQ0NE + (i * 2)); + iop_doorbell_disable(IMU_DB_SQ0NF + (i * 2)); + iounmap(imu_queue[i].txbase); + if (imu_queue[i].rxbase) { + iounmap(imu_queue[i].rxbase); + imu_queue[i].rxbase = 0; + } + } + + //todo: update class_simple_device_remove(MKDEV(imu_major, 0)); + unregister_chrdev_region(MKDEV(imu_major, 0), IMU_MAX_MINORS); + + printk(KERN_INFO "%s driver ver %s removed\n", + MODULE_NAME, MODULE_VERS); +} + +module_init(imu_dev_init); +module_exit(imu_dev_cleanup); + +MODULE_AUTHOR("Greg Tucker"); +MODULE_DESCRIPTION("IMU dev interface"); diff --git a/arch/arm/mach-iop13xx/imu/imu.c b/arch/arm/mach-iop13xx/imu/imu.c new file mode 100644 index 0000000..6626486 --- /dev/null +++ b/arch/arm/mach-iop13xx/imu/imu.c @@ -0,0 +1,151 @@ +/* + * arch/arm/mach-iop13xx/imu/iop1340-imu.c + * + * Support for IMU communication for the Intel IOP1340 chipset + * + * Copyright (C) 2005, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * Author: Greg Tucker <greg.b.tucker@xxxxxxxxx> + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#define __KERNEL_SYSCALLS__ +#include <linux/reboot.h> +#include <linux/unistd.h> /* for execve */ +#include <asm/arch/iop13xx-imu.h> + +extern imu_handler *imu_irq_table[]; +extern struct imu_queue_params imu_queue[]; + +static irqreturn_t imu_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned int doorbell, queue; + unsigned int dbcr = *(volatile int *)IMU_DBCR; + + pr_debug("got imu interrupt. IMU_DBCR=0x%x\n", dbcr); + + do { + doorbell = dbcr >> 28; + queue = (dbcr >> 24) & 0xf; + + if (doorbell != 0xf && imu_irq_table[doorbell]) { + imu_irq_table[doorbell] (doorbell); + *(volatile int *)IMU_DBCR = 1 << doorbell; // clear status + } + + if (queue != 0xf + && imu_irq_table[(NR_IMU_DOORBELLS - 1) + queue]) + imu_irq_table[(NR_IMU_DOORBELLS - 1) + + queue] (queue / 2); + + dbcr = *(volatile int *)IMU_DBCR; + + } while ((dbcr >> 24) != 0xff); + + return IRQ_HANDLED; +} + +static void deferred_reset(void *dummy) +{ + static int in_shutdown = 0; + static char *envp[] = { + "HOME=/", "TERM=linux", + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL + }; + char *argv[] = { + "/sbin/shutdown", "-r", "now", NULL + }; + + // todo: start watchdog timer + + if (!in_shutdown) { + in_shutdown = 1; + printk(KERN_CRIT "IMU reset: Shutting down the system now.\n"); + if (0 > execve("/sbin/shutdown", argv, envp)) + printk("IMU reset: Could not use /sbin/shutdown\n"); + else + return; + } + + printk("IMU reset: Starting hard reset\n"); + //emergency_sync(); + //kill_proc(1, SIGINT, 1); + kernel_restart(NULL); +} + +void restart_callback(int irq) +{ + static DECLARE_WORK(reset_work, deferred_reset, NULL); + + printk("\nIMU reset: Got restart doorbell\n"); + schedule_work(&reset_work); + return; +} + +static int __init iop_imu_init(void) +{ + int i, err; + + for (i = 0; i < NR_IMU_DOORBELLS + NR_IMU_QUEUE_IRQS; i++) + imu_irq_table[i] = 0; + + err = request_irq(IRQ_IOP13XX_IMU, imu_interrupt, + SA_INTERRUPT, "imu interrupt", NULL); + + iop_doorbell_reg_callback(IMU_RESET_DOORBELL, restart_callback); + iop_doorbell_enable(IMU_RESET_DOORBELL); + + printk(KERN_INFO "IOP 8134x IMU driver\n"); + return err; +} + +static void __exit iop_imu_exit(void) +{ + free_irq(IRQ_IOP13XX_IMU, NULL); + return; +} + +EXPORT_SYMBOL(iop_doorbell_ring); +EXPORT_SYMBOL(iop_wait_on_doorbell); +EXPORT_SYMBOL(iop_doorbell_clear_status); +EXPORT_SYMBOL(iop_doorbell_check_status); +EXPORT_SYMBOL(iop_doorbell_enable); +EXPORT_SYMBOL(iop_doorbell_disable); +EXPORT_SYMBOL(iop_doorbell_mask); +EXPORT_SYMBOL(iop_doorbell_unmask); +EXPORT_SYMBOL(iop_doorbell_reg_callback); +EXPORT_SYMBOL(iop_mutex_lock); +EXPORT_SYMBOL(iop_mutex_trylock); +EXPORT_SYMBOL(iop_mutex_unlock); +EXPORT_SYMBOL(iop_queue_init); +EXPORT_SYMBOL(iop_queue_allocate); +EXPORT_SYMBOL(iop_queue_postmsg); + +EXPORT_SYMBOL(iop_queue_getmsg); +EXPORT_SYMBOL(iop_queue_rxfree); +EXPORT_SYMBOL(iop_queue_rx_not_empty); +EXPORT_SYMBOL(iop_queue_tx_not_full); + +EXPORT_SYMBOL(imu_queue); + +module_init(iop_imu_init); +module_exit(iop_imu_exit); + +MODULE_AUTHOR("Greg Tucker"); +MODULE_DESCRIPTION("IMU interface"); diff --git a/include/asm-arm/arch-iop13xx/iop13xx-imu.h b/include/asm-arm/arch-iop13xx/iop13xx-imu.h new file mode 100644 index 0000000..f7ecd6a --- /dev/null +++ b/include/asm-arm/arch-iop13xx/iop13xx-imu.h @@ -0,0 +1,207 @@ +/* + * include/asm-arm/arch-iop13xx/iop1340-imu.h + * + * IMU hardware support on IOP342 + * + * Copyright (C) 2005, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * Author: Greg Tucker <greg.b.tucker@xxxxxxxxx> + * + */ + +#ifndef IOP34X_H +#define IOP34X_H + +#ifndef NULL +# define NULL (void *) 0 +#endif + +typedef int imu_handler(int); + +void iop_mutex_lock(int mutex); +int iop_mutex_trylock(int mutex); +void iop_mutex_unlock(int mutex); + +int iop_queue_init(int queueid, void *phys_base, void *vert_base, + int msg_size, int num_items, + void (*rcd_callback) (int), void (*error_callback) (int)); +void *iop_queue_allocate(int queueid); +int iop_queue_postmsg(int queueid, void *msg_adr); + +int iop_doorbell_reg_callback(int doorbell, void (*callback) (int)); +void iop_doorbell_clear_status(int doorbell); +int iop_doorbell_check_status(int doorbell); +void iop_doorbell_enable(int doorbell); +void iop_doorbell_disable(int doorbell); +void iop_doorbell_mask(int doorbell); +void iop_doorbell_unmask(int doorbell); +void iop_doorbell_ring(int doorbell); +inline void iop_wait_on_doorbell(int doorbell); + +void *iop_queue_getmsg(int queueid); +int iop_queue_rxfree(int queueid, void *msg_adr); +int iop_queue_rx_not_empty(int queueid); +int iop_queue_tx_not_full(int queueid); + +/* Core ID - CIDR */ + +#define CIDR_READ() \ + ({unsigned _val_; asm volatile("mrc\tp6, 0, %0, c0, c0, 0" : "=r" (_val_)); _val_;}) +#define COREID0 0 +#define COREID1 1 + +/* -------------------------------------------------------------------------- + * IMU/IMM registers + */ + +/* Reset cause status - RCSR */ + +#define RCSR_READ() \ + ({unsigned _val_; asm volatile("mrc\tp6, 0, %0, c0, c1, 0" : "=r" (_val_));_val_;}) +#define RCSR_MU_RESET (1<<21) +#define RCSR_TMPI3_RESET (1<<18) +#define RCSR_TMPI2_RESET (1<<15) +#define RCSR_TMPI1_RESET (1<<12) +#define RCSR_TMPI0_RESET (1<<9) +#define RCSR_WATCHDOG_RESET (1<<5) +#define RCSR_TARGETED_RESET (1<<4) +#define RCSR_ID_RESET_MASK (0xf) +#define RCSR_SYSTEM_RESET_MASK (RCSR_MU_RESET | RCSR_TARGETED_RESET | RCSR_TMPI3_RESET | \ + RCSR_TMPI2_RESET | RCSR_TMPI1_RESET | RCSR_TMPI0_RESET | \ + RCSR_WATCHDOG_RESET | RCSR_TARGETED_RESET) + +/* #define is_sys_reset() ((RCSR_READ() & RCSR_SYSTEM_RESET_MASK) == 0) currently not working in simulator */ +#define is_sys_reset() 1 + +/* Software Interrupt Generation Register - SINTGENR */ + +#define SINTGENR_READ() \ + ({ unsigned _val_;asm volatile("mrc\tp6, 0, %0, c1, c1, 0" : "=r" (_val_));_val_;}) +#define SINTGENR_WRITE(val) \ + ({ asm volatile("mcr\tp6, 0, %0, c1, c1, 0" : : "r" (val)); }) + +/* Targeted Reset Register - TARRSTR */ + +# define TARRSTR_WRITE(val) \ + ({ asm volatile("mcr\tp6, 0, %0, c2, c1, 0" : : "r" (val)); }) + +#ifdef linux +# define IMU_BASE IOP13XX_PMMR_VIRT_MEM_BASE +#else +# define IMU_BASE 0xffd80000 +#endif + +/* IMU doorbell registers */ + +#define IMU_DBCR (IMU_BASE+0xa00) +#define IMU_DBER (IMU_BASE+0xa04) +#define IMU_DBAR (IMU_BASE+0xa10) +#define IMU_DBEOR (IMU_BASE+0xa14) +#define NR_IMU_DOORBELLS 14 + +/* Conventions on doorbell use */ + +#define IMU_RESET_DOORBELL 3 + +/* IMU send queue */ + +#define IMU_Q0_BASE (IMU_BASE+0xa20) +#define IMU_Q1_BASE (IMU_BASE+0xa40) +#define IMU_Q2_BASE (IMU_BASE+0xa60) +#define IMU_Q3_BASE (IMU_BASE+0xa80) + +#define IMU_SQPG0 (IMU_Q0_BASE+0x0) +#define IMU_SQCR0 (IMU_Q0_BASE+0x4) +#define IMU_SQLBAR0 (IMU_Q0_BASE+0x8) +#define IMU_SQUBAR0 (IMU_Q0_BASE+0xc) +#define IMU_RQPG0 (IMU_Q0_BASE+0x10) +#define IMU_RQCR0 (IMU_Q0_BASE+0x14) +#define IMU_RQLBAR0 (IMU_Q0_BASE+0x18) +#define IMU_RQUBAR0 (IMU_Q0_BASE+0x1c) + +#define IMU_SQPG1 (IMU_Q1_BASE+0x0) +#define IMU_SQCR1 (IMU_Q1_BASE+0x4) +#define IMU_SQLBAR1 (IMU_Q1_BASE+0x8) +#define IMU_SQUBAR1 (IMU_Q1_BASE+0xc) +#define IMU_RQPG1 (IMU_Q1_BASE+0x10) +#define IMU_RQCR1 (IMU_Q1_BASE+0x14) +#define IMU_RQLBAR1 (IMU_Q1_BASE+0x18) +#define IMU_RQUBAR1 (IMU_Q1_BASE+0x1c) + +#define IMU_SQPG2 (IMU_Q2_BASE+0x0) +#define IMU_SQCR2 (IMU_Q2_BASE+0x4) +#define IMU_SQLBAR2 (IMU_Q2_BASE+0x8) +#define IMU_SQUBAR2 (IMU_Q2_BASE+0xc) +#define IMU_RQPG2 (IMU_Q2_BASE+0x10) +#define IMU_RQCR2 (IMU_Q2_BASE+0x14) +#define IMU_RQLBAR2 (IMU_Q2_BASE+0x18) +#define IMU_RQUBAR2 (IMU_Q2_BASE+0x1c) + +#define IMU_SQPG3 (IMU_Q3_BASE+0x0) +#define IMU_SQCR3 (IMU_Q3_BASE+0x4) +#define IMU_SQLBAR3 (IMU_Q3_BASE+0x8) +#define IMU_SQUBAR3 (IMU_Q3_BASE+0xc) +#define IMU_RQPG3 (IMU_Q3_BASE+0x10) +#define IMU_RQCR3 (IMU_Q3_BASE+0x14) +#define IMU_RQLBAR3 (IMU_Q3_BASE+0x18) +#define IMU_RQUBAR3 (IMU_Q3_BASE+0x1c) +#define NR_IMU_QUEUES 3 + +/* IMU queue interrupts */ + +#define IMU_DB_SQ0NF 16 +#define IMU_DB_RQ0NE 17 +#define IMU_DB_SQ1NF 18 +#define IMU_DB_RQ1NE 19 +#define IMU_DB_SQ2NF 20 +#define IMU_DB_RQ2NE 21 +#define IMU_DB_SQ3NF 22 +#define IMU_DB_RQ3NE 23 +#define IMU_DB_QUEUE_IRQ_OFF 16 +#define NR_IMU_QUEUE_IRQS 8 + +#ifndef __ASSEMBLER__ +struct imu_queue { + unsigned int sqpg; /* Send queue put/get register */ + unsigned int sqcr; /* Send queue control register */ + unsigned int sqlbar; /* Send queue lower base address reg */ + unsigned int squbar; /* Send queue upper base address reg */ + unsigned int rqpg; /* Receive queue put/get register */ + unsigned int rqcr; /* Receive queue control register */ + unsigned int rqlbar; /* Receive queue lower base address reg */ + unsigned int rqubar; /* Receive queue upper base address reg */ +}; + +struct imu_queue_params { + void *txbase; + void *rxbase; + int alloc; + int msg_size; + int items; +}; +#endif + +/* Test and set semaphore registers */ + +#define IMU_TSR_BASE (IMU_BASE+0xb00) +#define IMU_TSR(x) ((volatile char*)(IMU_TSR_BASE+x)) + +/* Some conventions on the semaphores */ + +#define HW_MUTEX_FLASH_READ_CORE0 0 +#define HW_MUTEX_FLASH_READ_CORE1 1 + +#endif /* IOP34X_H */ - To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html