/* * IBM Smart Capture Card driver. * * Copyright(c) 1996,1997,1998,1999 Koji OKAMURA. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Changed by Koji OKAMURA * Version 0.43, Aug. 17, 1999. * Version 0.42, Oct. 22, 1998. * Version 0.41, Sep. 28, 1997. * Version 0.32, Aug. 8, 1996. */ /* I got Major Number at Mon, 29 Sep 1997 15:49:32 -0700 (PDT) !!*/ #define ISCC_MAJOR 93 #include #include #include #include #include #include #include #include #include #include #include #include #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) #include #endif #include #include #include #include #include #include #include "iscc_ioctl.h" #include "iscc_cs.h" /* #define PCMCIA_DEBUG */ #ifdef PCMCIA_DEBUG static int pc_debug = PCMCIA_DEBUG; static char *version = "iscc.c 0.43 1999/8/17 (Koji OKAMURA)\n"; #endif #define init_iscc init_module static u_long irq_mask = 0xdeb8; static int mem_speed = 0; static void iscc_config(dev_link_t *link); static void iscc_release(u_long arg); static int iscc_event(event_t event, int priority,event_callback_args_t *args); static dev_link_t *iscc_attach(void); static void iscc_detach(dev_link_t *); int iscc_major=ISCC_MAJOR; int iscc_minor=0; void iscc_interrupt(int reg); static dev_info_t dev_info = "iscc_cs"; static dev_link_t *dev_table[NISCC] = { NULL, /* ... */ }; #define ISCC_WINDOW_SIZE 0x4000 typedef struct iscc_dev { dev_node_t node; caddr_t Base; u_long flags; iscc_card_t iscc; } iscc_dev_t; static void cs_error(int func, int ret) { CardServices(ReportError, dev_info, (void *)func, (void *)ret); } static dev_link_t *iscc_attach(void) { client_reg_t client_reg; dev_link_t *link; iscc_dev_t *dev; int ret; int i; #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc_attach()\n"); #endif for (i = 0; i < NISCC; i++) if (dev_table[i] == NULL) break; if (i == NISCC) { printk(KERN_NOTICE "pcmem_cs: no devices available\n"); return NULL; } /* Initialize the dev_link_t structure */ link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); dev_table[i] = link; memset(link, 0, sizeof(struct dev_link_t)); link->release.function = &iscc_release; link->release.data = (u_long)link; /* The io structure describes IO port mapping */ link->io.NumPorts1 = 16; link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; link->io.IOAddrLines = 5; /* Interrupt setup */ link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; link->irq.IRQInfo2 = irq_mask; link->irq.Handler = iscc_interrupt; /* General socket configuration */ link->conf.Vcc = 50; link->conf.IntType = INT_MEMORY_AND_IO; link->conf.ConfigIndex = 1; link->conf.Present = PRESENT_OPTION; /* Allocate space for private device-specific data */ dev = kmalloc(sizeof(iscc_dev_t), GFP_KERNEL); memset(dev, 0, sizeof(iscc_dev_t)); link->priv = dev; /* Register with Card Services */ client_reg.dev_info = &dev_info; client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; client_reg.EventMask = CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; client_reg.event_handler = &iscc_event; client_reg.Version = 0x0210; client_reg.event_callback_args.client_data = link; ret = CardServices(RegisterClient, &link->handle, &client_reg); if (ret != 0) { cs_error(RegisterClient, ret); iscc_detach(link); return NULL; } return link; } /* iscc_attach */ static void iscc_detach(dev_link_t *link) { int i; #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc_detach(0x%p)\n", link); #endif /* Locate device structure */ for (i = 0; i < NISCC; i++) if (dev_table[i] == link) break; if (i == NISCC) return; if (link->state & DEV_CONFIG) { printk("iscc_cs: detach postponed, '%s' still locked\n", link->dev->dev_name); link->state |= DEV_STALE_LINK; return; } if (link->handle) CardServices(DeregisterClient, link->handle); kfree_s(link, sizeof(struct dev_link_t)); dev_table[i] = NULL; } /* iscc_detach */ static void iscc_config(dev_link_t *link) { client_handle_t handle; tuple_t tuple; cisparse_t parse; iscc_dev_t *dev; int i, j, ret; u_char buf[64]; win_req_t req; #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc_config(0x%p)\n", link); #endif for (i = 0; i < NISCC; i++) if (dev_table[i] == link) break; iscc_minor = i; handle = link->handle; dev = link->priv; do { tuple.DesiredTuple = CISTPL_CONFIG; ret = CardServices(GetFirstTuple, handle, &tuple); if (ret != CS_SUCCESS) break; tuple.TupleData = buf; tuple.TupleDataMax = 64; tuple.TupleOffset = 0; ret = CardServices(GetTupleData, handle, &tuple); if (ret != CS_SUCCESS) break; ret = CardServices(ParseTuple, handle, &tuple, &parse); if (ret != CS_SUCCESS) break; link->conf.ConfigBase = parse.config.base; } while (0); if (ret != CS_SUCCESS) { cs_error(ParseTuple, ret); link->state &= ~DEV_CONFIG_PENDING; return; } /* Configure card */ link->state |= DEV_CONFIG; do { for (j = 0x300; j < 0x400; j += 0x20) { link->io.BasePort1 = j; link->io.BasePort2 = j+0x10; ret = CardServices(RequestIO, link->handle, &link->io); if (ret == CS_SUCCESS) break; } if (ret != CS_SUCCESS) { cs_error(RequestIO, ret); break; } ret = CardServices(RequestConfiguration, link->handle, &link->conf); if (ret != CS_SUCCESS) { cs_error(RequestConfiguration, ret); break; } req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE; req.Base = 0; req.Size = ISCC_WINDOW_SIZE; req.AccessSpeed = mem_speed ; link->win = (window_handle_t)link->handle; ret = CardServices(RequestWindow, &link->win, &req); if (ret != 0) { cs_error(RequestWindow, ret); break; } } while (0); sprintf(dev->node.dev_name, "iscc%d",iscc_minor); dev->node.major = iscc_major; dev->node.minor = iscc_minor; dev->Base = ioremap(req.Base, req.Size); link->dev = &dev->node; link->state &= ~DEV_CONFIG_PENDING; if (ret != 0) { iscc_release((u_long)link); return; } { dev->iscc.mode = ISCC_NTSC; dev->iscc.geomet.width = ISCC_NTSCWIDTH; dev->iscc.geomet.height = ISCC_NTSCHEIGHT; dev->iscc.format = ISCC_YUV422; dev->iscc.spdmode = ISCC_FAST; dev->iscc.color.brightness = ISCC_DEFBRIGHTNESS; dev->iscc.color.contrast = ISCC_DEFCONTRAST; dev->iscc.color.saturation = ISCC_DEFSATURATION; dev->iscc.color.hue = ISCC_DEFHUE; dev->iscc.tv.tuntype = 0x61; InitVPX(link->io.BasePort1, dev->iscc.mode,0, dev->iscc.geomet.width, dev->iscc.geomet.height,1, dev->iscc.format, iscc_minor); SetVPXColor(dev->iscc.color.brightness, dev->iscc.color.contrast, dev->iscc.color.saturation, dev->iscc.color.hue, iscc_minor); SetTVChannel(dev->iscc.tv.tuntype, dev->iscc.tv.channel, dev->iscc.tv.fine, dev->iscc.tv.country, iscc_minor); } printk("iscc%d: port %x, mem %x, size %d\n", iscc_minor,link->io.BasePort1,(int)req.Base,(int)req.Size); printk("iscc device loaded\n"); } /* iscc_config */ static void iscc_release(u_long arg) { dev_link_t *link ; iscc_dev_t *info ; link = (dev_link_t *)arg; info = link->priv; #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc_release(0x%p)\n", link); #endif if (link->open) { printk("iscc_cs: release postponed, '%s' still open\n", link->dev->dev_name); link->state |= DEV_STALE_CONFIG; return; } /* Unlink the device chain */ link->dev = NULL; /* Don't bother checking to see if these succeed or not */ CardServices(ReleaseWindow, link->win); CardServices(ReleaseConfiguration, link->handle); CardServices(ReleaseIO, link->handle, &link->io); CardServices(ReleaseIRQ, link->handle, &link->irq); link->state &= ~DEV_CONFIG; if (link->state & DEV_STALE_LINK) iscc_detach(link); } /* iscc_release */ static int iscc_event(event_t event, int priority,event_callback_args_t *args) { dev_link_t *link = args->client_data; #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc_event()\n"); #endif switch (event) { #ifdef PCMCIA_DEBUG case CS_EVENT_REGISTRATION_COMPLETE: if (pc_debug) printk("iscc_cs: registration complete\n"); break; #endif case CS_EVENT_CARD_REMOVAL: link->state &= ~DEV_PRESENT; if (link->state & DEV_CONFIG) { link->release.expires = RUN_AT(5); add_timer(&link->release); } break; case CS_EVENT_CARD_INSERTION: link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; iscc_config(link); break; case CS_EVENT_PM_SUSPEND: link->state |= DEV_SUSPEND; /* Fall through... */ case CS_EVENT_RESET_PHYSICAL: if (link->state & DEV_CONFIG) CardServices(ReleaseConfiguration, link->handle); break; case CS_EVENT_PM_RESUME: link->state &= ~DEV_SUSPEND; /* Fall through... */ case CS_EVENT_CARD_RESET: if (link->state & DEV_CONFIG) CardServices(RequestConfiguration, link->handle, &link->conf); break; } return 0; } /* iscc_event */ void iscc_interrupt(int reg) { printk("iscc_cs: interrupt\n"); } /* iscc_interrupt */ #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) static ssize_t iscc_read(struct file *file, char *buf, size_t count,loff_t *ppos) #else static int iscc_read(struct inode *inode, struct file *file, char *buf, int count) #endif { dev_link_t *link; iscc_dev_t *dev; u_long read=0; u_long p, from, nb=0, ncp=0; int ret; memreq_t mem; int max_p; int adhoc_p; int nminor; #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) nminor = MINOR(file->f_dentry->d_inode->i_rdev); #else nminor = MINOR(inode->i_rdev); #endif #ifdef PCMCIA_DEBUG if (pc_debug) { printk("iscc_read(%d) : read request of %d bytes (fpos=%d).\n", nminor,count, (int)file->f_pos); } #endif if(nminor & 0x80) /* 128 >= minor -> ctl */ return 0; link = dev_table[(nminor & 0x7f)]; dev = link->priv; adhoc_p = dev->iscc.geomet.width*2*8; /* ad hoc */ max_p = adhoc_p + dev->iscc.geomet.width*dev->iscc.geomet.height*2; if(file->f_pos==0) file->f_pos += adhoc_p; p = file->f_pos ; if(p < max_p){ if(dev->iscc.spdmode == ISCC_SLOW) SetVPXRect(20, 0, (nminor & 0x7f)); { mem.CardOffset = p & ~(ISCC_WINDOW_SIZE-1); mem.Page = 0; from = p & (ISCC_WINDOW_SIZE-1); for ( ; count > 0; count -= nb) { ret = CardServices(MapMemPage, link->win, &mem); if (ret != CS_SUCCESS) { cs_error(MapMemPage, ret); return -EIO; } nb = (from+count > ISCC_WINDOW_SIZE) ? ISCC_WINDOW_SIZE-from : count; ncp = nb; if(p + nb > max_p) ncp = max_p - p; copy_to_user(buf+read,dev->Base+from, ncp); read += ncp; from = 0; mem.CardOffset += ISCC_WINDOW_SIZE; } file->f_pos += read; } if(dev->iscc.spdmode == ISCC_SLOW) SetVPXRect(dev->iscc.geomet.width, dev->iscc.geomet.height, (nminor&0x7f)); } return read; /* success */ } /* iscc_read */ static int iscc_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg) { dev_link_t *link; iscc_dev_t *dev; int retval; #ifdef PCMCIA_DEBUG if (pc_debug) { printk("iscc(%d) : trying ioctl %u\n", MINOR(inode->i_rdev), cmd ); } #endif link = dev_table[(MINOR(inode->i_rdev) & 0x7f)]; dev = link->priv; if (!arg) return -EINVAL; switch (cmd) { case ISCCSETMODE: retval = verify_area(VERIFY_READ, (void *) arg, sizeof(int)); if (retval) return retval; { int modetmp; copy_from_user(&modetmp, (void *) arg, sizeof(int)); if(modetmp != ISCC_NTSC && modetmp != ISCC_PAL ) return -EINVAL; dev->iscc.mode = modetmp; } break; case ISCCGETMODE: retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int)); if (retval) return retval; copy_to_user((int *) arg, &dev->iscc.mode, sizeof(int)); break; case ISCCSETGEO: retval = verify_area(VERIFY_READ, (void *) arg, sizeof(iscc_geomet_t)); if (retval) return retval; { iscc_geomet_t iscctmp; copy_from_user(&iscctmp, (void *) arg, sizeof(iscc_geomet_t)); if(dev->iscc.mode == ISCC_NTSC){ if(iscctmp.width <= 0 || iscctmp.width > ISCC_NTSCWIDTH || iscctmp.height<= 0 || iscctmp.height> ISCC_NTSCHEIGHT) return -EINVAL; }else { if(iscctmp.width <= 0 || iscctmp.width > ISCC_PALWIDTH || iscctmp.height<= 0 || iscctmp.height> ISCC_PALHEIGHT) return -EINVAL; } dev->iscc.geomet = iscctmp; } SetVPXRect( dev->iscc.geomet.width, dev->iscc.geomet.height, (MINOR(inode->i_rdev)&0x7f)); break; case ISCCGETGEO: retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(iscc_geomet_t)); if (retval) return retval; copy_to_user((int *) arg, &dev->iscc.geomet, sizeof(iscc_geomet_t)); break; case ISCCSETFMT : retval = verify_area(VERIFY_READ, (void *) arg, sizeof(int)); if (retval) return retval; { int fmttmp; copy_from_user(&fmttmp, (void *) arg, sizeof(int)); if(fmttmp < ISCC_YUV422 || fmttmp > ISCC_RGB555) return -EINVAL; dev->iscc.format = fmttmp; } SetVPXFmt(dev->iscc.format, (MINOR(inode->i_rdev)&0x7f)); break; case ISCCGETFMT : retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int)); if (retval) return retval; copy_to_user((int *) arg, &dev->iscc.format, sizeof(int)); break; case ISCCSETSPDMODE : retval = verify_area(VERIFY_READ, (void *) arg, sizeof(int)); if (retval) return retval; { int spdtmp; copy_from_user(&spdtmp, (void *) arg, sizeof(int)); if(spdtmp != ISCC_SLOW && spdtmp != ISCC_FAST) return -EINVAL; dev->iscc.spdmode = spdtmp; } break; case ISCCGETSPDMODE : retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int)); if (retval) return retval; copy_to_user((int *) arg, &dev->iscc.spdmode, sizeof(int)); break; case ISCCSETCOLOR: retval = verify_area(VERIFY_READ, (void *) arg, sizeof(iscc_color_t)); if (retval) return retval; { iscc_color_t clrtmp; copy_from_user(&clrtmp, (void *) arg, sizeof(iscc_color_t)); dev->iscc.color = clrtmp; } SetVPXColor(dev->iscc.color.brightness, dev->iscc.color.contrast, dev->iscc.color.saturation, dev->iscc.color.hue, (MINOR(inode->i_rdev)&0x7f)); break; case ISCCGETCOLOR: retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(iscc_color_t)); if (retval) return retval; copy_to_user((int *) arg, &dev->iscc.color, sizeof(iscc_color_t)); break; case ISCCSETTVCHANNEL: retval = verify_area(VERIFY_READ, (void *) arg, sizeof(iscc_tv_t)); if (retval) return retval; { iscc_tv_t tvtmp; copy_from_user(&tvtmp, (void *) arg, sizeof(iscc_tv_t)); dev->iscc.tv = tvtmp; } SetTVChannel(dev->iscc.tv.tuntype, dev->iscc.tv.channel, dev->iscc.tv.fine, dev->iscc.tv.country, (MINOR(inode->i_rdev)&0x7f)); break; case ISCCGETTVCHANNEL: retval = verify_area(VERIFY_WRITE, (void *) arg, sizeof(iscc_tv_t)); if (retval) return retval; copy_to_user((int *) arg, &dev->iscc.tv, sizeof(iscc_tv_t)); break; default: #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc: unsupported ioctl (%d).\n",cmd); #endif return -EINVAL; } return 0; } /* iscc_ioctl */ static int iscc_open(struct inode *inode, struct file *file) { dev_link_t *link; iscc_dev_t *dev; #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc_open(%d)\n", MINOR(inode->i_rdev)); #endif if(MINOR(inode->i_rdev) & 0x80) /* 128 >= minor -> ctl */ return 0; link = dev_table[(MINOR(inode->i_rdev) & 0x7f)]; dev = link->priv; if (dev->flags & ISCC_OPEN) return -EBUSY; dev->flags |= ISCC_OPEN; return 0; } /* iscc_open */ #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) static int iscc_close(struct inode *inode, struct file *file) #else static void iscc_close(struct inode *inode, struct file *file) #endif { dev_link_t *link; iscc_dev_t *dev; #ifdef PCMCIA_DEBUG if (pc_debug) printk("iscc_close(%d)\n", MINOR(inode->i_rdev)); #endif if(MINOR(inode->i_rdev) & 0x80) /* 128 >= minor -> ctl */ #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) return 0; #else return; #endif link = dev_table[(MINOR(inode->i_rdev) & 0x7f)]; dev = link->priv; dev->flags &= ~ISCC_OPEN; #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) return 0; #else return; #endif } /* iscc_close */ static struct file_operations iscc_fops = { NULL, /* lseek */ iscc_read, /* read */ NULL, /* write */ NULL, /* readdir */ NULL, /* select */ iscc_ioctl, /* ioctl */ NULL, /* mmap */ iscc_open, /* open */ #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) NULL, /* flush */ #endif iscc_close, /* release */ NULL, /* fsync */ NULL, /* fasync */ NULL, /* check_media_change */ NULL /* revalidate */ #if (LINUX_VERSION_CODE >= VERSION(2,2,0)) ,NULL /* lock */ #endif }; int init_iscc(void) { int i=0; servinfo_t serv; #ifdef PCMCIA_DEBUG if (pc_debug) printk(version); #endif CardServices(GetCardServicesInfo, &serv); if (serv.Revision != CS_RELEASE_CODE) { printk("iscc: Card Services release does not match!\n"); return -1; } register_pcmcia_driver(&dev_info, &iscc_attach, &iscc_detach); /* for (i = MAX_CHRDEV-1; i > 0; i--) { if (register_chrdev(i, "iscc_cs", &iscc_fops) == 0){ iscc_major=i; break; } else unregister_chrdev(i, "iscc_cs"); } */ if (register_chrdev(iscc_major, "iscc_cs", &iscc_fops)) { unregister_chrdev(i, "iscc_cs"); } return 0; } void cleanup_module(void) { dev_link_t *link; int i; printk("iscc_cs: unloading\n"); unregister_pcmcia_driver(&dev_info); unregister_chrdev(iscc_major, "iscc_cs"); for (i = 0; i < NISCC; i++) { link = dev_table[i]; if (link) { if (link->state & DEV_CONFIG) iscc_release((u_long)link); iscc_detach(link); } } }