Re: [PARPORT] low-level parallel port driver

From: Uwe Bonnes (bon@elektron.ikp.physik.tu-darmstadt.de)
Date: Tue Jul 25 2000 - 10:19:28 EDT

  • Next message: Darryl DeGraff: "Re: [PARPORT] low-level parallel port driver"

    Darryl DeGraff writes:
    >
    >
    > Tim Waugh wrote:
    >
    > > The 2.2 kernel doesn't support ECP at all, at least not in the
    > > drivers/misc parport stuff.
    >
    > > You'll want to back-port some of the bits from
    > > drivers/parport/parport_pc.c found in 2.4-test kernels, it sounds
    > > like.
    >
    > Well, that sounds OK to me. Before I embark on such a project,
    > is it reasonable to have to diddle with the SuperIO registers
    > to get the ECP configuration setup? (As mentioned before, the
    > hardware is not fully set up for this after boot and there is no
    > 'bios setttings' that can be changed, like on PC's).
    >
    Hallo,

    I once had a kernel module to read out and change the settings of the
    SMC Superio. It was for 2.0. Perhaps it helps

    Bye

    Uwe Bonnes bon@elektron.ikp.physik.tu-darmstadt.de

    Institut fuer Kernphysik Schlossgartenstrasse 9 64289 Darmstadt
    --------- Tel. 06151 162516 -------- Fax. 06151 164321 ----------

    /*
     * Copyright (C) 1996 by Uwe Bonnes
     *
     * This code adds an entry /proc/smcio to reflect the settings and
     * change selected values of the configuration registers of the
     * SMC Super I/O Chips FDC37C66[56]
     * Only either of these chips is supported at a time in the system
     */

    #include <linux/module.h>
    #include <linux/proc_fs.h>
    #include <linux/ioport.h>
    #include <linux/major.h>

    #include <asm/io.h>

    #define NO_SMC_REGS 16
    #define PRIMARY_PORT 0x03F0
    #define SECONDARY_PORT 0x0370
    #define SMC665_COOKIE 0x55
    #define SMC666_COOKIE 0x44
    #define SMC665 0x65
    #define SMC666 0x66
    #define SMC_CRD 0xD

    #define PROC_BLOCK_SIZE (3*1024) /* 4K page size, but our output routines
                                          * use some slack for overruns
                                          */

    #define SMC_PPADDR_MASK ~0x3
    #define SMC_PPADDR_NULL 0x0
    #define SMC_PPADDR_3BC 0x1
    #define SMC_PPADDR_378 0x2
    #define SMC_PPADDR_278 0x3

    #define SMC_PPFIFO_MASK 0xf0
      
    #define SMC_PPMODE_MASK ~0x3
    #define SMC_PPMODE_SPP 0x0
    #define SMC_PPMODE_EPP 0x1
    #define SMC_PPMODE_ECP 0x2
    #define SMC_PPMODE_ALL 0x3
      
    #define SMC_PPECPMODE_MASK 0x3
      
    #define SMC_PPEPPTYPE_MASK 0x40

    #define SMC_UART_PORTSIZE 8

    unsigned char smc_io_reg[NO_SMC_REGS], cookie;
    int port, once =0; /* cookie and port are global values */
    static int read_smcio(struct inode * inode, struct file * file,
                             char * buf, int count);
    static int write_smcio(struct inode * inode, struct file * file,
                             const char * buf, int count);
    static struct file_operations proc_smcio_operations = {
        NULL, /* lseek */
        read_smcio, /* read */
        write_smcio, /* write */
    };
    struct inode_operations smcio_inode_operations = {
        &proc_smcio_operations, /* default scsi directory file-ops */
    };
     
    static void read_smcio_reg(int port, unsigned cookie)
    {
            int i;
             
            cli();
            outb(cookie,port);
            outb(cookie,port);
            sti();
            for (i=0; i < NO_SMC_REGS; i++) {
                    outb((unsigned char) i,port);
                    smc_io_reg[i] = inb((port+1));
            }
            outb(0xAA,port);
            once++;

    }

    static void write_smcio_reg(int port, unsigned cookie)
    {
            int i;
            
            cli();
            outb(cookie,port);
            outb(cookie,port);
            sti();

            for (i=0; i < NO_SMC_REGS; i++) {
                    outb((unsigned char) i,port);
                    outb(smc_io_reg[i],(port+1));
            }
            outb(0xAA,port);
    }

    static int read_smcio(struct inode *inode, struct file *file, char *buf,
                          int count)
    {
            unsigned char content[(NO_SMC_REGS*5)+1];
            unsigned long p = file->f_pos;
            int read,i;

            if (count < 0)
                    return -EINVAL;
            if (p >= ((NO_SMC_REGS*5)+1))
                    return 0;
            if (count > ( (NO_SMC_REGS*5)+1 - p))
                    count = (NO_SMC_REGS*5)+1 - p;
            read = 0;
            read_smcio_reg(port,cookie);
            for (i=0; i < NO_SMC_REGS; i++)
                    sprintf(content+ i*5, "0x%.2x ",smc_io_reg[i]);
            sprintf(content+ (NO_SMC_REGS*5), "\n");
            memcpy_tofs(buf,(void *)content +p,count);
            read += count;
            file->f_pos += read;
            return read;
    }
    /* Check if new comm is compatible with the other using comm4
    -1 means error */
    static int comm3choose_uart(int value)
    {
            int addr = (smc_io_reg[1] & 0x60) >>5;

            printk("doing search with comm3 used\n");
            if ((addr == 0) && (value == 0x238)) return 0;
            if ((addr == 1) && (value == 0x2e8)) return 0;
            if ((addr == 2) && (value == 0x2e0)) return 0;
            if ((addr == 3) && (value == 0x228)) return 0;
            printk("uart incompatible addresses 0x%3x\n",
                   value);
            return -1;
    }
    /* Check if new comm is compatible with the other using comm4
    -1 means error */
    static int comm4choose_uart(int value)
    {
            int addr = (smc_io_reg[1] & 0x60) >>5;

            printk("doing search with comm4 used\n");
            if ((addr == 0) && (value == 0x338)) return 0;
            if ((addr == 1) && (value == 0x3e8)) return 0;
            if ((addr == 2) && (value == 0x2e8)) return 0;
            if ((addr == 3) && (value == 0x220)) return 0;
            printk("uart incompatible addresses 0x%3x\n",
                   value);
            return -1;
    }
    /* Check which comm to use for the requested address, if other comm doesnt matter*/
    static int simplechoose_uart( int value)
    {
            printk("doing simple comm search\n");
            smc_io_reg[1] &= ~0x60;
            if (value == 0x338)
                    return 0;
            if (value == 0x238)
                    return 1;
            if (value == 0x3e8) {
                    smc_io_reg[1] |= 0x20;
                    return 0;
            }
            if (value == 0x2e8) {
                    smc_io_reg[1] |= 0x20;
                    return 1;
            }
            if (value == 0x2e0) {
                    smc_io_reg[1] |= 0x40;
                    return 1;
            }
            if (value == 0x2e8) {
                    smc_io_reg[1] |= 0x40;
                    return 0;
            }
            if (value == 0x2e0) {
                    smc_io_reg[1] |= 0x40;
                    return 1;
            }
            if (value == 0x220) {
                    smc_io_reg[1] |= 0x60;
                    return 0;
            }
            if (value == 0x228) {
                    smc_io_reg[1] |= 0x60;
                    return 1;
            }
            return -1;
    }
    /* Check if requested region is valid */
    static int checkaddr_uart(int value)
    {
            if ((value == 0x3f8) || (value == 0x2f8) || (value == 0x338) ||
                (value == 0x238) || (value == 0x3e8) || (value == 0x2e8) ||
                (value == 0x2e0) || (value == 0x220) || (value == 0x228))
                    return value;
            else
                    return -1;
    }

    /* check which port region the uart at present uses */
    static int getold_uart(int value)
    {
            switch (value) {
            case 0 :
                    return 0x3f8;
            case 1:
                    return 0x2f8;
            case 2:
                    switch ((smc_io_reg[1] & 0x60)>>5) {
                    case 0:
                            return 0x338;
                    case 1:
                            return 0x3e8;
                    case 2:
                            return 0x2e8;
                    default:
                            return 0x220;
                    }
            default:
                    switch ((smc_io_reg[1] & 0x60)>>5) {
                    case 0:
                            return 0x238;
                    case 1:
                            return 0x2e8;
                    case 2:
                            return 0x2e0;
                    default:
                            return 0x228;
                    }
            }
    }
    /* Simple interface to the configuration registers of the Chip
     * Only some changes are sensible
     * Only some registers may be changed (* denotes reset default)
     * "smcio ppadr 0x[0|3BC|378|278(*)]" address of parallel port
     * change only if not in use by lp
     * "smcio ppmode [spp(*)|epp|ecp|all]" change only if not in use by lp
     * "smcio ppepptype 1.[7|9(*)]"
     * "smcio ppfifothr (val) (*=0)"
     * "smcio uart1addr 0x[3F8(*)|2F8|338|238|3E8|2E8|2E0|220|228]"
     * "smcio uart1en enable(*)|disable"
     * "smcio uart1pwr on(*)|off"
     * "smcio uart2addr 0x[3F8|2F8(*)|338|238|3E8|2E8|2E0|220|228]"
     * "smcio uart2en enable(*)|disable"
     * "smcio uart2pwr on(*)|off"
     * "smcio irq pp|oc"
     */
    static int write_smcio(struct inode * inode, struct file * file,
                           const char *buf, int count)
    {
        char * page, *p;
        unsigned char pat=0;
        int ret = 0,value;

        /* first read actual values */

        if(count > PROC_BLOCK_SIZE) {
                return(-EOVERFLOW);
        }
        if (!(page = (char *) __get_free_page(GFP_KERNEL)))
                return(-ENOMEM);
        memcpy_fromfs(page, buf, count);
        if(!page ||strncmp("smcio", page, 5))
            return(-EINVAL);
        read_smcio_reg(port,cookie);
        if(!strncmp("ppadr", page + 6, 5)) {

                p = page + 12;
                value = simple_strtoul(p, &p, 0);
                printk("smcio ppadr 0x%.3x\n", value);
                if (register_chrdev(LP_MAJOR,"lp",NULL)) {
                        printk("/dev/lp in use! Change not allowed\n");
                        free_page((ulong) page);
                        return -EBUSY;
                }
                unregister_chrdev(LP_MAJOR,"lp");
                /* other devices, that use the parallel port, should be checked here too!*/
                if (value == 0)
                        pat = SMC_PPADDR_NULL;
                else if (value == 0x3BC)
                        pat = SMC_PPADDR_3BC;
                else if (value == 0x378)
                        pat = SMC_PPADDR_378;
                else if (value == 0x278)
                        pat = SMC_PPADDR_278;
                else
                        ret = -EINVAL;
                if (!ret) {
                        smc_io_reg[1] &= SMC_PPADDR_MASK;
                        smc_io_reg[1] |= pat;
                }
        }
        if(!strncmp("ppmode", page + 6, 6)) {

                if (register_chrdev(LP_MAJOR,"lp",NULL)) {
                        printk("/dev/lp in use! Change not allowed\n");
                        free_page((ulong) page);
                        return -EBUSY;
                }
                if(!strncmp("spp", page + 13, 3))
                        pat = SMC_PPMODE_SPP;
                else if(!strncmp("epp", page + 13, 3))
                        pat = SMC_PPMODE_EPP;
                else if(!strncmp("ecp", page + 13, 3))
                        pat = SMC_PPMODE_ECP;
                else if(!strncmp("all", page + 13, 3))
                        pat = SMC_PPMODE_ALL;
                else
                        ret = -EINVAL;
                if (!ret) {
                        printk("smcio ppmode %d\n", pat);
                        smc_io_reg[4] &= SMC_PPMODE_MASK;
                        smc_io_reg[4] |= pat;
                }
        }
        if(!strncmp("ppfifothr", page + 6, 9)) {

                p = page + 16;
                value = simple_strtoul(p, &p, 0);
                printk("smcio ppfifothr %d\n", value);
                if ((value > 15)||( value < 0))
                        ret = -EINVAL;
                else {
                        smc_io_reg[0xa] &= SMC_PPFIFO_MASK;
                        smc_io_reg[0xa] |= (unsigned char) value;
                }
        }
        if(!strncmp("ppepptype", page + 6, 9)) {

                p = page + 18;
                value = simple_strtoul(p, &p, 0);
                printk("smcio ppepptype 1.%d\n", value);
                if (value == 7)
                        smc_io_reg[4] |= SMC_PPEPPTYPE_MASK;
                else if (value == 9)
                        smc_io_reg[4] &= ~SMC_PPEPPTYPE_MASK;
                else
                        ret = -EINVAL;
        }
        if(!strncmp("uartreset", page + 6, 10)) {
                
                page = page +17;
                smc_io_reg[2] &= ~0x3;
                printk("smcio uartreset\n");
        }
        if(!strncmp("uart1addr", page + 6, 9)) {
                int value_wanted,oldvalue, otheruart;

                p = page + 16;
                value_wanted = (simple_strtoul(p, &p, 0));
                value = checkaddr_uart(value_wanted);
                oldvalue = getold_uart(smc_io_reg[2] & 0x3);
                otheruart = getold_uart((smc_io_reg[2] & 0x30) >>4);
                if (value == -1) {
                        printk("uart1addr requested region 0x%3x illegal\n",
                               value_wanted);
                        ret = -EADDRINUSE;
                }
                else if (check_region(value, SMC_UART_PORTSIZE) < 0) {
                        printk("uart1addr requested region 0x%3x not free\n",
                               value);
                        ret =-EADDRINUSE;
                }
                else if ( (smc_io_reg[2] & 0x4) &&
                         (check_region(oldvalue, SMC_UART_PORTSIZE) < 0)) {
                        printk("uart1addr old region 0x%3x not free\n",oldvalue);
                        ret = -EINVAL;
                }
                else if ((smc_io_reg[2] & 0x40) && (value == otheruart)) {
                        printk("uart1addr address 0x%3x is uart2 address\n",
                               otheruart);
                        ret = -EADDRINUSE;
                }
                /* if new address is comm 1 or comm 2, we are nearly done */
                else if (value == 0x3f8)
                        smc_io_reg[2] &= ~0x3;
                else if (value == 0x2f8) {
                        smc_io_reg[2] &= ~0x2;
                        smc_io_reg[2] |= ~0x1;
                }
                /* if otheruart is comm 1 or comm 2 , we don't care for it
                   but set the value straight*/
                else if (!(smc_io_reg[2] & 0x20)) {
                        smc_io_reg[2] &= ~0x1;
                        smc_io_reg[2] |= 0x2 |simplechoose_uart(value);
                }
                /* other uart uses comm4 */
                else if (( smc_io_reg[2] & 0x30) == 0x30) {
                        if (comm4choose_uart(value) != -1) {
                                smc_io_reg[2] &= ~0x1;
                                smc_io_reg[2] |= 0x2;
                        }
                        else
                        ret = -EADDRINUSE;
                }
                else {
                        if (comm3choose_uart(value) != -1)
                                smc_io_reg[2] |= 0x3;
                        else
                        ret = -EADDRINUSE;
                }
                if (!(ret <0))
                   printk("smcio uart1addr change from 0x%3x to 0x%3x allowed\n",
                               oldvalue,value);
        }
                        
        if(!strncmp("uart2addr", page + 6, 9)) {
                int value_wanted,oldvalue, otheruart;

                p = page + 16;
                value_wanted = (simple_strtoul(p, &p, 0));
                value = checkaddr_uart(value_wanted);
                oldvalue = getold_uart((smc_io_reg[2] & 0x30)>>4);
                otheruart = getold_uart(smc_io_reg[2] & 0x3);
                if (value == -1) {
                        printk("uart2addr requested region 0x%3x illegal\n",
                               value_wanted);
                        ret = -EADDRINUSE;
                }
                else if (check_region(value, SMC_UART_PORTSIZE) < 0) {
                        printk("uart2addr requested region 0x%3x not free\n",
                               value);
                        ret =-EADDRINUSE;
                }
                else if ( (smc_io_reg[2] & 0x40) &&
                         (check_region(oldvalue, SMC_UART_PORTSIZE) < 0)) {
                        printk("uart2addr old region 0x%3x not free\n",oldvalue);
                        ret = -EINVAL;
                }
                else if ((smc_io_reg[2] & 0x4) && (value == otheruart)) {
                        printk("uart2addr address 0x%3x is uart2 address\n",
                               otheruart);
                        ret = -EADDRINUSE;
                }
                /* if new address is comm 1 or comm 2, we are nearly done */
                else if (value == 0x3f8)
                        smc_io_reg[2] &= ~0x30;
                else if (value == 0x2f8) {
                        smc_io_reg[2] &= ~0x20;
                        smc_io_reg[2] |= ~0x10;
                }
                /* if otheruart is comm 1 or comm 2 , we don't care for it
                   but set the value straight*/
                else if (!(smc_io_reg[2] & 0x2)) {
                        smc_io_reg[2] &= ~0x10;
                        smc_io_reg[2] |= 0x20 | (simplechoose_uart(value)<<4);
                }
                /* other uart uses comm4 */
                else if ( (smc_io_reg[2] & 0x3) == 0x3) {
                        if (comm4choose_uart(value) != -1) {
                                smc_io_reg[2] &= ~0x10;
                                smc_io_reg[2] |= 0x20;
                        }
                        else
                        ret = -EADDRINUSE;
                }
                else {
                        if (comm3choose_uart(value) != -1)
                                smc_io_reg[2] |= 0x30;
                        else
                        ret = -EADDRINUSE;
                }
                if (! (ret < 0))
                   printk("smcio uart2addr change from 0x%3x to 0x%3x allowed\n",
                               oldvalue,value);
                        
        }
        if(!strncmp("uart1pwr", page + 6, 8)) {
                int value;
                
                value = getold_uart(smc_io_reg[2] & 0x3);
                if (check_region(value, SMC_UART_PORTSIZE) < 0) {
                               printk("uart1 region at 0x%.3x not free\n",
                                      value);
                               ret = -EBUSY;
                       }
                else if (!strncmp("off", page + 15, 3)) {
                        printk("Power down down uart1\n");
                        smc_io_reg[2] &= ~ 0x8;
                }
                else if (!strncmp("on", page + 15, 2)) {
                        printk("Power up for uart1\n");
                        smc_io_reg[2] |= 0x8;
                }
        }
        if(!strncmp("uart2pwr", page + 6, 5)) {
                int value;
                
                value = getold_uart((smc_io_reg[2] & 0x30)>>4);
                if (check_region(value, SMC_UART_PORTSIZE) < 0) {
                               printk("uart2 region at 0x%.3x not free\n",
                                      value);
                               ret = -EBUSY;
                       }
                else if (!strncmp("off", page + 15, 3)) {
                        printk("Power down for uart2\n");
                        smc_io_reg[2] &= ~0x80;
                }
                else if (!strncmp("on", page + 15, 2)) {
                        printk("Power up for uart2\n");
                        smc_io_reg[2] |= 0x80;
                }
        }
        if(!strncmp("uart1en", page + 6, 7)) {
                int value;
                
                value = getold_uart(smc_io_reg[2] & 0x3);
                if (check_region(value, SMC_UART_PORTSIZE) < 0) {
                               printk("uart1 region at 0x%.3x not free\n",
                                      value);
                               ret = -EBUSY;
                       }
                else if (!strncmp("disable", page + 14, 7)) {
                        printk("Disabling uart1\n");
                        smc_io_reg[2] &= ~ 0x4;
                }
                else if (!strncmp("enable", page + 14, 6)) {
                        printk("Enabling uart1\n");
                        smc_io_reg[2] |= 0x4;
                }
                else ret = -EINVAL;
        }
        if(!strncmp("uart2en", page + 6, 7)) {
                int value;
                
                value = getold_uart((smc_io_reg[2] & 0x30)>>4);
                if (check_region(value, SMC_UART_PORTSIZE) < 0) {
                               printk("uart2 region at 0x%.3x not free\n",
                                      value);
                               ret = -EBUSY;
                       }
                else if (!strncmp("disable", page + 14, 7)) {
                        printk("Disabling uart2\n");
                        smc_io_reg[2] &= ~0x40;
                }
                else if (!strncmp("enable", page + 14, 6)) {
                        printk("Enabling uart2\n");
                        smc_io_reg[2] |= 0x40;
                }
                else ret = -EINVAL;
        }
        if(!strncmp("irq", page + 6, 3)) {
                if(!strncmp("pp", page + 10, 2)) {
                        printk("IRQ active high, inactive low\n");
                        smc_io_reg[1] |= 0x10;
                }
                else if(!strncmp("oc", page + 10, 2)) {
                        printk("IRQ active low, open collector\n");
                        smc_io_reg[1] &= ~0x10;
                }
                else {
                        printk("IRQ bad value\n");
                        ret = -EINVAL;
                }
        }
        if (!(ret < 0)) write_smcio_reg(port,cookie);
        free_page((ulong) page);
        return ( (ret)?ret:count);
    }
      
    int smcio_read_mem(char *buf, char **start, off_t offset,
                       int len, int unused)
    {
            int i;
            len=0;
      
            read_smcio_reg(port,cookie);
            for (i=0; i < NO_SMC_REGS; i++)
                    len+= sprintf(buf+len, "0x%x ",smc_io_reg[i]);
            /* enqueue the messages */
            len+= sprintf(buf+len, "\n");
            return len;
    }

    struct proc_dir_entry smcio_proc_entry =
    {
            0, /* low_ino: the inode -- dynamic */
            5, "smcio", /* len of name and name */
            S_IFREG | S_IRUGO |S_IWUSR, /* mode */
            1, 0, 0, /* nlinks, owner, group */
            NO_SMC_REGS*5+1, /* size */
            &smcio_inode_operations, /* operations -- use default */
            &smcio_read_mem, /* function used to read data */
            /* nothing more */
    };

    #ifdef MODULE
    #define smcio_init init_module
    #endif
    int smcio_init(void)
    {
            if (proc_register_dynamic(&proc_root, &smcio_proc_entry)) {
                    printk(KERN_INFO "Couldn't get a proc-entry for smcio\n");
                    return -EIO;
            }
            port = PRIMARY_PORT;
            cookie = SMC665_COOKIE;
            read_smcio_reg(port,cookie);
            if (smc_io_reg[SMC_CRD] == SMC665 ) return 0;
            cookie = SMC666_COOKIE;
            read_smcio_reg(port,cookie);
            if (smc_io_reg[SMC_CRD] == SMC666 ) return 0;
            port = PRIMARY_PORT;
            read_smcio_reg(port,cookie);
            if (smc_io_reg[SMC_CRD] == SMC666 ) return 0;
            printk(KERN_INFO "No SMC Super I/O found.\n");
            proc_unregister(&proc_root, smcio_proc_entry.low_ino);
            return -EIO;
    }

    #ifdef MODULE
    void cleanup_module(void)
    {
            proc_unregister(&proc_root, smcio_proc_entry.low_ino);
    }
    #endif

    /*
     * Overrides for Emacs so that we get a uniform tabbing style.
     * Emacs will notice this stuff at the end of the file and automatically
     * adjust the settings for this buffer only. This must remain at the end
     * of the file.
     * ---------------------------------------------------------------------------
     * Local variables:
     * c-indent-level: 8
     * c-brace-imaginary-offset: 0
     * c-brace-offset: -8
     * c-argdecl-indent: 8
     * c-label-offset: -8
     * c-continued-statement-offset: 8
     * c-continued-brace-offset: 8
     * indent-tabs-mode: nil
     * tab-width: 8
     * End:
     */
    /*
    elektron:~/tmp/misc> gcc -O2 -Wall -D__KERNEL__ -I/usr/src/linux/include -DMODULE -DCPU=586 -c -o smcio.o smcio.c
    */

    -- To unsubscribe, send mail to: linux-parport-request@torque.net --
    -- with the single word "unsubscribe" in the body of the message. --



    This archive was generated by hypermail 2b29 : Tue Jul 25 2000 - 10:21:47 EDT