/* usdb.c */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <mach/regs-irq.h>
#include <plat/regs-udc.h>
#include <plat/irq.h>
#define MON_DEBUG
#undef PDEBUG
#ifdef MON_DEBUG
# ifdef __KERNEL__
# define PDEBUG(fmt, args...) printk(KERN_DEBUG "debug info: "fmt, ##args)
# else
# define PDEBUG(fmt, args...)
# endif
#endif
#define PDEBUGG(fmt, args...)
MODULE_LICENSE("GPL");
struct circular_buffer{
int head;
int tail;
char *page;
};
static int mon_major = 0;
static int dev_id = 0;
static char *dev_name = "usb_monitor";
static void __iomem *base_addr;
#define BUFSIZE (1 << 12)
static struct cdev cdev;
static struct semaphore buf_sema;
static struct tasklet_struct mon_tasklet;
static struct circular_buffer buffer = {
.head = 0,
.tail = 0
};
/****************************** IO Functions ******************************/
static void udc_write8(u8 value, u32 reg){
iowrite8(value, base_addr + reg);
}
static u8 udc_read8(u32 reg){
return ioread8(base_addr + reg);
}
/************************** Interrupt handling ****************************/
static void dat_handler(unsigned long args){
u16 size = 0x0000U;
int head = buffer.head;
int tail = buffer.tail;
char *page = buffer.page;
PDEBUG("Proceed to data transfer\n");
size = udc_read8(S3C2410_UDC_OUT_FIFO_CNT1_REG) |
(udc_read8(S3C2410_UDC_OUT_FIFO_CNT2_REG) << 8);
/* Verify space availablility, load data into the buffer */
while (size-- && ((tail + 1) % BUFSIZE != head)){
*(char *)(page + tail) = udc_read8(S3C2410_UDC_EP3_FIFO_REG);
tail = (tail + 1) % BUFSIZE;
}
/* Disable irq when there is no more buffer space */
if (size)
disable_irq(IRQ_USBD);
buffer.tail = tail;
}
static irqreturn_t mon_isr(int irq, void *id){
static int cnt = 0;
u16 tfr = 0x0000;
/* Clear interrupts */
udc_write8(udc_read8(S3C2410_UDC_EP_INT_REG),
S3C2410_UDC_EP_INT_REG);
udc_write8(0x03, S3C2410_UDC_INDEX_REG);
tfr = udc_read8(S3C2410_UDC_OUT_FIFO_CNT1_REG) |
(udc_read8(S3C2410_UDC_OUT_FIFO_CNT2_REG) << 8);
PDEBUG("USBD interrupt has been triggered %i times\n", ++cnt);
PDEBUG("%u-bytes data to be transferred\n", tfr);
/* Is it an interrupt of EP3 */
if (tfr)
tasklet_schedule(&mon_tasklet);
return IRQ_HANDLED;
}
static int mon_release(void){
/*
* disable and waiting for
* current irq being finished
*/
disable_irq(IRQ_USBD);
free_irq(IRQ_USBD, &dev_id);
enable_irq(IRQ_USBD);
iounmap(base_addr);
release_mem_region(S3C2410_PA_USBDEV, S3C24XX_SZ_USBDEV);
return 0;
}
static int mon_open(void){
int retval = 0;
PDEBUG("Allocating io memory and ISR\n");
if (!request_mem_region(S3C2410_PA_USBDEV, S3C24XX_SZ_USBDEV, dev_name))
return -EBUSY;
base_addr = ioremap_nocache(S3C2410_PA_USBDEV, S3C24XX_SZ_USBDEV);
if (!base_addr){
retval = -ENOMEM;
goto merr;
}
retval = request_irq(IRQ_USBD, mon_isr,
IRQF_SHARED | IRQF_TRIGGER_LOW,
dev_name, &dev_id);
if (retval)
goto ierr;
return 0;
ierr:
iounmap(base_addr);
merr:
release_mem_region(S3C2410_PA_USBDEV, S3C24XX_SZ_USBDEV);
return retval;
}
static ssize_t mon_read(struct file *filp, char __user *buf, size_t count,
loff_t *pos){
int head;
int tail;
int size;
char *page = buffer.page;
int retval = 0;
PDEBUG("Output data from buffer\n");
if (down_interruptible(&buf_sema))
return -ERESTARTSYS;
/* Read the position and the data size */
head = buffer.head;
tail = buffer.tail;
size = (tail + BUFSIZE - head) % BUFSIZE;
/* No data available */
if (!size)
goto exit;
/* Copy the data to user space */
retval = copy_to_user(buf, (char *)(page + head), size);
if (retval){
retval = -EFAULT;
goto exit;
}
/* Re-enable the irq when there is space available */
if ((tail + 1) % BUFSIZE == head)
enable_irq(IRQ_USBD);
/* Update buffer's position pointers */
buffer.head += size;
retval = size;
exit:
up(&buf_sema);
return retval;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.read = mon_read
};
static void __exit mon_cleanup(void){
dev_t devno = MKDEV(mon_major, 0);
/* free the irq before killing the data handling tasklet */
mon_release();
tasklet_kill(&mon_tasklet);
kfree(buffer.page);
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
}
static int __init mon_init(void){
dev_t devno;
int retval = 0;
/*
* create device major and minor number
*/
if (mon_major){
devno = MKDEV(mon_major, 0);
retval = register_chrdev_region(devno, 1, dev_name);
}
else{
retval = alloc_chrdev_region(&devno, 0, 1, dev_name);
mon_major = MAJOR(devno);
}
if (retval)
goto exit;
/* Initialize the buffer */
buffer.page = kmalloc(BUFSIZE, GFP_KERNEL);
if (!buffer.page){
retval = -ENOMEM;
goto err1;
}
memset(buffer.page, 0, BUFSIZE);
/* Initialize a tasklet for data handling */
tasklet_init(&mon_tasklet, dat_handler, 0x0UL);
if ((retval = mon_open()))
goto err2;
sema_init(&buf_sema, 1);
/*
* add a char device
*/
cdev_init(&cdev, &fops);
cdev.owner = THIS_MODULE;
retval = cdev_add(&cdev, devno, 1);
if (retval)
goto err3;
return 0;
err3:
mon_release();
err2:
kfree(buffer.page);
err1:
unregister_chrdev_region(devno, 1);
exit:
return retval;
}
module_init(mon_init);
module_exit(mon_cleanup);