/* * MSP serial driver * * 2010 (C) Tim Sander * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License Version * 2 as published by the Free Software Foundation. * */ //#define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hwlibs/fpgaStruct.h" #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36) #define MX35_IO_ADDRESS IO_ADDRESS #endif #define DRV_NAME "msp" #define GPIO_IRQ_NR1 32+32+3 /* GPIO Port Number of first FPGA INTERUPT */ #define GPIO_IRQ_NR2 32+32+4 /* GPIO Port Number of second FPGA INTERUPT */ #define GPIO_IRQ_NR3 32+32+5 /* GPIO Port Number of third FPGA INTERUPT */ #define GPIO_IRQ_MSP GPIO_IRQ_NR2 #define MSP_IRQ_MASK IRQ_FPGA_READ_MSP_FIFOS // always points to MSP IRQ #define FPGA_BASE 0xB6000000 #define FPGA_LEN FPGA_MMAP_LEN #define MSP_BUSY_FLAG 0x8000 #define MSP_FIFO_SIZE 512 #define MSP_IRQ_DELAY 5120 //5120=512 fifo size 307200000=1ms // Timebase= 3,2552µs =1/(19200*16) #define MSP_DEVICES (4) struct msp_priv; struct msp_misc { int minor; atomic_t free; struct msp_priv *priv; }; struct msp_priv { Fpga __iomem *fpga; struct platform_device *pdev; struct kfifo fifos[MSP_DEVICES]; wait_queue_head_t wqs[MSP_DEVICES]; struct msp_misc misc[MSP_DEVICES]; }; static struct msp_priv *current_instance; static inline int msp_get_device_no(struct msp_priv *priv, struct msp_misc *misc) { return misc - priv->misc; } static int msp_misc_get(struct msp_priv *priv, struct inode *inode, struct msp_misc **ret_misc) { struct msp_misc *misc; int minor = iminor(inode); int i; dev_dbg(&priv->pdev->dev, "%s: minor: %d\n", __func__, minor); for (i = 0; i < ARRAY_SIZE(priv->misc); i++) { misc = &priv->misc[i]; dev_dbg(&priv->pdev->dev, "%s: misc->minor: %d, free %d\n", __func__, misc->minor, atomic_read(&misc->free)); if (misc->minor == minor) { if (!atomic_sub_and_test(1, &misc->free)) { atomic_inc(&misc->free); return -EBUSY; } dev_dbg(&priv->pdev->dev, "%s: using device #%d\n", __func__, i); *ret_misc = misc; return 0; } } return -ENODEV; } static int msp_misc_put(struct msp_priv *priv, struct msp_misc *misc) { atomic_inc(&misc->free); return 0; } static int msp_ser_open(struct inode *inode, struct file *file) { struct msp_priv *priv = current_instance; struct msp_misc *misc; int err; int interruptMaskValue; int interruptMaskEnable; int interruptMaskSelect; err = msp_misc_get(priv, inode, &misc); if (err) return err; file->private_data = misc; // Only REMOVE active low resets from the FPGA's bootup state. Do NOT SET a reset, if the msp driver could be re-started. iowrite16(0xffff,&priv->fpga->regsCard.nRstCardIF); // remove all card resets interruptMaskValue =MSP_IRQ_MASK; // wanted value interruptMaskEnable=MSP_IRQ_MASK; // mask of the bits that are allowed to be changed interruptMaskSelect=(interruptMaskEnable<<8) | (interruptMaskValue & 0xff); // fpga value 8 bit enable mask, 8 bit value // note: register interruptMaskStatus is read only and may show the currently selected interrupt bits //dev_info(&priv->pdev->dev,"not enabling interrupts for testing"); iowrite16(interruptMaskSelect,&priv->fpga->regsGeneral.interruptMaskSelect); return 0; } static int msp_ser_release(struct inode *inode, struct file *file) { struct msp_misc *misc = file->private_data; struct msp_priv *priv = misc->priv; msp_misc_put(priv, misc); //iowrite16(ioread16(&priv->fpga->regsGeneral.interruptMask)&^MSP_IRQ_MASK,&priv->fpga->regsGeneral.interruptMask)://disable interrupt FIXME use count has to be right return 0; } static void msp_ser_read_fpga_buffer(struct msp_priv *priv) { int i; short wakeup[MSP_DEVICES]; //wakeup keeps track which buffers need a wakup call short serData,numValues; unsigned char chan; for(i=0;ifpga->mspUart.mspUart); //read data from fpga while((serData&MSP_BUSY_FLAG)!=MSP_BUSY_FLAG) { chan=(serData>>8)&0x1f; serData&=0xff; dev_dbg(&priv->pdev->dev, "%s: serialData:%x channel:%x", __func__,serData,chan); if(chan>MSP_DEVICES) { pr_info("msp_ser_read_fpga_buffer: woha! The fpga has more channels than the driver! Please set MSP_DEVICES according to your HW."); break; } wakeup[chan]=1; if(!kfifo_is_full(&priv->fifos[chan])) { dev_dbg(&priv->pdev->dev, "%s: writing into fifo serialData:%x channel:%x inbuf:%i", __func__,serData,chan,kfifo_len(&priv->fifos[chan])); kfifo_in(&priv->fifos[chan],&serData,1); dev_dbg(&priv->pdev->dev, "%s: written into fifo serialData:%x channel:%x inbuf:%i", __func__,serData,chan,kfifo_len(&priv->fifos[chan])); } else { pr_info("msp_ser_read_fpga_buffer: The fifo buffer is full. Serial data is lost!"); break; } serData=ioread16(priv->fpga->mspUart.mspUart); //read data from fpga } for(i=0;ipdev->dev, "%s: device wake_up_interruptible(%i)", __func__,i); wake_up_interruptible(&priv->wqs[i]); } } numValues=ioread16(&priv->fpga->regsGeneral.fifoMspToCpuCount); //just used to reset the interrupt request } static ssize_t msp_ser_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned lenout; struct msp_misc *misc = file->private_data; struct msp_priv *priv = misc->priv; int device_number = msp_get_device_no(priv, misc); /*(void)priv; (void)device_number;*/ dev_dbg(&priv->pdev->dev, "%s: device #%d\n", __func__, device_number); while(true) { dev_dbg(&priv->pdev->dev, "%s: device #%d checking data used:%i count:%i\n", __func__, device_number,kfifo_len(&priv->fifos[device_number]),count); if(kfifo_len(&priv->fifos[device_number])) { dev_dbg(&priv->pdev->dev, "%s: device #%d copy data to user\n", __func__, device_number); if(!kfifo_to_user(&priv->fifos[device_number],buf,count,&lenout)) { return lenout; } else { return -EFAULT; } } if(file->f_flags& O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&priv->wqs[device_number]); if(signal_pending(current)) return -EINTR; //Alas, not enough data available, going to sleep dev_dbg(&priv->pdev->dev, "%s: device #%d not enough data available going to sleep.\n", __func__, device_number); } return -EINVAL; } static ssize_t msp_ser_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int res,i; static unsigned char inbuffer[MSP_FIFO_SIZE]; struct msp_misc *misc = file->private_data; struct msp_priv *priv = misc->priv; int device_number = msp_get_device_no(priv, misc); unsigned int free=MSP_FIFO_SIZE-ioread32(&priv->fpga->regsGeneral.fifoCpuToMspCount); (void)priv; (void)device_number; dev_dbg(&priv->pdev->dev, "%s: device #%d\n", __func__, device_number); if(count>MSP_FIFO_SIZE) return -EINVAL; if(freepdev->dev, "copy_from_user"); res=copy_from_user(inbuffer,buf,count); if(res) { return res; } for(i=0;ipdev->dev, "%s: msp[%i]=%x\n", __func__, device_number,inbuffer[i]); iowrite16(inbuffer[i],&priv->fpga->mspUart.mspUart[device_number]); } return count; } static unsigned int msp_ser_poll(struct file *file,poll_table *wait) { struct msp_misc *misc = file->private_data; struct msp_priv *priv = misc->priv; unsigned int mask=0; int device_number = msp_get_device_no(priv, misc); dev_dbg(&priv->pdev->dev, "%s: using device #%d poll_wait\n", __func__, device_number); poll_wait(file,&priv->wqs[device_number],wait); if(kfifo_len(&priv->fifos[device_number])) { //we have data available for reading mask |= POLLIN |POLLRDNORM; } if((MSP_FIFO_SIZE/2-ioread32(&priv->fpga->regsGeneral.fifoCpuToMspCount))>0) { //we have space in input buffer mask |= POLLOUT |POLLWRNORM; //for writing } return mask; } static irqreturn_t msp_irq_handler(int irq,void *data) { msp_ser_read_fpga_buffer(data); return IRQ_HANDLED; } static struct file_operations msp_fops = { .owner = THIS_MODULE, .open = msp_ser_open, .release = msp_ser_release, .read = msp_ser_read, .write = msp_ser_write, .poll = msp_ser_poll, }; static struct miscdevice msp_miscdev[] = { { .minor = MISC_DYNAMIC_MINOR, .name = "msp1", .fops = &msp_fops, }, { .minor = MISC_DYNAMIC_MINOR, .name = "msp2", .fops = &msp_fops, }, { .minor = MISC_DYNAMIC_MINOR, .name = "msp3", .fops = &msp_fops, }, { .minor = MISC_DYNAMIC_MINOR, .name = "msp4", .fops = &msp_fops, }, }; static int msp_probe_cdev(struct platform_device *pdev) { struct msp_priv *priv = platform_get_drvdata(pdev); int i, err,have_misc=0; for (i = 0; i < ARRAY_SIZE(msp_miscdev); i++) { init_waitqueue_head(&priv->wqs[i]); err = misc_register(&msp_miscdev[i]); if (err) goto exit_unregister; have_misc=1; priv->misc[i].minor = msp_miscdev[i].minor; priv->misc[i].priv = priv; atomic_set(&priv->misc[i].free, 1); dev_info(&pdev->dev, "registered %s with minor %d\n", msp_miscdev[i].name, msp_miscdev[i].minor); err=kfifo_alloc(&priv->fifos[i],MSP_FIFO_SIZE,GFP_KERNEL); if(err) goto exit_unregister; have_misc=0; } return 0; exit_unregister: printk("ERROR IN CDEV\n"); for (i--; i >= 0; i--) { kfifo_free(&priv->fifos[i]); if(have_misc) misc_deregister(&msp_miscdev[i]); have_misc=1; } return err; } static int msp_probe(struct platform_device *pdev) { struct msp_priv *priv; struct resource *res; void __iomem *base; int err, irq; if (current_instance) return -EBUSY; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!res || irq <= 0) { err = -ENODEV; goto exit; } /* requesting region collides with fpga driver, but we want both drivers access the same region if (!request_mem_region(res->start, resource_size(res), pdev->name)) { pr_err("msp driver request region failed"); err = -EBUSY; goto exit; } */ base = ioremap(res->start, resource_size(res)); if (!base) { err = -ENOMEM; goto exit; } priv = kzalloc(sizeof(struct msp_priv), GFP_KERNEL); if (!priv) { err = -ENOMEM; goto exit_iounmap; } platform_set_drvdata(pdev, priv); priv->fpga = (Fpga*)base; iowrite32(MSP_IRQ_DELAY,&priv->fpga->regsGeneral.fifoCpuRdIntDelaytime); priv->pdev = pdev; current_instance = priv; if(request_irq(irq,msp_irq_handler,IRQF_SHARED|IRQF_TRIGGER_LOW,"msp",current_instance)) { err = - ENODEV; goto exit_free; } //enable_irq(irq); err = msp_probe_cdev(pdev); if (err) goto exit_irq; return 0; exit_irq: free_irq(irq,pdev); exit_free: kfree(priv); exit_iounmap: iounmap(base); /* exit_release: release_mem_region(res->start, resource_size(res)); */ exit: return err; } static void msp_remove_cdev(struct platform_device *pdev) { int i; for (i = ARRAY_SIZE(msp_miscdev) - 1; i >= 0; i--) misc_deregister(&msp_miscdev[i]); } static int msp_remove(struct platform_device *pdev) { struct msp_priv *priv = platform_get_drvdata(pdev); struct resource *res; int irq = platform_get_irq(pdev, 0); msp_remove_cdev(pdev); iounmap(priv->fpga); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //release_mem_region(res->start, resource_size(res)); free_irq(irq,pdev); current_instance = NULL; kfree(priv); return 0; } static struct platform_driver msp_driver = { .probe = msp_probe, .remove = msp_remove, .driver.name = DRV_NAME, }; /* this should go into the board file - START - */ static struct resource msp_resources[] = { { .start = FPGA_BASE, .end = FPGA_BASE + FPGA_LEN - 1, .flags = IORESOURCE_MEM, }, { .start = 132, //FIXME gpio_to_irq(GPIO_IRQ_MSP), .end = 132, //FIXME gpio_to_irq(GPIO_IRQ_MSP), .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_LOWLEVEL, }, }; /* not needed in board file */ static void msp_release(struct device *dev) { dev_info(dev, "%s\n", __func__); } static struct platform_device msp_device = { .name = DRV_NAME, .id = -1, .resource = msp_resources, .num_resources = ARRAY_SIZE(msp_resources), .dev.release = msp_release, }; static int msp_device_register(void) { return platform_device_register(&msp_device); } /* not needed in board file */ static void msp_device_unregister(void) { platform_device_unregister(&msp_device); } /* this should go into the board file - END - */ static int __init msp_init(void) { int err; pr_info("%s 'serial' driver loaded\n", DRV_NAME); err = msp_device_register(); if (err) goto exit; err = platform_driver_register(&msp_driver); if (err) { dev_info("%s platform register failed\n",DRV_NAME); goto exit_unregister; } return 0; exit_unregister: msp_device_unregister(); exit: return err; } static void __exit msp_exit(void) { msp_device_unregister(); platform_driver_unregister(&msp_driver); pr_info("%s 'serial' driver removed\n", DRV_NAME); } module_init(msp_init); module_exit(msp_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("msp serial driver"); MODULE_AUTHOR("Tim Sander"); MODULE_ALIAS("devname:msp"); MODULE_ALIAS("platform:msp");