Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* luscher.c - A char type DMA device using link list
- */
- // source: https://forums.xilinx.com/t5/Embedded-Linux/AXI-DMA-with-Zynq-Running-Linux/m-p/522755/highlight/true#M10649
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/io.h>
- #include <linux/dma-mapping.h>
- #include <linux/interrupt.h>
- #include <linux/fs.h>
- #include <linux/proc_fs.h>
- #include <linux/errno.h> /* error codes */
- #include <linux/types.h> /* size_t */
- #include <linux/fcntl.h> /* O_ACCMODE */
- #include <linux/seq_file.h>
- #include <linux/cdev.h>
- #include <asm/uaccess.h>
- #include <linux/of_address.h>
- #include <linux/of_device.h>
- #include <linux/of_platform.h>
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR
- ("Dave Warren - Luscher UK ltd");
- MODULE_DESCRIPTION
- ("luscher - loadable module dma driver");
- #define DRIVER_NAME "luscher"
- /* DMA state */
- #define DMA_IDLE 1 // Can accept data
- #define DMA_STARTED 2
- #define DMA_DATA_COMPLETE 3 // All buffers here
- /* AXI DMA defs */
- /* Offsets to registers (as long), simple mode */
- #define MM2S_DMACR 0 // Control Reg
- #define MM2S_DMASR 1 // Status reg
- #define MM2S_SA 6 // Start address
- #define MM2S_LENGHT 10 // DMA length
- /* Extra for SG mode DMA */
- #define MM2S_CDESC 2 // Current descriptor ptr (write before DMA, read only while DMA active)
- #define MM2S_TDESC 4 // Tail descriptor ptr (write to start DMA)
- /* Control Bits */
- #define DMACR_RUNSTOP 1<<0
- #define DMACR_RESET 1<<2
- /* Status bits */
- #define DMASR_HALTED 1<<0
- #define DMASR_IDLE 1<<1
- /* Descriptor offsets */
- #define DESC_SIZE 0x40 // Size of a descriptor
- #define DESC_NXTDESC 0 // Pointer to next
- #define DESC_BUFFER_ADDRESS 8 // Data address
- #define DESC_CONTROL 0x18 //
- #define DESC_STATUS 0x1c //
- /* DESC_CONTROL FLAGS */
- #define TXSOF 1<<27 // Start of frame
- #define TXEOF 1<<26 // End of frame
- /* DESC_STATUS FLAGS */
- #define CMPLT 1<<31 // Completed
- /*
- * Ioctl definitions
- */
- /* Use 0xAA as magic number */
- #define LUSCHER_IOC_MAGIC 0xAA
- #define LUSCHER_IOCRESET _IO(LUSCHER_IOC_MAGIC, 0)
- #define LUSCHER_SETSIZE _IOW(LUSCHER_IOC_MAGIC, 1, int)
- #define LUSCHER_IOC_MAXNR 1
- int luscher_major = 0; /* major device node */
- int luscher_minor = 0;
- // Private data allocated on module load
- struct luscher_local {
- // The platform device pointer
- //struct platform_device *pdev;
- // The AXI DMA
- int irq;
- unsigned long mem_start;
- unsigned long mem_end;
- void __iomem *base_addr;
- // The allocated DMA buffers
- u32 buff_size;
- u32 buff_number;
- phys_addr_t b_phys; /* buffer physical */
- void __iomem *b_virt; /* buffer virtual */
- int bsize; /* size ask to alloc */
- // DMA info
- int dma_bytes; // Size of DMA
- int dma_b_pos; // where we are in current buffer
- int dma_state; // what we are doing
- int dma_next_in;// next buffer
- // File info
- wait_queue_head_t inq; /* input que for block */
- struct semaphore sem; /* mutual exclusion semaphore */
- struct cdev cdev; /* Char device structure */
- };
- /* prototypes of functions */
- ssize_t luscher_write(struct file *filp, const char __user *buf, size_t count,
- loff_t *f_pos);
- //ssize_t luscher_read(struct file *filp, const char __user *buf, size_t count,
- // loff_t *f_pos);
- long luscher_ioctl(struct file *filp,
- unsigned int cmd, unsigned long arg);
- int luscher_open(struct inode *inode, struct file *filp);
- int luscher_release(struct inode *inode, struct file *filp);
- static int luscher_init(void);
- static void luscher_exit(void);
- struct file_operations luscher_fops = {
- .owner = THIS_MODULE,
- //.llseek = scull_llseek,
- //.read = luscher_read,
- .write = luscher_write,
- .compat_ioctl = luscher_ioctl, /* not called */
- .unlocked_ioctl = luscher_ioctl,
- .open = luscher_open,
- .release = luscher_release,
- };
- /*
- * Open and close
- */
- int luscher_open(struct inode *inode, struct file *filp)
- {
- struct luscher_local *lp; /* device information */
- lp = container_of(inode->i_cdev, struct luscher_local, cdev);
- filp->private_data = lp; /* for other methods */
- printk("open\n");
- return 0; /* success */
- }
- int luscher_release(struct inode *inode, struct file *filp)
- {
- struct luscher_local *lp = filp->private_data;
- int y;
- int *dma_reg;
- int *p;
- p = (int *)(lp->b_virt + (lp->buff_size * lp->buff_number));
- dma_reg = (int *)lp->base_addr;
- dma_reg[MM2S_DMACR] = DMACR_RESET;
- lp->dma_next_in = 0;
- lp->dma_b_pos = 0;
- dma_reg[MM2S_DMACR] = 0;
- lp->dma_state = DMA_IDLE;
- lp->dma_bytes = 0x100000;
- printk("release\n");
- for(y=0; y < lp->buff_number; y++)
- p[(DESC_STATUS + (DESC_SIZE * y))/4] = CMPLT; // mark as empty
- return 0;
- }
- //ssize_t luscher_read(struct file *filp, const char __user *buf, size_t count,
- // loff_t *f_pos)
- //{
- // return count;
- //}
- ssize_t luscher_write(struct file *filp, const char __user *buf, size_t count,
- loff_t *f_pos)
- {
- struct luscher_local *lp = filp->private_data;
- int last_buffer = 0;
- int *p; /* pointer to buffer mem */
- int *dma_reg; /* pointer to DMA registers */
- int dma_status;
- int control;
- int y;
- int start_dma = 0;
- ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
- p = (int *)(lp->b_virt + (lp->buff_size * lp->buff_number));
- dma_reg = (int *)lp->base_addr;
- /*return count; /* hack */
- if (down_interruptible(&lp->sem))
- return -ERESTARTSYS;
- if (lp->dma_state == DMA_DATA_COMPLETE)
- goto out;
- /* Do blocking write if no space in the buffer */
- /* control = p[(DESC_STATUS + (DESC_SIZE * lp->dma_next_in))/4];
- printk("control %x", control); */
- while ((p[(DESC_STATUS + (DESC_SIZE * lp->dma_next_in))/4] & CMPLT) == 0){ /* buffer not free */
- up(&lp->sem); /* release the lock */
- if (filp->f_flags & O_NONBLOCK)
- return -EAGAIN;
- wait_event_interruptible_timeout(lp->inq,
- ( p[(DESC_STATUS + (DESC_SIZE * lp->dma_next_in))/4] & CMPLT ) , 2 );
- //if (wait_event_interruptible(lp->inq,
- // ( p[(DESC_STATUS + (DESC_SIZE * lp->dma_next_in))/4] & CMPLT ) ) )
- // return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
- /* otherwise loop, but first reacquire the lock */
- if (down_interruptible(&lp->sem))
- return -ERESTARTSYS;
- }
- /* write only up to the end of this dma */
- if ((count >= lp->dma_bytes ) && (count <= lp->buff_size - lp->dma_b_pos)){
- last_buffer = 2;
- count = lp->dma_bytes;
- }
- /* write to end of buffer */
- else if (count >= lp->buff_size - lp->dma_b_pos) {
- last_buffer = 1;
- count = lp->buff_size - lp->dma_b_pos;
- }
- if (copy_from_user(lp->b_virt + (lp->dma_next_in * lp->buff_size) + lp->dma_b_pos, buf, count)) {
- retval = -EFAULT;
- goto out;
- }
- *f_pos += count;
- lp->dma_bytes -= count;
- lp->dma_b_pos += count;
- if(last_buffer ) { // switch to next buffer, and advance DMA pointer
- //printk("dma status %x cdesc %x\n", dma_reg[MM2S_DMASR],dma_reg[MM2S_CDESC]);
- p[(DESC_STATUS + (DESC_SIZE * lp->dma_next_in))/4] = 0; /* buffer full */
- control = lp->dma_b_pos;
- if(lp->dma_state == DMA_IDLE) {
- control |= TXSOF ;
- lp->dma_state = DMA_STARTED;
- wmb();
- printk("DMA_STARTED\n");
- start_dma = 1;
- //for(y = 0; y < (lp->buff_number * DESC_SIZE / 4) ; y = y + 4)
- // printk("%x %x %x %x\n", p[y], p[y + 1], p[y + 2], p[y+3]);
- }
- if(last_buffer == 2) {
- control |= TXEOF;
- lp->dma_state = DMA_DATA_COMPLETE;
- wmb();
- printk("DMA_DATA_COMPLETE\n");
- }
- p[(DESC_CONTROL + (DESC_SIZE * lp->dma_next_in))/4] = control; /* size */
- if (start_dma) {
- dma_status = dma_reg[MM2S_DMASR];
- if(dma_status & DMASR_HALTED) {
- dma_reg[MM2S_CDESC] = (lp->b_phys + (lp->buff_size * lp->buff_number));
- wmb();
- dma_reg[MM2S_DMACR] = DMACR_RUNSTOP; /* Start DMA */
- do {
- dma_status = dma_reg[MM2S_DMASR];
- }
- while(dma_status & DMASR_HALTED) ;
- }
- }
- //dma_map_single(&lp->pdev->dev, lp->b_virt + (lp->dma_next_in * lp->buff_size),
- // lp->buff_size, DMA_TO_DEVICE);
- wmb();
- dma_reg[MM2S_TDESC] = lp->b_phys + (lp->buff_size * lp->buff_number)
- + (DESC_SIZE * lp->dma_next_in); /* tail pointer advance */
- if(++lp->dma_next_in >= lp->buff_number)
- lp->dma_next_in = 0;
- lp->dma_b_pos = 0;
- //printk("nextIn %d\n", lp->dma_next_in);
- }
- retval = count;
- out:
- up(&lp->sem);
- return retval;
- }
- /*
- * The ioctl() implementation
- */
- long luscher_ioctl(/*struct inode *inode, */ struct file *filp,
- unsigned int cmd, unsigned long arg)
- {
- struct luscher_local *lp = filp->private_data;
- int *p; /* pointer to buffer mem */
- int *dma_reg; /* pointer to DMA registers */
- int err = 0, y;
- long retval = 0;
- /* printk("ioctl %d\n", cmd); /* debug ioctrl */
- /*
- * extract the type and number bitfields, and don't decode
- * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
- */
- if (_IOC_TYPE(cmd) != LUSCHER_IOC_MAGIC) return -ENOTTY;
- if (_IOC_NR(cmd) > LUSCHER_IOC_MAXNR) return -ENOTTY;
- /*
- * the direction is a bitmask, and VERIFY_WRITE catches R/W
- * transfers. `Type' is user-oriented, while
- * access_ok is kernel-oriented, so the concept of "read" and
- * "write" is reversed
- */
- if (_IOC_DIR(cmd) & _IOC_READ)
- err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
- else if (_IOC_DIR(cmd) & _IOC_WRITE)
- err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
- if (err) return -EFAULT;
- p = (int *)(lp->b_virt + (lp->buff_size * lp->buff_number));
- dma_reg = (int *)lp->base_addr;
- switch(cmd) {
- case LUSCHER_IOCRESET: /* Reset hardware and pointers, abort the DMA */
- dma_reg[MM2S_DMACR] = DMACR_RESET;
- lp->dma_next_in = 0;
- lp->dma_b_pos = 0;
- dma_reg[MM2S_DMACR] = 0;
- lp->dma_state = DMA_DATA_COMPLETE; /* any write returns */
- lp->dma_bytes = 0x100000;
- for(y=0; y < lp->buff_number; y++)
- p[(DESC_STATUS + (DESC_SIZE * y))/4] = CMPLT; // mark as empty
- //for(y = 0; y < (lp->buff_number * DESC_SIZE / 4) ; y = y + 4)
- // printk("%x %x %x %x\n", p[y], p[y + 1], p[y + 2], p[y+3]);
- /* printk("ioctl - reset\n"); */
- break;
- case LUSCHER_SETSIZE: /* Set: size of transfer */
- dma_reg[MM2S_DMACR] = DMACR_RESET;
- lp->dma_next_in = 0;
- lp->dma_b_pos = 0;
- dma_reg[MM2S_DMACR] = 0;
- lp->dma_state = DMA_IDLE;
- for(y=0; y < lp->buff_number; y++)
- p[(DESC_STATUS + (DESC_SIZE * y))/4] = CMPLT; // mark as empty
- /* for(y = 0; y < (lp->buff_number * DESC_SIZE / 4) ; y = y + 4)
- printk("%x %x %x %x\n", p[y], p[y + 1], p[y + 2], p[y+3]); */
- retval = __get_user(lp->dma_bytes, (int __user *)arg);
- /* printk("ioctl - DMA %d bytes\n", lp->dma_bytes); */
- break;
- default: /* redundant, as cmd was checked against MAXNR */
- return -ENOTTY;
- }
- return retval;
- }
- static irqreturn_t luscher_irq(int irq, void *lp)
- { // int service
- printk("luscher interrupt\n");
- return IRQ_HANDLED;
- }
- static int luscher_probe(struct platform_device *pdev)
- {
- struct resource *r_irq; /* Interrupt resources */
- struct resource *r_mem; /* IO mem resources */
- struct device *dev = &pdev->dev;
- struct luscher_local *lp = NULL;
- struct device_node *np = pdev->dev.of_node;
- dev_t devno = 0;
- int y;
- int *p;
- unsigned int x;
- int rc = 0;
- rc = LUSCHER_IOCRESET;
- y = LUSCHER_SETSIZE;
- /* printk(" IOCTL %d %d\n",rc,y); */
- /* Dynamic major minor device numbers */
- rc = alloc_chrdev_region(&devno, luscher_minor, 1,
- "luscher");
- luscher_major = MAJOR(devno);
- if (rc < 0) {
- printk(KERN_WARNING "luscher: can't get major %d\n", luscher_major);
- return rc;
- }
- dev_info(dev, "Device Tree Probing\n");
- /* Get iospace for the device */
- r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!r_mem) {
- dev_err(dev, "invalid address\n");
- return -ENODEV;
- }
- lp = (struct luscher_local *) kmalloc(sizeof(struct luscher_local), GFP_KERNEL);
- if (!lp) {
- dev_err(dev, "Cound not allocate luscher local memory\n");
- return -ENOMEM;
- }
- dev_set_drvdata(dev, lp); /* set private data */
- lp->mem_start = r_mem->start;
- lp->mem_end = r_mem->end;
- if (!request_mem_region(lp->mem_start,
- lp->mem_end - lp->mem_start + 1,
- DRIVER_NAME)) {
- dev_err(dev, "Couldn't lock memory region at %p\n",
- (void *)lp->mem_start);
- rc = -EBUSY;
- goto error1;
- }
- lp->base_addr = ioremap(lp->mem_start, lp->mem_end - lp->mem_start + 1);
- if (!lp->base_addr) {
- dev_err(dev, "luscher: Could not allocate iomem\n");
- rc = -EIO;
- goto error2;
- }
- /* Get IRQ for the device */
- r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (!r_irq) {
- dev_info(dev, "no IRQ found\n");
- dev_info(dev, "luscher at 0x%08x mapped to 0x%08x\n",
- (unsigned int __force)lp->mem_start,
- (unsigned int __force)lp->base_addr);
- return 0;
- }
- lp->irq = r_irq->start;
- rc = request_irq(lp->irq, &luscher_irq, 0, DRIVER_NAME, lp);
- if (rc) {
- dev_err(dev, "testmodule: Could not allocate interrupt %d.\n",
- lp->irq);
- goto error3;
- }
- dev_info(dev,"luscher at 0x%08x mapped to 0x%08x, irq=%d\n",
- (unsigned int __force)lp->mem_start,
- (unsigned int __force)lp->base_addr,
- lp->irq);
- of_property_read_u32(np, "buff-size", &lp->buff_size);
- printk(" buff size 0x%08x\n", lp->buff_size);
- of_property_read_u32(np, "buff-number", &lp->buff_number);
- printk(" buff number 0x%08x\n", lp->buff_number);
- /* ask for the buffers and a descriptor chain */
- lp->bsize = (lp->buff_size * lp->buff_number) + (lp->buff_number * DESC_SIZE);
- /* try to alloc the buffer */
- lp->b_virt = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(lp->bsize),
- &lp->b_phys, GFP_KERNEL);
- if (!lp->b_virt) {
- dev_err(&pdev->dev,
- "Buffer memory allocation failed\n");
- printk("Buffer memory allocation failed\n");
- rc = -ENOMEM;
- goto error3;
- }
- printk("DMA buffer at %x size %d\n", lp->b_phys, lp->bsize);
- printk("SG table at %x\n", (lp->b_phys + (lp->buff_size * lp->buff_number)));
- // clear buffer
- memset(lp->b_virt,0,lp->bsize);
- /* fill in some addresses */
- p = (int *)(lp->b_virt + (lp->buff_size * lp->buff_number));
- x = (unsigned int) lp->b_phys;
- for(y=0; y < lp->buff_number; y++)
- { // circular linked list
- p[(DESC_CONTROL + (DESC_SIZE * y))/4] = lp->buff_size;
- p[(DESC_STATUS + (DESC_SIZE * y))/4] = CMPLT; // mark as empty
- p[(DESC_BUFFER_ADDRESS + (DESC_SIZE * y))/4] = x + (y * lp->buff_size); // physical address
- if((y + 1) == lp->buff_number)
- p[(DESC_NXTDESC + (DESC_SIZE * y))/4] = x + (lp->buff_number * lp->buff_size); // back to top
- else
- p[(DESC_NXTDESC + (DESC_SIZE * y))/4] = x + (lp->buff_number * lp->buff_size) + ((y + 1) * DESC_SIZE);
- }
- lp->dma_state = DMA_DATA_COMPLETE;
- lp->dma_bytes = 0x1000;
- lp->dma_b_pos = 0;
- lp->dma_next_in = 0;
- //lp->pdev = pdev;
- sema_init(&lp->sem, 1); /* set the semaphore */
- init_waitqueue_head(&lp->inq); /* init the que for blocking */
- /* register the file ops (things can start) */
- cdev_init(&lp->cdev, &luscher_fops);
- lp->cdev.owner = THIS_MODULE;
- lp->cdev.ops = &luscher_fops;
- /* Fail gracefully if need be */
- if ((rc = cdev_add (&lp->cdev, devno, 1)))
- printk(KERN_NOTICE "Error %d adding fops", rc);
- return 0; // All ok
- error3:
- free_irq(lp->irq, lp);
- error2:
- release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
- error1:
- kfree(lp);
- dev_set_drvdata(dev, NULL);
- unregister_chrdev_region(devno, 1);
- return rc;
- }
- static int luscher_remove(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct luscher_local *lp = dev_get_drvdata(dev);
- dev_t devno = MKDEV(luscher_major, luscher_minor);
- dma_free_coherent(&pdev->dev, PAGE_ALIGN(lp->bsize), lp->b_virt,
- lp->b_phys);
- free_irq(lp->irq, lp);
- release_mem_region(lp->mem_start, lp->mem_end - lp->mem_start + 1);
- kfree(lp);
- dev_set_drvdata(dev, NULL);
- /* cleanup_module is never called if registering failed */
- unregister_chrdev_region(devno, 1);
- return 0;
- }
- #ifdef CONFIG_OF
- static struct of_device_id luscher_of_match[] = {
- { .compatible = "vendor,luscher", },
- { /* end of list */ },
- };
- MODULE_DEVICE_TABLE(of, luscher_of_match);
- #else
- # define luscher_of_match
- #endif
- static struct platform_driver luscher_driver = {
- .driver = {
- .name = DRIVER_NAME,
- .owner = THIS_MODULE,
- .of_match_table = luscher_of_match,
- },
- .probe = luscher_probe,
- .remove = luscher_remove,
- };
- static int __init luscher_init(void)
- {
- /* printk("<1>Hello module world.\n"); */
- return platform_driver_register(&luscher_driver);
- }
- static void __exit luscher_exit(void)
- {
- platform_driver_unregister(&luscher_driver);
- /* printk(KERN_ALERT "Goodbye module world.\n"); */
- }
- module_init(luscher_init);
- module_exit(luscher_exit);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement