/* * ppci.c : Linux driver for PCI parallel interfaces driver based on TTL * and LVDS called Fird P-PCI and P-PCI-LV, respectively. * Copyright 2001 by Yoshiji Yasu, . * * This progam is free software; you can redistribute it and/or * modify it. * * version: 0.97 19-Jul-2002, Yoshiji.YASU@kek.jp, * http://www-online.kek.jp/~yasu/BELLE/ppci.html */ #include #include #include #include #include #include #include "ppci.h" //#define DEBUG // Debug flag #define MAX_BUFFER_SIZE 128*1024 #define PPCI_IRQ_SHARED 1 // This is used for shared IRQ. #define MIN_SIZE 4 // Minumum transfer size is 4 bytes. #define MAX_DEVICE 4 // Maximum number of P-PCI devices in a PC. /* * Minor numer corresponds to the value i of a database ppcidev[i]. * This means ppcidev[0] corresponds to the database * for device ppci0(minor number=0). */ struct ppci_device { char *io_base; unsigned int irq; char *name; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) wait_queue_head_t waitq; #else struct wait_queue *waitq; #endif struct ppci ppci; int status; int intcounter; }; static int needed_pages; static char *data_buf[MAX_DEVICE]; // Pointer for DMA data buffer static int found_card=0; // Actual number inserted in PC. static struct ppci_device ppcidev[MAX_DEVICE]; // Driver database for P-PCI /* * This print contents of P-PCI registers out a PC console. * You can see those message by typeing a dmesg command. */ void dump_reg( int minor ) { printk("Output data = %x\n", inl(ppcidev[minor].ppci.data)); printk("LED = %x\n", inl(ppcidev[minor].ppci.led)); printk("Switch Register = %x\n", inl(ppcidev[minor].ppci.sw)); printk("Transfer Address = %x\n", inl(ppcidev[minor].ppci.address)); printk("Transfer Count = %x\n", inl(ppcidev[minor].ppci.length)); printk("Mode = %x\n", inl(ppcidev[minor].ppci.mode)); printk("Status = %x\n", inl(ppcidev[minor].ppci.status)); printk("Actual transfer count = %x\n", inl(ppcidev[minor].ppci.retlen)); printk("Size of data in FIFO = %x\n", inl(ppcidev[minor].ppci.fifolen)); } /* * This clears an interrupt for completion of transfer. */ void disable_intppci(struct ppci_device *own_ppcidev) { int data; data = inl(own_ppcidev->ppci.mode); data |= MODE_INTCLR; outl(data, own_ppcidev->ppci.mode); } /* * This enables an interrupt for normal completion of transfer. */ void enable_intppci_end(struct ppci_device *own_ppcidev) { int data; data = inl(own_ppcidev->ppci.mode); data &= ~MODE_ENDMSK; outl(data, own_ppcidev->ppci.mode); } /* * This enables an interrupt for forced completion of transfer. */ void enable_intppci_stop(struct ppci_device *own_ppcidev) { int data; data = inl(own_ppcidev->ppci.mode); data &= ~MODE_STPMSK; outl(data, own_ppcidev->ppci.mode); } /* * This enables an interrupt for starting request of transfer. * This device driver does not use the interrupt now. */ void enable_intppci_start(struct ppci_device *own_ppcidev) { int data; data = inl(own_ppcidev->ppci.mode); data &= ~MODE_RSTTMSK; outl(data, own_ppcidev->ppci.mode); } /* * This enables interrupts for starting request, normal completion * and forced completion of transfer. */ void enable_intppci_all(struct ppci_device *own_ppcidev) { int data; data = inl(own_ppcidev->ppci.mode); data &= ~(MODE_STPMSK|MODE_RSTTMSK|MODE_ENDMSK); outl(data, own_ppcidev->ppci.mode); } /* * This enables interrupts for normal completion and * forced completion of transfer. This device driver uses * the routine for enabling the interrupts. */ void enable_intppci(struct ppci_device *own_ppcidev) { int data; data = inl(own_ppcidev->ppci.mode); data &= ~(MODE_STPMSK|MODE_ENDMSK); outl(data, own_ppcidev->ppci.mode); data = inl(own_ppcidev->ppci.mode); } /* * This is an interrupt method registered at loading this device driver. */ static void ppci_interrupt(int irq, struct ppci_device *own_ppcidev, struct pt_regs *regs) { int status, mode; status = inl(own_ppcidev->ppci.status); if(!(status & (STATUS_STPINT|STATUS_ENDINT))) { // No interrupt from PPCI // printk("No interrupt from PPCI\n"); return ; } #ifdef DEBUG printk("ppci_interrupt : interrupt occurred...\n"); #endif own_ppcidev->status = inl(own_ppcidev->ppci.status); disable_intppci(own_ppcidev); // Disable Interrupt of PPCI mode = inl(own_ppcidev->ppci.mode); mode &= ~MODE_MSTSTT; outl(mode, own_ppcidev->ppci.mode); // Force to stop the transfer anyway. while (irq != own_ppcidev->irq) { printk("unexpected interrupt...\n"); return; } wake_up_interruptible(&own_ppcidev->waitq); } /* * This is "open" sytem call interface. */ static int ppci_open(struct inode *inode, struct file * file) { // MOD_INC_USE_COUNT; return 0; } /* * This is "close" sytem call interface. */ static int ppci_release(struct inode * inode, struct file * file) { int minor; printk("release:enter...\n"); // MOD_DEC_USE_COUNT; // release PRV right of P-PCI. minor = MINOR(inode->i_rdev); outl( MODE_ENDMSK|MODE_STPMSK|MODE_INTCLR|MODE_PRV, ppcidev[minor].ppci.mode); // release PRV and disable interrupt. return 0; } /* * Initial mode of P-PCI * Direction of data : 32-bit Input * Control right : this board gets if possible. * run mode : master mode */ int ppci_get_control_input(struct ppci *ppci) { int status; int mode; // set 1 into RUNMOD and then set 0 into PRV for getting PRV. outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK, ppci->mode); outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK, ppci->mode); status = inl(ppci->status); #ifdef DEBUG printk("ppci->status = %x\n", status); #endif mode = inl(ppci->mode); #ifdef DEBUG printk("ppci->mode = %x\n", mode); #endif if(status & STATUS_PRV) { // another board has the control right. outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_PRV, ppci->mode); // status = inl(ppci->status); #ifdef DEBUG printk("reset_control_input: non-PRV...\n"); #endif } else { // this board has the control right. #ifdef DEBUG printk("reset_control_input: PRV...\n"); #endif } if(status & STATUS_PRV) { // another board has the control right. if(!(status & STATUS_DIR)) { // this indicates output. So, it is invalid. printk("ppci:input:Direction fail...\n"); printk("ppci:input:status = %x\n", status); return -EINVAL; } } else { // this board has the control right. if(status & STATUS_DIR) { // this indicates output. So, it is invalid. printk("ppci:input:Direction fail...\n"); printk("ppci:input:status = %x\n", status); return -EINVAL; } } return 0; } /* * Initial mode of P-PCI * Direction of data : 32-bit Output * Control right : this board gets if possible. * run mode : master mode */ int ppci_get_control_output(struct ppci *ppci) { int status; int mode; // set 1 into RUNMOD and then set 0 into PRV for getting PRV. outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_DIR0|MODE_DIR1|MODE_DIR2|MODE_DIR3|MODE_DIR, ppci->mode); outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_DIR0|MODE_DIR1|MODE_DIR2|MODE_DIR3|MODE_DIR, ppci->mode); status = inl(ppci->status); #ifdef DEBUG printk("ppci->status = %x\n", status); #endif mode = inl(ppci->mode); #ifdef DEBUG printk("ppci->mode = %x\n", mode); #endif if(status & STATUS_PRV) { // another device has the control right. outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_DIR0|MODE_DIR1|MODE_DIR2|MODE_DIR3|MODE_DIR|MODE_PRV, ppci->mode); // status = inl(ppci->status); #ifdef DEBUG printk("reset_control_output: non-PRV...\n"); #endif } else { // this board has the control right. #ifdef DEBUG printk("reset_control_output: PRV...\n"); #endif } if(status & STATUS_PRV) { // another board has the control right. if(status & STATUS_DIR) { // this indicates input. Thus, it is invalid. printk("ppci:output:Direction fail...\n"); return -EINVAL; } } else { // this board has the control right. if(!(status & STATUS_DIR)) { // this indicates input. Thus, it is invalid. printk("ppci:output:Direction fail...\n"); return -EINVAL; } } return 0; } /* * Initial mode of P-PCI * Direction of data : 32-bit Input * Control right : external * run mode : master mode */ void ppci_release_control_input(struct ppci *ppci) { // set 1 into RUNMOD and then set 1 into PRV for releasing PRV. outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_PRV, ppci->mode); outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_PRV, ppci->mode); } /* * Initial mode of P-PCI * Direction of data : 32-bit Output * Control right : external * run mode : master mode */ void ppci_release_control_output(struct ppci *ppci) { // set 1 into RUNMOD and then set 1 into PRV for releasing PRV. outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_PRV|MODE_DIR0|MODE_DIR1|MODE_DIR2|MODE_DIR3|MODE_DIR, ppci->mode); outl(MODE_RUNMOD|MODE_INTCLR|MODE_RSTTMSK|MODE_STPMSK|MODE_ENDMSK|MODE_PRV|MODE_DIR0|MODE_DIR1|MODE_DIR2|MODE_DIR3|MODE_DIR, ppci->mode); } /* * This sets intial mode into the mode register of P-PCI. */ int ppci_reset(struct ppci *ppci, int direction) { if( direction ) // output return ppci_get_control_output(ppci); else return ppci_get_control_input(ppci); } void ppci_release_control(struct ppci *ppci, int direction) { if( direction ) // output ppci_release_control_output(ppci); else ppci_release_control_input(ppci); } /* * This is "ioctl" system call interface. */ static int ppci_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int retval = 0; int minor; int data; struct ppci ppci; minor = MINOR(inode->i_rdev); switch ( cmd ) { case PPCIIOC_RESET : retval = ppci_reset(&ppcidev[minor].ppci, arg); #ifdef DEBUG printk("PPCI: Reset function...\n"); dump_reg(minor); #endif break; case PPCIIOC_PUT_DATA: if (copy_from_user(&data, (int *)arg, sizeof(int))) return -EFAULT; #ifdef DEBUG printk("ppci:put data = %x\n", data); #endif outl(data, ppcidev[minor].ppci.data); break; case PPCIIOC_GET_DATA: data = inl(ppcidev[minor].ppci.data); #ifdef DEBUG printk("ppci:get data = %x\n", data); #endif if (copy_to_user((int *)arg, &data, sizeof(int))) return -EFAULT; break; case PPCIIOC_RELEASE_CONTROL: ppci_release_control(&ppcidev[minor].ppci, arg); #ifdef DEBUG printk("PPCI: Release control function...\n"); dump_reg(minor); #endif break; case PPCIIOC_DUMP_REGISTERS: ppci.data = inl(ppcidev[minor].ppci.data); ppci.led = inl(ppcidev[minor].ppci.led); ppci.sw = inl(ppcidev[minor].ppci.sw); ppci.address = inl(ppcidev[minor].ppci.address); ppci.length = inl(ppcidev[minor].ppci.length); ppci.mode = inl(ppcidev[minor].ppci.mode); ppci.status = inl(ppcidev[minor].ppci.status); ppci.retlen = inl(ppcidev[minor].ppci.retlen); ppci.fifolen = inl(ppcidev[minor].ppci.fifolen); if (copy_to_user((int *)arg, &ppci, sizeof(struct ppci))) return -EFAULT; break; case PPCIIOC_FIFO_CLEAR: data = inl(ppcidev[minor].ppci.mode); data |= MODE_FIFOCLR; outl(data, ppcidev[minor].ppci.mode); break; default: retval = -EINVAL; }; return retval; } /* * This calculates number of page from byte length. * This is not used now. */ static inline int __get_order(unsigned long size) { int order; size = (size-1) >> (PAGE_SHIFT-1); order = -1; do { size >>= 1; order++; } while (size); return order; } /* * This is "read" system call interface. * The return value is the actually transferred count in bytes. * At the moment, the return value is the specified count * because the word count of P-PCI does not count actual number of * transferred data. In near future, this function will be * implemented. */ static ssize_t ppci_read (struct file *file, char *buf, size_t count, loff_t *ppos) { int minor, length, data; minor = MINOR(file->f_dentry->d_inode->i_rdev); if( count > MAX_BUFFER_SIZE || count < MIN_SIZE ) return -EINVAL; enable_intppci(&ppcidev[minor]); outl( virt_to_phys(data_buf[minor]), ppcidev[minor].ppci.address ); outl( count-1, ppcidev[minor].ppci.length); data = inl(ppcidev[minor].ppci.mode); data |= MODE_MSTSTT; cli(); ppcidev[minor].status = 0; outl(data, ppcidev[minor].ppci.mode); interruptible_sleep_on_timeout(&ppcidev[minor].waitq, PPCI_TIMEOUT); #ifdef DEBUG if(ppcidev[minor].status & STATUS_ENDINT) printk("Transfer was successfully done\n"); if(ppcidev[minor].status & STATUS_STPINT) printk("Transfer was forced to be done\n"); #endif #ifdef DEBUG printk("ppci:read:read data_buf[0],[1],[2],[3],[4],[5],[6],[7]= %x %x %x %x %x %x %x %x\n", data_buf[minor][0],data_buf[minor][1],data_buf[minor][2],data_buf[minor][3],data_buf[minor][4],data_buf[minor][5],data_buf[minor][6],data_buf[minor][7]); #endif if(!(ppcidev[minor].status & (STATUS_ENDINT| STATUS_STPINT))) { #ifdef DEBUG printk("ppci_read:Timeout occurred...\n"); #endif return -EFAULT; } length = inl(ppcidev[minor].ppci.retlen); if (copy_to_user(buf, data_buf[minor], length)) return -EFAULT; #ifdef DEBUG printk("ppci_read:exit...\n"); #endif return length; } /* * This is "write" system call interface. * The return value is the actually transferred count in bytes. * At the moment, the return value is the specified count * because the word count of P-PCI does not count actual number of * transferred data. In near future, this function will be * implemented. */ static ssize_t ppci_write (struct file *file, const char *buf, size_t count, loff_t *ppos) { int minor, length, data; minor = MINOR(file->f_dentry->d_inode->i_rdev); if( count > MAX_BUFFER_SIZE || count < MIN_SIZE ) return -EINVAL; if (copy_from_user(data_buf[minor], buf, count)) return -EFAULT; #ifdef DEBUG printk("ppci:read:read data_buf[0],[1],[2],[3],[4],[5],[6],[7]= %x %x %x %x %x %x %x %x\n", data_buf[minor][0],data_buf[minor][1],data_buf[minor][2],data_buf[minor][3],data_buf[minor][4],data_buf[minor][5],data_buf[minor][6],data_buf[minor][7]); #endif enable_intppci(&ppcidev[minor]); outl( virt_to_phys(data_buf[minor]), ppcidev[minor].ppci.address ); outl( count-1, ppcidev[minor].ppci.length); data = inl(ppcidev[minor].ppci.mode); data |= MODE_MSTSTT; cli(); ppcidev[minor].status = 0; outl(data, ppcidev[minor].ppci.mode); interruptible_sleep_on_timeout(&ppcidev[minor].waitq, PPCI_TIMEOUT); #ifdef DEBUG if(ppcidev[minor].status & STATUS_ENDINT) printk("Transfer was successfully done\n"); if(ppcidev[minor].status & STATUS_STPINT) printk("Transfer was forced to be done\n"); #endif if(!(ppcidev[minor].status & (STATUS_ENDINT| STATUS_STPINT))) { #ifdef DEBUG printk("ppci_write:Timeout occurred...\n"); #endif return -EFAULT; } length = inl(ppcidev[minor].ppci.retlen); return length; } static struct file_operations ppci_fops = { ioctl: ppci_ioctl, open: ppci_open, release: ppci_release, read: ppci_read, write: ppci_write, }; /* * This is called at loading this device driver by insmod command. */ int init_module(void) { struct pci_dev *pcidev=NULL; int retval; int i; if(!pci_present()) return -ENODEV; needed_pages = __get_order(MAX_BUFFER_SIZE); for( i = 0; i < MAX_DEVICE; i++) { pcidev = pci_find_device(PCI_VENDOR_ID_PPCI, PCI_DEVICE_ID_PPCI, pcidev); if(pcidev == NULL) break; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) if( pci_enable_device(pcidev) ) continue; ppcidev[i].io_base = (char *)pci_resource_start(pcidev, 0); #else ppcidev[i].io_base = (char *)(pcidev->base_address[0] & PCI_BASE_ADDRESS_MEM_MASK); #endif data_buf[i] = (char *)__get_free_pages(GFP_KERNEL, needed_pages); if( !data_buf[i] ) { printk("ppci:write:cound not get free pages...\n"); return(-ENOMEM); } printk("ppci:IO_BASE = %x\n", (int)ppcidev[i].io_base); ppcidev[i].ppci.data = (int)(ppcidev[i].io_base + REG_DATA); ppcidev[i].ppci.led = (int)(ppcidev[i].io_base + REG_LED); ppcidev[i].ppci.sw = (int)(ppcidev[i].io_base + REG_SW); ppcidev[i].ppci.address = (int)(ppcidev[i].io_base + REG_ADDRESS); ppcidev[i].ppci.length = (int)(ppcidev[i].io_base + REG_LENGTH); ppcidev[i].ppci.mode = (int)(ppcidev[i].io_base + REG_MODE); ppcidev[i].ppci.status = (int)(ppcidev[i].io_base + REG_STATUS); ppcidev[i].ppci.retlen = (int)(ppcidev[i].io_base + REG_RETLEN); ppcidev[i].ppci.fifolen = (int)(ppcidev[i].io_base + REG_FIFOLEN); ppcidev[i].irq = pcidev->irq; printk("irq number of PPCI = %x\n", ppcidev[i].irq); dump_reg(i); #ifdef PPCI_IRQ_SHARED retval = request_irq(ppcidev[i].irq, (void *)ppci_interrupt, SA_INTERRUPT|SA_SHIRQ, CARD_NAME, &ppcidev[i]); #else retval = request_irq(ppcidev[i].irq, (void *)ppci_interrupt, SA_INTERRUPT, CARD_NAME, &ppcidev[i]); #endif if (retval){ printk("ppci: interrupt registration fault on level %d\n", ppcidev[i].irq); return retval; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) init_waitqueue_head(&ppcidev[i].waitq); #else ppcidev[i].waitq = NULL; #endif found_card++; } if( !found_card ) return -ENODEV; retval = register_chrdev(PPCI_MAJOR,CARD_NAME,&ppci_fops); if (retval) { printk("unable to get major %d for PPCI\n", PPCI_MAJOR); return retval; } printk("PPCI has been installed.\n"); return 0; } /* * This is called at releasing this device driver by rmmod command. */ void cleanup_module(void) { int i; for(i=0; i< found_card; i++) { free_irq(ppcidev[i].irq, &ppcidev[i]); free_pages((int)data_buf[i], needed_pages); } unregister_chrdev(PPCI_MAJOR,CARD_NAME); printk("PPCI has been removed.\n"); }