diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-06-17 14:08:29 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-06-17 14:08:29 +0000 |
commit | 57d569635c05dc4ea9b9f1f8dcec69b9ddc989b2 (patch) | |
tree | 1f703abf7d95dcd50ee52da3b96eb1b4b2b4ea53 /drivers | |
parent | 59223edaa18759982db0a8aced0e77457d10c68e (diff) |
The rest of 2.3.6.
Diffstat (limited to 'drivers')
80 files changed, 41514 insertions, 0 deletions
diff --git a/drivers/block/MAKEDEV-IDE45 b/drivers/block/MAKEDEV-IDE45 new file mode 100644 index 000000000..c55a9b3cf --- /dev/null +++ b/drivers/block/MAKEDEV-IDE45 @@ -0,0 +1,99 @@ +#!/bin/sh +# +# Andre Hedrick <hedrick@astro.dyer.vanderbilt.edu> +# +# The song goes, "I did it the hard way..........." +# + +if [ ! -f /dev/hdi ]; then \ + echo "Making IDE4 Primary Devices hdi's"; \ + mknod /dev/hdi b 56 0; \ + mknod /dev/hdi1 b 56 1; \ + mknod /dev/hdi2 b 56 2; \ + mknod /dev/hdi3 b 56 3; \ + mknod /dev/hdi4 b 56 4; \ + mknod /dev/hdi5 b 56 5; \ + mknod /dev/hdi6 b 56 6; \ + mknod /dev/hdi7 b 56 7; \ + mknod /dev/hdi8 b 56 8; \ + mknod /dev/hdi9 b 56 9; \ + mknod /dev/hdi10 b 56 10; \ + mknod /dev/hdi11 b 56 11; \ + mknod /dev/hdi12 b 56 12; \ + mknod /dev/hdi13 b 56 13; \ + mknod /dev/hdi14 b 56 14; \ + mknod /dev/hdi15 b 56 15; \ + mknod /dev/hdi16 b 56 16; \ + chown root.disk /dev/hdi*; \ + chmod 660 /dev/hdi*; \ +fi + +if [ ! -f /dev/hdj ]; then \ + echo "Making IDE4 Secondary Devices hdj's"; \ + mknod /dev/hdj b 56 64; \ + mknod /dev/hdj1 b 56 65; \ + mknod /dev/hdj2 b 56 66; \ + mknod /dev/hdj3 b 56 67; \ + mknod /dev/hdj4 b 56 68; \ + mknod /dev/hdj5 b 56 69; \ + mknod /dev/hdj6 b 56 70; \ + mknod /dev/hdj7 b 56 71; \ + mknod /dev/hdj8 b 56 72; \ + mknod /dev/hdj9 b 56 73; \ + mknod /dev/hdj10 b 56 74; \ + mknod /dev/hdj11 b 56 75; \ + mknod /dev/hdj12 b 56 76; \ + mknod /dev/hdj13 b 56 77; \ + mknod /dev/hdj14 b 56 78; \ + mknod /dev/hdj15 b 56 79; \ + mknod /dev/hdj16 b 56 80; \ + chown root.disk /dev/hdj*; \ + chmod 660 /dev/hdj*; \ +fi + +if [ ! -f /dev/hdk ]; then \ + echo "Making IDE5 Primary Devices hdk's"; \ + mknod /dev/hdk b 57 0; \ + mknod /dev/hdk1 b 57 1; \ + mknod /dev/hdk2 b 57 2; \ + mknod /dev/hdk3 b 57 3; \ + mknod /dev/hdk4 b 57 4; \ + mknod /dev/hdk5 b 57 5; \ + mknod /dev/hdk6 b 57 6; \ + mknod /dev/hdk7 b 57 7; \ + mknod /dev/hdk8 b 57 8; \ + mknod /dev/hdk9 b 57 9; \ + mknod /dev/hdk10 b 57 10; \ + mknod /dev/hdk11 b 57 11; \ + mknod /dev/hdk12 b 57 12; \ + mknod /dev/hdk13 b 57 13; \ + mknod /dev/hdk14 b 57 14; \ + mknod /dev/hdk15 b 57 15; \ + mknod /dev/hdk16 b 57 16; \ + chown root.disk /dev/hdk*; \ + chmod 660 /dev/hdk*; \ +fi + +if [ ! -f /dev/hdl ]; then \ + echo "Making IDE5 Secondary Devices hdl's"; \ + mknod /dev/hdl b 57 64; \ + mknod /dev/hdl1 b 57 65; \ + mknod /dev/hdl2 b 57 66; \ + mknod /dev/hdl3 b 57 67; \ + mknod /dev/hdl4 b 57 68; \ + mknod /dev/hdl5 b 57 69; \ + mknod /dev/hdl6 b 57 70; \ + mknod /dev/hdl7 b 57 71; \ + mknod /dev/hdl8 b 57 72; \ + mknod /dev/hdl9 b 57 73; \ + mknod /dev/hdl10 b 57 74; \ + mknod /dev/hdl11 b 57 75; \ + mknod /dev/hdl12 b 57 76; \ + mknod /dev/hdl13 b 57 77; \ + mknod /dev/hdl14 b 57 78; \ + mknod /dev/hdl15 b 57 79; \ + mknod /dev/hdl16 b 57 80; \ + chown root.disk /dev/hdl*; \ + chmod 660 /dev/hdl*; \ +fi + diff --git a/drivers/block/MAKEDEV-IDE67 b/drivers/block/MAKEDEV-IDE67 new file mode 100644 index 000000000..27728be28 --- /dev/null +++ b/drivers/block/MAKEDEV-IDE67 @@ -0,0 +1,99 @@ +#!/bin/sh +# +# Andre Hedrick <hedrick@astro.dyer.vanderbilt.edu> +# +# The song goes, "I did it the hard way..........." +# + +if [ ! -f /dev/hdm ]; then \ + echo "Making IDE6 Primary Devices hdm's"; \ + mknod /dev/hdm b 88 0; \ + mknod /dev/hdm1 b 88 1; \ + mknod /dev/hdm2 b 88 2; \ + mknod /dev/hdm3 b 88 3; \ + mknod /dev/hdm4 b 88 4; \ + mknod /dev/hdm5 b 88 5; \ + mknod /dev/hdm6 b 88 6; \ + mknod /dev/hdm7 b 88 7; \ + mknod /dev/hdm8 b 88 8; \ + mknod /dev/hdm9 b 88 9; \ + mknod /dev/hdm10 b 88 10; \ + mknod /dev/hdm11 b 88 11; \ + mknod /dev/hdm12 b 88 12; \ + mknod /dev/hdm13 b 88 13; \ + mknod /dev/hdm14 b 88 14; \ + mknod /dev/hdm15 b 88 15; \ + mknod /dev/hdm16 b 88 16; \ + chown root.disk /dev/hdm*; \ + chmod 660 /dev/hdm*; \ +fi + +if [ ! -f /dev/hdn ]; then \ + echo "Making IDE6 Secondary Devices hdn's"; \ + mknod /dev/hdn b 88 64; \ + mknod /dev/hdn1 b 88 65; \ + mknod /dev/hdn2 b 88 66; \ + mknod /dev/hdn3 b 88 67; \ + mknod /dev/hdn4 b 88 68; \ + mknod /dev/hdn5 b 88 69; \ + mknod /dev/hdn6 b 88 70; \ + mknod /dev/hdn7 b 88 71; \ + mknod /dev/hdn8 b 88 72; \ + mknod /dev/hdn9 b 88 73; \ + mknod /dev/hdn10 b 88 74; \ + mknod /dev/hdn11 b 88 75; \ + mknod /dev/hdn12 b 88 76; \ + mknod /dev/hdn13 b 88 77; \ + mknod /dev/hdn14 b 88 78; \ + mknod /dev/hdn15 b 88 79; \ + mknod /dev/hdn16 b 88 80; \ + chown root.disk /dev/hdn*; \ + chmod 660 /dev/hdn*; \ +fi + +if [ ! -f /dev/hdo ]; then \ + echo "Making IDE7 Primary Devices hdo's"; \ + mknod /dev/hdo b 89 0; \ + mknod /dev/hdo1 b 89 1; \ + mknod /dev/hdo2 b 89 2; \ + mknod /dev/hdo3 b 89 3; \ + mknod /dev/hdo4 b 89 4; \ + mknod /dev/hdo5 b 89 5; \ + mknod /dev/hdo6 b 89 6; \ + mknod /dev/hdo7 b 89 7; \ + mknod /dev/hdo8 b 89 8; \ + mknod /dev/hdo9 b 89 9; \ + mknod /dev/hdo10 b 89 10; \ + mknod /dev/hdo11 b 89 11; \ + mknod /dev/hdo12 b 89 12; \ + mknod /dev/hdo13 b 89 13; \ + mknod /dev/hdo14 b 89 14; \ + mknod /dev/hdo15 b 89 15; \ + mknod /dev/hdo16 b 89 16; \ + chown root.disk /dev/hdo*; \ + chmod 660 /dev/hdo*; \ +fi + +if [ ! -f /dev/hdp ]; then \ + echo "Making IDE7 Secondary Devices hdp's"; \ + mknod /dev/hdp b 89 64; \ + mknod /dev/hdp1 b 89 65; \ + mknod /dev/hdp2 b 89 66; \ + mknod /dev/hdp3 b 89 67; \ + mknod /dev/hdp4 b 89 68; \ + mknod /dev/hdp5 b 89 69; \ + mknod /dev/hdp6 b 89 70; \ + mknod /dev/hdp7 b 89 71; \ + mknod /dev/hdp8 b 89 72; \ + mknod /dev/hdp9 b 89 73; \ + mknod /dev/hdp10 b 89 74; \ + mknod /dev/hdp11 b 89 75; \ + mknod /dev/hdp12 b 89 76; \ + mknod /dev/hdp13 b 89 77; \ + mknod /dev/hdp14 b 89 78; \ + mknod /dev/hdp15 b 89 79; \ + mknod /dev/hdp16 b 89 80; \ + chown root.disk /dev/hdp*; \ + chmod 660 /dev/hdp*; \ +fi + diff --git a/drivers/block/aec6210.c b/drivers/block/aec6210.c new file mode 100644 index 000000000..03ef37c90 --- /dev/null +++ b/drivers/block/aec6210.c @@ -0,0 +1,63 @@ +/* + * linux/drivers/block/aec6210.c Version 0.01 Nov 17, 1998 + * + * Copyright (C) 1998 Andre Hedrick (hedrick@astro.dyer.vanderbilt.edu) + * + * pio 0 :: 40: 00 07 00 00 00 00 00 00 02 07 a6 04 00 02 00 02 + * pio 1 :: 40: 0a 07 00 00 00 00 00 00 02 07 a6 05 00 02 00 02 + * pio 2 :: 40: 08 07 00 00 00 00 00 00 02 07 a6 05 00 02 00 02 + * pio 3 :: 40: 03 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * pio 4 :: 40: 01 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * dma 0 :: 40: 0a 07 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * dma 1 :: 40: 02 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * dma 2 :: 40: 01 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * 50: ff ff ff ff 00 06 04 00 00 00 00 00 00 00 00 00 + * + * udma 0 :: 40: 01 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * 50: ff ff ff ff 01 06 04 00 00 00 00 00 00 00 00 00 + * + * udma 1 :: 40: 01 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * 50: ff ff ff ff 01 06 04 00 00 00 00 00 00 00 00 00 + * + * udma 2 :: 40: 01 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * 50: ff ff ff ff 02 06 04 00 00 00 00 00 00 00 00 00 + * + * auto :: 40: 01 04 00 00 00 00 00 00 02 05 a6 05 00 02 00 02 + * 50: ff ff ff ff 02 06 04 00 00 00 00 00 00 00 00 00 + * + * auto :: 40: 01 04 01 04 01 04 01 04 02 05 a6 cf 00 02 00 02 + * 50: ff ff ff ff aa 06 04 00 00 00 00 00 00 00 00 00 + * + * NO-Devices + * 40: 00 00 00 00 00 00 00 00 02 05 a6 00 00 02 00 02 + * 50: ff ff ff ff 00 06 00 00 00 00 00 00 00 00 00 00 + + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> + +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/ide.h> + +#include <asm/io.h> +#include <asm/irq.h> + +__initfunc(unsigned int pci_init_aec6210 (struct pci_dev *dev, const char *name)) +{ + if (dev->rom_address) { + pci_write_config_dword(dev, PCI_ROM_ADDRESS, + dev->rom_address | PCI_ROM_ADDRESS_ENABLE); + printk("%s: ROM enabled at 0x%08lx\n", + name, dev->rom_address); + } + return dev->irq; +} diff --git a/drivers/block/alim15x3.c b/drivers/block/alim15x3.c new file mode 100644 index 000000000..ce5d0cb63 --- /dev/null +++ b/drivers/block/alim15x3.c @@ -0,0 +1,348 @@ +/* + * linux/drivers/block/alim15x3.c Version 0.04 Feb. 8, 1999 + * + * Copyright (C) 1998-99 Michel Aubry, Maintainer + * Copyright (C) 1998-99 Andrzej Krzysztofowicz, Maintainer + * Copyright (C) 1998-99 Andre Hedrick, Integrater and Maintainer + * + * (U)DMA capable version of ali 1533/1543(C) + * + * Default disable (U)DMA on all devices execpt hard disks. + * This measure of overkill is needed to stablize the chipset code. + * + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/ide.h> + +#include <asm/io.h> + +#define DISPLAY_ALI_TIMINGS + +#if defined(DISPLAY_ALI_TIMINGS) && defined(CONFIG_PROC_FS) +#include <linux/stat.h> +#include <linux/proc_fs.h> + +static int ali_get_info(char *buffer, char **addr, off_t offset, int count, int dummy); +extern int (*ali_display_info)(char *, char **, off_t, int, int); /* ide-proc.c */ +struct pci_dev *bmide_dev; + +char *fifo[4] = { + "FIFO Off", + "FIFO On ", + "DMA mode", + "PIO mode" }; + +char *udmaT[8] = { + "1.5T", + " 2T", + "2.5T", + " 3T", + "3.5T", + " 4T", + " 6T", + " 8T" +}; + +char *channel_status[8] = { + "OK ", + "busy ", + "DRQ ", + "DRQ busy ", + "error ", + "error busy ", + "error DRQ ", + "error DRQ busy" +}; +#endif /* defined(DISPLAY_ALI_TIMINGS) && defined(CONFIG_PROC_FS) */ + +__initfunc(unsigned int pci_init_ali15x3 (struct pci_dev *dev, const char *name)) +{ + byte confreg0 = 0, confreg1 =0, progif = 0; + int errors = 0; + + if (pci_read_config_byte(dev, 0x50, &confreg1)) + goto veryspecialsettingserror; + if (!(confreg1 & 0x02)) + if (pci_write_config_byte(dev, 0x50, confreg1 | 0x02)) + goto veryspecialsettingserror; + + if (pci_read_config_byte(dev, PCI_CLASS_PROG, &progif)) + goto veryspecialsettingserror; + if (!(progif & 0x40)) { + /* + * The way to enable them is to set progif + * writable at 0x4Dh register, and set bit 6 + * of progif to 1: + */ + if (pci_read_config_byte(dev, 0x4d, &confreg0)) + goto veryspecialsettingserror; + if (confreg0 & 0x80) + if (pci_write_config_byte(dev, 0x4d, confreg0 & ~0x80)) + goto veryspecialsettingserror; + if (pci_write_config_byte(dev, PCI_CLASS_PROG, progif | 0x40)) + goto veryspecialsettingserror; + if (confreg0 & 0x80) + if (pci_write_config_byte(dev, 0x4d, confreg0)) + errors++; + } + + if ((pci_read_config_byte(dev, PCI_CLASS_PROG, &progif)) || (!(progif & 0x40))) + goto veryspecialsettingserror; + + printk("%s: enabled read of IDE channels state (en/dis-abled) %s.\n", + name, errors ? "with Error(s)" : "Succeeded" ); + return 0; + +veryspecialsettingserror: + printk("%s: impossible to enable read of IDE channels state (en/dis-abled)!\n", name); + return 0; +} + +int ali15x3_dmaproc (ide_dma_action_t func, ide_drive_t *drive) +{ + switch (func) { + case ide_dma_check: + if (drive->media == ide_cdrom) { + ide_hwif_t *hwif = HWIF(drive); + struct pci_dev *dev = hwif->pci_dev; + struct hd_driveid *id = drive->id; + byte cd_dma_fifo = 0; + + pci_read_config_byte(dev, 0x53, &cd_dma_fifo); + + if (((id->field_valid & 4) || (id->field_valid & 2)) && + (id->capability & 1) && hwif->autodma) { + unsigned long dma_set_bit = hwif->dma_base + 2; +#if 0 + if (cd_dma_fifo & 0x02) + pci_write_config_byte(dev, 0x53, cd_dma_fifo & ~0x02); + pci_write_config_byte(dev, 0x53, cd_dma_fifo|0x01); +#else + pci_write_config_byte(dev, 0x53, cd_dma_fifo|0x01|0x02); +#endif + if (drive->select.b.unit & 0x01) { + outb(inb(dma_set_bit)|0x40, dma_set_bit); + } else { + outb(inb(dma_set_bit)|0x20, dma_set_bit); + } + } else { + if (cd_dma_fifo & 0x01) + pci_write_config_byte(dev, 0x53, cd_dma_fifo & ~0x01); + pci_write_config_byte(dev, 0x53, cd_dma_fifo|0x02); + } + } else if (drive->media != ide_disk) { + return ide_dmaproc(ide_dma_off_quietly, drive); + } + default: + break; + } + return ide_dmaproc(func, drive); /* use standard DMA stuff */ +} + +__initfunc(void ide_init_ali15x3 (ide_hwif_t *hwif)) +{ + struct pci_dev *dev; + byte ideic, inmir; + byte irq_routing_table[] = { -1, 9, 3, 10, 4, 5, 7, 6, + 1, 11, 0, 12, 0, 14, 0, 15 }; + hwif->irq = hwif->channel ? 15 : 14; + for (dev = pci_devices; dev; dev=dev->next) /* look for ISA bridge */ + if (dev->vendor==PCI_VENDOR_ID_AL && + dev->device==PCI_DEVICE_ID_AL_M1533) + break; + if (dev) { + pci_read_config_byte(dev, 0x58, &ideic); + ideic = ideic & 0x03; + if ((hwif->channel && ideic == 0x03) || + (!hwif->channel && !ideic)) { + pci_read_config_byte(dev, 0x44, &inmir); + inmir = inmir & 0x0f; + hwif->irq = irq_routing_table[inmir]; + } else + if (hwif->channel && !(ideic & 0x01)) { + pci_read_config_byte(dev, 0x75, &inmir); + inmir = inmir & 0x0f; + hwif->irq = irq_routing_table[inmir]; + } + } +#if defined(DISPLAY_ALI_TIMINGS) && defined(CONFIG_PROC_FS) + bmide_dev = hwif->pci_dev; + ali_display_info = &ali_get_info; +#endif /* defined(DISPLAY_ALI_TIMINGS) && defined(CONFIG_PROC_FS) */ + + if (hwif->dma_base) + hwif->dmaproc = &ali15x3_dmaproc; + return; +} + +#if defined(DISPLAY_ALI_TIMINGS) && defined(CONFIG_PROC_FS) +static int ali_get_info(char *buffer, char **addr, off_t offset, int count, int dummy) +{ + byte reg53h, reg5xh, reg5yh, reg5xh1, reg5yh1; + unsigned int bibma; + byte c0, c1; + byte rev, tmp; + char *p = buffer; + char *q; + + /* fetch rev. */ + pci_read_config_byte(bmide_dev, 0x08, &rev); + if (rev >= 0xc1) /* M1543C or newer */ + udmaT[7] = " ???"; + else + fifo[3] = " ??? "; + + /* first fetch bibma: */ + pci_read_config_dword(bmide_dev, 0x20, &bibma); + bibma = (bibma & 0xfff0) ; + /* + * at that point bibma+0x2 et bibma+0xa are byte + * registers to investigate: + */ + c0 = inb((unsigned short)bibma + 0x02); + c1 = inb((unsigned short)bibma + 0x0a); + + p += sprintf(p, + "\n Ali M15x3 Chipset.\n"); + p += sprintf(p, + " ------------------\n"); + pci_read_config_byte(bmide_dev, 0x78, ®53h); + p += sprintf(p, "PCI Clock: %d.\n", reg53h); + + pci_read_config_byte(bmide_dev, 0x53, ®53h); + p += sprintf(p, + "CD_ROM FIFO:%s, CD_ROM DMA:%s\n", + (reg53h & 0x02) ? "Yes" : "No ", + (reg53h & 0x01) ? "Yes" : "No " ); + pci_read_config_byte(bmide_dev, 0x74, ®53h); + p += sprintf(p, + "FIFO Status: contains %d Words, runs%s%s\n\n", + (reg53h & 0x3f), + (reg53h & 0x40) ? " OVERWR" : "", + (reg53h & 0x80) ? " OVERRD." : "." ); + + p += sprintf(p, + "-------------------primary channel-------------------secondary channel---------\n\n"); + + pci_read_config_byte(bmide_dev, 0x09, ®53h); + p += sprintf(p, + "channel status: %s %s\n", + (reg53h & 0x20) ? "On " : "Off", + (reg53h & 0x10) ? "On " : "Off" ); + + p += sprintf(p, + "both channels togth: %s %s\n", + (c0&0x80) ? "No " : "Yes", + (c1&0x80) ? "No " : "Yes" ); + + pci_read_config_byte(bmide_dev, 0x76, ®53h); + p += sprintf(p, + "Channel state: %s %s\n", + channel_status[reg53h & 0x07], + channel_status[(reg53h & 0x70) >> 4] ); + + pci_read_config_byte(bmide_dev, 0x58, ®5xh); + pci_read_config_byte(bmide_dev, 0x5c, ®5yh); + p += sprintf(p, + "Add. Setup Timing: %dT %dT\n", + (reg5xh & 0x07) ? (reg5xh & 0x07) : 8, + (reg5yh & 0x07) ? (reg5yh & 0x07) : 8 ); + + pci_read_config_byte(bmide_dev, 0x59, ®5xh); + pci_read_config_byte(bmide_dev, 0x5d, ®5yh); + p += sprintf(p, + "Command Act. Count: %dT %dT\n" + "Command Rec. Count: %dT %dT\n\n", + (reg5xh & 0x70) ? ((reg5xh & 0x70) >> 4) : 8, + (reg5yh & 0x70) ? ((reg5yh & 0x70) >> 4) : 8, + (reg5xh & 0x0f) ? (reg5xh & 0x0f) : 16, + (reg5yh & 0x0f) ? (reg5yh & 0x0f) : 16 ); + + p += sprintf(p, + "----------------drive0-----------drive1------------drive0-----------drive1------\n\n"); + p += sprintf(p, + "DMA enabled: %s %s %s %s\n", + (c0&0x20) ? "Yes" : "No ", + (c0&0x40) ? "Yes" : "No ", + (c1&0x20) ? "Yes" : "No ", + (c1&0x40) ? "Yes" : "No " ); + + pci_read_config_byte(bmide_dev, 0x54, ®5xh); + pci_read_config_byte(bmide_dev, 0x55, ®5yh); + q = "FIFO threshold: %2d Words %2d Words %2d Words %2d Words\n"; + if (rev < 0xc1) { + if ((rev == 0x20) && (pci_read_config_byte(bmide_dev, 0x4f, &tmp), (tmp &= 0x20))) { + p += sprintf(p, q, 8, 8, 8, 8); + } else { + p += sprintf(p, q, + (reg5xh & 0x03) + 12, + ((reg5xh & 0x30)>>4) + 12, + (reg5yh & 0x03) + 12, + ((reg5yh & 0x30)>>4) + 12 ); + } + } else { + p += sprintf(p, q, + (tmp = (reg5xh & 0x03)) ? (tmp << 3) : 4, + (tmp = ((reg5xh & 0x30)>>4)) ? (tmp << 3) : 4, + (tmp = (reg5yh & 0x03)) ? (tmp << 3) : 4, + (tmp = ((reg5yh & 0x30)>>4)) ? (tmp << 3) : 4 ); + } + +#if 0 + p += sprintf(p, + "FIFO threshold: %2d Words %2d Words %2d Words %2d Words\n", + (reg5xh & 0x03) + 12, + ((reg5xh & 0x30)>>4) + 12, + (reg5yh & 0x03) + 12, + ((reg5yh & 0x30)>>4) + 12 ); +#endif + + p += sprintf(p, + "FIFO mode: %s %s %s %s\n", + fifo[((reg5xh & 0x0c) >> 2)], + fifo[((reg5xh & 0xc0) >> 6)], + fifo[((reg5yh & 0x0c) >> 2)], + fifo[((reg5yh & 0xc0) >> 6)] ); + + pci_read_config_byte(bmide_dev, 0x5a, ®5xh); + pci_read_config_byte(bmide_dev, 0x5b, ®5xh1); + pci_read_config_byte(bmide_dev, 0x5e, ®5yh); + pci_read_config_byte(bmide_dev, 0x5f, ®5yh1); + + p += sprintf(p,/* + "------------------drive0-----------drive1------------drive0-----------drive1------\n")*/ + "Dt RW act. Cnt %2dT %2dT %2dT %2dT\n" + "Dt RW rec. Cnt %2dT %2dT %2dT %2dT\n\n", + (reg5xh & 0x70) ? ((reg5xh & 0x70) >> 4) : 8, + (reg5xh1 & 0x70) ? ((reg5xh1 & 0x70) >> 4) : 8, + (reg5yh & 0x70) ? ((reg5yh & 0x70) >> 4) : 8, + (reg5yh1 & 0x70) ? ((reg5yh1 & 0x70) >> 4) : 8, + (reg5xh & 0x0f) ? (reg5xh & 0x0f) : 16, + (reg5xh1 & 0x0f) ? (reg5xh1 & 0x0f) : 16, + (reg5yh & 0x0f) ? (reg5yh & 0x0f) : 16, + (reg5yh1 & 0x0f) ? (reg5yh1 & 0x0f) : 16 ); + + p += sprintf(p, + "-----------------------------------UDMA Timings--------------------------------\n\n"); + + pci_read_config_byte(bmide_dev, 0x56, ®5xh); + pci_read_config_byte(bmide_dev, 0x57, ®5yh); + p += sprintf(p, + "UDMA: %s %s %s %s\n" + "UDMA timings: %s %s %s %s\n\n", + (reg5xh & 0x08) ? "OK" : "No", + (reg5xh & 0x80) ? "OK" : "No", + (reg5yh & 0x08) ? "OK" : "No", + (reg5yh & 0x80) ? "OK" : "No", + udmaT[(reg5xh & 0x07)], + udmaT[(reg5xh & 0x70) >> 4], + udmaT[reg5yh & 0x07], + udmaT[(reg5yh & 0x70) >> 4] ); + + return p-buffer; /* => must be less than 4k! */ +} +#endif /* defined(DISPLAY_ALI_TIMINGS) && defined(CONFIG_PROC_FS) */ diff --git a/drivers/block/blkpg.c b/drivers/block/blkpg.c new file mode 100644 index 000000000..6f5674072 --- /dev/null +++ b/drivers/block/blkpg.c @@ -0,0 +1,290 @@ +/* + * Partition table and disk geometry handling + * + * This obsoletes the partition-handling code in genhd.c: + * Userspace can look at a disk in arbitrary format and tell + * the kernel what partitions there are on the disk, and how + * these should be numbered. + * It also allows one to repartition a disk that is being used. + * + * A single ioctl with lots of subfunctions: + * + * Device number stuff: + * get_whole_disk() (given the device number of a partition, find + * the device number of the encompassing disk) + * get_all_partitions() (given the device number of a disk, return the + * device numbers of all its known partitions) + * + * Partition stuff: + * add_partition() + * delete_partition() + * test_partition_in_use() (also for test_disk_in_use) + * + * Geometry stuff: + * get_geometry() + * set_geometry() + * get_bios_drivedata() + * + * For today, only the partition stuff - aeb, 990515 + */ + +#include <linux/errno.h> +#include <linux/fs.h> /* for BLKRASET, ... */ +#include <linux/sched.h> /* for capable() */ +#include <linux/blk.h> /* for set_device_ro() */ +#include <linux/blkpg.h> +#include <linux/genhd.h> +#include <linux/swap.h> /* for is_swap_partition() */ +#include <linux/module.h> /* for EXPORT_SYMBOL */ + +#include <asm/uaccess.h> + +/* + * What is the data describing a partition? + * + * 1. a device number (kdev_t) + * 2. a starting sector and number of sectors (hd_struct) + * given in the part[] array of the gendisk structure for the drive. + * + * The number of sectors is replicated in the sizes[] array of + * the gendisk structure for the major, which again is copied to + * the blk_size[][] array. + * (However, hd_struct has the number of 512-byte sectors, + * g->sizes[] and blk_size[][] have the number of 1024-byte blocks.) + * Note that several drives may have the same major. + */ + +/* a linear search, superfluous when dev is a pointer */ +static struct gendisk *get_gendisk(kdev_t dev) { + struct gendisk *g; + int m = MAJOR(dev); + + for (g = gendisk_head; g; g = g->next) + if (g->major == m) + break; + return g; +} + +/* moved here from md.c - will be discarded later */ +char *partition_name (kdev_t dev) { + static char name[40]; /* kdevname returns 32 bytes */ + /* disk_name requires 32 bytes */ + struct gendisk *hd = get_gendisk (dev); + + if (!hd) { + sprintf (name, "[dev %s]", kdevname(dev)); + return (name); + } + + return disk_name (hd, MINOR(dev), name); /* routine in genhd.c */ +} + +/* + * Add a partition. + * + * returns: EINVAL: bad parameters + * ENXIO: cannot find drive + * EBUSY: proposed partition overlaps an existing one + * or has the same number as an existing one + * 0: all OK. + */ +int add_partition(kdev_t dev, struct blkpg_partition *p) { + struct gendisk *g; + long long ppstart, pplength; + long pstart, plength; + int i, drive, first_minor, end_minor, minor; + + /* convert bytes to sectors, check for fit in a hd_struct */ + ppstart = (p->start >> 9); + pplength = (p->length >> 9); + pstart = ppstart; + plength = pplength; + if (pstart != ppstart || plength != pplength + || pstart < 0 || plength < 0) + return -EINVAL; + + /* find the drive major */ + g = get_gendisk(dev); + if (!g) + return -ENXIO; + + /* existing drive? */ + drive = (MINOR(dev) >> g->minor_shift); + first_minor = (drive << g->minor_shift); + end_minor = first_minor + g->max_p; + if (drive >= g->nr_real) + return -ENXIO; + + /* drive and partition number OK? */ + if (first_minor != MINOR(dev) || p->pno <= 0 || p->pno >= g->max_p) + return -EINVAL; + + /* partition number in use? */ + minor = first_minor + p->pno; + if (g->part[minor].nr_sects != 0) + return -EBUSY; + + /* overlap? */ + for (i=first_minor+1; i<end_minor; i++) + if (!(pstart+plength <= g->part[i].start_sect || + pstart >= g->part[i].start_sect + g->part[i].nr_sects)) + return -EBUSY; + + /* all seems OK */ + g->part[minor].start_sect = pstart; + g->part[minor].nr_sects = plength; + if (g->sizes) + g->sizes[minor] = (plength >> (BLOCK_SIZE_BITS - 9)); + return 0; +} + +/* + * Delete a partition given by partition number + * + * returns: EINVAL: bad parameters + * ENXIO: cannot find partition + * EBUSY: partition is busy + * 0: all OK. + * + * Note that the dev argument refers to the entire disk, not the partition. + */ +int del_partition(kdev_t dev, struct blkpg_partition *p) { + struct gendisk *g; + kdev_t devp; + int drive, first_minor, minor; + + /* find the drive major */ + g = get_gendisk(dev); + if (!g) + return -ENXIO; + + /* drive and partition number OK? */ + drive = (MINOR(dev) >> g->minor_shift); + first_minor = (drive << g->minor_shift); + if (first_minor != MINOR(dev) || p->pno <= 0 || p->pno >= g->max_p) + return -EINVAL; + + /* existing drive and partition? */ + minor = first_minor + p->pno; + if (drive >= g->nr_real || g->part[minor].nr_sects == 0) + return -ENXIO; + + /* partition in use? Incomplete check for now. */ + devp = MKDEV(MAJOR(dev), minor); + if (get_super(devp) || /* mounted? */ + is_swap_partition(devp)) + return -EBUSY; + + /* all seems OK */ + fsync_dev(devp); + invalidate_buffers(devp); + + g->part[minor].start_sect = 0; + g->part[minor].nr_sects = 0; + if (g->sizes) + g->sizes[minor] = 0; + + return 0; +} + +int blkpg_ioctl(kdev_t dev, struct blkpg_ioctl_arg *arg) +{ + struct blkpg_ioctl_arg a; + struct blkpg_partition p; + int len; + + if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg))) + return -EFAULT; + + switch (a.op) { + case BLKPG_ADD_PARTITION: + case BLKPG_DEL_PARTITION: + len = a.datalen; + if (len < sizeof(struct blkpg_partition)) + return -EINVAL; + if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition))) + return -EFAULT; + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + if (a.op == BLKPG_ADD_PARTITION) + return add_partition(dev, &p); + else + return del_partition(dev, &p); + default: + return -EINVAL; + } +} + +/* + * Common ioctl's for block devices + */ + +int blk_ioctl(kdev_t dev, unsigned int cmd, unsigned long arg) +{ + int intval; + + switch (cmd) { + case BLKROSET: + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + if (get_user(intval, (int *)(arg))) + return -EFAULT; + set_device_ro(dev, intval); + return 0; + case BLKROGET: + intval = (is_read_only(dev) != 0); + return put_user(intval, (int *)(arg)); + + case BLKRASET: + if(!capable(CAP_SYS_ADMIN)) + return -EACCES; + if(!dev || arg > 0xff) + return -EINVAL; + read_ahead[MAJOR(dev)] = arg; + return 0; + case BLKRAGET: + if (!arg) + return -EINVAL; + return put_user(read_ahead[MAJOR(dev)], (long *) arg); + + case BLKFLSBUF: + if(!capable(CAP_SYS_ADMIN)) + return -EACCES; + if (!dev) + return -EINVAL; + fsync_dev(dev); + invalidate_buffers(dev); + return 0; + + case BLKSSZGET: + /* get block device sector size as needed e.g. by fdisk */ + intval = get_hardsect_size(dev); + return put_user(intval, (int *) arg); + +#if 0 + case BLKGETSIZE: + /* Today get_gendisk() requires a linear scan; + add this when dev has pointer type. */ + g = get_gendisk(dev); + if (!g) + longval = 0; + else + longval = g->part[MINOR(dev)].nr_sects; + return put_user(longval, (long *) arg); +#endif +#if 0 + case BLKRRPART: /* Re-read partition tables */ + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + return reread_partitions(dev, 1); +#endif + + case BLKPG: + return blkpg_ioctl(dev, (struct blkpg_ioctl_arg *) arg); + + default: + return -EINVAL; + } +} + +EXPORT_SYMBOL(blk_ioctl); diff --git a/drivers/block/buddha.c b/drivers/block/buddha.c new file mode 100644 index 000000000..8086dac56 --- /dev/null +++ b/drivers/block/buddha.c @@ -0,0 +1,161 @@ +/* + * linux/drivers/block/buddha.c -- Amiga Buddha and Catweasel IDE Driver + * + * Copyright (C) 1997 by Geert Uytterhoeven + * + * This driver was written by based on the specifications in README.buddha. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * TODO: + * - test it :-) + * - tune the timings using the speed-register + */ + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/zorro.h> +#include <linux/ide.h> + +#include <asm/amigahw.h> +#include <asm/amigaints.h> + + + /* + * The Buddha has 2 IDE interfaces, the Catweasel has 3 + */ + +#define BUDDHA_NUM_HWIFS 2 +#define CATWEASEL_NUM_HWIFS 3 + + + /* + * Bases of the IDE interfaces (relative to the board address) + */ + +#define BUDDHA_BASE1 0x800 +#define BUDDHA_BASE2 0xa00 +#define BUDDHA_BASE3 0xc00 + +static const u_int buddha_bases[CATWEASEL_NUM_HWIFS] = { + BUDDHA_BASE1, BUDDHA_BASE2, BUDDHA_BASE3 +}; + + + /* + * Offsets from one of the above bases + */ + +#define BUDDHA_DATA 0x00 +#define BUDDHA_ERROR 0x06 /* see err-bits */ +#define BUDDHA_NSECTOR 0x0a /* nr of sectors to read/write */ +#define BUDDHA_SECTOR 0x0e /* starting sector */ +#define BUDDHA_LCYL 0x12 /* starting cylinder */ +#define BUDDHA_HCYL 0x16 /* high byte of starting cyl */ +#define BUDDHA_SELECT 0x1a /* 101dhhhh , d=drive, hhhh=head */ +#define BUDDHA_STATUS 0x1e /* see status-bits */ +#define BUDDHA_CONTROL 0x11a + +static int buddha_offsets[IDE_NR_PORTS] = { + BUDDHA_DATA, BUDDHA_ERROR, BUDDHA_NSECTOR, BUDDHA_SECTOR, BUDDHA_LCYL, + BUDDHA_HCYL, BUDDHA_SELECT, BUDDHA_STATUS, BUDDHA_CONTROL +}; + + + /* + * Other registers + */ + +#define BUDDHA_IRQ1 0xf00 /* MSB = 1, Harddisk is source of */ +#define BUDDHA_IRQ2 0xf40 /* interrupt */ +#define BUDDHA_IRQ3 0xf80 + +static const int buddha_irqports[CATWEASEL_NUM_HWIFS] = { + BUDDHA_IRQ1, BUDDHA_IRQ2, BUDDHA_IRQ3 +}; + +#define BUDDHA_IRQ_MR 0xfc0 /* master interrupt enable */ + + + /* + * Board information + */ + +static u_long buddha_board = 0; +static int buddha_num_hwifs = -1; + + + /* + * Check and acknowledge the interrupt status + */ + +static int buddha_ack_intr(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = inb(hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & 0x80)) + return 0; + return 1; +} + + + /* + * Any Buddha or Catweasel boards present? + */ + +static int find_buddha(void) +{ + u_int key; + const struct ConfigDev *cd; + + buddha_num_hwifs = 0; + if ((key = zorro_find(ZORRO_PROD_INDIVIDUAL_COMPUTERS_BUDDHA, 0, 0))) + buddha_num_hwifs = BUDDHA_NUM_HWIFS; + else if ((key = zorro_find(ZORRO_PROD_INDIVIDUAL_COMPUTERS_CATWEASEL, 0, + 0))) + buddha_num_hwifs = CATWEASEL_NUM_HWIFS; + if (key) { + cd = zorro_get_board(key); + buddha_board = (u_long)cd->cd_BoardAddr; + if (buddha_board) { + buddha_board = ZTWO_VADDR(buddha_board); + /* write to BUDDHA_IRQ_MR to enable the board IRQ */ + *(char *)(buddha_board+BUDDHA_IRQ_MR) = 0; + zorro_config_board(key, 0); + } + } + return buddha_num_hwifs; +} + + + /* + * Probe for a Buddha or Catweasel IDE interface + * We support only _one_ of them, no multiple boards! + */ + +void buddha_init(void) +{ + hw_regs_t hw; + int i, index; + + if (buddha_num_hwifs < 0 && !find_buddha()) + return; + + for (i = 0; i < buddha_num_hwifs; i++) { + ide_setup_ports(&hw, (ide_ioreg_t)(buddha_board+buddha_bases[i]), + buddha_offsets, 0, + (ide_ioreg_t)(buddha_board+buddha_irqports[i]), + buddha_ack_intr, IRQ_AMIGA_PORTS); + index = ide_register_hw(&hw, NULL); + if (index != -1) + printk("ide%d: %s IDE interface\n", index, + buddha_num_hwifs == BUDDHA_NUM_HWIFS ? "Buddha" : + "Catweasel"); + } +} diff --git a/drivers/block/cy82c693.c b/drivers/block/cy82c693.c new file mode 100644 index 000000000..e1e46ea8a --- /dev/null +++ b/drivers/block/cy82c693.c @@ -0,0 +1,432 @@ +/* + * linux/drivers/block/cy82c693.c Version 0.33 Jan. 23, 1999 + * + * Copyright (C) 1998, 1999 Andreas S. Krebs (akrebs@altavista.net), Maintainer + * Copyright (C) 1998 Andre Hedrick, Integrater + * + * CYPRESS CY82C693 chipset IDE controller + * + * The CY82C693 chipset is used on Digital's PC-Alpha 164SX boards. + * Writting the driver was quite simple, since most of the job is + * done by the generic pci-ide support. + * The hard part was finding the CY82C693's datasheet on Cypress's + * web page :-(. But Altavista solved this problem :-). + * + * + * Notes: + * - I recently got a 16.8G IBM DTTA, so I was able to test it with + * a large and fast disk - the results look great, so I'd say the + * driver is working fine :-) + * hdparm -t reports 8.17 MB/sec at about 6% CPU usage for the DTTA + * - this is my first linux driver, so there's probably a lot of room + * for optimizations and bug fixing, so feel free to do it. + * - use idebus=xx parameter to set PCI bus speed - needed to calc + * timings for PIO modes (default will be 40) + * - if using PIO mode it's a good idea to set the PIO mode and + * 32-bit I/O support (if possible), e.g. hdparm -p2 -c1 /dev/hda + * - I had some problems with my IBM DHEA with PIO modes < 2 + * (lost interrupts) ????? + * - first tests with DMA look okay, they seem to work, but there is a + * problem with sound - the BusMaster IDE TimeOut should fixed this + * + * + * History: + * ASK@1999-01-23: v0.33 made a few minor code clean ups + * removed DMA clock speed setting by default + * added boot message + * ASK@1998-11-01: v0.32 added support to set BusMaster IDE TimeOut + * added support to set DMA Controller Clock Speed + * ASK@1998-10-31: v0.31 fixed problem with setting to high DMA modes on some drive + * ASK@1998-10-29: v0.3 added support to set DMA modes + * ASK@1998-10-28: v0.2 added support to set PIO modes + * ASK@1998-10-27: v0.1 first version - chipset detection + * + */ + +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/ide.h> + +#include <asm/io.h> + +#include "ide_modes.h" + +/* the current version */ +#define CY82_VERSION "CY82C693U driver v0.33 99-01-23 Andreas S. Krebs (akrebs@altavista.net)" + +/* + * The following are used to debug the driver. + */ +#define CY82C693_DEBUG_LOGS 0 +#define CY82C693_DEBUG_INFO 0 + +/* define CY82C693_SETDMA_CLOCK to set DMA Controller Clock Speed to ATCLK */ +#undef CY82C693_SETDMA_CLOCK + +/* + * note: the value for busmaster timeout is tricky and i got it by trial and error ! + * using a to low value will cause DMA timeouts and drop IDE performance + * using a to high value will cause audio playback to scatter + * if you know a better value or how to calc it, please let me know + */ +#define BUSMASTER_TIMEOUT 0x50 /* twice the value written in cy82c693ub datasheet */ +/* + * the value above was tested on my machine and it seems to work okay + */ + +/* here are the offset definitions for the registers */ +#define CY82_IDE_CMDREG 0x04 +#define CY82_IDE_ADDRSETUP 0x48 +#define CY82_IDE_MASTER_IOR 0x4C +#define CY82_IDE_MASTER_IOW 0x4D +#define CY82_IDE_SLAVE_IOR 0x4E +#define CY82_IDE_SLAVE_IOW 0x4F +#define CY82_IDE_MASTER_8BIT 0x50 +#define CY82_IDE_SLAVE_8BIT 0x51 + +#define CY82_INDEX_PORT 0x22 +#define CY82_DATA_PORT 0x23 + +#define CY82_INDEX_CTRLREG1 0x01 +#define CY82_INDEX_CHANNEL0 0x30 +#define CY82_INDEX_CHANNEL1 0x31 +#define CY82_INDEX_TIMEOUT 0x32 + +/* the max PIO mode - from datasheet */ +#define CY82C693_MAX_PIO 4 + +/* the min and max PCI bus speed in MHz - from datasheet */ +#define CY82C963_MIN_BUS_SPEED 25 +#define CY82C963_MAX_BUS_SPEED 33 + +/* the struct for the PIO mode timings */ +typedef struct pio_clocks_s { + byte address_time; /* Address setup (clocks) */ + byte time_16r; /* clocks for 16bit IOR (0xF0=Active/data, 0x0F=Recovery) */ + byte time_16w; /* clocks for 16bit IOW (0xF0=Active/data, 0x0F=Recovery) */ + byte time_8; /* clocks for 8bit (0xF0=Active/data, 0x0F=Recovery) */ +} pio_clocks_t; + +/* + * calc clocks using bus_speed + * returns (rounded up) time in bus clocks for time in ns + */ +static int calc_clk (int time, int bus_speed) +{ + int clocks; + + clocks = (time*bus_speed+999)/1000 -1; + + if (clocks < 0) + clocks = 0; + + if (clocks > 0x0F) + clocks = 0x0F; + + return clocks; +} + +/* + * compute the values for the clock registers for PIO + * mode and pci_clk [MHz] speed + * + * NOTE: for mode 0,1 and 2 drives 8-bit IDE command control registers are used + * for mode 3 and 4 drives 8 and 16-bit timings are the same + * + */ +static void compute_clocks (byte pio, pio_clocks_t *p_pclk) +{ + int clk1, clk2; + int bus_speed; + + bus_speed = ide_system_bus_speed(); /* get speed of PCI bus */ + /* we don't check against CY82C693's min and max speed, + * so you can play with the idebus=xx parameter + */ + + if (pio > CY82C693_MAX_PIO) + pio = CY82C693_MAX_PIO; + + /* let's calc the address setup time clocks */ + p_pclk->address_time = (byte)calc_clk(ide_pio_timings[pio].setup_time, bus_speed); + + /* let's calc the active and recovery time clocks */ + clk1 = calc_clk(ide_pio_timings[pio].active_time, bus_speed); + + /* calc recovery timing */ + clk2 = ide_pio_timings[pio].cycle_time - + ide_pio_timings[pio].active_time - + ide_pio_timings[pio].setup_time; + + clk2 = calc_clk(clk2, bus_speed); + + clk1 = (clk1<<4)|clk2; /* combine active and recovery clocks */ + + /* note: we use the same values for 16bit IOR and IOW + * those are all the same, since I don't have other + * timings than those from ide_modes.h + */ + + p_pclk->time_16r = (byte)clk1; + p_pclk->time_16w = (byte)clk1; + + /* what are good values for 8bit ?? */ + p_pclk->time_8 = (byte)clk1; +} + +/* + * set DMA mode a specific channel for CY82C693 + */ +static void cy82c693_dma_enable (ide_drive_t *drive, int mode, int single) +{ + byte index; + byte data; + + if (mode>2) /* make sure we set a valid mode */ + mode = 2; + + if (mode > drive->id->tDMA) /* to be absolutly sure we have a valid mode */ + mode = drive->id->tDMA; + + index = (HWIF(drive)->channel==0) ? CY82_INDEX_CHANNEL0 : CY82_INDEX_CHANNEL1; + +#if CY82C693_DEBUG_LOGS + /* for debug let's show the previous values */ + + OUT_BYTE(index, CY82_INDEX_PORT); + data = IN_BYTE(CY82_DATA_PORT); + + printk (KERN_INFO "%s (ch=%d, dev=%d): DMA mode is %d (single=%d)\n", drive->name, HWIF(drive)->channel, drive->select.b.unit, (data&0x3), ((data>>2)&1)); +#endif /* CY82C693_DEBUG_LOGS */ + + data = (byte)mode|(byte)(single<<2); + + OUT_BYTE(index, CY82_INDEX_PORT); + OUT_BYTE(data, CY82_DATA_PORT); + +#if CY82C693_DEBUG_INFO + printk (KERN_INFO "%s (ch=%d, dev=%d): set DMA mode to %d (single=%d)\n", drive->name, HWIF(drive)->channel, drive->select.b.unit, mode, single); +#endif /* CY82C693_DEBUG_INFO */ + + /* + * note: below we set the value for Bus Master IDE TimeOut Register + * I'm not absolutly sure what this does, but it solved my problem + * with IDE DMA and sound, so I now can play sound and work with + * my IDE driver at the same time :-) + * + * If you know the correct (best) value for this register please + * let me know - ASK + */ + + data = BUSMASTER_TIMEOUT; + OUT_BYTE(CY82_INDEX_TIMEOUT, CY82_INDEX_PORT); + OUT_BYTE(data, CY82_DATA_PORT); + +#if CY82C693_DEBUG_INFO + printk (KERN_INFO "%s: Set IDE Bus Master TimeOut Register to 0x%X\n", drive->name, data); +#endif /* CY82C693_DEBUG_INFO */ +} + +/* + * used to set DMA mode for CY82C693 (single and multi modes) + */ +static int cy82c693_dmaproc(ide_dma_action_t func, ide_drive_t *drive) +{ + /* + * if the function is dma on, set dma mode for drive everything + * else is done by the defaul func + */ + if (func == ide_dma_on) { + struct hd_driveid *id = drive->id; + +#if CY82C693_DEBUG_INFO + printk (KERN_INFO "dma_on: %s\n", drive->name); +#endif /* CY82C693_DEBUG_INFO */ + + if (id != NULL) { + /* Enable DMA on any drive that has DMA (multi or single) enabled */ + if (id->field_valid & 2) { /* regular DMA */ + int mmode, smode; + + mmode = id->dma_mword & (id->dma_mword >> 8); + smode = id->dma_1word & (id->dma_1word >> 8); + + if (mmode != 0) + cy82c693_dma_enable(drive, (mmode >> 1), 0); /* enable multi */ + else if (smode != 0) + cy82c693_dma_enable(drive, (smode >> 1), 1); /* enable single */ + } + } + } + return ide_dmaproc(func, drive); +} + +/* + * tune ide drive - set PIO mode + */ +static void cy82c693_tune_drive (ide_drive_t *drive, byte pio) +{ + ide_hwif_t *hwif = HWIF(drive); + struct pci_dev *dev = hwif->pci_dev; + pio_clocks_t pclk; + unsigned int addrCtrl; + + /* select primary or secondary channel */ + if (hwif->index > 0) /* drive is on the secondary channel */ + dev = dev->next; + +#if CY82C693_DEBUG_LOGS + /* for debug let's show the register values */ + + if (drive->select.b.unit == 0) { + /* + * get master drive registers + * address setup control register + * is 32 bit !!! + */ + pci_read_config_dword(dev, CY82_IDE_ADDRSETUP, &addrCtrl); + addrCtrl &= 0x0F; + + /* now let's get the remaining registers */ + pci_read_config_byte(dev, CY82_IDE_MASTER_IOR, &pclk.time_16r); + pci_read_config_byte(dev, CY82_IDE_MASTER_IOW, &pclk.time_16w); + pci_read_config_byte(dev, CY82_IDE_MASTER_8BIT, &pclk.time_8); + } else { + /* + * set slave drive registers + * address setup control register + * is 32 bit !!! + */ + pci_read_config_dword(dev, CY82_IDE_ADDRSETUP, &addrCtrl); + + addrCtrl &= 0xF0; + addrCtrl >>= 4; + + /* now let's get the remaining registers */ + pci_read_config_byte(dev, CY82_IDE_SLAVE_IOR, &pclk.time_16r); + pci_read_config_byte(dev, CY82_IDE_SLAVE_IOW, &pclk.time_16w); + pci_read_config_byte(dev, CY82_IDE_SLAVE_8BIT, &pclk.time_8); + } + + printk (KERN_INFO "%s (ch=%d, dev=%d): PIO timing is (addr=0x%X, ior=0x%X, iow=0x%X, 8bit=0x%X)\n", drive->name, hwif->channel, drive->select.b.unit, addrCtrl, pclk.time_16r, pclk.time_16w, pclk.time_8); +#endif /* CY82C693_DEBUG_LOGS */ + + /* first let's calc the pio modes */ + pio = ide_get_best_pio_mode(drive, pio, CY82C693_MAX_PIO, NULL); + +#if CY82C693_DEBUG_INFO + printk (KERN_INFO "%s: Selected PIO mode %d\n", drive->name, pio); +#endif /* CY82C693_DEBUG_INFO */ + + compute_clocks(pio, &pclk); /* let's calc the values for this PIO mode */ + + /* now let's write the clocks registers */ + if (drive->select.b.unit == 0) { + /* + * set master drive + * address setup control register + * is 32 bit !!! + */ + pci_read_config_dword(dev, CY82_IDE_ADDRSETUP, &addrCtrl); + + addrCtrl &= (~0xF); + addrCtrl |= (unsigned int)pclk.address_time; + pci_write_config_dword(dev, CY82_IDE_ADDRSETUP, addrCtrl); + + /* now let's set the remaining registers */ + pci_write_config_byte(dev, CY82_IDE_MASTER_IOR, pclk.time_16r); + pci_write_config_byte(dev, CY82_IDE_MASTER_IOW, pclk.time_16w); + pci_write_config_byte(dev, CY82_IDE_MASTER_8BIT, pclk.time_8); + + addrCtrl &= 0xF; + } else { + /* + * set slave drive + * address setup control register + * is 32 bit !!! + */ + pci_read_config_dword(dev, CY82_IDE_ADDRSETUP, &addrCtrl); + + addrCtrl &= (~0xF0); + addrCtrl |= ((unsigned int)pclk.address_time<<4); + pci_write_config_dword(dev, CY82_IDE_ADDRSETUP, addrCtrl); + + /* now let's set the remaining registers */ + pci_write_config_byte(dev, CY82_IDE_SLAVE_IOR, pclk.time_16r); + pci_write_config_byte(dev, CY82_IDE_SLAVE_IOW, pclk.time_16w); + pci_write_config_byte(dev, CY82_IDE_SLAVE_8BIT, pclk.time_8); + + addrCtrl >>= 4; + addrCtrl &= 0xF; + } + +#if CY82C693_DEBUG_INFO + printk (KERN_INFO "%s (ch=%d, dev=%d): set PIO timing to (addr=0x%X, ior=0x%X, iow=0x%X, 8bit=0x%X)\n", drive->name, hwif->channel, drive->select.b.unit, addrCtrl, pclk.time_16r, pclk.time_16w, pclk.time_8); +#endif /* CY82C693_DEBUG_INFO */ +} + +/* + * this function is called during init and is used to setup the cy82c693 chip + */ +static void init_cy82c693_chip (struct pci_dev *dev) +{ + static int initDone = 0; +#ifdef CY82C693_SETDMA_CLOCK + byte data; +#endif /* CY82C693_SETDMA_CLOCK */ + + if (initDone != 0) /* only perform setup once */ + return; + initDone = 1; + + /* write info about this verion of the driver */ + printk (KERN_INFO CY82_VERSION "\n"); + +#ifdef CY82C693_SETDMA_CLOCK + /* okay let's set the DMA clock speed */ + + OUT_BYTE(CY82_INDEX_CTRLREG1, CY82_INDEX_PORT); + data = IN_BYTE(CY82_DATA_PORT); + +#if CY82C693_DEBUG_INFO + printk (KERN_INFO "CY82U693: Peripheral Configuration Register: 0x%X\n", data); +#endif /* CY82C693_DEBUG_INFO */ + + /* + * for some reason sometimes the DMA controller + * speed is set to ATCLK/2 ???? - we fix this here + * + * note: i don't know what causes this strange behaviour, + * but even changing the dma speed doesn't solve it :-( + * the ide performance is still only half the normal speed + * + * if anybody knows what goes wrong with my machine, please + * let me know - ASK + */ + + data |= 0x03; + + OUT_BYTE(CY82_INDEX_CTRLREG1, CY82_INDEX_PORT); + OUT_BYTE(data, CY82_DATA_PORT); + +#if CY82C693_DEBUG_INFO + printk (KERN_INFO "CY82U693: New Peripheral Configuration Register: 0x%X\n", data); +#endif /* CY82C693_DEBUG_INFO */ + +#endif /* CY82C693_SETDMA_CLOCK */ +} + +/* + * the init function - called for each ide channel once + */ +__initfunc(void ide_init_cy82c693(ide_hwif_t *hwif)) +{ + hwif->chipset = ide_cy82c693; + if (hwif->dma_base) + hwif->dmaproc = &cy82c693_dmaproc; + hwif->tuneproc = &cy82c693_tune_drive; + + init_cy82c693_chip(hwif->pci_dev); +} + diff --git a/drivers/block/falconide.c b/drivers/block/falconide.c new file mode 100644 index 000000000..321569eb1 --- /dev/null +++ b/drivers/block/falconide.c @@ -0,0 +1,66 @@ +/* + * linux/drivers/block/falconide.c -- Atari Falcon IDE Driver + * + * Created 12 Jul 1997 by Geert Uytterhoeven + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> + +#include <asm/atarihw.h> +#include <asm/atariints.h> +#include <asm/atari_stdma.h> + + + /* + * Base of the IDE interface + */ + +#define ATA_HD_BASE 0xfff00000 + + /* + * Offsets from the above base + */ + +#define ATA_HD_DATA 0x00 +#define ATA_HD_ERROR 0x05 /* see err-bits */ +#define ATA_HD_NSECTOR 0x09 /* nr of sectors to read/write */ +#define ATA_HD_SECTOR 0x0d /* starting sector */ +#define ATA_HD_LCYL 0x11 /* starting cylinder */ +#define ATA_HD_HCYL 0x15 /* high byte of starting cyl */ +#define ATA_HD_SELECT 0x19 /* 101dhhhh , d=drive, hhhh=head */ +#define ATA_HD_STATUS 0x1d /* see status-bits */ +#define ATA_HD_CONTROL 0x39 + +static int falconide_offsets[IDE_NR_PORTS] = { + ATA_HD_DATA, ATA_HD_ERROR, ATA_HD_NSECTOR, ATA_HD_SECTOR, ATA_HD_LCYL, + ATA_HD_HCYL, ATA_HD_SELECT, ATA_HD_STATUS, ATA_HD_CONTROL, -1 +}; + + + /* + * Probe for a Falcon IDE interface + */ + +void falconide_init(void) +{ + if (MACH_IS_ATARI && ATARIHW_PRESENT(IDE)) { + hw_regs_t hw; + int index; + + ide_setup_ports(&hw, (ide_ioreg_t)ATA_HD_BASE, falconide_offsets, + 0, 0, NULL, IRQ_MFP_IDE); + index = ide_register_hw(&hw, NULL); + + if (index != -1) + printk("ide%d: Falcon IDE interface\n", index); + } +} diff --git a/drivers/block/gayle.c b/drivers/block/gayle.c new file mode 100644 index 000000000..920f2ee0a --- /dev/null +++ b/drivers/block/gayle.c @@ -0,0 +1,169 @@ +/* + * linux/drivers/block/gayle.c -- Amiga Gayle IDE Driver + * + * Created 9 Jul 1997 by Geert Uytterhoeven + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/ide.h> + +#include <asm/amigahw.h> +#include <asm/amigaints.h> + + + /* + * Bases of the IDE interfaces + */ + +#define GAYLE_BASE_4000 0xdd2020 /* A4000/A4000T */ +#define GAYLE_BASE_1200 0xda0000 /* A1200/A600 */ + + /* + * Offsets from one of the above bases + */ + +#define GAYLE_DATA 0x00 +#define GAYLE_ERROR 0x06 /* see err-bits */ +#define GAYLE_NSECTOR 0x0a /* nr of sectors to read/write */ +#define GAYLE_SECTOR 0x0e /* starting sector */ +#define GAYLE_LCYL 0x12 /* starting cylinder */ +#define GAYLE_HCYL 0x16 /* high byte of starting cyl */ +#define GAYLE_SELECT 0x1a /* 101dhhhh , d=drive, hhhh=head */ +#define GAYLE_STATUS 0x1e /* see status-bits */ +#define GAYLE_CONTROL 0x101a + +static int gayle_offsets[IDE_NR_PORTS] = { + GAYLE_DATA, GAYLE_ERROR, GAYLE_NSECTOR, GAYLE_SECTOR, GAYLE_LCYL, + GAYLE_HCYL, GAYLE_SELECT, GAYLE_STATUS, -1, -1 +}; + + + /* + * These are at different offsets from the base + */ + +#define GAYLE_IRQ_4000 0xdd3020 /* MSB = 1, Harddisk is source of */ +#define GAYLE_IRQ_1200 0xda9000 /* interrupt */ + + + /* + * Offset of the secondary port for IDE doublers + * Note that GAYLE_CONTROL is NOT available then! + */ + +#define GAYLE_NEXT_PORT 0x1000 + +#ifndef CONFIG_BLK_DEV_IDEDOUBLER +#define GAYLE_NUM_HWIFS 1 +#define GAYLE_NUM_PROBE_HWIFS GAYLE_NUM_HWIFS +#define GAYLE_HAS_CONTROL_REG 1 +#else /* CONFIG_BLK_DEV_IDEDOUBLER */ +#define GAYLE_NUM_HWIFS 2 +#define GAYLE_NUM_PROBE_HWIFS (ide_doubler ? GAYLE_NUM_HWIFS : \ + GAYLE_NUM_HWIFS-1) +#define GAYLE_HAS_CONTROL_REG (!ide_doubler) +int ide_doubler = 0; /* support IDE doublers? */ +#endif /* CONFIG_BLK_DEV_IDEDOUBLER */ + + + /* + * Check and acknowledge the interrupt status + */ + +static int gayle_ack_intr_a4000(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = inb(hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & 0x80)) + return 0; + return 1; +} + +static int gayle_ack_intr_a1200(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = inb(hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & 0x80)) + return 0; + (void)inb(hwif->io_ports[IDE_STATUS_OFFSET]); + outb(0x7c | (ch & 0x03), hwif->io_ports[IDE_IRQ_OFFSET]); + return 1; +} + + /* + * Probe for a Gayle IDE interface (and optionally for an IDE doubler) + */ + +void gayle_init(void) +{ + int a4000, i; + + if (!MACH_IS_AMIGA) + return; + + if (!(a4000 = AMIGAHW_PRESENT(A4000_IDE)) && !AMIGAHW_PRESENT(A1200_IDE)) + return; + + for (i = 0; i < GAYLE_NUM_PROBE_HWIFS; i++) { + ide_ioreg_t base, ctrlport, irqport; + ide_ack_intr_t *ack_intr; + hw_regs_t hw; + int index; + + if (a4000) { + base = (ide_ioreg_t)ZTWO_VADDR(GAYLE_BASE_4000); + irqport = (ide_ioreg_t)ZTWO_VADDR(GAYLE_IRQ_4000); + ack_intr = gayle_ack_intr_a4000; + } else { + base = (ide_ioreg_t)ZTWO_VADDR(GAYLE_BASE_1200); + irqport = (ide_ioreg_t)ZTWO_VADDR(GAYLE_IRQ_1200); + ack_intr = gayle_ack_intr_a1200; + } + + if (GAYLE_HAS_CONTROL_REG) + ctrlport = base + GAYLE_CONTROL; + else + ctrlport = 0; + + base += i*GAYLE_NEXT_PORT; + + ide_setup_ports(&hw, base, gayle_offsets, + ctrlport, irqport, ack_intr, IRQ_AMIGA_PORTS); + + index = ide_register_hw(&hw, NULL); + if (index != -1) { + switch (i) { + case 0: + printk("ide%d: Gayle IDE interface (A%d style)\n", index, + a4000 ? 4000 : 1200); + break; +#ifdef CONFIG_BLK_DEV_IDEDOUBLER + case 1: + printk("ide%d: IDE doubler\n", index); + break; +#endif /* CONFIG_BLK_DEV_IDEDOUBLER */ + } + } +#if 1 /* TESTING */ + if (i == 1) { + volatile u_short *addr = (u_short *)base; + u_short data; + printk("+++ Probing for IDE doubler... "); + *addr = 0xffff; + data = *addr; + printk("probe returned 0x%02x (PLEASE REPORT THIS!!)\n", data); + } +#endif /* TESTING */ + } +} diff --git a/drivers/block/hpt343.c b/drivers/block/hpt343.c new file mode 100644 index 000000000..f759cbf8a --- /dev/null +++ b/drivers/block/hpt343.c @@ -0,0 +1,392 @@ +/* + * linux/drivers/block/hpt343.c Version 0.23 May 12, 1999 + * + * Copyright (C) 1998-99 Andre Hedrick + * (hedrick@astro.dyer.vanderbilt.edu) + * + * 00:12.0 Unknown mass storage controller: + * Triones Technologies, Inc. + * Unknown device 0003 (rev 01) + * + * hde: UDMA 2 (0x0000 0x0002) (0x0000 0x0010) + * hdf: UDMA 2 (0x0002 0x0012) (0x0010 0x0030) + * hde: DMA 2 (0x0000 0x0002) (0x0000 0x0010) + * hdf: DMA 2 (0x0002 0x0012) (0x0010 0x0030) + * hdg: DMA 1 (0x0012 0x0052) (0x0030 0x0070) + * hdh: DMA 1 (0x0052 0x0252) (0x0070 0x00f0) + * + * drive_number + * = ((HWIF(drive)->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + * = ((hwif->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> + +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/ide.h> + +#include <asm/io.h> +#include <asm/irq.h> + +#include "ide_modes.h" + +#ifndef SPLIT_BYTE +#define SPLIT_BYTE(B,H,L) ((H)=(B>>4), (L)=(B-((B>>4)<<4))) +#endif + +#define HPT343_DEBUG_DRIVE_INFO 0 +#define HPT343_DISABLE_ALL_DMAING 0 +#define HPT343_DMA_DISK_ONLY 0 + +extern char *ide_xfer_verbose (byte xfer_rate); + +static void hpt343_clear_chipset (ide_drive_t *drive) +{ + int drive_number = ((HWIF(drive)->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + unsigned int reg1 = 0, tmp1 = 0; + unsigned int reg2 = 0, tmp2 = 0; + + pci_read_config_dword(HWIF(drive)->pci_dev, 0x44, ®1); + pci_read_config_dword(HWIF(drive)->pci_dev, 0x48, ®2); + tmp1 = ((0x00 << (3*drive_number)) | (reg1 & ~(7 << (3*drive_number)))); + tmp2 = ((0x00 << drive_number) | reg2); + pci_write_config_dword(HWIF(drive)->pci_dev, 0x44, tmp1); + pci_write_config_dword(HWIF(drive)->pci_dev, 0x48, tmp2); +} + +static int hpt343_tune_chipset (ide_drive_t *drive, byte speed) +{ + int err; + byte hi_speed, lo_speed; + int drive_number = ((HWIF(drive)->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + unsigned int reg1 = 0, tmp1 = 0; + unsigned int reg2 = 0, tmp2 = 0; + + SPLIT_BYTE(speed, hi_speed, lo_speed); + + if (hi_speed & 7) { + hi_speed = (hi_speed & 4) ? 0x01 : 0x10; + } else { + lo_speed <<= 5; + lo_speed >>= 5; + } + + pci_read_config_dword(HWIF(drive)->pci_dev, 0x44, ®1); + pci_read_config_dword(HWIF(drive)->pci_dev, 0x48, ®2); + tmp1 = ((lo_speed << (3*drive_number)) | (reg1 & ~(7 << (3*drive_number)))); + tmp2 = ((hi_speed << drive_number) | reg2); + err = ide_wait_cmd(drive, WIN_SETFEATURES, speed, SETFEATURES_XFER, 0, NULL); + pci_write_config_dword(HWIF(drive)->pci_dev, 0x44, tmp1); + pci_write_config_dword(HWIF(drive)->pci_dev, 0x48, tmp2); + +#if HPT343_DEBUG_DRIVE_INFO + printk("%s: %s drive%d (0x%04x 0x%04x) (0x%04x 0x%04x)" \ + " (0x%02x 0x%02x) 0x%04x\n", + drive->name, ide_xfer_verbose(speed), + drive_number, reg1, tmp1, reg2, tmp2, + hi_speed, lo_speed, err); +#endif /* HPT343_DEBUG_DRIVE_INFO */ + + return(err); +} + +/* + * This allows the configuration of ide_pci chipset registers + * for cards that learn about the drive's UDMA, DMA, PIO capabilities + * after the drive is reported by the OS. Initally for designed for + * HPT343 UDMA chipset by HighPoint|Triones Technologies, Inc. + */ +static int config_chipset_for_dma (ide_drive_t *drive) +{ + struct hd_driveid *id = drive->id; + byte speed = 0x00; + +#if HPT343_DISABLE_ALL_DMAING + return ((int) ide_dma_off); +#elif HPT343_DMA_DISK_ONLY + if (drive->media != ide_disk) + return ((int) ide_dma_off_quietly); +#endif /* HPT343_DISABLE_ALL_DMAING */ + + if (id->dma_ultra & 0x0004) { + if (!((id->dma_ultra >> 8) & 4)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0404; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + speed = XFER_UDMA_2; + } else if (id->dma_ultra & 0x0002) { + if (!((id->dma_ultra >> 8) & 2)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0202; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + speed = XFER_UDMA_1; + } else if (id->dma_ultra & 0x0001) { + if (!((id->dma_ultra >> 8) & 1)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0101; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + speed = XFER_UDMA_0; + } else if (id->dma_mword & 0x0004) { + if (!((id->dma_mword >> 8) & 4)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0404; + drive->id->dma_1word &= ~0x0F00; + } + speed = XFER_MW_DMA_2; + } else if (id->dma_mword & 0x0002) { + if (!((id->dma_mword >> 8) & 2)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0202; + drive->id->dma_1word &= ~0x0F00; + } + speed = XFER_MW_DMA_1; + } else if (id->dma_mword & 0x0001) { + if (!((id->dma_mword >> 8) & 1)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0101; + drive->id->dma_1word &= ~0x0F00; + } + speed = XFER_MW_DMA_0; + } else if (id->dma_1word & 0x0004) { + if (!((id->dma_1word >> 8) & 4)) { + drive->id->dma_1word &= ~0x0F00; + drive->id->dma_1word |= 0x0404; + drive->id->dma_mword &= ~0x0F00; + } + speed = XFER_SW_DMA_2; + } else if (id->dma_1word & 0x0002) { + if (!((id->dma_1word >> 8) & 2)) { + drive->id->dma_1word &= ~0x0F00; + drive->id->dma_1word |= 0x0202; + drive->id->dma_mword &= ~0x0F00; + } + speed = XFER_SW_DMA_1; + } else if (id->dma_1word & 0x0001) { + if (!((id->dma_1word >> 8) & 1)) { + drive->id->dma_1word &= ~0x0F00; + drive->id->dma_1word |= 0x0101; + drive->id->dma_mword &= ~0x0F00; + } + speed = XFER_SW_DMA_0; + } else { + return ((int) ide_dma_off_quietly); + } + + (void) hpt343_tune_chipset(drive, speed); + + return ((int) ((id->dma_ultra >> 8) & 7) ? ide_dma_on : + ((id->dma_mword >> 8) & 7) ? ide_dma_on : + ((id->dma_1word >> 8) & 7) ? ide_dma_on : + ide_dma_off_quietly); +} + +static void config_chipset_for_pio (ide_drive_t *drive) +{ + unsigned short eide_pio_timing[6] = {960, 480, 240, 180, 120, 90}; + unsigned short xfer_pio = drive->id->eide_pio_modes; + + byte timing, speed, pio; + + pio = ide_get_best_pio_mode(drive, 255, 5, NULL); + + if (xfer_pio> 4) + xfer_pio = 0; + + if (drive->id->eide_pio_iordy > 0) { + for (xfer_pio = 5; + xfer_pio>0 && + drive->id->eide_pio_iordy>eide_pio_timing[xfer_pio]; + xfer_pio--); + } else { + xfer_pio = (drive->id->eide_pio_modes & 4) ? 0x05 : + (drive->id->eide_pio_modes & 2) ? 0x04 : + (drive->id->eide_pio_modes & 1) ? 0x03 : xfer_pio; + } + + timing = (xfer_pio >= pio) ? xfer_pio : pio; + + switch(timing) { + case 4: speed = XFER_PIO_4;break; + case 3: speed = XFER_PIO_3;break; + case 2: speed = XFER_PIO_2;break; + case 1: speed = XFER_PIO_1;break; + default: + speed = (!drive->id->tPIO) ? XFER_PIO_0 : XFER_PIO_SLOW; + break; + } + + (void) hpt343_tune_chipset(drive, speed); +} + +#if 0 +static void hpt343_tune_drive (ide_drive_t *drive, byte pio) +{ +} +#endif + +static int config_drive_xfer_rate (ide_drive_t *drive) +{ + struct hd_driveid *id = drive->id; + ide_dma_action_t dma_func = ide_dma_on; + + if (id && (id->capability & 1) && HWIF(drive)->autodma) { + /* Consult the list of known "bad" drives */ + if (ide_dmaproc(ide_dma_bad_drive, drive)) { + dma_func = ide_dma_off; + goto fast_ata_pio; + } + dma_func = ide_dma_off_quietly; + if (id->field_valid & 4) { + if (id->dma_ultra & 0x0007) { + /* Force if Capable UltraDMA */ + dma_func = config_chipset_for_dma(drive); + if ((id->field_valid & 2) && + (dma_func != ide_dma_on)) + goto try_dma_modes; + } + } else if (id->field_valid & 2) { +try_dma_modes: + if ((id->dma_mword & 0x0007) || + (id->dma_1word & 0x0007)) { + /* Force if Capable regular DMA modes */ + dma_func = config_chipset_for_dma(drive); + if (dma_func != ide_dma_on) + goto no_dma_set; + } + } else if (ide_dmaproc(ide_dma_good_drive, drive)) { + if (id->eide_dma_time > 150) { + goto no_dma_set; + } + /* Consult the list of known "good" drives */ + dma_func = config_chipset_for_dma(drive); + if (dma_func != ide_dma_on) + goto no_dma_set; + } else { + goto fast_ata_pio; + } + } else if ((id->capability & 8) || (id->field_valid & 2)) { +fast_ata_pio: + dma_func = ide_dma_off_quietly; +no_dma_set: + + config_chipset_for_pio(drive); + } + return HWIF(drive)->dmaproc(dma_func, drive); +} + +/* + * hpt343_dmaproc() initiates/aborts (U)DMA read/write operations on a drive. + * + * This is specific to the HPT343 UDMA bios-less chipset + * and HPT345 UDMA bios chipset (stamped HPT363) + * by HighPoint|Triones Technologies, Inc. + */ + +int hpt343_dmaproc (ide_dma_action_t func, ide_drive_t *drive) +{ + switch (func) { + case ide_dma_check: + hpt343_clear_chipset(drive); + return config_drive_xfer_rate(drive); +#if 0 + case ide_dma_off: + case ide_dma_off_quietly: + case ide_dma_on: + case ide_dma_check: + return config_drive_xfer_rate(drive); + case ide_dma_read: + case ide_dma_write: + case ide_dma_begin: + case ide_dma_end: + case ide_dma_test_irq: +#endif + default: + break; + } + return ide_dmaproc(func, drive); /* use standard DMA stuff */ +} + +/* + * If the BIOS does not set the IO base addaress to XX00, 343 will fail. + */ +#define HPT343_PCI_INIT_REG 0x80 + +__initfunc(unsigned int pci_init_hpt343 (struct pci_dev *dev, const char *name)) +{ + int i; + unsigned short cmd; + unsigned long hpt343IoBase = dev->base_address[4] & PCI_BASE_ADDRESS_IO_MASK; +#if 0 + unsigned char misc10 = inb(hpt343IoBase + 0x0010); + unsigned char misc11 = inb(hpt343IoBase + 0x0011); +#endif + + pci_write_config_byte(dev, HPT343_PCI_INIT_REG, 0x00); + pci_read_config_word(dev, PCI_COMMAND, &cmd); + pci_write_config_word(dev, PCI_COMMAND, cmd & ~PCI_COMMAND_IO); + + dev->base_address[0] = (hpt343IoBase + 0x20); + dev->base_address[1] = (hpt343IoBase + 0x34); + dev->base_address[2] = (hpt343IoBase + 0x28); + dev->base_address[3] = (hpt343IoBase + 0x3c); + + for(i=0; i<4; i++) + dev->base_address[i] |= PCI_BASE_ADDRESS_SPACE_IO; + + /* + * Since 20-23 can be assigned and are R/W, we correct them. + */ + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, dev->base_address[0]); + pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, dev->base_address[1]); + pci_write_config_dword(dev, PCI_BASE_ADDRESS_2, dev->base_address[2]); + pci_write_config_dword(dev, PCI_BASE_ADDRESS_3, dev->base_address[3]); + pci_write_config_word(dev, PCI_COMMAND, cmd); + +#if 0 + outb(misc10|0x78, (hpt343IoBase + 0x0010)); + outb(misc11, (hpt343IoBase + 0x0011)); +#endif + +#ifdef DEBUG + printk("%s: 0x%02x 0x%02x\n", + (pcicmd & PCI_COMMAND_MEMORY) ? "HPT345" : name, + inb(hpt343IoBase + 0x0010), + inb(hpt343IoBase + 0x0011)); +#endif + + if (cmd & PCI_COMMAND_MEMORY) { + if (dev->rom_address) { + pci_write_config_byte(dev, PCI_ROM_ADDRESS, dev->rom_address | PCI_ROM_ADDRESS_ENABLE); + printk(KERN_INFO "HPT345: ROM enabled at 0x%08lx\n", dev->rom_address); + } + } else { + pci_write_config_byte(dev, PCI_LATENCY_TIMER, 0x20); + } + return dev->irq; +} + +__initfunc(void ide_init_hpt343 (ide_hwif_t *hwif)) +{ + if (hwif->dma_base) { + unsigned short pcicmd = 0; + + pci_read_config_word(hwif->pci_dev, PCI_COMMAND, &pcicmd); + hwif->autodma = (pcicmd & PCI_COMMAND_MEMORY) ? 1 : 0; + hwif->dmaproc = &hpt343_dmaproc; + } +} diff --git a/drivers/block/macide.c b/drivers/block/macide.c new file mode 100644 index 000000000..2771ea702 --- /dev/null +++ b/drivers/block/macide.c @@ -0,0 +1,167 @@ +/* + * linux/drivers/block/macide.c -- Macintosh IDE Driver + * + * Copyright (C) 1998 by Michael Schmitz + * + * This driver was written based on information obtained from the MacOS IDE + * driver binary by Mikael Forselius + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/zorro.h> +#include <linux/ide.h> + +#include <asm/machw.h> +#include <asm/macintosh.h> +#include <asm/macints.h> + + /* + * Base of the IDE interface (see ATAManager ROM code) + */ + +#define MAC_HD_BASE 0x50f1a000 + + /* + * Offsets from the above base (scaling 4) + */ + +#define MAC_HD_DATA 0x00 +#define MAC_HD_ERROR 0x04 /* see err-bits */ +#define MAC_HD_NSECTOR 0x08 /* nr of sectors to read/write */ +#define MAC_HD_SECTOR 0x0c /* starting sector */ +#define MAC_HD_LCYL 0x10 /* starting cylinder */ +#define MAC_HD_HCYL 0x14 /* high byte of starting cyl */ +#define MAC_HD_SELECT 0x18 /* 101dhhhh , d=drive, hhhh=head */ +#define MAC_HD_STATUS 0x1c /* see status-bits */ +#define MAC_HD_CONTROL 0x38 /* control/altstatus */ + +static int macide_offsets[IDE_NR_PORTS] = { + MAC_HD_DATA, MAC_HD_ERROR, MAC_HD_NSECTOR, MAC_HD_SECTOR, MAC_HD_LCYL, + MAC_HD_HCYL, MAC_HD_SELECT, MAC_HD_STATUS, MAC_HD_CONTROL +}; + + /* + * Other registers + */ + + /* + * IDE interrupt status register for both (?) hwifs on Quadra + * Initial setting: 0xc + * Guessing again: + * Bit 0+1: some interrupt flags + * Bit 2+3: some interrupt enable + * Bit 4: ?? + * Bit 5: IDE interrupt flag (any hwif) + * Bit 6: maybe IDE interrupt enable (any hwif) ?? + * Bit 7: Any interrupt condition + * + * Only relevant item: bit 5, to be checked by mac_ack_intr + */ + +#define MAC_HD_ISR 0x101 + + /* + * IDE interrupt glue - seems to be wired to Nubus, Slot C? + * (ROM code disassembly again) + * First try: just use Nubus interrupt for Slot C. Have Nubus code call + * a wrapper to ide_intr that checks the ISR (see above). + * Need to #define IDE_IRQ_NUBUS though. + * Alternative method: set a mac_ide_hook function pointer to the wrapper + * here and have via_do_nubus call that hook if set. + * + * Quadra needs the hook, Powerbook can use Nubus slot C. + * Checking the ISR on Quadra is done by mac_ack_intr (see Amiga code). mac_ide_intr + * mac_ide_intr is obsolete except for providing the hwgroup argument. + */ + + /* The Mac hwif data, for passing hwgroup to ide_intr */ +static ide_hwif_t *mac_hwif = NULL; + + /* The function pointer used in the Nubus handler */ +void (*mac_ide_intr_hook)(int, void *, struct pt_regs *) = NULL; + + /* + * Only purpose: feeds the hwgroup to the main IDE handler. + * Obsolete as soon as Nubus code is fixed WRT pseudo slot C int. + * (should be the case on Powerbooks) + * Alas, second purpose: feed correct irq to IDE handler (I know, + * that's cheating) :-((( + * Fix needed for interrupt code: accept Nubus ints in the regular + * request_irq code, then register Powerbook IDE as Nubus slot C, + * Quadra as slot F (F for fictious). + */ +void mac_ide_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + ide_intr(mac_hwif->irq, mac_hwif->hwgroup, regs); +} + + /* + * Check the interrupt status + * + * Note: In 2.0 kernels, there have been timing problems with the + * Powerbook IDE interface (BUSY was asserted too long after the + * interrupt triggered). Result: repeated errors, recalibrate etc. + * Adding a wait loop to read_intr, write_intr and set_geom_intr + * fixed the problem (waits in read/write_intr were present for Amiga + * already). + * Powerbooks were not tested with 2.1 due to lack of FPU emulation + * (thanks Apple for using LC040). If the BUSY problem resurfaces in + * 2.1, my best bet would be to add the wait loop right here, afterr + * checking the interrupt register. + */ + +static int mac_ack_intr(ide_hwif_t *hwif) +{ + unsigned char ch; + + ch = inb(hwif->io_ports[IDE_IRQ_OFFSET]); + if (!(ch & 0x20)) + return 0; + return 1; +} + + /* + * Probe for a Macintosh IDE interface + */ + +void macide_init(void) +{ + hw_regs_t hw; + int index = -1; + + if (MACH_IS_MAC) { + switch(macintosh_config->ide_type) { + case 0: + break; + + case MAC_IDE_QUADRA: + ide_setup_ports(&hw, (ide_ioreg_t)MAC_HD_BASE, macide_offsets, + 0, (ide_ioreg_t)(MAC_HD_BASE+MAC_HD_ISR), + mac_ack_intr, IRQ_MAC_NUBUS); + index = ide_register_hw(&hw, &mac_hwif); + mac_ide_intr_hook = mac_ide_intr; + break; + + default: + ide_setup_ports(&hw, (ide_ioreg_t)MAC_HD_BASE, macide_offsets, + 0, 0, NULL, IRQ_MAC_NUBUS); + index = ide_register_hw(&hw, &mac_hwif); + break; + } + + if (index != -1) { + if (macintosh_config->ide_type == MAC_IDE_QUADRA) + printk("ide%d: Macintosh Quadra IDE interface\n", index); + else + printk("ide%d: Macintosh Powerbook IDE interface\n", index); + } + } +} diff --git a/drivers/block/pdc202xx.c b/drivers/block/pdc202xx.c new file mode 100644 index 000000000..f0591397c --- /dev/null +++ b/drivers/block/pdc202xx.c @@ -0,0 +1,567 @@ +/* + * linux/drivers/block/pdc202xx.c Version 0.26 May 12, 1999 + * + * Copyright (C) 1998-99 Andre Hedrick + * (hedrick@astro.dyer.vanderbilt.edu) + * + * Promise Ultra33 cards with BIOS v1.20 through 1.28 will need this + * compiled into the kernel if you have more than one card installed. + * Note that BIOS v1.29 is reported to fix the problem. Since this is + * safe chipset tuning, including this support is harmless + * + * The latest chipset code will support the following :: + * Three Ultra33 controllers and 12 drives. + * 8 are UDMA supported and 4 are limited to DMA mode 2 multi-word. + * The 8/4 ratio is a BIOS code limit by promise. + * + * UNLESS you enable "PDC202XX_FORCE_BURST_BIT" + * + * There is only one BIOS in the three contollers. + * + * May 8 20:56:17 Orion kernel: + * Uniform Multi-Platform E-IDE driver Revision: 6.19 + * PDC20246: IDE controller on PCI bus 00 dev a0 + * PDC20246: not 100% native mode: will probe irqs later + * PDC20246: ROM enabled at 0xfebd0000 + * PDC20246: (U)DMA Burst Bit ENABLED Primary PCI Mode Secondary PCI Mode. + * ide0: BM-DMA at 0xef80-0xef87, BIOS settings: hda:DMA, hdb:DMA + * ide1: BM-DMA at 0xef88-0xef8f, BIOS settings: hdc:pio, hdd:pio + * PDC20246: IDE controller on PCI bus 00 dev 98 + * PDC20246: not 100% native mode: will probe irqs later + * PDC20246: ROM enabled at 0xfebc0000 + * PDC20246: (U)DMA Burst Bit ENABLED Primary PCI Mode Secondary PCI Mode. + * ide2: BM-DMA at 0xef40-0xef47, BIOS settings: hde:DMA, hdf:DMA + * ide3: BM-DMA at 0xef48-0xef4f, BIOS settings: hdg:DMA, hdh:DMA + * PDC20246: IDE controller on PCI bus 00 dev 90 + * PDC20246: not 100% native mode: will probe irqs later + * PDC20246: ROM enabled at 0xfebb0000 + * PDC20246: (U)DMA Burst Bit DISABLED Primary PCI Mode Secondary PCI Mode. + * PDC20246: FORCING BURST BIT 0x00 -> 0x01 ACTIVE + * ide4: BM-DMA at 0xef00-0xef07, BIOS settings: hdi:DMA, hdj:pio + * ide5: BM-DMA at 0xef08-0xef0f, BIOS settings: hdk:pio, hdl:pio + * PIIX3: IDE controller on PCI bus 00 dev 39 + * PIIX3: device not capable of full native PCI mode + * + * ide0 at 0xeff0-0xeff7,0xefe6 on irq 19 + * ide1 at 0xefa8-0xefaf,0xebe6 on irq 19 + * ide2 at 0xefa0-0xefa7,0xef7e on irq 18 + * ide3 at 0xef68-0xef6f,0xef66 on irq 18 + * ide4 at 0xef38-0xef3f,0xef62 on irq 17 + * hda: QUANTUM FIREBALL ST6.4A, 6149MB w/81kB Cache, CHS=13328/15/63, UDMA(33) + * hdb: QUANTUM FIREBALL ST3.2A, 3079MB w/81kB Cache, CHS=6256/16/63, UDMA(33) + * hde: Maxtor 72004 AP, 1916MB w/128kB Cache, CHS=3893/16/63, DMA + * hdf: Maxtor 71626 A, 1554MB w/64kB Cache, CHS=3158/16/63, DMA + * hdi: Maxtor 90680D4, 6485MB w/256kB Cache, CHS=13176/16/63, UDMA(33) + * hdj: Maxtor 90680D4, 6485MB w/256kB Cache, CHS=13176/16/63, UDMA(33) + * + * Promise Ultra66 cards with BIOS v1.11 this + * compiled into the kernel if you have more than one card installed. + * + * PDC20262: IDE controller on PCI bus 00 dev a0 + * PDC20262: not 100% native mode: will probe irqs later + * PDC20262: ROM enabled at 0xfebb0000 + * PDC20262: (U)DMA Burst Bit ENABLED Primary PCI Mode Secondary PCI Mode. + * ide0: BM-DMA at 0xef00-0xef07, BIOS settings: hda:pio, hdb:pio + * ide1: BM-DMA at 0xef08-0xef0f, BIOS settings: hdc:pio, hdd:pio + * + * UDMA 4/2 and UDMA 3/1 only differ by the testing bit 13 in word93. + * Chipset timing speeds must be identical + * + * drive_number + * = ((HWIF(drive)->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + * = ((hwif->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> +#include <linux/hdreg.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/ide.h> + +#include <asm/io.h> +#include <asm/irq.h> + +#define PDC202XX_DEBUG_DRIVE_INFO 0 +#define PDC202XX_DECODE_REGISTER_INFO 0 +#define PDC202XX_FORCE_BURST_BIT 0 +#define PDC202XX_FORCE_MASTER_MODE 0 + +extern char *ide_xfer_verbose (byte xfer_rate); + +/* A Register */ +#define SYNC_ERRDY_EN 0xC0 + +#define SYNC_IN 0x80 /* control bit, different for master vs. slave drives */ +#define ERRDY_EN 0x40 /* control bit, different for master vs. slave drives */ +#define IORDY_EN 0x20 /* PIO: IOREADY */ +#define PREFETCH_EN 0x10 /* PIO: PREFETCH */ + +#define PA3 0x08 /* PIO"A" timing */ +#define PA2 0x04 /* PIO"A" timing */ +#define PA1 0x02 /* PIO"A" timing */ +#define PA0 0x01 /* PIO"A" timing */ + +/* B Register */ + +#define MB2 0x80 /* DMA"B" timing */ +#define MB1 0x40 /* DMA"B" timing */ +#define MB0 0x20 /* DMA"B" timing */ + +#define PB4 0x10 /* PIO_FORCE 1:0 */ + +#define PB3 0x08 /* PIO"B" timing */ /* PIO flow Control mode */ +#define PB2 0x04 /* PIO"B" timing */ /* PIO 4 */ +#define PB1 0x02 /* PIO"B" timing */ /* PIO 3 half */ +#define PB0 0x01 /* PIO"B" timing */ /* PIO 3 other half */ + +/* C Register */ +#define IORDYp_NO_SPEED 0x4F +#define SPEED_DIS 0x0F + +#define DMARQp 0x80 +#define IORDYp 0x40 +#define DMAR_EN 0x20 +#define DMAW_EN 0x10 + +#define MC3 0x08 /* DMA"C" timing */ +#define MC2 0x04 /* DMA"C" timing */ +#define MC1 0x02 /* DMA"C" timing */ +#define MC0 0x01 /* DMA"C" timing */ + +#if PDC202XX_DECODE_REGISTER_INFO + +#define REG_A 0x01 +#define REG_B 0x02 +#define REG_C 0x04 +#define REG_D 0x08 + +static void decode_registers (byte registers, byte value) +{ + byte bit = 0, bit1 = 0, bit2 = 0; + + switch(registers) { + case REG_A: + bit2 = 0; + printk("A Register "); + if (value & 0x80) printk("SYNC_IN "); + if (value & 0x40) printk("ERRDY_EN "); + if (value & 0x20) printk("IORDY_EN "); + if (value & 0x10) printk("PREFETCH_EN "); + if (value & 0x08) { printk("PA3 ");bit2 |= 0x08; } + if (value & 0x04) { printk("PA2 ");bit2 |= 0x04; } + if (value & 0x02) { printk("PA1 ");bit2 |= 0x02; } + if (value & 0x01) { printk("PA0 ");bit2 |= 0x01; } + printk("PIO(A) = %d ", bit2); + break; + case REG_B: + bit1 = 0;bit2 = 0; + printk("B Register "); + if (value & 0x80) { printk("MB2 ");bit1 |= 0x80; } + if (value & 0x40) { printk("MB1 ");bit1 |= 0x40; } + if (value & 0x20) { printk("MB0 ");bit1 |= 0x20; } + printk("DMA(B) = %d ", bit1 >> 5); + if (value & 0x10) printk("PIO_FORCED/PB4 "); + if (value & 0x08) { printk("PB3 ");bit2 |= 0x08; } + if (value & 0x04) { printk("PB2 ");bit2 |= 0x04; } + if (value & 0x02) { printk("PB1 ");bit2 |= 0x02; } + if (value & 0x01) { printk("PB0 ");bit2 |= 0x01; } + printk("PIO(B) = %d ", bit2); + break; + case REG_C: + bit2 = 0; + printk("C Register "); + if (value & 0x80) printk("DMARQp "); + if (value & 0x40) printk("IORDYp "); + if (value & 0x20) printk("DMAR_EN "); + if (value & 0x10) printk("DMAW_EN "); + + if (value & 0x08) { printk("MC3 ");bit2 |= 0x08; } + if (value & 0x04) { printk("MC2 ");bit2 |= 0x04; } + if (value & 0x02) { printk("MC1 ");bit2 |= 0x02; } + if (value & 0x01) { printk("MC0 ");bit2 |= 0x01; } + printk("DMA(C) = %d ", bit2); + break; + case REG_D: + printk("D Register "); + break; + default: + return; + } + printk("\n %s ", (registers & REG_D) ? "DP" : + (registers & REG_C) ? "CP" : + (registers & REG_B) ? "BP" : + (registers & REG_A) ? "AP" : "ERROR"); + for (bit=128;bit>0;bit/=2) + printk("%s", (value & bit) ? "1" : "0"); + printk("\n"); +} + +#endif /* PDC202XX_DECODE_REGISTER_INFO */ + +static int config_chipset_for_dma (ide_drive_t *drive, byte ultra) +{ + struct hd_driveid *id = drive->id; + ide_hwif_t *hwif = HWIF(drive); + struct pci_dev *dev = hwif->pci_dev; + + int err; + unsigned int drive_conf; + byte drive_pci; + byte test1, test2, speed; + byte AP, BP, CP, DP, EP; + int drive_number = ((hwif->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + byte udma_66 = ((id->word93 & 0x2000) && (dev->device == PCI_DEVICE_ID_PROMISE_20262)) ? 1 : 0; + byte udma_33 = ultra ? (inb((dev->base_address[4] & PCI_BASE_ADDRESS_IO_MASK) + 0x001f) & 1) : 0; + + pci_read_config_byte(dev, 0x50, &EP); + + switch(drive_number) { + case 0: drive_pci = 0x60; + pci_read_config_dword(dev, drive_pci, &drive_conf); + if ((drive_conf != 0x004ff304) && (drive_conf != 0x004ff3c4)) + goto chipset_is_set; + pci_read_config_byte(dev, (drive_pci), &test1); + if (!(test1 & SYNC_ERRDY_EN)) + pci_write_config_byte(dev, (drive_pci), test1|SYNC_ERRDY_EN); + break; + case 1: drive_pci = 0x64; + pci_read_config_dword(dev, drive_pci, &drive_conf); + if ((drive_conf != 0x004ff304) && (drive_conf != 0x004ff3c4)) + goto chipset_is_set; + pci_read_config_byte(dev, 0x60, &test1); + pci_read_config_byte(dev, (drive_pci), &test2); + if ((test1 & SYNC_ERRDY_EN) && !(test2 & SYNC_ERRDY_EN)) + pci_write_config_byte(dev, (drive_pci), test2|SYNC_ERRDY_EN); + break; + case 2: drive_pci = 0x68; + pci_read_config_dword(dev, drive_pci, &drive_conf); + if ((drive_conf != 0x004ff304) && (drive_conf != 0x004ff3c4)) + goto chipset_is_set; + pci_read_config_byte(dev, (drive_pci), &test1); + if (!(test1 & SYNC_ERRDY_EN)) + pci_write_config_byte(dev, (drive_pci), test1|SYNC_ERRDY_EN); + break; + case 3: drive_pci = 0x6c; + pci_read_config_dword(dev, drive_pci, &drive_conf); + if ((drive_conf != 0x004ff304) && (drive_conf != 0x004ff3c4)) + goto chipset_is_set; + pci_read_config_byte(dev, 0x68, &test1); + pci_read_config_byte(dev, (drive_pci), &test2); + if ((test1 & SYNC_ERRDY_EN) && !(test2 & SYNC_ERRDY_EN)) + pci_write_config_byte(dev, (drive_pci), test2|SYNC_ERRDY_EN); + break; + default: + return ide_dma_off; + } + + if (drive->media != ide_disk) + return ide_dma_off_quietly; + + pci_read_config_byte(dev, (drive_pci), &AP); + pci_read_config_byte(dev, (drive_pci)|0x01, &BP); + pci_read_config_byte(dev, (drive_pci)|0x02, &CP); + pci_read_config_byte(dev, (drive_pci)|0x03, &DP); + + if (id->capability & 4) { /* IORDY_EN */ + pci_write_config_byte(dev, (drive_pci), AP|IORDY_EN); + pci_read_config_byte(dev, (drive_pci), &AP); + } + + if (drive->media == ide_disk) { /* PREFETCH_EN */ + pci_write_config_byte(dev, (drive_pci), AP|PREFETCH_EN); + pci_read_config_byte(dev, (drive_pci), &AP); + } + + if ((BP & 0xF0) && (CP & 0x0F)) { + /* clear DMA modes of upper 842 bits of B Register */ + /* clear PIO forced mode upper 1 bit of B Register */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP & ~0xF0); + pci_read_config_byte(dev, (drive_pci)|0x01, &BP); + + /* clear DMA modes of lower 8421 bits of C Register */ + pci_write_config_byte(dev, (drive_pci)|0x02, CP & ~0x0F); + pci_read_config_byte(dev, (drive_pci)|0x02, &CP); + } + + pci_read_config_byte(dev, (drive_pci), &AP); + pci_read_config_byte(dev, (drive_pci)|0x01, &BP); + pci_read_config_byte(dev, (drive_pci)|0x02, &CP); + + if ((id->dma_ultra & 0x0010) && (udma_66) && (udma_33)) { + if (!((id->dma_ultra >> 8) & 16)) { + drive->id->dma_ultra &= ~0xFF00; + drive->id->dma_ultra |= 0x1010; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 8 == UDMA mode 4 == speed 6 plus cable */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x20); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x01); + speed = XFER_UDMA_4; + } else if ((id->dma_ultra & 0x0008) && (udma_66) && (udma_33)) { + if (!((id->dma_ultra >> 8) & 8)) { + drive->id->dma_ultra &= ~0xFF00; + drive->id->dma_ultra |= 0x0808; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 7 == UDMA mode 3 == speed 5 plus cable */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x40); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x02); + speed = XFER_UDMA_3; + } else if ((id->dma_ultra & 0x0004) && (udma_33)) { + if (!((id->dma_ultra >> 8) & 4)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0404; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 6 == UDMA mode 2 */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x20); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x01); + speed = XFER_UDMA_2; + } else if ((id->dma_ultra & 0x0002) && (udma_33)) { + if (!((id->dma_ultra >> 8) & 2)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0202; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 5 == UDMA mode 1 */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x40); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x02); + speed = XFER_UDMA_1; + } else if ((id->dma_ultra & 0x0001) && (udma_33)) { + if (!((id->dma_ultra >> 8) & 1)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0101; + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 4 == UDMA mode 0 */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x60); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x03); + speed = XFER_UDMA_0; + } else if (id->dma_mword & 0x0004) { + if (!((id->dma_mword >> 8) & 4)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0404; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 4 == DMA mode 2 multi-word */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x60); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x03); + speed = XFER_MW_DMA_2; + } else if (id->dma_mword & 0x0002) { + if (!((id->dma_mword >> 8) & 2)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0202; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 3 == DMA mode 1 multi-word */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x60); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x04); + speed = XFER_MW_DMA_1; + } else if (id->dma_mword & 0x0001) { + if (!((id->dma_mword >> 8) & 1)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0101; + drive->id->dma_1word &= ~0x0F00; + } + /* speed 2 == DMA mode 0 multi-word */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x60); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x05); + speed = XFER_MW_DMA_0; + } else if (id->dma_1word & 0x0004) { + if (!((id->dma_1word >> 8) & 4)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + drive->id->dma_1word |= 0x0404; + } + /* speed 2 == DMA mode 2 single-word */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x60); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x05); + speed = XFER_SW_DMA_2; + } else if (id->dma_1word & 0x0002) { + if (!((id->dma_1word >> 8) & 2)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + drive->id->dma_1word |= 0x0202; + } + /* speed 1 == DMA mode 1 single-word */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0x80); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x06); + speed = XFER_SW_DMA_1; + } else if (id->dma_1word & 0x0001) { + if (!((id->dma_1word >> 8) & 1)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + drive->id->dma_1word |= 0x0101; + } + /* speed 0 == DMA mode 0 single-word */ + pci_write_config_byte(dev, (drive_pci)|0x01, BP|0xC0); + pci_write_config_byte(dev, (drive_pci)|0x02, CP|0x0B); + speed = XFER_SW_DMA_0; + } else { + /* restore original pci-config space */ + pci_write_config_dword(dev, drive_pci, drive_conf); + return ide_dma_off_quietly; + } + + err = ide_wait_cmd(drive, WIN_SETFEATURES, speed, SETFEATURES_XFER, 0, NULL); + +#if PDC202XX_DECODE_REGISTER_INFO + pci_read_config_byte(dev, (drive_pci), &AP); + pci_read_config_byte(dev, (drive_pci)|0x01, &BP); + pci_read_config_byte(dev, (drive_pci)|0x02, &CP); + + decode_registers(REG_A, AP); + decode_registers(REG_B, BP); + decode_registers(REG_C, CP); + decode_registers(REG_D, DP); +#endif /* PDC202XX_DECODE_REGISTER_INFO */ + +#if PDC202XX_DEBUG_DRIVE_INFO + printk("%s: %s drive%d 0x%08x ", + drive->name, ide_xfer_verbose(speed), + drive_number, drive_conf); + pci_read_config_dword(dev, drive_pci, &drive_conf); + printk("0x%08x\n", drive_conf); +#endif /* PDC202XX_DEBUG_DRIVE_INFO */ + +chipset_is_set: + + return ((int) ((id->dma_ultra >> 11) & 3) ? ide_dma_on : + ((id->dma_ultra >> 8) & 7) ? ide_dma_on : + ((id->dma_mword >> 8) & 7) ? ide_dma_on : + ((id->dma_1word >> 8) & 7) ? ide_dma_on : + ide_dma_off_quietly); +} + +/* 0 1 2 3 4 5 6 7 8 + * 960, 480, 390, 300, 240, 180, 120, 90, 60 + * 180, 150, 120, 90, 60 + * DMA_Speed + * 180, 120, 90, 90, 90, 60, 30 + * 11, 5, 4, 3, 2, 1, 0 + */ + +static int config_drive_xfer_rate (ide_drive_t *drive) +{ + struct hd_driveid *id = drive->id; + ide_hwif_t *hwif = HWIF(drive); + ide_dma_action_t dma_func = ide_dma_off_quietly; + + if (id && (id->capability & 1) && hwif->autodma) { + /* Consult the list of known "bad" drives */ + if (ide_dmaproc(ide_dma_bad_drive, drive)) { + return HWIF(drive)->dmaproc(ide_dma_off, drive); + } + + if (id->field_valid & 4) { + if (id->dma_ultra & 0x001F) { + /* Force if Capable UltraDMA */ + dma_func = config_chipset_for_dma(drive, 1); + if ((id->field_valid & 2) && + (dma_func != ide_dma_on)) + goto try_dma_modes; + } + } else if (id->field_valid & 2) { +try_dma_modes: + if ((id->dma_mword & 0x0004) || + (id->dma_1word & 0x0004)) { + /* Force if Capable regular DMA modes */ + dma_func = config_chipset_for_dma(drive, 0); + } + } else if ((ide_dmaproc(ide_dma_good_drive, drive)) && + (id->eide_dma_time > 150)) { + /* Consult the list of known "good" drives */ + dma_func = config_chipset_for_dma(drive, 0); + } + } + return HWIF(drive)->dmaproc(dma_func, drive); +} + +/* + * pdc202xx_dmaproc() initiates/aborts (U)DMA read/write operations on a drive. + */ +int pdc202xx_dmaproc (ide_dma_action_t func, ide_drive_t *drive) +{ + switch (func) { + case ide_dma_check: + return config_drive_xfer_rate(drive); + default: + break; + } + return ide_dmaproc(func, drive); /* use standard DMA stuff */ +} + +__initfunc(unsigned int pci_init_pdc202xx (struct pci_dev *dev, const char *name)) +{ + unsigned long high_16 = dev->base_address[4] & PCI_BASE_ADDRESS_IO_MASK; + byte udma_speed_flag = inb(high_16 + 0x001f); + byte primary_mode = inb(high_16 + 0x001a); + byte secondary_mode = inb(high_16 + 0x001b); + + if (dev->rom_address) { + pci_write_config_dword(dev, PCI_ROM_ADDRESS, dev->rom_address | PCI_ROM_ADDRESS_ENABLE); + printk("%s: ROM enabled at 0x%08lx\n", name, dev->rom_address); + } + + if ((dev->class >> 8) != PCI_CLASS_STORAGE_IDE) { + byte irq = 0, irq2 = 0; + pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq); + pci_read_config_byte(dev, (PCI_INTERRUPT_LINE)|0x80, &irq2); /* 0xbc */ + if (irq != irq2) { + pci_write_config_byte(dev, (PCI_INTERRUPT_LINE)|0x80, irq); /* 0xbc */ + printk("%s: pci-config space interrupt mirror fixed.\n", name); + } + } + + printk("%s: (U)DMA Burst Bit %sABLED " \ + "Primary %s Mode " \ + "Secondary %s Mode.\n", + name, + (udma_speed_flag & 1) ? "EN" : "DIS", + (primary_mode & 1) ? "MASTER" : "PCI", + (secondary_mode & 1) ? "MASTER" : "PCI" ); + +#if PDC202XX_FORCE_BURST_BIT + if (!(udma_speed_flag & 1)) { + printk("%s: FORCING BURST BIT 0x%02x -> 0x%02x ", name, udma_speed_flag, (udma_speed_flag|1)); + outb(udma_speed_flag|1, high_16 + 0x001f); + printk("%sCTIVE\n", (inb(high_16 + 0x001f) & 1) ? "A" : "INA"); + } +#endif /* PDC202XX_FORCE_BURST_BIT */ + +#if PDC202XX_FORCE_MASTER_MODE + if (!(primary_mode & 1)) { + printk("%s: FORCING PRIMARY MODE BIT 0x%02x -> 0x%02x ", + name, primary_mode, (primary_mode|1)); + outb(primary_mode|1, high_16 + 0x001a); + printk("%s\n", (inb(high_16 + 0x001a) & 1) ? "MASTER" : "PCI"); + } + + if (!(secondary_mode & 1)) { + printk("%s: FORCING SECONDARY MODE BIT 0x%02x -> 0x%02x ", + name, secondary_mode, (secondary_mode|1)); + outb(secondary_mode|1, high_16 + 0x001b); + printk("%s\n", (inb(high_16 + 0x001b) & 1) ? "MASTER" : "PCI"); + } +#endif /* PDC202XX_FORCE_MASTER_MODE */ + return dev->irq; +} + +__initfunc(void ide_init_pdc202xx (ide_hwif_t *hwif)) +{ + if (hwif->dma_base) { + hwif->dmaproc = &pdc202xx_dmaproc; + } +} diff --git a/drivers/block/piix.c b/drivers/block/piix.c new file mode 100644 index 000000000..e89e5dfb6 --- /dev/null +++ b/drivers/block/piix.c @@ -0,0 +1,294 @@ +/* + * linux/drivers/block/piix.c Version 0.23 May 29, 1999 + * + * Copyright (C) 1998-1999 Andrzej Krzysztofowicz, Author and Maintainer + * Copyright (C) 1998-1999 Andre Hedrick, Author and Maintainer + * + * PIO mode setting function for Intel chipsets. + * For use instead of BIOS settings. + * + * 40-41 + * 42-43 + * + * 41 + * 43 + * + * | PIO 0 | c0 | 80 | 0 | piix_tune_drive(drive, 0); + * | PIO 2 | SW2 | d0 | 90 | 4 | piix_tune_drive(drive, 2); + * | PIO 3 | MW1 | e1 | a1 | 9 | piix_tune_drive(drive, 3); + * | PIO 4 | MW2 | e3 | a3 | b | piix_tune_drive(drive, 4); + * + * sitre = word40 & 0x4000; primary + * sitre = word42 & 0x4000; secondary + * + * 44 8421|8421 hdd|hdb + * + * 48 8421 hdd|hdc|hdb|hda udma enabled + * + * 0001 hda + * 0010 hdb + * 0100 hdc + * 1000 hdd + * + * 4a 84|21 hdb|hda + * 4b 84|21 hdd|hdc + * + * 00|00 udma 0 + * 01|01 udma 1 + * 10|10 udma 2 + * 11|11 reserved + * + * pci_read_config_word(HWIF(drive)->pci_dev, 0x40, ®40); + * pci_read_config_word(HWIF(drive)->pci_dev, 0x42, ®42); + * pci_read_config_word(HWIF(drive)->pci_dev, 0x44, ®44); + * pci_read_config_word(HWIF(drive)->pci_dev, 0x48, ®48); + * pci_read_config_word(HWIF(drive)->pci_dev, 0x4a, ®4a); + * + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/pci.h> +#include <linux/hdreg.h> +#include <linux/ide.h> + +#include <asm/delay.h> +#include <asm/io.h> + +#include "ide_modes.h" + +#define PIIX_DEBUG_DRIVE_INFO 0 + +extern char *ide_xfer_verbose (byte xfer_rate); + +/* + * + */ +static byte piix_dma_2_pio (byte xfer_rate) { + switch(xfer_rate) { + case XFER_UDMA_4: + case XFER_UDMA_3: + case XFER_UDMA_2: + case XFER_UDMA_1: + case XFER_UDMA_0: + case XFER_MW_DMA_2: + case XFER_PIO_4: + return 4; + case XFER_MW_DMA_1: + case XFER_PIO_3: + return 3; + case XFER_SW_DMA_2: + case XFER_PIO_2: + return 2; + case XFER_MW_DMA_0: + case XFER_SW_DMA_1: + case XFER_SW_DMA_0: + case XFER_PIO_1: + case XFER_PIO_0: + case XFER_PIO_SLOW: + default: + return 0; + } +} + +/* + * Based on settings done by AMI BIOS + * (might be usefull if drive is not registered in CMOS for any reason). + */ +static void piix_tune_drive (ide_drive_t *drive, byte pio) +{ + unsigned long flags; + u16 master_data; + byte slave_data; + int is_slave = (&HWIF(drive)->drives[1] == drive); + int master_port = HWIF(drive)->index ? 0x42 : 0x40; + int slave_port = 0x44; + /* ISP RTC */ + byte timings[][2] = { { 0, 0 }, + { 0, 0 }, + { 1, 0 }, + { 2, 1 }, + { 2, 3 }, }; + +#if 1 + pio = ide_get_best_pio_mode(drive, pio, 5, NULL); +#else + pio = ide_get_best_pio_mode(drive, pio, 4, NULL); +#endif + pci_read_config_word(HWIF(drive)->pci_dev, master_port, &master_data); + if (is_slave) { + master_data = master_data | 0x4000; + if (pio > 1) + /* enable PPE, IE and TIME */ + master_data = master_data | 0x0070; + pci_read_config_byte(HWIF(drive)->pci_dev, slave_port, &slave_data); + slave_data = slave_data & (HWIF(drive)->index ? 0x0f : 0xf0); + slave_data = slave_data | ((timings[pio][0] << 2) | (timings[pio][1] + << (HWIF(drive)->index ? 4 : 0))); + } else { + master_data = master_data & 0xccf8; + if (pio > 1) + /* enable PPE, IE and TIME */ + master_data = master_data | 0x0007; + master_data = master_data | (timings[pio][0] << 12) | + (timings[pio][1] << 8); + } + save_flags(flags); + cli(); + pci_write_config_word(HWIF(drive)->pci_dev, master_port, master_data); + if (is_slave) + pci_write_config_byte(HWIF(drive)->pci_dev, slave_port, slave_data); + restore_flags(flags); +} + +static int piix_config_drive_for_dma(ide_drive_t *drive, int ultra) +{ + struct hd_driveid *id = drive->id; + ide_hwif_t *hwif = HWIF(drive); + struct pci_dev *dev = hwif->pci_dev; + + unsigned long flags; + int sitre; + short reg4042, reg44, reg48, reg4a; + byte speed; + int u_speed; + byte maslave = hwif->channel ? 0x42 : 0x40; + int drive_number = ((hwif->channel ? 2 : 0) + (drive->select.b.unit & 0x01)); + int a_speed = 2 << (drive_number * 4); + int u_flag = 1 << drive_number; + + pci_read_config_word(dev, maslave, ®4042); + sitre = (reg4042 & 0x4000) ? 1 : 0; + pci_read_config_word(dev, 0x44, ®44); + pci_read_config_word(dev, 0x48, ®48); + pci_read_config_word(dev, 0x4a, ®4a); + + save_flags(flags); + cli(); + + if (id->dma_ultra && (ultra)) { + if (!(reg48 & u_flag)) { + pci_write_config_word(dev, 0x48, reg48|u_flag); + } + } else { + if (reg48 & u_flag) { + pci_write_config_word(dev, 0x48, reg48 & ~u_flag); + } + } + + if ((id->dma_ultra & 0x0004) && (ultra)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + if (!((id->dma_ultra >> 8) & 4)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0404; + } + u_speed = 2 << (drive_number * 4); + if (!(reg4a & u_speed)) { + pci_write_config_word(dev, 0x4a, reg4a|u_speed); + } + speed = XFER_UDMA_2; + } else if ((id->dma_ultra & 0x0002) && (ultra)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + if (!((id->dma_ultra >> 8) & 2)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0202; + } + u_speed = 1 << (drive_number * 4); + if (!(reg4a & u_speed)) { + pci_write_config_word(dev, 0x4a, reg4a & ~a_speed); + pci_write_config_word(dev, 0x4a, reg4a|u_speed); + } + speed = XFER_UDMA_1; + } else if ((id->dma_ultra & 0x0001) && (ultra)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + if (!((id->dma_ultra >> 8) & 1)) { + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_ultra |= 0x0101; + } + u_speed = 0 << (drive_number * 4); + if (!(reg4a & u_speed)) { + pci_write_config_word(dev, 0x4a, reg4a & ~a_speed); + pci_write_config_word(dev, 0x4a, reg4a|u_speed); + } + speed = XFER_UDMA_0; + } else if (id->dma_mword & 0x0004) { + if (reg4a & a_speed) + pci_write_config_word(dev, 0x4a, reg4a & ~a_speed); + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + if (!((id->dma_mword >> 8) & 4)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0404; + } + speed = XFER_MW_DMA_2; + } else if (id->dma_mword & 0x0002) { + if (reg4a & a_speed) + pci_write_config_word(dev, 0x4a, reg4a & ~a_speed); + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_1word &= ~0x0F00; + if (!((id->dma_mword >> 8) & 2)) { + drive->id->dma_mword &= ~0x0F00; + drive->id->dma_mword |= 0x0202; + } + speed = XFER_MW_DMA_1; + } else if (id->dma_1word & 0x0004) { + if (reg4a & a_speed) + pci_write_config_word(dev, 0x4a, reg4a & ~a_speed); + drive->id->dma_ultra &= ~0x0F00; + drive->id->dma_mword &= ~0x0F00; + if (!((id->dma_1word >> 8) & 4)) { + drive->id->dma_1word &= ~0x0F00; + drive->id->dma_1word |= 0x0404; + } + speed = XFER_SW_DMA_2; + } else { +#if 0 + speed = XFER_PIO_0; +#else + speed = XFER_PIO_0 + ide_get_best_pio_mode(drive, 255, 5, NULL); +#endif + } + + restore_flags(flags); + piix_tune_drive(drive, piix_dma_2_pio(speed)); + + (void) ide_wait_cmd(drive, WIN_SETFEATURES, speed, SETFEATURES_XFER, 0, NULL); + +#if PIIX_DEBUG_DRIVE_INFO + printk("%s: %s drive%d ", + drive->name, + ide_xfer_verbose(speed), + drive_number); + printk("\n"); +#endif /* PIIX_DEBUG_DRIVE_INFO */ + + return ((int) ((id->dma_ultra >> 8) & 7) ? ide_dma_on : + ((id->dma_mword >> 8) & 7) ? ide_dma_on : + ((id->dma_1word >> 8) & 7) ? ide_dma_on : + ide_dma_off_quietly); +} + +static int piix_dmaproc(ide_dma_action_t func, ide_drive_t *drive) +{ + int ultra = (HWIF(drive)->pci_dev->device == PCI_DEVICE_ID_INTEL_82371AB) ? 1 : 0; + switch (func) { + case ide_dma_check: + return ide_dmaproc((ide_dma_action_t) piix_config_drive_for_dma(drive, ultra), drive); + default : + break; + } + /* Other cases are done by generic IDE-DMA code. */ + return ide_dmaproc(func, drive); +} + +void ide_init_piix (ide_hwif_t *hwif) +{ + hwif->tuneproc = &piix_tune_drive; + if (hwif->dma_base) { + hwif->dmaproc = &piix_dmaproc; + } +} diff --git a/drivers/char/defkeymap.c b/drivers/char/defkeymap.c new file mode 100644 index 000000000..5d8b98e55 --- /dev/null +++ b/drivers/char/defkeymap.c @@ -0,0 +1,265 @@ + +/* Do not edit this file! It was automatically generated by */ +/* loadkeys --mktable defkeymap.map > defkeymap.c */ + +#include <linux/types.h> +#include <linux/keyboard.h> +#include <linux/kd.h> + +u_short plain_map[NR_KEYS] = { + 0xf200, 0xf01b, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, + 0xf037, 0xf038, 0xf039, 0xf030, 0xf02d, 0xf03d, 0xf07f, 0xf009, + 0xfb71, 0xfb77, 0xfb65, 0xfb72, 0xfb74, 0xfb79, 0xfb75, 0xfb69, + 0xfb6f, 0xfb70, 0xf05b, 0xf05d, 0xf201, 0xf702, 0xfb61, 0xfb73, + 0xfb64, 0xfb66, 0xfb67, 0xfb68, 0xfb6a, 0xfb6b, 0xfb6c, 0xf03b, + 0xf027, 0xf060, 0xf700, 0xf05c, 0xfb7a, 0xfb78, 0xfb63, 0xfb76, + 0xfb62, 0xfb6e, 0xfb6d, 0xf02c, 0xf02e, 0xf02f, 0xf700, 0xf30c, + 0xf703, 0xf020, 0xf207, 0xf100, 0xf101, 0xf102, 0xf103, 0xf104, + 0xf105, 0xf106, 0xf107, 0xf108, 0xf109, 0xf208, 0xf209, 0xf307, + 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301, + 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf03c, 0xf10a, + 0xf10b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603, + 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116, + 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, +}; + +static u_short shift_map[NR_KEYS] = { + 0xf200, 0xf01b, 0xf021, 0xf040, 0xf023, 0xf024, 0xf025, 0xf05e, + 0xf026, 0xf02a, 0xf028, 0xf029, 0xf05f, 0xf02b, 0xf07f, 0xf009, + 0xfb51, 0xfb57, 0xfb45, 0xfb52, 0xfb54, 0xfb59, 0xfb55, 0xfb49, + 0xfb4f, 0xfb50, 0xf07b, 0xf07d, 0xf201, 0xf702, 0xfb41, 0xfb53, + 0xfb44, 0xfb46, 0xfb47, 0xfb48, 0xfb4a, 0xfb4b, 0xfb4c, 0xf03a, + 0xf022, 0xf07e, 0xf700, 0xf07c, 0xfb5a, 0xfb58, 0xfb43, 0xfb56, + 0xfb42, 0xfb4e, 0xfb4d, 0xf03c, 0xf03e, 0xf03f, 0xf700, 0xf30c, + 0xf703, 0xf020, 0xf207, 0xf10a, 0xf10b, 0xf10c, 0xf10d, 0xf10e, + 0xf10f, 0xf110, 0xf111, 0xf112, 0xf113, 0xf213, 0xf203, 0xf307, + 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301, + 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf03e, 0xf10a, + 0xf10b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603, + 0xf20b, 0xf601, 0xf602, 0xf117, 0xf600, 0xf20a, 0xf115, 0xf116, + 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, +}; + +static u_short altgr_map[NR_KEYS] = { + 0xf200, 0xf200, 0xf200, 0xf040, 0xf200, 0xf024, 0xf200, 0xf200, + 0xf07b, 0xf05b, 0xf05d, 0xf07d, 0xf05c, 0xf200, 0xf200, 0xf200, + 0xfb71, 0xfb77, 0xf918, 0xfb72, 0xfb74, 0xfb79, 0xfb75, 0xfb69, + 0xfb6f, 0xfb70, 0xf200, 0xf07e, 0xf201, 0xf702, 0xf914, 0xfb73, + 0xf917, 0xf919, 0xfb67, 0xfb68, 0xfb6a, 0xfb6b, 0xfb6c, 0xf200, + 0xf200, 0xf200, 0xf700, 0xf200, 0xfb7a, 0xfb78, 0xf916, 0xfb76, + 0xf915, 0xfb6e, 0xfb6d, 0xf200, 0xf200, 0xf200, 0xf700, 0xf30c, + 0xf703, 0xf200, 0xf207, 0xf50c, 0xf50d, 0xf50e, 0xf50f, 0xf510, + 0xf511, 0xf512, 0xf513, 0xf514, 0xf515, 0xf208, 0xf202, 0xf911, + 0xf912, 0xf913, 0xf30b, 0xf90e, 0xf90f, 0xf910, 0xf30a, 0xf90b, + 0xf90c, 0xf90d, 0xf90a, 0xf310, 0xf206, 0xf200, 0xf07c, 0xf516, + 0xf517, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603, + 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116, + 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, +}; + +static u_short ctrl_map[NR_KEYS] = { + 0xf200, 0xf200, 0xf200, 0xf000, 0xf01b, 0xf01c, 0xf01d, 0xf01e, + 0xf01f, 0xf07f, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf008, 0xf200, + 0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015, 0xf009, + 0xf00f, 0xf010, 0xf01b, 0xf01d, 0xf201, 0xf702, 0xf001, 0xf013, + 0xf004, 0xf006, 0xf007, 0xf008, 0xf00a, 0xf00b, 0xf00c, 0xf200, + 0xf007, 0xf000, 0xf700, 0xf01c, 0xf01a, 0xf018, 0xf003, 0xf016, + 0xf002, 0xf00e, 0xf00d, 0xf200, 0xf20e, 0xf07f, 0xf700, 0xf30c, + 0xf703, 0xf000, 0xf207, 0xf100, 0xf101, 0xf102, 0xf103, 0xf104, + 0xf105, 0xf106, 0xf107, 0xf108, 0xf109, 0xf208, 0xf204, 0xf307, + 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301, + 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf200, 0xf10a, + 0xf10b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603, + 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116, + 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, +}; + +static u_short shift_ctrl_map[NR_KEYS] = { + 0xf200, 0xf200, 0xf200, 0xf000, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf200, 0xf200, + 0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015, 0xf009, + 0xf00f, 0xf010, 0xf200, 0xf200, 0xf201, 0xf702, 0xf001, 0xf013, + 0xf004, 0xf006, 0xf007, 0xf008, 0xf00a, 0xf00b, 0xf00c, 0xf200, + 0xf200, 0xf200, 0xf700, 0xf200, 0xf01a, 0xf018, 0xf003, 0xf016, + 0xf002, 0xf00e, 0xf00d, 0xf200, 0xf200, 0xf200, 0xf700, 0xf30c, + 0xf703, 0xf200, 0xf207, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf208, 0xf200, 0xf307, + 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301, + 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf200, 0xf200, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603, + 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116, + 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, +}; + +static u_short alt_map[NR_KEYS] = { + 0xf200, 0xf81b, 0xf831, 0xf832, 0xf833, 0xf834, 0xf835, 0xf836, + 0xf837, 0xf838, 0xf839, 0xf830, 0xf82d, 0xf83d, 0xf87f, 0xf809, + 0xf871, 0xf877, 0xf865, 0xf872, 0xf874, 0xf879, 0xf875, 0xf869, + 0xf86f, 0xf870, 0xf85b, 0xf85d, 0xf80d, 0xf702, 0xf861, 0xf873, + 0xf864, 0xf866, 0xf867, 0xf868, 0xf86a, 0xf86b, 0xf86c, 0xf83b, + 0xf827, 0xf860, 0xf700, 0xf85c, 0xf87a, 0xf878, 0xf863, 0xf876, + 0xf862, 0xf86e, 0xf86d, 0xf82c, 0xf82e, 0xf82f, 0xf700, 0xf30c, + 0xf703, 0xf820, 0xf207, 0xf500, 0xf501, 0xf502, 0xf503, 0xf504, + 0xf505, 0xf506, 0xf507, 0xf508, 0xf509, 0xf208, 0xf209, 0xf907, + 0xf908, 0xf909, 0xf30b, 0xf904, 0xf905, 0xf906, 0xf30a, 0xf901, + 0xf902, 0xf903, 0xf900, 0xf310, 0xf206, 0xf200, 0xf83c, 0xf50a, + 0xf50b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603, + 0xf118, 0xf210, 0xf211, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116, + 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, +}; + +static u_short ctrl_alt_map[NR_KEYS] = { + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf811, 0xf817, 0xf805, 0xf812, 0xf814, 0xf819, 0xf815, 0xf809, + 0xf80f, 0xf810, 0xf200, 0xf200, 0xf201, 0xf702, 0xf801, 0xf813, + 0xf804, 0xf806, 0xf807, 0xf808, 0xf80a, 0xf80b, 0xf80c, 0xf200, + 0xf200, 0xf200, 0xf700, 0xf200, 0xf81a, 0xf818, 0xf803, 0xf816, + 0xf802, 0xf80e, 0xf80d, 0xf200, 0xf200, 0xf200, 0xf700, 0xf30c, + 0xf703, 0xf200, 0xf207, 0xf500, 0xf501, 0xf502, 0xf503, 0xf504, + 0xf505, 0xf506, 0xf507, 0xf508, 0xf509, 0xf208, 0xf200, 0xf307, + 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301, + 0xf302, 0xf303, 0xf300, 0xf20c, 0xf206, 0xf200, 0xf200, 0xf50a, + 0xf50b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, + 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603, + 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf20c, + 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d, + 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, +}; + +ushort *key_maps[MAX_NR_KEYMAPS] = { + plain_map, shift_map, altgr_map, 0, + ctrl_map, shift_ctrl_map, 0, 0, + alt_map, 0, 0, 0, + ctrl_alt_map, 0 +}; + +unsigned int keymap_count = 7; + + +/* + * Philosophy: most people do not define more strings, but they who do + * often want quite a lot of string space. So, we statically allocate + * the default and allocate dynamically in chunks of 512 bytes. + */ + +char func_buf[] = { + '\033', '[', '[', 'A', 0, + '\033', '[', '[', 'B', 0, + '\033', '[', '[', 'C', 0, + '\033', '[', '[', 'D', 0, + '\033', '[', '[', 'E', 0, + '\033', '[', '1', '7', '~', 0, + '\033', '[', '1', '8', '~', 0, + '\033', '[', '1', '9', '~', 0, + '\033', '[', '2', '0', '~', 0, + '\033', '[', '2', '1', '~', 0, + '\033', '[', '2', '3', '~', 0, + '\033', '[', '2', '4', '~', 0, + '\033', '[', '2', '5', '~', 0, + '\033', '[', '2', '6', '~', 0, + '\033', '[', '2', '8', '~', 0, + '\033', '[', '2', '9', '~', 0, + '\033', '[', '3', '1', '~', 0, + '\033', '[', '3', '2', '~', 0, + '\033', '[', '3', '3', '~', 0, + '\033', '[', '3', '4', '~', 0, + '\033', '[', '1', '~', 0, + '\033', '[', '2', '~', 0, + '\033', '[', '3', '~', 0, + '\033', '[', '4', '~', 0, + '\033', '[', '5', '~', 0, + '\033', '[', '6', '~', 0, + '\033', '[', 'M', 0, + '\033', '[', 'P', 0, +}; + + +char *funcbufptr = func_buf; +int funcbufsize = sizeof(func_buf); +int funcbufleft = 0; /* space left */ + +char *func_table[MAX_NR_FUNC] = { + func_buf + 0, + func_buf + 5, + func_buf + 10, + func_buf + 15, + func_buf + 20, + func_buf + 25, + func_buf + 31, + func_buf + 37, + func_buf + 43, + func_buf + 49, + func_buf + 55, + func_buf + 61, + func_buf + 67, + func_buf + 73, + func_buf + 79, + func_buf + 85, + func_buf + 91, + func_buf + 97, + func_buf + 103, + func_buf + 109, + func_buf + 115, + func_buf + 120, + func_buf + 125, + func_buf + 130, + func_buf + 135, + func_buf + 140, + func_buf + 145, + 0, + 0, + func_buf + 149, + 0, +}; + +struct kbdiacr accent_table[MAX_DIACR] = { + {'`', 'A', '\300'}, {'`', 'a', '\340'}, + {'\'', 'A', '\301'}, {'\'', 'a', '\341'}, + {'^', 'A', '\302'}, {'^', 'a', '\342'}, + {'~', 'A', '\303'}, {'~', 'a', '\343'}, + {'"', 'A', '\304'}, {'"', 'a', '\344'}, + {'O', 'A', '\305'}, {'o', 'a', '\345'}, + {'0', 'A', '\305'}, {'0', 'a', '\345'}, + {'A', 'A', '\305'}, {'a', 'a', '\345'}, + {'A', 'E', '\306'}, {'a', 'e', '\346'}, + {',', 'C', '\307'}, {',', 'c', '\347'}, + {'`', 'E', '\310'}, {'`', 'e', '\350'}, + {'\'', 'E', '\311'}, {'\'', 'e', '\351'}, + {'^', 'E', '\312'}, {'^', 'e', '\352'}, + {'"', 'E', '\313'}, {'"', 'e', '\353'}, + {'`', 'I', '\314'}, {'`', 'i', '\354'}, + {'\'', 'I', '\315'}, {'\'', 'i', '\355'}, + {'^', 'I', '\316'}, {'^', 'i', '\356'}, + {'"', 'I', '\317'}, {'"', 'i', '\357'}, + {'-', 'D', '\320'}, {'-', 'd', '\360'}, + {'~', 'N', '\321'}, {'~', 'n', '\361'}, + {'`', 'O', '\322'}, {'`', 'o', '\362'}, + {'\'', 'O', '\323'}, {'\'', 'o', '\363'}, + {'^', 'O', '\324'}, {'^', 'o', '\364'}, + {'~', 'O', '\325'}, {'~', 'o', '\365'}, + {'"', 'O', '\326'}, {'"', 'o', '\366'}, + {'/', 'O', '\330'}, {'/', 'o', '\370'}, + {'`', 'U', '\331'}, {'`', 'u', '\371'}, + {'\'', 'U', '\332'}, {'\'', 'u', '\372'}, + {'^', 'U', '\333'}, {'^', 'u', '\373'}, + {'"', 'U', '\334'}, {'"', 'u', '\374'}, + {'\'', 'Y', '\335'}, {'\'', 'y', '\375'}, + {'T', 'H', '\336'}, {'t', 'h', '\376'}, + {'s', 's', '\337'}, {'"', 'y', '\377'}, + {'s', 'z', '\337'}, {'i', 'j', '\377'}, +}; + +unsigned int accent_table_size = 68; diff --git a/drivers/char/i2c-parport.c b/drivers/char/i2c-parport.c new file mode 100644 index 000000000..cafe38f37 --- /dev/null +++ b/drivers/char/i2c-parport.c @@ -0,0 +1,149 @@ +/* + * I2C driver for parallel port + * + * Author: Phil Blundell <philb@gnu.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This driver implements a simple I2C protocol by bit-twiddling some + * signals on the parallel port. Since the outputs on the parallel port + * aren't open collector, three lines rather than two are used: + * + * D0 clock out + * D1 data out + * BUSY data in + */ + +#include <linux/parport.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <asm/spinlock.h> + +#define I2C_DELAY 10 + +static int debug = 0; + +struct parport_i2c_bus +{ + struct i2c_bus i2c; + struct parport_i2c_bus *next; +}; + +static struct parport_i2c_bus *bus_list; + +#ifdef __SMP__ +static spinlock_t bus_list_lock = SPIN_LOCK_UNLOCKED; +#endif + +/* software I2C functions */ + +static void i2c_setlines(struct i2c_bus *bus, int clk, int data) +{ + struct parport *p = bus->data; + parport_write_data(p, (clk?1:0) | (data?2:0)); + udelay(I2C_DELAY); +} + +static int i2c_getdataline(struct i2c_bus *bus) +{ + struct parport *p = bus->data; + return (parport_read_status(p) & PARPORT_STATUS_BUSY) ? 0 : 1; +} + +static struct i2c_bus parport_i2c_bus_template = +{ + "...", + I2C_BUSID_PARPORT, + NULL, + + SPIN_LOCK_UNLOCKED, + + NULL, + NULL, + + i2c_setlines, + i2c_getdataline, + NULL, + NULL, +}; + +static void i2c_parport_attach(struct parport *port) +{ + struct parport_i2c_bus *b = kmalloc(sizeof(struct parport_i2c_bus), + GFP_KERNEL); + b->i2c = parport_i2c_bus_template; + b->i2c.data = port; + strncpy(b->i2c.name, port->name, 32); + spin_lock(&bus_list_lock); + b->next = bus_list; + bus_list = b; + spin_unlock(&bus_list_lock); + i2c_register_bus(&b->i2c); + if (debug) + printk(KERN_DEBUG "i2c: attached to %s\n", port->name); +} + +static void i2c_parport_detach(struct parport *port) +{ + struct parport_i2c_bus *b, *old_b = NULL; + spin_lock(&bus_list_lock); + b = bus_list; + while (b) + { + if (b->i2c.data == port) + { + if (old_b) + old_b->next = b->next; + else + bus_list = b->next; + i2c_unregister_bus(&b->i2c); + kfree(b); + break; + } + old_b = b; + b = b->next; + } + spin_unlock(&bus_list_lock); + if (debug) + printk(KERN_DEBUG "i2c: detached from %s\n", port->name); +} + +static struct parport_driver parport_i2c_driver = +{ + "i2c", + i2c_parport_attach, + i2c_parport_detach +}; + +#ifdef MODULE +int init_module(void) +#else +int __init i2c_parport_init(void) +#endif +{ + printk("I2C: driver for parallel port v0.1 philb@gnu.org\n"); + parport_register_driver(&parport_i2c_driver); + return 0; +} + +#ifdef MODULE +MODULE_PARM(debug, "i"); + +void cleanup_module(void) +{ + struct parport_i2c_bus *b = bus_list; + while (b) + { + struct parport_i2c_bus *next = b->next; + i2c_unregister_bus(&b->i2c); + kfree(b); + b = next; + } + parport_unregister_driver(&parport_i2c_driver); +} +#endif diff --git a/drivers/i2o/Config.in b/drivers/i2o/Config.in new file mode 100644 index 000000000..d6ae26f64 --- /dev/null +++ b/drivers/i2o/Config.in @@ -0,0 +1,12 @@ +mainmenu_option next_comment +comment 'I2O device support' + +tristate 'I2O support' CONFIG_I2O + +dep_tristate 'I2O PCI support' CONFIG_I2O_PCI $CONFIG_I2O +dep_tristate 'I2O Block OSM' CONFIG_I2O_BLOCK $CONFIG_I2O +dep_tristate 'I2O LAN OSM' CONFIG_I2O_LAN $CONFIG_I2O +dep_tristate 'I2O SCSI OSM' CONFIG_I2O_SCSI $CONFIG_I2O +dep_tristate 'I2O /proc support' CONFIG_I2O_PROC $CONFIG_I2O + +endmenu diff --git a/drivers/i2o/Makefile b/drivers/i2o/Makefile new file mode 100644 index 000000000..d70b42310 --- /dev/null +++ b/drivers/i2o/Makefile @@ -0,0 +1,75 @@ +# +# Makefile for the kernel I2O OSM. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definition is now inherited from the +# parent makefile. +# + +# +# Note : at this point, these files are compiled on all systems. +# In the future, some of these should be built conditionally. +# + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + + +L_TARGET := i2o.a +L_OBJS := +M_OBJS := + +ifeq ($(CONFIG_I2O_PCI),y) +L_OBJS += i2o_pci.o +else + ifeq ($(CONFIG_I2O_PCI),m) + M_OBJS += i2o_pci.o + endif +endif + +ifeq ($(CONFIG_I2O),y) +LX_OBJS += i2o_core.o i2o_config.o +else + ifeq ($(CONFIG_I2O),m) + MX_OBJS += i2o_core.o i2o_config.o + endif +endif + +ifeq ($(CONFIG_I2O_BLOCK),y) +LX_OBJS += i2o_block.o +else + ifeq ($(CONFIG_I2O_BLOCK),m) + MX_OBJS += i2o_block.o + endif +endif + +ifeq ($(CONFIG_I2O_LAN),y) +LX_OBJS += i2o_lan.o +else + ifeq ($(CONFIG_I2O_LAN),m) + MX_OBJS += i2o_lan.o + endif +endif + +ifeq ($(CONFIG_I2O_SCSI),y) +LX_OBJS += i2o_scsi.o +else + ifeq ($(CONFIG_I2O_SCSI),m) + MX_OBJS += i2o_scsi.o + endif +endif + +ifeq ($(CONFIG_I2O_PROC),y) +LX_OBJS += i2o_proc.o +else + ifeq ($(CONFIG_I2O_PROC),m) + MX_OBJS += i2o_proc.o + endif +endif + +include $(TOPDIR)/Rules.make + diff --git a/drivers/i2o/README b/drivers/i2o/README new file mode 100644 index 000000000..4e6d2c16d --- /dev/null +++ b/drivers/i2o/README @@ -0,0 +1,78 @@ + + Linux I2O Support (c) Copyright 1999 Red Hat Software + and others. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version + 2 of the License, or (at your option) any later version. + +AUTHORS (so far) + +Alan Cox, Building Number Three Ltd. + Core code, SCSI and Block OSMs + +Steve Ralston, LSI Logic Corp. + Debugging SCSI and Block OSM + +Deepak Saxena, Intel Corp. + /proc interface, bug fixes + Ioctl interfaces for control + +Philip Rumpf + Fixed assorted dumb SMP locking bugs + +Juha Sievanen, University Of Helsinki Finland + LAN OSM + Bug fixes + Core code extensions + +CREDITS + + This work was made possible by + +Red Hat Software + Funding for the Building #3 part of the project + +Symbios Logic (Now LSI) + Host adapters, hints, known to work platforms when I hit + compatibility problems + +BoxHill Corporation + Loan of initial FibreChannel disk array used for development work. + +STATUS: + +o The core setup works within limits. +o The scsi layer seems to almost work. I'm still chasing down the hang + bug. +o The block OSM is fairly minimal but does seem to work. + + +TO DO: + +General: +o Support multiple IOP's and tell them about each other +o Provide hidden address space if asked +o Long term message flow control +o PCI IOP's without interrupts are not supported yet +o Push FAIL handling into the core +o DDM control interfaces for module load etc + +Block: +o Real error handler +o Multiple major numbers +o Read ahead and cache handling stuff. Talk to Ingo and people +o Power management +o Finish Media changers + +SCSI: +o Find the right way to associate drives/luns/busses + +Net: +o Port the existing RCPCI work to the frame work or write a new + driver. This one is with the Finns + +Tape: +o Anyone seen anything implementing this ? + diff --git a/drivers/i2o/README.ioctl b/drivers/i2o/README.ioctl new file mode 100644 index 000000000..501c93af9 --- /dev/null +++ b/drivers/i2o/README.ioctl @@ -0,0 +1,398 @@ + +Linux I2O User Space Interface +rev 0.3 - 04/20/99 + +============================================================================= +Originally written by Deepak Saxena(deepak.saxena@intel.com) +Currently maintained by Deepak Saxena(deepak.saxena@intel.com) +============================================================================= + +I. Introduction + +The Linux I2O susbsytem provides a set of ioctl() commands than can be +utilized by user space applications to communicate with IOPs and devices +on individual IOPs. This document defines the specific ioctl() commands +that are available to the user and provides examples of their uses. + +This document assumes the reader is familiar with or has access to the +I2O specification as no I2O message parameters are outlined. For information +on the specification, see http://www.i2osig.org + +This document and the I2O user space interface are currently maintained +by Deepak Saxena. Please send all comments, errata, and bug fixes to +deepak.saxena@intel.com + +II. IOP Access + +Access to the I2O subsystem is provided through the device file named +/dev/i2octl. This file is a character file with major number 10 and minor +number 166. It can be created through the following command: + + mknod /dev/i2octl c 10 166 + +III. Determining the IOP Count + + SYNOPSIS + + ioctl(fd, I2OGETIOPS, int *count); + + u8 count[MAX_I2O_CONTROLLERS]; + + DESCRIPTION + + This function returns the system's active IOP table. count should + point to a buffer containing MAX_I2O_CONTROLLERS entries. Upon + returning, each entry will contain a non-zero value if the given + IOP unit is active, and NULL if it is inactive or non-existent. + + RETURN VALUE. + + Returns 0 if no errors occur, and -1 otherwise. If an error occurs, + errno is set appropriately: + + EIO Unkown error + +IV. ExecHrtGet Message + + SYNOPSIS + + ioctl(fd, I2OHRTGET, struct i2o_cmd_hrt *hrt); + + struct i2o_cmd_hrtlct + { + u32 iop; /* IOP unit number */ + void *resbuf; /* Buffer for result */ + u32 *reslen; /* Buffer length in bytes */ + }; + + DESCRIPTION + + This function posts an ExecHrtHet message to the IOP specified by + hrt->iop and returns the data in the buffer pointed to by hrt->buf + The size of the data written is placed into the memory pointed to + by hrt->len. + + RETURNS + + This function returns 0 if no errors occur. If an error occurs, -1 + is returned and errno is set appropriately: + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(hrt->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + +V. ExecLctNotify Message + + SYNOPSIS + + ioctl(fd, I2OLCTGET, struct i2o_cmd_lct *lct); + + struct i2o_cmd_hrtlct + { + u32 iop; /* IOP unit number */ + void *resbuf; /* Buffer for result */ + u32 *reslen; /* Buffer length in bytes */ + }; + + DESCRIPTION + + This function posts an ExecLctGet message to the IOP specified by + lct->iop and returns the data in the buffer pointed to by lct->buf + The size of the data written is placed into the memory pointed to + by lct->reslen. + + RETURNS + + This function returns 0 if no errors occur. If an error occurs, -1 + is returned and errno is set appropriately: + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(lct->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + +VI. UtilParamsSet Message + + SYNOPSIS + + ioctl(fd, I2OPARMSET, struct i2o_parm_setget *ops); + + struct i2o_cmd_psetget + { + u32 iop; /* IOP unit number */ + u32 tid; /* Target device TID */ + void *opbuf; /* Operation List buffer */ + u32 oplen; /* Operation List buffer length in bytes */ + void *resbuf; /* Result List buffer */ + u32 *reslen; /* Result List buffer length in bytes */ + }; + + DESCRIPTION + + This function posts a UtilParamsSet message to the device identified + by ops->iop and ops->tid. The operation list for the message is + sent through the ops->oplen buffer, and the result list is written + into the buffer pointed to by ops->oplen. The number of bytes + written is placed into *(ops->reslen). + + RETURNS + + The return value is the size in bytes of the data written into + ops->resbuf if no errors occur. If an error occurs, -1 is returned + and errno is set appropriatly: + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(ops->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + + A return value of 0 does not mean that the value was actually + changed properly on the IOP. The user should check the result + list to determine the specific status of the transaction. + +VII. UtilParamsGet Message + + SYNOPSIS + + ioctl(fd, I2OPARMGET, struct i2o_parm_setget *ops); + + struct i2o_parm_setget + { + u32 iop; /* IOP unit number */ + u32 tid; /* Target device TID */ + void *opbuf; /* Operation List buffer */ + u32 oplen; /* Operation List buffer length in bytes */ + void *resbuf; /* Result List buffer */ + u32 *reslen; /* Result List buffer length in bytes */ + }; + + DESCRIPTION + + This function posts a UtilParamsGet message to the device identified + by ops->iop and ops->tid. The operation list for the message is + sent through the ops->oplen buffer, and the result list is written + into the buffer pointed to by ops->oplen. The actual size of data + written is placed into *(ops->reslen). + + RETURNS + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(ops->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + + A return value of 0 does not mean that the value was actually + properly retreived. The user should check the result list + to determine the specific status of the transaction. + +VIII. ExecSwDownload Message + + SYNOPSIS + + ioctl(fd, I2OSWDL, struct i2o_sw_xfer *sw); + + struct i2o_sw_xfer + { + u32 iop; /* IOP unit number */ + u8 dl_flags; /* DownLoadFlags field */ + u8 sw_type; /* Software type */ + u32 sw_id; /* Software ID */ + void *buf; /* Pointer to software buffer */ + u32 *swlen; /* Length of software data */ + u32 *maxfrag; /* Number of fragments */ + u32 *curfrag; /* Current fragment number */ + }; + + DESCRIPTION + + This function downloads the software pointed to by sw->buf to the + iop identified by sw->iop. The DownloadFlags, SwID, and SwType fields + of the ExecSwDownload message are filed in with the values of + sw->dl_flags, sw->sw_id, and sw->sw_type. + + Once the ioctl() is called and software transfer begins, the + user can read the value *(sw->maxfrag) and *(sw->curfrag) to + determine the status of the software transfer. As the IOP + is very slow when it comes to SW transfers, this can be + used by a separate thread to report status to the user. The + user _should not_ write to this memory location until the ioctl() + has returned. + + RETURNS + + This function returns 0 no errors occur. If an error occurs, -1 + is returned and errno is set appropriatly: + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(ops->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + +IX. ExecSwUpload Message + + SYNOPSIS + + ioctl(fd, I2OSWUL, struct i2o_sw_xfer *sw); + + struct i2o_sw_xfer + { + u32 iop; /* IOP unit number */ + u8 flags; /* Unused */ + u8 sw_type; /* Software type */ + u32 sw_id; /* Software ID */ + void *buf; /* Pointer to software buffer */ + u32 *swlen; /* Length in bytes of software */ + u32 *maxfrag; /* Number of fragments */ + u32 *curfrag; /* Current fragment number */ + }; + + DESCRIPTION + + This function uploads software from the IOP identified by sw->iop + and places it in the buffer pointed to by sw->buf. The SwID, SwType + and SwSize fields of the ExecSwDownload message are filed in + with the values of sw->sw_id, sw->sw_type, sw->swlen, and. The + actual size of the module is written into *(sw->buflen). + + Once the ioctl() is called and software transfer begins, the + user can read the value *(sw->maxfrag) and *(sw->curfrag) to + determine the status of the software transfer. As the IOP + is very slow when it comes to SW transfers, this can be + used by a separate thread to report status to the user. The + user _should not_ write to this memory location until the ioctl() + has returned. + + RETURNS + + This function returns 0 if no errors occur. If an error occurs, -1 + is returned and errno is set appropriatly: + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(ops->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + +X. ExecSwRemove Message + + SYNOPSIS + + ioctl(fd, I2OSWDEL, struct i2o_sw_xfer *sw); + + struct i2o_sw_xfer + { + u32 iop; /* IOP unit number */ + u8 flags; /* Unused */ + u8 sw_type; /* Software type */ + u32 sw_id; /* Software ID */ + void *buf; /* Unused */ + u32 *swlen; /* Length in bytes of software data */ + u32 *maxfrag; /* Unused */ + u32 *curfrag; /* Unused */ + }; + + DESCRIPTION + + This function uploads software from the IOP identified by sw->iop + and places it in the buffer pointed to by sw->buf. The SwID, SwType + and SwSize fields of the ExecSwDownload message are filed in + with the values of sw->dl_flags, sw->sw_id, and sw->sw_type. The + actual size of the module is written into *(sw->buflen). + + RETURNS + + This function returns 0 if no errors occur. If an error occurs, -1 + is returned and errno is set appropriatly: + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(ops->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + +X. UtilConfigDialog Message + + SYNOPSIS + + ioctl(fd, I2OHTML, struct i2o_html *htquery); + + struct i2o_html + { + u32 iop; /* IOP unit number */ + u32 tid; /* Target device ID */ + u32 page; /* HTML page */ + void *resbuf; /* Buffer for reply HTML page */ + u32 *reslen; /* Length in bytes of reply buffer */ + void *qbuf; /* Pointer to HTTP query string */ + u32 qlen; /* Length in bytes of query string buffer */ + }; + + DESCRIPTION + + This function posts an UtilConfigDialog message to the device identified + by htquery->iop and htquery->tid. The requested HTML page number is + provided by the htquery->page field, and the resultant data is stored + in the buffer pointed to by htquery->resbuf. If there is an HTTP query + string that is to be sent to the device, it should be sent in the buffer + pointed to by htquery->qbuf. If there is no query string, this field + should be set to NULL. The actual size of the reply received is written + into *(htquery->reslen) + + RETURNS + + This function returns 0 if no error occur. If an error occurs, -1 + is returned and J errno is set appropriatly: + + ETIMEDOUT Timeout waiting for reply message + ENOMEM Kernel memory allocation error + ENOBUFS Buffer not large enough. If this occurs, the required + buffer length is written into *(ops->reslen) + EFAULT Invalid user space pointer was passed + ENXIO Invalid IOP number + EIO Unkown error + +XI. Events + + In the process of determining this. Current idea is to have use + the select() interface to allow user apps to periodically poll + the /dev/i2octl device for events. When select() notifies the user + that an event is available, the user would call read() to retrieve + a list of all the events that are pending for the specific device. + +============================================================================= +Revision History +============================================================================= + +Rev 0.1 - 04/01/99 +- Initial revision + +Rev 0.2 - 04/06/99 +- Changed return values to match UNIX ioctl() standard. Only return values + are 0 and -1. All errors are reported through errno. +- Added summary of proposed possible event interfaces + +Rev 0.3 - 04/20/99 +- Changed all ioctls() to use pointers to user data instead of actual data +- Updated error values to match the code + + diff --git a/drivers/i2o/README.lan b/drivers/i2o/README.lan new file mode 100644 index 000000000..1d1ba0f14 --- /dev/null +++ b/drivers/i2o/README.lan @@ -0,0 +1,38 @@ + + Linux I2O LAN OSM + (c) University of Helsinki, Department of Computer Science + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version + 2 of the License, or (at your option) any later version. + +AUTHORS +Auvo Häkkinen, Auvo.Hakkinen@cs.Helsinki.FI +Juha Sievänen, Juha.Sievanen@cs.Helsinki.FI + +CREDITS + + This work was made possible by + +European Committee + Funding for the project + +SysKonnect + Loaning of FDDI cards + +ASUSTeK + I2O motherboard + +STATUS: +o The FDDI part of LAN OSM is working to some extent. +o Only packet per bucket is now supported. + +TO DO: + +LAN: +o Add support for bactches +o Find why big packets flow from I2O box out, but don't want to come in +o Find the bug in i2o_set_multicast_list(), which kills interrupt + handler in i2o_wait_reply() +o Add support for Ethernet, Token Ring, AnyLAN, Fibre Channel diff --git a/drivers/i2o/i2o_block.c b/drivers/i2o/i2o_block.c new file mode 100644 index 000000000..5d543b1cc --- /dev/null +++ b/drivers/i2o/i2o_block.c @@ -0,0 +1,1071 @@ +/* + * I2O block device driver. + * + * (C) Copyright 1999 Red Hat Software + * + * Written by Alan Cox, Building Number Three Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This is an initial test release. Most of the good code was taken + * from the nbd driver by Pavel Machek, who in turn took some of it + * from loop.c. Isn't free software great for reusability 8) + * + * Fixes: + * Steve Ralston: Multiple device handling error fixes, + * Added a queue depth. + */ + +#include <linux/major.h> + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/stat.h> +#include <linux/errno.h> +#include <linux/file.h> +#include <linux/ioctl.h> +#include <linux/i2o.h> +#include <linux/blkdev.h> +#include <linux/malloc.h> +#include <linux/hdreg.h> + +#include <linux/notifier.h> +#include <linux/reboot.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/atomic.h> + +#define MAJOR_NR I2O_MAJOR + +#include <linux/blk.h> + +#define MAX_I2OB 16 + +#define MAX_I2OB_DEPTH 4 + +/* + * Some of these can be made smaller later + */ + +static int i2ob_blksizes[MAX_I2OB<<4]; +static int i2ob_hardsizes[MAX_I2OB<<4]; +static int i2ob_sizes[MAX_I2OB<<4]; +static int i2ob_media_change_flag[MAX_I2OB]; +static u32 i2ob_max_sectors[MAX_I2OB<<4]; + +static int i2ob_context; + +#ifdef __SMP__ +static spinlock_t i2ob_lock = SPIN_LOCK_UNLOCKED; +#endif + +struct i2ob_device +{ + struct i2o_controller *controller; + struct i2o_device *i2odev; + int tid; + int flags; + int refcnt; + struct request *head, *tail; + int done_flag; +}; + +/* + * Each I2O disk is one of these. + */ + +static struct i2ob_device i2ob_dev[MAX_I2OB<<4]; +static int i2ob_devices = 0; +static struct hd_struct i2ob[MAX_I2OB<<4]; +static struct gendisk i2ob_gendisk; /* Declared later */ + +static atomic_t queue_depth; /* For flow control later on */ + +#define DEBUG( s ) +/* #define DEBUG( s ) printk( s ) + */ + +static int i2ob_install_device(struct i2o_controller *, struct i2o_device *, int); +static void i2ob_end_request(struct request *); +static void do_i2ob_request(void); + +/* + * Get a message + */ + +static u32 i2ob_get(struct i2ob_device *dev) +{ + struct i2o_controller *c=dev->controller; + return I2O_POST_READ32(c); +} + +/* + * Turn a Linux block request into an I2O block read/write. + */ + +static int i2ob_send(u32 m, struct i2ob_device *dev, struct request *req, u32 base, int unit) +{ + struct i2o_controller *c = dev->controller; + int tid = dev->tid; + u32 *msg; + u32 *mptr; + u64 offset; + struct buffer_head *bh = req->bh; + static int old_qd = 2; + int count = req->nr_sectors<<9; + + /* + * Build a message + */ + + msg = bus_to_virt(c->mem_offset + m); + + msg[2] = i2ob_context|(unit<<8); + msg[3] = (u32)req; /* 64bit issue again here */ + msg[5] = req->nr_sectors << 9; + + /* This can be optimised later - just want to be sure its right for + starters */ + offset = ((u64)(req->sector+base)) << 9; + msg[6] = offset & 0xFFFFFFFF; + msg[7] = (offset>>32); + mptr=msg+8; + + if(req->cmd == READ) + { + msg[1] = I2O_CMD_BLOCK_READ<<24|HOST_TID<<12|tid; + /* We don't yet do cache/readahead and other magic */ + msg[4] = 1<<16; + while(bh!=NULL) + { + *mptr++ = 0x10000000|(bh->b_size); + *mptr++ = virt_to_bus(bh->b_data); + count -= bh->b_size; + bh = bh->b_reqnext; + } + } + else if(req->cmd == WRITE) + { + msg[1] = I2O_CMD_BLOCK_WRITE<<24|HOST_TID<<12|tid; + msg[4] = 1<<16; + while(bh!=NULL) + { + *mptr++ = 0x14000000|(bh->b_size); + count -= bh->b_size; + *mptr++ = virt_to_bus(bh->b_data); + bh = bh->b_reqnext; + } + } + mptr[-2]|= 0xC0000000; + msg[0] = I2O_MESSAGE_SIZE(mptr-msg) | SGL_OFFSET_8; + + if(req->current_nr_sectors > 8) + printk("Gathered sectors %ld.\n", + req->current_nr_sectors); + + if(count != 0) + { + printk("Request count botched by %d.\n", count); + msg[5] -= count; + } + +// printk("Send for %p\n", req); + + i2o_post_message(c,m); + atomic_inc(&queue_depth); + if(atomic_read(&queue_depth)>old_qd) + { + old_qd=atomic_read(&queue_depth); + printk("Depth now %d.\n", old_qd); + } + return 0; +} + +/* + * Remove a request from the _locked_ request list. We update both the + * list chain and if this is the last item the tail pointer. + */ + +static void i2ob_unhook_request(struct i2ob_device *dev, struct request *req) +{ + struct request **p = &dev->head; + struct request *nt = NULL; + static int crap = 0; + + while(*p!=NULL) + { + if(*p==req) + { + if(dev->tail==req) + dev->tail = nt; + *p=req->next; + return; + } + nt=*p; + p=&(nt->next); + } + if(!crap++) + printk("i2o_block: request queue corrupt!\n"); +} + +/* + * Request completion handler + */ + +static void i2ob_end_request(struct request *req) +{ + /* + * Loop until all of the buffers that are linked + * to this request have been marked updated and + * unlocked. + */ + while (end_that_request_first( req, !req->errors, "i2o block" )); + + /* + * It is now ok to complete the request. + */ + end_that_request_last( req ); +} + + +/* + * OSM reply handler. This gets all the message replies + */ + +static void i2o_block_reply(struct i2o_handler *h, struct i2o_controller *c, struct i2o_message *msg) +{ + struct request *req; + u8 st; + u32 *m = (u32 *)msg; + u8 unit = (m[2]>>8)&0xF0; /* low 4 bits are partition */ + + if(m[0] & (1<<13)) + { + printk("IOP fail.\n"); + printk("From %d To %d Cmd %d.\n", + (m[1]>>12)&0xFFF, + m[1]&0xFFF, + m[1]>>24); + printk("Failure Code %d.\n", m[4]>>24); + if(m[4]&(1<<16)) + printk("Format error.\n"); + if(m[4]&(1<<17)) + printk("Path error.\n"); + if(m[4]&(1<<18)) + printk("Path State.\n"); + if(m[4]&(1<<18)) + printk("Congestion.\n"); + + m=(u32 *)bus_to_virt(m[7]); + printk("Failing message is %p.\n", m); + + /* We need to up the request failure count here and maybe + abort it */ + req=(struct request *)m[3]; + /* Now flush the message by making it a NOP */ + m[0]&=0x00FFFFFF; + m[0]|=(I2O_CMD_UTIL_NOP)<<24; + i2o_post_message(c,virt_to_bus(m)); + + } + else + { + if(m[2]&0x80000000) + { + int * ptr = (int *)m[3]; + if(m[4]>>24) + *ptr = -1; + else + *ptr = 1; + return; + } + /* + * Lets see what is cooking. We stuffed the + * request in the context. + */ + + req=(struct request *)m[3]; + st=m[4]>>24; + + if(st!=0) + { + printk(KERN_ERR "i2ob: error %08X\n", m[4]); + /* + * Now error out the request block + */ + req->errors++; + } + } + /* + * Dequeue the request. + */ + + spin_lock(&io_request_lock); + spin_lock(&i2ob_lock); + i2ob_unhook_request(&i2ob_dev[unit], req); + i2ob_end_request(req); + + /* + * We may be able to do more I/O + */ + + atomic_dec(&queue_depth); + do_i2ob_request(); + spin_unlock(&i2ob_lock); + spin_unlock(&io_request_lock); +} + +static struct i2o_handler i2o_block_handler = +{ + i2o_block_reply, + "I2O Block OSM", + 0 +}; + + +/* + * Flush all pending requests as errors. Must call with the queue + * locked. + */ + +#if 0 +static void i2ob_clear_queue(struct i2ob_device *dev) +{ + struct request *req; + + while (1) { + req = dev->tail; + if (!req) + return; + req->errors++; + i2ob_end_request(req); + + if (dev->tail == dev->head) + dev->head = NULL; + dev->tail = dev->tail->next; + } +} +#endif + +/* + * The I2O block driver is listed as one of those that pulls the + * front entry off the queue before processing it. This is important + * to remember here. If we drop the io lock then CURRENT will change + * on us. We must unlink CURRENT in this routine before we return, if + * we use it. + */ + +static void do_i2ob_request(void) +{ + struct request *req; + int unit; + struct i2ob_device *dev; + u32 m; + + while (CURRENT) { + /* + * On an IRQ completion if there is an inactive + * request on the queue head it means it isnt yet + * ready to dispatch. + */ + if(CURRENT->rq_status == RQ_INACTIVE) + return; + + /* + * Queue depths probably belong with some kind of + * generic IOP commit control. Certainly its not right + * its global! + */ + if(atomic_read(&queue_depth)>=MAX_I2OB_DEPTH) + break; + + req = CURRENT; + unit = MINOR(req->rq_dev); + dev = &i2ob_dev[(unit&0xF0)]; + /* Get a message */ + m = i2ob_get(dev); + /* No messages -> punt + FIXME: if we have no messages, and there are no messages + we deadlock now. Need a timer/callback ?? */ + if(m==0xFFFFFFFF) + { + printk("i2ob: no messages!\n"); + break; + } + req->errors = 0; + CURRENT = CURRENT->next; + req->next = NULL; + + if (dev->head == NULL) { + dev->head = req; + dev->tail = req; + } else { + dev->tail->next = req; + dev->tail = req; + } + i2ob_send(m, dev, req, i2ob[unit].start_sect, (unit&0xF0)); + } +} + +static void i2ob_request(void) +{ + unsigned long flags; + spin_lock_irqsave(&i2ob_lock, flags); + do_i2ob_request(); + spin_unlock_irqrestore(&i2ob_lock, flags); +} + +/* + * SCSI-CAM for ioctl geometry mapping + * Duplicated with SCSI - this should be moved into somewhere common + * perhaps genhd ? + */ + +static void i2o_block_biosparam( + unsigned long capacity, + unsigned short *cyls, + unsigned char *hds, + unsigned char *secs) +{ + unsigned long heads, sectors, cylinders, temp; + + cylinders = 1024L; /* Set number of cylinders to max */ + sectors = 62L; /* Maximize sectors per track */ + + temp = cylinders * sectors; /* Compute divisor for heads */ + heads = capacity / temp; /* Compute value for number of heads */ + if (capacity % temp) { /* If no remainder, done! */ + heads++; /* Else, increment number of heads */ + temp = cylinders * heads; /* Compute divisor for sectors */ + sectors = capacity / temp; /* Compute value for sectors per + track */ + if (capacity % temp) { /* If no remainder, done! */ + sectors++; /* Else, increment number of sectors */ + temp = heads * sectors; /* Compute divisor for cylinders */ + cylinders = capacity / temp;/* Compute number of cylinders */ + } + } + /* if something went wrong, then apparently we have to return + a geometry with more than 1024 cylinders */ + if (cylinders == 0 || heads > 255 || sectors > 63 || cylinders >1023) + { + unsigned long temp_cyl; + + heads = 64; + sectors = 32; + temp_cyl = capacity / (heads * sectors); + if (temp_cyl > 1024) + { + heads = 255; + sectors = 63; + } + cylinders = capacity / (heads * sectors); + } + *cyls = (unsigned int) cylinders; /* Stuff return values */ + *secs = (unsigned int) sectors; + *hds = (unsigned int) heads; +} + +/* + * Rescan the partition tables + */ + +static int do_i2ob_revalidate(kdev_t dev, int maxu) +{ + int minor=MINOR(dev); + int i; + + minor&=0xF0; + + i2ob_dev[minor].refcnt++; + if(i2ob_dev[minor].refcnt>maxu+1) + { + i2ob_dev[minor].refcnt--; + return -EBUSY; + } + + for( i = 15; i>=0 ; i--) + { + int m = minor+i; + kdev_t d = MKDEV(MAJOR_NR, m); + struct super_block *sb = get_super(d); + + sync_dev(d); + if(sb) + invalidate_inodes(sb); + invalidate_buffers(d); + i2ob_gendisk.part[m].start_sect = 0; + i2ob_gendisk.part[m].nr_sects = 0; + } + + /* + * Do a physical check and then reconfigure + */ + + i2ob_install_device(i2ob_dev[minor].controller, i2ob_dev[minor].i2odev, + minor); + i2ob_dev[minor].refcnt--; + return 0; +} + +/* + * Issue device specific ioctl calls. + */ + +static int i2ob_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct i2ob_device *dev; + int minor; + + /* Anyone capable of this syscall can do *real bad* things */ + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (!inode) + return -EINVAL; + minor = MINOR(inode->i_rdev); + if (minor >= (MAX_I2OB<<4)) + return -ENODEV; + + dev = &i2ob_dev[minor]; + switch (cmd) { + case BLKRASET: + if(!capable(CAP_SYS_ADMIN)) return -EACCES; + if(arg > 0xff) return -EINVAL; + read_ahead[MAJOR(inode->i_rdev)] = arg; + return 0; + + case BLKRAGET: + if (!arg) return -EINVAL; + return put_user(read_ahead[MAJOR(inode->i_rdev)], + (long *) arg); + case BLKGETSIZE: + return put_user(i2ob[minor].nr_sects, (long *) arg); + + case BLKFLSBUF: + if(!capable(CAP_SYS_ADMIN)) + return -EACCES; + + fsync_dev(inode->i_rdev); + invalidate_buffers(inode->i_rdev); + return 0; + + case HDIO_GETGEO: + { + struct hd_geometry g; + int u=minor&0xF0; + i2o_block_biosparam(i2ob_sizes[u]<<1, + &g.cylinders, &g.heads, &g.sectors); + g.start = i2ob[minor].start_sect; + return copy_to_user((void *)arg,&g, sizeof(g))?-EFAULT:0; + } + + case BLKRRPART: + if(!capable(CAP_SYS_ADMIN)) + return -EACCES; + return do_i2ob_revalidate(inode->i_rdev,1); + + default: + return blk_ioctl(inode->i_rdev, cmd, arg); + } +} + +/* + * Issue UTIL_CLAIM messages + */ + +static int i2ob_claim_device(struct i2ob_device *dev, int onoff) +{ + return i2o_issue_claim(dev->controller, dev->tid, i2ob_context, onoff, &dev->done_flag); +} + +/* + * Close the block device down + */ + +static int i2ob_release(struct inode *inode, struct file *file) +{ + struct i2ob_device *dev; + int minor; + + minor = MINOR(inode->i_rdev); + if (minor >= (MAX_I2OB<<4)) + return -ENODEV; + sync_dev(inode->i_rdev); + dev = &i2ob_dev[(minor&0xF0)]; + if (dev->refcnt <= 0) + printk(KERN_ALERT "i2ob_release: refcount(%d) <= 0\n", dev->refcnt); + dev->refcnt--; + if(dev->refcnt==0) + { + /* + * Flush the onboard cache on unmount + */ + u32 msg[5]; + int *query_done = &dev->done_flag; + msg[0] = FIVE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_BLOCK_CFLUSH<<24|HOST_TID<<12|dev->tid; + msg[2] = i2ob_context|0x80000000; + msg[3] = (u32)query_done; + msg[4] = 60<<16; + i2o_post_wait(dev->controller, dev->tid, msg, 20, query_done,2); + /* + * Unlock the media + */ + msg[0] = FIVE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_BLOCK_MUNLOCK<<24|HOST_TID<<12|dev->tid; + msg[2] = i2ob_context|0x80000000; + msg[3] = (u32)query_done; + msg[4] = -1; + i2o_post_wait(dev->controller, dev->tid, msg, 20, query_done,2); + + /* + * Now unclaim the device. + */ + if (i2ob_claim_device(dev, 0)<0) + printk(KERN_ERR "i2ob_release: controller rejected unclaim.\n"); + + } + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * Open the block device. + */ + +static int i2ob_open(struct inode *inode, struct file *file) +{ + int minor; + struct i2ob_device *dev; + + if (!inode) + return -EINVAL; + minor = MINOR(inode->i_rdev); + if (minor >= MAX_I2OB<<4) + return -ENODEV; + dev=&i2ob_dev[(minor&0xF0)]; + + if(dev->refcnt++==0) + { + u32 msg[6]; + int *query_done; + + + if(i2ob_claim_device(dev, 1)<0) + { + dev->refcnt--; + return -EBUSY; + } + + query_done = &dev->done_flag; + /* + * Mount the media if needed. Note that we don't use + * the lock bit. Since we have to issue a lock if it + * refuses a mount (quite possible) then we might as + * well just send two messages out. + */ + msg[0] = FIVE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_BLOCK_MMOUNT<<24|HOST_TID<<12|dev->tid; + msg[2] = i2ob_context|0x80000000; + msg[3] = (u32)query_done; + msg[4] = -1; + msg[5] = 0; + i2o_post_wait(dev->controller, dev->tid, msg, 24, query_done,2); + /* + * Lock the media + */ + msg[0] = FIVE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_BLOCK_MLOCK<<24|HOST_TID<<12|dev->tid; + msg[2] = i2ob_context|0x80000000; + msg[3] = (u32)query_done; + msg[4] = -1; + i2o_post_wait(dev->controller, dev->tid, msg, 20, query_done,2); + } + MOD_INC_USE_COUNT; + return 0; +} + +/* + * Issue a device query + */ + +static int i2ob_query_device(struct i2ob_device *dev, int table, + int field, void *buf, int buflen) +{ + return i2o_query_scalar(dev->controller, dev->tid, i2ob_context, + table, field, buf, buflen, &dev->done_flag); +} + + +/* + * Install the I2O block device we found. + */ + +static int i2ob_install_device(struct i2o_controller *c, struct i2o_device *d, int unit) +{ + u64 size; + u32 blocksize; + u32 limit; + u8 type; + u32 flags, status; + struct i2ob_device *dev=&i2ob_dev[unit]; + int i; + + /* + * Ask for the current media data. If that isn't supported + * then we ask for the device capacity data + */ + + if(i2ob_query_device(dev, 0x0004, 1, &blocksize, 4) != 0 + || i2ob_query_device(dev, 0x0004, 0, &size, 8) !=0 ) + { + i2ob_query_device(dev, 0x0000, 3, &blocksize, 4); + i2ob_query_device(dev, 0x0000, 4, &size, 8); + } + + i2ob_query_device(dev, 0x0000, 5, &flags, 4); + i2ob_query_device(dev, 0x0000, 6, &status, 4); + i2ob_sizes[unit] = (int)(size>>10); + i2ob_hardsizes[unit] = blocksize; + i2ob_gendisk.part[unit].nr_sects = i2ob_sizes[unit]; + + /* Setting this higher than 1024 breaks the symbios for some reason */ + + limit=4096; /* 8 deep scatter gather */ + + printk("Byte limit is %d.\n", limit); + + for(i=unit;i<=unit+15;i++) + i2ob_max_sectors[i]=(limit>>9); + + i2ob[unit].nr_sects = (int)(size>>9); + + i2ob_query_device(dev, 0x0000, 0, &type, 1); + + sprintf(d->dev_name, "%s%c", i2ob_gendisk.major_name, 'a' + (unit>>4)); + + printk("%s: ", d->dev_name); + if(status&(1<<10)) + printk("RAID "); + switch(type) + { + case 0: printk("Disk Storage");break; + case 4: printk("WORM");break; + case 5: printk("CD-ROM");break; + case 7: printk("Optical device");break; + default: + printk("Type %d", type); + } + if(((flags & (1<<3)) && !(status & (1<<3))) || + ((flags & (1<<4)) && !(status & (1<<4)))) + { + printk(" Not loaded.\n"); + return 0; + } + printk(" %dMb, %d byte sectors", + (int)(size>>20), blocksize); + if(status&(1<<0)) + { + u32 cachesize; + i2ob_query_device(dev, 0x0003, 0, &cachesize, 4); + cachesize>>=10; + if(cachesize>4095) + printk(", %dMb cache", cachesize>>10); + else + printk(", %dKb cache", cachesize); + } + printk(".\n"); + printk("%s: Maximum sectors/read set to %d.\n", + d->dev_name, i2ob_max_sectors[unit]); + resetup_one_dev(&i2ob_gendisk, unit>>4); + return 0; +} + +static void i2ob_probe(void) +{ + int i; + int unit = 0; + int warned = 0; + + for(i=0; i< MAX_I2O_CONTROLLERS; i++) + { + struct i2o_controller *c=i2o_find_controller(i); + struct i2o_device *d; + + if(c==NULL) + continue; + + for(d=c->devices;d!=NULL;d=d->next) + { + if(d->class!=I2O_CLASS_RANDOM_BLOCK_STORAGE) + continue; + + if(unit<MAX_I2OB<<4) + { + /* + * Get the device and fill in the + * Tid and controller. + */ + struct i2ob_device *dev=&i2ob_dev[unit]; + dev->i2odev = d; + dev->controller = c; + dev->tid = d->id; + + /* + * Insure the device can be claimed + * before installing it. + */ + if(i2ob_claim_device(dev, 1)==0) + { + printk(KERN_INFO "Claimed Dev %x Tid %d Unit %d\n",dev,dev->tid,unit); + i2ob_install_device(c,d,unit); + unit+=16; + + /* + * Now that the device has been + * installed, unclaim it so that + * it can be claimed by either + * the block or scsi driver. + */ + if (i2ob_claim_device(dev, 0)<0) + printk(KERN_INFO "Could not unclaim Dev %x Tid %d\n",dev,dev->tid); + + } + else + printk(KERN_INFO "TID %d not claimed\n",dev->tid); + } + else + { + if(!warned++) + printk("i2o_block: too many controllers, registering only %d.\n", unit>>4); + } + } + } + i2ob_devices = unit; +} + +/* + * Have we seen a media change ? + */ + +static int i2ob_media_change(kdev_t dev) +{ + int i=MINOR(dev); + i>>=4; + if(i2ob_media_change_flag[i]) + { + i2ob_media_change_flag[i]=0; + return 1; + } + return 0; +} + +static int i2ob_revalidate(kdev_t dev) +{ + return do_i2ob_revalidate(dev, 0); +} + +static int i2ob_reboot_event(struct notifier_block *n, unsigned long code, void *p) +{ + int i; + + if(code != SYS_RESTART && code != SYS_HALT && code != SYS_POWER_OFF) + return NOTIFY_DONE; + for(i=0;i<MAX_I2OB;i++) + { + struct i2ob_device *dev=&i2ob_dev[(i<<4)]; + + if(dev->refcnt!=0) + { + /* + * Flush the onboard cache on power down + * also unlock the media + */ + u32 msg[5]; + int *query_done = &dev->done_flag; + msg[0] = FIVE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_BLOCK_CFLUSH<<24|HOST_TID<<12|dev->tid; + msg[2] = i2ob_context|0x80000000; + msg[3] = (u32)query_done; + msg[4] = 60<<16; + i2o_post_wait(dev->controller, dev->tid, msg, 20, query_done,2); + /* + * Unlock the media + */ + msg[0] = FIVE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_BLOCK_MUNLOCK<<24|HOST_TID<<12|dev->tid; + msg[2] = i2ob_context|0x80000000; + msg[3] = (u32)query_done; + msg[4] = -1; + i2o_post_wait(dev->controller, dev->tid, msg, 20, query_done,2); + } + } + return NOTIFY_DONE; +} + +struct notifier_block i2ob_reboot_notifier = +{ + i2ob_reboot_event, + NULL, + 0 +}; + +static struct file_operations i2ob_fops = +{ + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + i2ob_ioctl, /* ioctl */ + NULL, /* mmap */ + i2ob_open, /* open */ + NULL, /* flush */ + i2ob_release, /* release */ + NULL, /* fsync */ + NULL, /* fasync */ + i2ob_media_change, /* Media Change */ + i2ob_revalidate, /* Revalidate */ + NULL /* File locks */ +}; + +/* + * Partitioning + */ + +static void i2ob_geninit(struct gendisk *gd) +{ +} + +static struct gendisk i2ob_gendisk = +{ + MAJOR_NR, + "i2ohd", + 4, + 1<<4, + MAX_I2OB, + i2ob_geninit, + i2ob, + i2ob_sizes, + 0, + NULL, + NULL +}; + +/* + * And here should be modules and kernel interface + * (Just smiley confuses emacs :-) + */ + +#ifdef MODULE +#define i2ob_init init_module +#endif + +int i2ob_init(void) +{ + int i; + + printk("I2O block device OSM v0.06. (C) 1999 Red Hat Software.\n"); + + /* + * Register the block device interfaces + */ + + if (register_blkdev(MAJOR_NR, "i2o_block", &i2ob_fops)) { + printk("Unable to get major number %d for i2o_block\n", + MAJOR_NR); + return -EIO; + } +#ifdef MODULE + printk("i2o_block: registered device at major %d\n", MAJOR_NR); +#endif + + /* + * Now fill in the boiler plate + */ + + blksize_size[MAJOR_NR] = i2ob_blksizes; + hardsect_size[MAJOR_NR] = i2ob_hardsizes; + blk_size[MAJOR_NR] = i2ob_sizes; + max_sectors[MAJOR_NR] = i2ob_max_sectors; + + blk_dev[MAJOR_NR].request_fn = i2ob_request; + for (i = 0; i < MAX_I2OB << 4; i++) { + i2ob_dev[i].refcnt = 0; + i2ob_dev[i].flags = 0; + i2ob_dev[i].controller = NULL; + i2ob_dev[i].i2odev = NULL; + i2ob_dev[i].tid = 0; + i2ob_dev[i].head = NULL; + i2ob_dev[i].tail = NULL; + i2ob_blksizes[i] = 1024; + i2ob_max_sectors[i] = 2; + } + + /* + * Register the OSM handler as we will need this to probe for + * drives, geometry and other goodies. + */ + + if(i2o_install_handler(&i2o_block_handler)<0) + { + unregister_blkdev(MAJOR_NR, "i2o_block"); + printk(KERN_ERR "i2o_block: unable to register OSM.\n"); + return -EINVAL; + } + i2ob_context = i2o_block_handler.context; + + /* + * Finally see what is actually plugged in to our controllers + */ + + i2ob_probe(); + + register_reboot_notifier(&i2ob_reboot_notifier); + return 0; +} + +#ifdef MODULE + +EXPORT_NO_SYMBOLS; +MODULE_AUTHOR("Red Hat Software"); +MODULE_DESCRIPTION("I2O Block Device OSM"); + +void cleanup_module(void) +{ + struct gendisk **gdp; + + unregister_reboot_notifier(&i2ob_reboot_notifier); + + /* + * Flush the OSM + */ + + i2o_remove_handler(&i2o_block_handler); + + /* + * Return the block device + */ + if (unregister_blkdev(MAJOR_NR, "i2o_block") != 0) + printk("i2o_block: cleanup_module failed\n"); + else + printk("i2o_block: module cleaned up.\n"); + + /* + * Why isnt register/unregister gendisk in the kernel ??? + */ + + for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next)) + if (*gdp == &i2ob_gendisk) + break; + +} +#endif diff --git a/drivers/i2o/i2o_config.c b/drivers/i2o/i2o_config.c new file mode 100644 index 000000000..c3c644883 --- /dev/null +++ b/drivers/i2o/i2o_config.c @@ -0,0 +1,613 @@ +/* + * I2O Configuration Interface Driver + * + * (C) Copyright 1999 Red Hat Software + * + * Written by Alan Cox, Building Number Three Ltd + * + * Modified 04/20/199 by Deepak Saxena + * - Added basic ioctl() support + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/i2o.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/malloc.h> +#include <linux/miscdevice.h> +#include <linux/kernel.h> +#include <linux/mm.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/spinlock.h> + +#include "i2o_proc.h" + +static int i2o_cfg_token = 0; +static int i2o_cfg_context = -1; +static void *page_buf; +static void *i2o_buffer; +static int i2o_ready; +static int i2o_pagelen; +static int i2o_error; +static int cfg_inuse; +static int i2o_eof; +static spinlock_t i2o_config_lock = SPIN_LOCK_UNLOCKED; +struct wait_queue *i2o_wait_queue; + +static int ioctl_getiops(unsigned long); +static int ioctl_gethrt(unsigned long); +static int ioctl_getlct(unsigned long); +static int ioctl_parms(unsigned long, unsigned int); +static int ioctl_html(unsigned long); +static int ioctl_swdl(unsigned long); +static int ioctl_swul(unsigned long); +static int ioctl_swdel(unsigned long); + +/* + * This is the callback for any message we have posted. The message itself + * will be returned to the message pool when we return from the IRQ + * + * This runs in irq context so be short and sweet. + */ +static void i2o_cfg_reply(struct i2o_handler *h, struct i2o_controller *c, struct i2o_message *m) +{ + i2o_cfg_token = I2O_POST_WAIT_OK; + + return; +} + +/* + * Each of these describes an i2o message handler. They are + * multiplexed by the i2o_core code + */ + +struct i2o_handler cfg_handler= +{ + i2o_cfg_reply, + "Configuration", + 0 +}; + +static long long cfg_llseek(struct file *file, long long offset, int origin) +{ + return -ESPIPE; +} + +/* i2ocontroller/i2odevice/page/?data */ + +static ssize_t cfg_write(struct file *file, const char *buf, size_t count, loff_t *ppos) +{ + printk(KERN_INFO "i2o_config write not yet supported\n"); + + return 0; +} + +/* To be written for event management support */ +static ssize_t cfg_read(struct file *file, char *buf, size_t count, loff_t *ptr) +{ + return 0; +} + +static int cfg_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + + /* Only 1 token, so lock... */ + spin_lock(&i2o_config_lock); + + switch(cmd) + { + case I2OGETIOPS: + ret = ioctl_getiops(arg); + break; + + case I2OHRTGET: + ret = ioctl_gethrt(arg); + break; + + case I2OLCTGET: + ret = ioctl_getlct(arg); + break; + + case I2OPARMSET: + ret = ioctl_parms(arg, I2OPARMSET); + break; + + case I2OPARMGET: + ret = ioctl_parms(arg, I2OPARMGET); + break; + + case I2OSWDL: + ret = ioctl_swdl(arg); + break; + + case I2OSWUL: + ret = ioctl_swul(arg); + break; + + case I2OSWDEL: + ret = ioctl_swdel(arg); + break; + + case I2OHTML: + ret = ioctl_html(arg); + break; + + default: + ret = -EINVAL; + } + + spin_unlock(&i2o_config_lock); + return ret; +} + +int ioctl_getiops(unsigned long arg) +{ + u8 *user_iop_table = (u8*)arg; + struct i2o_controller *c = NULL; + int i; + u8 foo[MAX_I2O_CONTROLLERS]; + + if(!access_ok(VERIFY_WRITE, user_iop_table, MAX_I2O_CONTROLLERS)) + return -EFAULT; + + for(i = 0; i < MAX_I2O_CONTROLLERS; i++) + { + c = i2o_find_controller(i); + if(c) + { + printk(KERN_INFO "ioctl: iop%d found\n", i); + foo[i] = 1; + i2o_unlock_controller(c); + } + else + { + printk(KERN_INFO "ioctl: iop%d not found\n", i); + foo[i] = 0; + } + } + + __copy_to_user(user_iop_table, foo, MAX_I2O_CONTROLLERS); + return 0; +} + +int ioctl_gethrt(unsigned long arg) +{ + struct i2o_controller *c; + struct i2o_cmd_hrtlct *cmd = (struct i2o_cmd_hrtlct*)arg; + struct i2o_cmd_hrtlct kcmd; + pi2o_hrt hrt; + u32 msg[6]; + u32 *workspace; + int len; + int token; + u32 reslen; + int ret = 0; + + if(copy_from_user(&kcmd, cmd, sizeof(struct i2o_cmd_hrtlct))) + return -EFAULT; + + if(get_user(reslen, kcmd.reslen) < 0) + return -EFAULT; + + if(kcmd.resbuf == NULL) + return -EFAULT; + + c = i2o_find_controller(kcmd.iop); + if(!c) + return -ENXIO; + + workspace = kmalloc(8192, GFP_KERNEL); + hrt = (pi2o_hrt)workspace; + if(workspace==NULL) + return -ENOMEM; + + memset(workspace, 0, 8192); + + msg[0]= SIX_WORD_MSG_SIZE| SGL_OFFSET_4; + msg[1]= I2O_CMD_HRT_GET<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2]= (u32)cfg_handler.context; + msg[3]= 0; + msg[4]= (0xD0000000 | 8192); + msg[5]= virt_to_phys(workspace); + + token = i2o_post_wait(c, ADAPTER_TID, msg, 6*4, &i2o_cfg_token,2); + if(token == I2O_POST_WAIT_TIMEOUT) + { + kfree(workspace); + i2o_unlock_controller(c); + return -ETIMEDOUT; + } + i2o_unlock_controller(c); + + len = 8 + ((hrt->entry_len * hrt->num_entries) << 2); + /* We did a get user...so assuming mem is ok...is this bad? */ + put_user(len, kcmd.reslen); + if(len > reslen) + ret = -ENOBUFS; + if(copy_to_user(kcmd.resbuf, (void*)hrt, len)) + ret = -EINVAL; + + kfree(workspace); + return ret; +} + +int ioctl_getlct(unsigned long arg) +{ + struct i2o_controller *c; + struct i2o_cmd_hrtlct *cmd = (struct i2o_cmd_hrtlct*)arg; + struct i2o_cmd_hrtlct kcmd; + pi2o_lct lct; + u32 msg[9]; + u32 *workspace; + int len; + int token; + int ret = 0; + u32 reslen; + + if(copy_from_user(&kcmd, cmd, sizeof(struct i2o_cmd_hrtlct))) + return -EFAULT; + + if(get_user(reslen, kcmd.reslen) < 0) + return -EFAULT; + + if(kcmd.resbuf == NULL) + return -EFAULT; + + c = i2o_find_controller(kcmd.iop); + if(!c) + return -ENXIO; + + workspace = kmalloc(8192, GFP_KERNEL); + lct = (pi2o_lct)workspace; + if(workspace==NULL) + return -ENOMEM; + + memset(workspace, 0, 8192); + + msg[0]= EIGHT_WORD_MSG_SIZE | SGL_OFFSET_6; + msg[1]= I2O_CMD_LCT_NOTIFY<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2]= (u32)cfg_handler.context; + msg[3]= 0; + msg[4]= 0xFFFFFFFF; + msg[5]= 0; + msg[6]= (0xD0000000 | 8192); + msg[7]= virt_to_phys(workspace); + + token = i2o_post_wait(c, ADAPTER_TID, msg, 8*4, &i2o_cfg_token,2); + if(token == I2O_POST_WAIT_TIMEOUT) + { + kfree(workspace); + i2o_unlock_controller(c); + return -ETIMEDOUT; + } + i2o_unlock_controller(c); + + len = (unsigned int)lct->table_size << 2; + put_user(len, kcmd.reslen); + if(len > reslen) + ret = -ENOBUFS; + else if(copy_to_user(kcmd.resbuf, (void*)lct, len)) + ret = -EINVAL; + + kfree(workspace); + return ret; +} + +static int ioctl_parms(unsigned long arg, unsigned int type) +{ + int ret = 0; + struct i2o_controller *c; + struct i2o_cmd_psetget *cmd = (struct i2o_cmd_psetget*)arg; + struct i2o_cmd_psetget kcmd; + u32 msg[9]; + u32 reslen; + int token; + u8 *ops; + u8 *res; + u16 *res16; + u32 *res32; + u16 count; + int len; + int i,j; + + u32 i2o_cmd = (type == I2OPARMGET ? + I2O_CMD_UTIL_PARAMS_GET : + I2O_CMD_UTIL_PARAMS_SET); + + if(copy_from_user(&kcmd, cmd, sizeof(struct i2o_cmd_psetget))) + return -EFAULT; + + if(get_user(reslen, kcmd.reslen)) + return -EFAULT; + + c = i2o_find_controller(kcmd.iop); + if(!c) + return -ENXIO; + + ops = (u8*)kmalloc(kcmd.oplen, GFP_KERNEL); + if(!ops) + return -ENOMEM; + + if(copy_from_user(ops, kcmd.opbuf, kcmd.oplen)) + { + kfree(ops); + return -EFAULT; + } + + /* + * It's possible to have a _very_ large table + * and that the user asks for all of it at once... + */ + res = (u8*)kmalloc(65536, GFP_KERNEL); + if(!res) + { + kfree(ops); + return -ENOMEM; + } + + res16 = (u16*)res; + + msg[0]=NINE_WORD_MSG_SIZE|SGL_OFFSET_5; + msg[1]=i2o_cmd<<24|HOST_TID<<12|cmd->tid; + msg[2]=(u32)cfg_handler.context; + msg[3]=0; + msg[4]=0; + msg[5]=0x54000000|kcmd.oplen; + msg[6]=virt_to_bus(ops); + msg[7]=0xD0000000|(65536); + msg[8]=virt_to_bus(res); + + /* + * Parm set sometimes takes a little while for some reason + */ + token = i2o_post_wait(c, kcmd.tid, msg, 9*4, &i2o_cfg_token,10); + if(token == I2O_POST_WAIT_TIMEOUT) + { + kfree(ops); + kfree(res); + return -ETIMEDOUT; + } + + kfree(ops); + + /* + * Determine required size...there's got to be a quicker way? + * Dump data to syslog for debugging failures + */ + count = res16[0]; + printk(KERN_INFO "%0#6x\n%0#6x\n", res16[0], res16[1]); + len = 4; + res16 += 2; + for(i = 0; i < count; i++ ) + { + len += res16[0] << 2; /* BlockSize field in ResultBlock */ + res32 = (u32*)res16; + for(j = 0; j < res16[0]; j++) + printk(KERN_INFO "%0#10x\n", res32[j]); + res16 += res16[0] << 1; /* Shift to next block */ + } + + put_user(len, kcmd.reslen); + if(len > reslen) + ret = -ENOBUFS; + else if(copy_to_user(cmd->resbuf, res, len)) + ret = -EFAULT; + + kfree(res); + + return ret; +} + +int ioctl_html(unsigned long arg) +{ + struct i2o_html *cmd = (struct i2o_html*)arg; + struct i2o_html kcmd; + struct i2o_controller *c; + u8 *res = NULL; + void *query = NULL; + int ret = 0; + int token; + u32 len; + u32 reslen; + u32 msg[MSG_FRAME_SIZE/4]; + + if(copy_from_user(&kcmd, cmd, sizeof(struct i2o_html))) + { + printk(KERN_INFO "i2o_config: can't copy html cmd\n"); + return -EFAULT; + } + + if(get_user(reslen, kcmd.reslen) < 0) + { + printk(KERN_INFO "i2o_config: can't copy html reslen\n"); + return -EFAULT; + } + + if(!kcmd.resbuf) + { + printk(KERN_INFO "i2o_config: NULL html buffer\n"); + return -EFAULT; + } + + c = i2o_find_controller(kcmd.iop); + if(!c) + return -ENXIO; + + if(kcmd.qlen) /* Check for post data */ + { + query = kmalloc(kcmd.qlen, GFP_KERNEL); + if(!query) + return -ENOMEM; + if(copy_from_user(query, kcmd.qbuf, kcmd.qlen)) + { + printk(KERN_INFO "i2o_config: could not get query\n"); + kfree(query); + return -EFAULT; + } + } + + res = kmalloc(4096, GFP_KERNEL); + if(!res) + return -ENOMEM; + + msg[1] = (I2O_CMD_UTIL_CONFIG_DIALOG << 24)|HOST_TID<<12|kcmd.tid; + msg[2] = i2o_cfg_context; + msg[3] = 0; + msg[4] = kcmd.page; + msg[5] = 0xD0000000|4096; + msg[6] = virt_to_bus(res); + if(!kcmd.qlen) /* Check for post data */ + msg[0] = SEVEN_WORD_MSG_SIZE|SGL_OFFSET_5; + else + { + msg[0] = NINE_WORD_MSG_SIZE|SGL_OFFSET_5; + msg[5] = 0x50000000|4096; + msg[7] = 0xD4000000|(kcmd.qlen); + msg[8] = virt_to_phys(query); + } + + token = i2o_post_wait(c, cmd->tid, msg, 9*4, &i2o_cfg_token, 10); + if(token == I2O_POST_WAIT_TIMEOUT) + { + kfree(res); + if(kcmd.qlen) kfree(query); + + return -ETIMEDOUT; + } + + len = strnlen(res, 8192); + put_user(len, kcmd.reslen); + if(len > reslen) + ret = -ENOMEM; + if(copy_to_user(kcmd.resbuf, res, len)) + ret = -EFAULT; + + kfree(res); + if(kcmd.qlen) + kfree(query); + + return ret; +} + +/* To be written */ +int ioctl_swdl(unsigned long arg) +{ + return -ENOSYS; +} + +/* To be written */ +int ioctl_swul(unsigned long arg) +{ + return -EINVAL; +} + +/* To be written */ +int ioctl_swdel(unsigned long arg) +{ + return 0; +} + +static int cfg_open(struct inode *inode, struct file *file) +{ + /* + * Should support multiple management users + */ + MOD_INC_USE_COUNT; + return 0; +} + +static int cfg_release(struct inode *inode, struct file *file) +{ + MOD_DEC_USE_COUNT; + return 0; +} + + +static struct file_operations config_fops = +{ + cfg_llseek, + cfg_read, + cfg_write, + NULL, + NULL /*cfg_poll*/, + cfg_ioctl, + NULL, /* No mmap */ + cfg_open, + NULL, /* No flush */ + cfg_release +}; + +static struct miscdevice i2o_miscdev = { + I2O_MINOR, + "i2octl", + &config_fops +}; + +#ifdef MODULE +int init_module(void) +#else +int i2o_config_init(void) +#endif +{ + printk(KERN_INFO "i2o configuration manager v 0.02\n"); + + if((page_buf = kmalloc(4096, GFP_KERNEL))==NULL) + { + printk(KERN_ERR "i2o_config: no memory for page buffer.\n"); + return -ENOBUFS; + } + if(misc_register(&i2o_miscdev)==-1) + { + printk(KERN_ERR "i2o_config: can't register device.\n"); + kfree(page_buf); + return -EBUSY; + } + /* + * Install our handler + */ + if(i2o_install_handler(&cfg_handler)<0) + { + kfree(page_buf); + printk(KERN_ERR "i2o_config: handler register failed.\n"); + misc_deregister(&i2o_miscdev); + return -EBUSY; + } + /* + * The low 16bits of the transaction context must match this + * for everything we post. Otherwise someone else gets our mail + */ + i2o_cfg_context = cfg_handler.context; + return 0; +} + +#ifdef MODULE + +void cleanup_module(void) +{ + misc_deregister(&i2o_miscdev); + + if(page_buf) + kfree(page_buf); + if(i2o_cfg_context != -1) + i2o_remove_handler(&cfg_handler); + if(i2o_buffer) + kfree(i2o_buffer); +} + +EXPORT_NO_SYMBOLS; +MODULE_AUTHOR("Red Hat Software"); +MODULE_DESCRIPTION("I2O Configuration"); + +#endif diff --git a/drivers/i2o/i2o_core.c b/drivers/i2o/i2o_core.c new file mode 100644 index 000000000..3a3f1fe94 --- /dev/null +++ b/drivers/i2o/i2o_core.c @@ -0,0 +1,2053 @@ +/* + * Core I2O structure managment + * + * (C) Copyright 1999 Red Hat Software + * + * Written by Alan Cox, Building Number Three Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * A lot of the I2O message side code from this is taken from the + * Red Creek RCPCI45 adapter driver by Red Creek Communications + * + * Some fixes and cleanup by Philipp Rumpf + * + * Additional fixes by Juha Sievänen <Juha.Sievanen@cs.Helsinki.FI> + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/i2o.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/malloc.h> + +#include <asm/io.h> +#include <asm/spinlock.h> + +#include "i2o_lan.h" + +/* + * Size of the I2O module table + */ + + +static struct i2o_handler *i2o_handlers[MAX_I2O_MODULES]; +static struct i2o_controller *i2o_controllers[MAX_I2O_CONTROLLERS]; +int i2o_num_controllers = 0; + + +extern int i2o_online_controller(struct i2o_controller *c); + +/* + * I2O configuration spinlock. This isnt a big deal for contention + * so we have one only + */ + +#ifdef __SMP__ +static spinlock_t i2o_configuration_lock = SPIN_LOCK_UNLOCKED; +#endif + +/* + * Install an I2O handler - these handle the asynchronous messaging + * from the card once it has initialised. + */ + +int i2o_install_handler(struct i2o_handler *h) +{ + int i; + spin_lock(&i2o_configuration_lock); + for(i=0;i<MAX_I2O_MODULES;i++) + { + if(i2o_handlers[i]==NULL) + { + h->context = i; + i2o_handlers[i]=h; + spin_unlock(&i2o_configuration_lock); + return 0; + } + } + spin_unlock(&i2o_configuration_lock); + return -ENOSPC; +} + +int i2o_remove_handler(struct i2o_handler *h) +{ + i2o_handlers[h->context]=NULL; + return 0; +} + + +/* + * Each I2O controller has a chain of devices on it - these match + * the useful parts of the LCT of the board. + */ + +int i2o_install_device(struct i2o_controller *c, struct i2o_device *d) +{ + spin_lock(&i2o_configuration_lock); + d->controller=c; + d->owner=NULL; + d->next=c->devices; + c->devices=d; + *d->dev_name = 0; + spin_unlock(&i2o_configuration_lock); + return 0; +} + +/* we need this version to call out of i2o_delete_controller */ + +int __i2o_delete_device(struct i2o_device *d) +{ + struct i2o_device **p; + + p=&(d->controller->devices); + + /* + * Hey we have a driver! + */ + + if(d->owner) + return -EBUSY; + + /* + * Seek, locate + */ + + while(*p!=NULL) + { + if(*p==d) + { + /* + * Destroy + */ + *p=d->next; + kfree(d); + return 0; + } + p=&((*p)->next); + } + printk(KERN_ERR "i2o_delete_device: passed invalid device.\n"); + return -EINVAL; +} + +int i2o_delete_device(struct i2o_device *d) +{ + int ret; + + spin_lock(&i2o_configuration_lock); + + ret = __i2o_delete_device(d); + + spin_unlock(&i2o_configuration_lock); + + return ret; +} + +/* + * Add and remove controllers from the I2O controller list + */ + +int i2o_install_controller(struct i2o_controller *c) +{ + int i; + spin_lock(&i2o_configuration_lock); + for(i=0;i<MAX_I2O_CONTROLLERS;i++) + { + if(i2o_controllers[i]==NULL) + { + i2o_controllers[i]=c; + c->next=i2o_controller_chain; + i2o_controller_chain=c; + c->unit = i; + sprintf(c->name, "i2o/iop%d", i); + i2o_num_controllers++; + spin_unlock(&i2o_configuration_lock); + return 0; + } + } + printk(KERN_ERR "No free i2o controller slots.\n"); + spin_unlock(&i2o_configuration_lock); + return -EBUSY; +} + +int i2o_delete_controller(struct i2o_controller *c) +{ + struct i2o_controller **p; + + spin_lock(&i2o_configuration_lock); + if(atomic_read(&c->users)) + { + spin_unlock(&i2o_configuration_lock); + return -EBUSY; + } + while(c->devices) + { + if(__i2o_delete_device(c->devices)<0) + { + /* Shouldnt happen */ + spin_unlock(&i2o_configuration_lock); + return -EBUSY; + } + } + c->destructor(c); + + p=&i2o_controller_chain; + + while(*p) + { + if(*p==c) + { + /* Prepare for restart */ +// i2o_clear_controller(c); + + *p=c->next; + spin_unlock(&i2o_configuration_lock); + if(c->page_frame); + kfree(c->page_frame); + i2o_controllers[c->unit]=NULL; + kfree(c); + i2o_num_controllers--; + return 0; + } + p=&((*p)->next); + } + spin_unlock(&i2o_configuration_lock); + printk(KERN_ERR "i2o_delete_controller: bad pointer!\n"); + return -ENOENT; +} + +void i2o_unlock_controller(struct i2o_controller *c) +{ + atomic_dec(&c->users); +} + +struct i2o_controller *i2o_find_controller(int n) +{ + struct i2o_controller *c; + + if(n<0 || n>=MAX_I2O_CONTROLLERS) + return NULL; + + spin_lock(&i2o_configuration_lock); + c=i2o_controllers[n]; + if(c!=NULL) + atomic_inc(&c->users); + spin_unlock(&i2o_configuration_lock); + return c; +} + + +/* + * Track if a device is being used by a driver + */ + +int i2o_claim_device(struct i2o_device *d, struct i2o_driver *r) +{ + spin_lock(&i2o_configuration_lock); + if(d->owner) + { + spin_unlock(&i2o_configuration_lock); + return -EBUSY; + } + atomic_inc(&d->controller->users); + d->owner=r; + spin_unlock(&i2o_configuration_lock); + return 0; +} + +int i2o_release_device(struct i2o_device *d) +{ + spin_lock(&i2o_configuration_lock); + if(d->owner==NULL) + { + spin_unlock(&i2o_configuration_lock); + return -EINVAL; + } + atomic_dec(&d->controller->users); + d->owner=NULL; + spin_unlock(&i2o_configuration_lock); + return 0; +} + +/* + * This is called by the bus specific driver layer when an interrupt + * or poll of this card interface is desired. + */ + +void i2o_run_queue(struct i2o_controller *c) +{ + struct i2o_message *m; + u32 mv; + + while((mv=I2O_REPLY_READ32(c))!=0xFFFFFFFF) + { + struct i2o_handler *i; + m=(struct i2o_message *)bus_to_virt(mv); + /* + * Temporary Debugging + */ + if(((m->function_addr>>24)&0xFF)==0x15) + printk("UTFR!\n"); +// printk("dispatching.\n"); + i=i2o_handlers[m->initiator_context&(MAX_I2O_MODULES-1)]; + if(i) + i->reply(i,c,m); + else + printk("Spurious reply\n"); + i2o_flush_reply(c,mv); + mb(); + } +} + + +/* + * Do i2o class name lookup + */ +const char *i2o_get_class_name(int class) +{ + int idx = 16; + static char *i2o_class_name[] = { + "Executive", + "Device Driver Module", + "Block Device", + "Tape Device", + "LAN Inteface", + "WAN Interface", + "Fibre Channel Port", + "Fibre Channel Device", + "SCSI Device", + "ATE Port", + "ATE Device", + "Floppy Controller", + "Floppy Device", + "Secondary Bus Port", + "Peer Transport Agent", + "Peer Transport", + "Unknown" + }; + + switch(class&0xFFF) + { + case I2O_CLASS_EXECUTIVE: + idx = 0; break; + case I2O_CLASS_DDM: + idx = 1; break; + case I2O_CLASS_RANDOM_BLOCK_STORAGE: + idx = 2; break; + case I2O_CLASS_SEQUENTIAL_STORAGE: + idx = 3; break; + case I2O_CLASS_LAN: + idx = 4; break; + case I2O_CLASS_WAN: + idx = 5; break; + case I2O_CLASS_FIBRE_CHANNEL_PORT: + idx = 6; break; + case I2O_CLASS_FIBRE_CHANNEL_PERIPHERAL: + idx = 7; break; + case I2O_CLASS_SCSI_PERIPHERAL: + idx = 8; break; + case I2O_CLASS_ATE_PORT: + idx = 9; break; + case I2O_CLASS_ATE_PERIPHERAL: + idx = 10; break; + case I2O_CLASS_FLOPPY_CONTROLLER: + idx = 11; break; + case I2O_CLASS_FLOPPY_DEVICE: + idx = 12; break; + case I2O_CLASS_BUS_ADAPTER_PORT: + idx = 13; break; + case I2O_CLASS_PEER_TRANSPORT_AGENT: + idx = 14; break; + case I2O_CLASS_PEER_TRANSPORT: + idx = 15; break; + } + + return i2o_class_name[idx]; +} + + +/* + * Wait up to 5 seconds for a message slot to be available. + */ + +u32 i2o_wait_message(struct i2o_controller *c, char *why) +{ + long time=jiffies; + u32 m; + while((m=I2O_POST_READ32(c))==0xFFFFFFFF) + { + if((jiffies-time)>=5*HZ) + { + printk(KERN_ERR "%s: Timeout waiting for message to send %s.\n", + c->name, why); + return 0xFFFFFFFF; + } + schedule(); + barrier(); + } + return m; +} + + +/* + * Wait up to 5 seconds for a reply to be available. + */ + +u32 i2o_wait_reply(struct i2o_controller *c, char *why, int timeout) +{ + u32 m; + long time=jiffies; + + while((m=I2O_REPLY_READ32(c))==0xFFFFFFFF) + { + if(jiffies-time >= timeout*HZ ) + { + printk(KERN_ERR "%s: timeout waiting for %s reply.\n", + c->name, why); + return 0xFFFFFFFF; + } + schedule(); + } + return m; +} + + + +/* Quiesce and clear IOP */ +int i2o_quiesce_controller(struct i2o_controller *c) +{ + u32 m; + u32 *msg; + + /* now we stop receiving messages to this IOP */ + m=i2o_wait_message(c, "Quiesce IOP"); + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)(c->mem_offset+m); + + msg[0]=FOUR_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1]=I2O_CMD_SYS_QUIESCE<<24|HOST_TID<<12|ADAPTER_TID; + msg[2]=0; + msg[3]=0; + + printk(KERN_DEBUG "Sending SysQuiesce to %s\n", c->name); + i2o_post_message(c,m); + + m=i2o_wait_reply(c, "System Quiesce", 20); + + if (m==0xFFFFFFFF) + return -ETIMEDOUT; + /* Someday we should check return status... */ + + return 0; +} + +int i2o_clear_controller(struct i2o_controller *c) +{ + u32 m; + u32 *msg; + + m=i2o_wait_message(c, "IOP Clear"); + if (m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)(c->mem_offset+m); + + msg[0]=FOUR_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1]=I2O_CMD_ADAPTER_CLEAR<<24|HOST_TID<<12|ADAPTER_TID; + msg[2]=0; + msg[3]=0; + + printk(KERN_DEBUG "Sending IOPClear to %s\n", c->name); + i2o_post_message(c, m); + + m=i2o_wait_reply(c, "IOP Clear timeout", 5); + + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + return 0; +} + + +/* + * i2o table walking. We just provide a single element retrieve. You can + * all sorts of fancy lookups in I2O but we have no performance critical + * lookups so why write all the code for it. + */ + +#if 0 +static int i2o_query_table_polled(struct i2o_controller *c, int tid, void *buf, int buflen, + int group, int field, u32 *key, int keylen) +{ + u32 m; + u32 *msg; + u16 op[64]; + u32 *p; + int i; + u32 *rbuf; + + op[0]=1; /* One Operation */ + op[1]=0; /* PAD */ + op[2]=2; /* LIST_GET */ + op[3]=group; /* group number */ + op[4]=1; /* 1 field */ + op[5]=field; /* Field number */ + op[6]=1; /* Key count */ + memcpy(op+7, key, keylen); /* Key */ + + m=i2o_wait_message(c, "I2O query table."); + if(m==0xFFFFFFFF) + { + return -ETIMEDOUT; + } + + msg=(u32 *)(c->mem_offset+m); + + rbuf=kmalloc(buflen+32, GFP_KERNEL); + if(rbuf==NULL) + { + printk(KERN_ERR "No free memory for table read.\n"); + return -ENOMEM; + } + msg[0]=NINE_WORD_MSG_SIZE|SGL_OFFSET_5; + msg[1]=I2O_CMD_UTIL_PARAMS_GET<<24|HOST_TID<<12|tid; + msg[2]=0; /* Context */ + msg[3]=0; + msg[4]=0; + msg[5]=0x54000000|(14); + msg[6]=virt_to_bus(op); + msg[7]=0xD0000000|(32+buflen); + msg[8]=virt_to_bus(rbuf); + + i2o_post_message(c,m); + barrier(); + + /* + * Now wait for a reply + */ + + + m=i2o_wait_reply(c, "Table read timeout", 5); + + if(m==0xFFFFFFFF) + { + kfree(rbuf); + return -ETIMEDOUT; + } + + msg = (u32 *)bus_to_virt(m); + + if(msg[4]>>24) + { + i2o_report_status(KERN_WARNING, "i2o_core", + (msg[1]>>24)&0xFF, (msg[4]>>24)&0xFF, + msg[4]&0xFFFF); + } + + p=rbuf; + + /* Ok 'p' is the reply block - lets see what happened */ + /* p0->p2 are the header */ + + /* FIXME: endians - turn p3 to little endian */ + + i=(p[0]&0xFFFF)<<2; /* Message size */ + if(i<buflen) + buflen=i; + + /* Do we have an error block ? */ + if(p[0]&0xFF000000) + { + printk(KERN_ERR "%s: error in field read.\n", + c->name); + kfree(rbuf); + return -EBADR; + } + + /* p[1] holds the more flag and row count - we dont care */ + + /* Ok it worked p[2]-> hold the data */ + memcpy(buf, p+2, buflen); + + kfree(rbuf); + + /* Finally return the message */ + I2O_REPLY_WRITE32(c,m); + return buflen; +} +#endif + +static int i2o_query_scalar_polled(struct i2o_controller *c, int tid, void *buf, int buflen, + int group, int field) +{ + u32 m; + u32 *msg; + u16 op[8]; + u32 *p; + int i; + u32 *rbuf; + + op[0]=1; /* One Operation */ + op[1]=0; /* PAD */ + op[2]=1; /* FIELD_GET */ + op[3]=group; /* group number */ + op[4]=1; /* 1 field */ + op[5]=field; /* Field number */ + + m=i2o_wait_message(c, "I2O query scalar."); + if(m==0xFFFFFFFF) + { + return -ETIMEDOUT; + } + + msg=(u32 *)(c->mem_offset+m); + + rbuf=kmalloc(buflen+32, GFP_KERNEL); + if(rbuf==NULL) + { + printk(KERN_ERR "No free memory for scalar read.\n"); + return -ENOMEM; + } + + msg[0]=NINE_WORD_MSG_SIZE|SGL_OFFSET_5; + msg[1]=I2O_CMD_UTIL_PARAMS_GET<<24|HOST_TID<<12|tid; + msg[2]=0; /* Context */ + msg[3]=0; + msg[4]=0; + msg[5]=0x54000000|12; + msg[6]=virt_to_bus(op); + msg[7]=0xD0000000|(32+buflen); + msg[8]=virt_to_bus(rbuf); + + i2o_post_message(c,m); + barrier(); + + /* + * Now wait for a reply + */ + + + m=i2o_wait_reply(c, "Scalar read timeout", 5); + + if(m==0xFFFFFFFF) + { + kfree(rbuf); + return -ETIMEDOUT; + } + + msg = (u32 *)bus_to_virt(m); + if(msg[4]>>24) + { + i2o_report_status(KERN_WARNING, "i2o_core", + (msg[1]>>24)&0xFF, (msg[4]>>24)&0xFF, + msg[4]&0xFFFF); + } + + p=rbuf; + + /* Ok 'p' is the reply block - lets see what happened */ + /* p0->p2 are the header */ + + /* FIXME: endians - turn p3 to little endian */ + + if((p[0]&0xFFFF)!=1) + printk(KERN_WARNING "Suspicious field read return 0x%08X\n", p[0]); + + i=(p[1]&0xFFFF)<<2; /* Message size */ + if(i<buflen) + buflen=i; + + /* Do we have an error block ? */ + if(p[1]&0xFF000000) + { + printk(KERN_ERR "%s: error in field read.\n", + c->name); + kfree(rbuf); + return -EBADR; + } + + /* p[1] holds the more flag and row count - we dont care */ + + /* Ok it worked p[2]-> hold the data */ + memcpy(buf, p+2, buflen); + + kfree(rbuf); + + /* Finally return the message */ + I2O_REPLY_WRITE32(c,m); + return buflen; +} + +/* + * Dump the information block associated with a given unit (TID) + */ + +void i2o_report_controller_unit(struct i2o_controller *c, int unit) +{ + char buf[64]; + + if(i2o_query_scalar_polled(c, unit, buf, 16, 0xF100, 3)>=0) + { + buf[16]=0; + printk(KERN_INFO " Vendor: %s\n", buf); + } + if(i2o_query_scalar_polled(c, unit, buf, 16, 0xF100, 4)>=0) + { + buf[16]=0; + printk(KERN_INFO " Device: %s\n", buf); + } +#if 0 + if(i2o_query_scalar_polled(c, unit, buf, 16, 0xF100, 5)>=0) + { + buf[16]=0; + printk(KERN_INFO "Description: %s\n", buf); + } +#endif + if(i2o_query_scalar_polled(c, unit, buf, 8, 0xF100, 6)>=0) + { + buf[8]=0; + printk(KERN_INFO " Rev: %s\n", buf); + } +} + + +/* + * Parse the hardware resource table. Right now we print it out + * and don't do a lot with it. We should collate these and then + * interact with the Linux resource allocation block. + * + * Lets prove we can read it first eh ? + * + * This is full of endianisms! + */ + +static int i2o_parse_hrt(struct i2o_controller *c, u8 *p) +{ + u32 *rows=(u32 *)p; + u8 *d; + int count; + int length; + int i; + int state; + + if(p[3]!=0) + { + printk(KERN_ERR "i2o: HRT table for controller is too new a version.\n"); + return -1; + } + + count=p[0]|(p[1]<<8); + length = p[2]; + + printk(KERN_INFO "HRT has %d entries of %d bytes each.\n", + count, length<<2); + + rows+=2; + + for(i=0;i<count;i++) + { + printk(KERN_INFO "Adapter %08X: ", rows[0]); + p=(u8 *)(rows+1); + d=(u8 *)(rows+2); + state=p[1]<<8|p[0]; + + printk("TID %04X:[", state&0xFFF); + state>>=12; + if(state&(1<<0)) + printk("H"); /* Hidden */ + if(state&(1<<2)) + { + printk("P"); /* Present */ + if(state&(1<<1)) + printk("C"); /* Controlled */ + } + if(state>9) + printk("*"); /* Hard */ + + printk("]:"); + + switch(p[3]&0xFFFF) + { + case 0: + /* Adapter private bus - easy */ + printk("Local bus %d: I/O at 0x%04X Mem 0x%08X", + p[2], d[1]<<8|d[0], *(u32 *)(d+4)); + break; + case 1: + /* ISA bus */ + printk("ISA %d: CSN %d I/O at 0x%04X Mem 0x%08X", + p[2], d[2], d[1]<<8|d[0], *(u32 *)(d+4)); + break; + + case 2: /* EISA bus */ + printk("EISA %d: Slot %d I/O at 0x%04X Mem 0x%08X", + p[2], d[3], d[1]<<8|d[0], *(u32 *)(d+4)); + break; + + case 3: /* MCA bus */ + printk("MCA %d: Slot %d I/O at 0x%04X Mem 0x%08X", + p[2], d[3], d[1]<<8|d[0], *(u32 *)(d+4)); + break; + + case 4: /* PCI bus */ + printk("PCI %d: Bus %d Device %d Function %d", + p[2], d[2], d[1], d[0]); + break; + + case 0x80: /* Other */ + default: + printk("Unsupported bus type."); + break; + } + printk("\n"); + rows+=length; + } + return 0; +} + +/* + * The logical configuration table tells us what we can talk to + * on the board. Most of the stuff isn't interesting to us. + */ + +static int i2o_parse_lct(struct i2o_controller *c, u32 *lct) +{ + int i; + int max; + int tid; + u32 *p; + struct i2o_device *d; + char str[22]; + + max=lct[0]&0xFFFF; + + max-=3; + max/=9; + + printk(KERN_INFO "LCT has %d entries.\n", max); + + if(max > 128) + { + printk(KERN_INFO "LCT was truncated.\n"); + max=128; + } + + if(lct[1]&(1<<0)) + printk(KERN_WARNING "Configuration dialog desired.\n"); + + p=lct+3; + + for(i=0;i<max;i++) + { + d = (struct i2o_device *)kmalloc(sizeof(struct i2o_device), GFP_KERNEL); + if(d==NULL) + { + printk("i2o_core: Out of memory for LCT data.\n"); + return -ENOMEM; + } + + d->controller = c; + d->next = NULL; + + d->id = tid = (p[0]>>16)&0xFFF; + d->class = p[3]&0xFFF; + d->subclass = p[4]&0xFFF; + d->parent = (p[5]>>12)&0xFFF; + d->flags = 0; + + printk(KERN_INFO "TID %d.\n", tid); + + i2o_report_controller_unit(c, tid); + + i2o_install_device(c, d); + + printk(KERN_INFO " Class: "); + + sprintf(str, "%-21s", i2o_get_class_name(d->class)); + printk("%s", str); + + printk(" Subclass: 0x%03X Flags: ", + d->subclass); + + if(p[2]&(1<<0)) + printk("C"); // ConfigDialog requested + if(p[2]&(1<<1)) + printk("M"); // Multi-user capable + if(!(p[2]&(1<<4))) + printk("P"); // Peer service enabled! + if(!(p[2]&(1<<5))) + printk("m"); // Mgmt service enabled! + printk("\n"); + p+=9; + } + return 0; +} + +#if 0 +/* Reset the IOP to sane state */ +/* I think we need handler for core (or executive class in I2O terms) */ +static int i2o_reset_adapter(struct i2o_controller *c) +{ + u32 m; + u8 *work8; + u32 *msg; + long time; + + /* First stop extral operations */ + m=i2o_wait_message(c, "quiesce IOP"); + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)(c->mem_offset+m); + + msg[0]=FOUR_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1]=I2O_CMD_SYS_QUIESCE<<24|HOST_TID<<12|ADAPTER_TID; + msg[2]=0; + msg[3]=0; + + i2o_post_message(c,m); + + m=i2o_wait_reply(c, "System Quiesce timeout", 5); + + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + /* Then reset the IOP */ + m=i2o_wait_message(c, "reset IOP"); + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)(c->mem_offset+m); + + work8=(void *)kmalloc(4, GFP_KERNEL); + if(work8==NULL) { + printk(KERN_ERR "IOP reset failed - no free memory.\n"); + return -ENOMEM; + } + + memset(work8, 0, 4); + + msg[0]=EIGHT_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1]=I2O_CMD_ADAPTER_RESET<<24|HOST_TID<<12|ADAPTER_TID; + msg[2]=0; + msg[3]=0; + msg[4]=0; + msg[5]=0; + msg[6]=virt_to_phys(work8); + msg[7]=0; /* 64bit host FIXME */ + + i2o_post_message(c,m); + + /* Wait for a reply */ + time=jiffies; + + while(work8[0]==0x01) { + if((jiffies-time)>=5*HZ) { + printk(KERN_ERR "IOP reset timeout.\n"); + kfree(work8); + return -ETIMEDOUT; + } + schedule(); + barrier(); + } + + if (work8[0]==0x02) + printk(KERN_WARNING "IOP Reset rejected\n"); + + return 0; +} +#endif + +/* + * Bring an I2O controller into HOLD state. See the 1.5 + * spec. Basically we go + * + * Wait for the message queue to initialise. + * If it didnt -> controller is dead + * + * Send a get status using the message queue + * Poll for a reply block 88 bytes long + * + * Send an initialise outbound queue + * Poll for a reply + * + * Post our blank messages to the queue FIFO + * + * Send GetHRT, Parse it + */ + +int i2o_activate_controller(struct i2o_controller *c) +{ + long time; + u32 m; + u8 *workspace; + u32 *msg; + int i; + + printk(KERN_INFO "Configuring I2O controller at 0x%08X.\n", (u32)c->mem_phys); + + /* First reset the IOP to sane state */ +// i2o_reset_adapter(c) + + m=i2o_wait_message(c, "initialise"); + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)(c->mem_offset+m); + + workspace = (void *)kmalloc(88, GFP_KERNEL); + if(workspace==NULL) + { + printk(KERN_ERR "IOP initialisation failed - no free memory.\n"); + return -ENOMEM; + } + + memset(workspace, 0, 88); + + msg[0]=NINE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1]=I2O_CMD_STATUS_GET<<24|HOST_TID<<12|ADAPTER_TID; + msg[2]=0; + msg[3]=0; + msg[4]=0; + msg[5]=0; + msg[6]=virt_to_phys(workspace); + msg[7]=0; /* 64bit host FIXME */ + msg[8]=88; + + i2o_post_message(c,m); + + /* + * Wait for a reply + */ + + time=jiffies; + + while(workspace[87]!=0xFF) + { + if((jiffies-time)>=5*HZ) + { + printk(KERN_ERR "IOP get status timeout.\n"); + kfree(workspace); + return -ETIMEDOUT; + } + schedule(); + barrier(); + } + + /* + * Ok the reply has arrived. Fill in the important stuff + */ + + c->status = workspace[10]; + c->i2oversion = (workspace[9]>>4)&0xFF; + c->inbound_size = (workspace[12]|(workspace[13]<<8))*4; /* 32bit words */ + + /* + * If the board is running, reset it - we have no idea + * what kind of a mess the previous owner left it in. + */ + +// if(c->status == ADAPTER_STATE_OPERATIONAL) +// i2o_reset_device(c); + + + m=i2o_wait_message(c, "initqueue"); + if(m==0xFFFFFFFF) + { + kfree(workspace); + return -ETIMEDOUT; + } + + msg=(u32 *)(c->mem_offset+m); + + msg[0]= EIGHT_WORD_MSG_SIZE| TRL_OFFSET_6; + msg[1]= I2O_CMD_OUTBOUND_INIT<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2]= 0; + msg[3]= 0x0106; /* Transaction context */ + msg[4]= 4096; /* Host page frame size */ + msg[5]= MSG_FRAME_SIZE<<16|0x80; /* Outbound msg frame size and Initcode */ + msg[6]= 0xD0000004; /* Simple SG LE, EOB */ + msg[7]= virt_to_phys(workspace); + *((u32 *)workspace)=0; + + /* + * Post it + */ + + i2o_post_message(c,m); + + barrier(); + + time=jiffies; + + while(workspace[0]!=I2O_CMD_OUTBOUND_INIT_COMPLETE) + { + if((jiffies-time)>=5*HZ) + { + printk(KERN_ERR "IOP outbound initialise failed.\n"); + kfree(workspace); + return -ETIMEDOUT; + } + schedule(); + barrier(); + } + + kfree(workspace); + + c->page_frame = kmalloc(MSG_POOL_SIZE, GFP_KERNEL); + if(c->page_frame==NULL) + { + printk(KERN_ERR "IOP init failed: no memory for message page.\n"); + return -ENOMEM; + } + + m=virt_to_phys(c->page_frame); + + for(i=0; i< NMBR_MSG_FRAMES; i++) + { + I2O_REPLY_WRITE32(c,m); + mb(); + m+=MSG_FRAME_SIZE; + } + + /* + * The outbound queue is initialised and loaded, + * + * Now we need the Hardware Resource Table. We must ask for + * this next we can't issue random messages yet. + */ + + + workspace=kmalloc(2048, GFP_KERNEL); + if(workspace==NULL) + { + printk(KERN_ERR "IOP init failed; no memory.\n"); + return -ENOMEM; + } + + m=i2o_wait_message(c, "I2O HRT timeout."); + if(m==0xFFFFFFFF) + { + kfree(workspace); + return -ETIMEDOUT; + } + + msg=(u32 *)(c->mem_offset+m); + + msg[0]= SIX_WORD_MSG_SIZE| SGL_OFFSET_4; + msg[1]= I2O_CMD_HRT_GET<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2]= 0x0; + msg[3]= 0x0; /* Transaction context */ + msg[4]= (0xD0000000 | 2048); /* Simple transaction , 2K */ + msg[5]= virt_to_phys(workspace); /* Dump it here */ + *((u32 *)workspace)=0xFFFFFFFF; + + i2o_post_message(c,m); + + barrier(); + + /* + * Now wait for a reply + */ + + m=i2o_wait_reply(c, "HRT table", 5); + + if(m==0xFFFFFFFF) + { + kfree(workspace); + return -ETIMEDOUT; + } + + msg=(u32 *)bus_to_virt(m); + + if(msg[4]>>24) + { + i2o_report_status(KERN_WARNING, "i2o_core", + (msg[1]>>24)&0xFF, (msg[4]>>24)&0xFF, + msg[4]&0xFFFF); + } + I2O_REPLY_WRITE32(c,m); + + i2o_parse_hrt(c, workspace); + + kfree(workspace); + + return i2o_online_controller(c); +// i2o_report_controller_unit(c, ADAPTER_TID); +} + + +/* + * Bring a controller online. Needs completing for multiple controllers + */ + +int i2o_online_controller(struct i2o_controller *c) +{ + u32 m; + u32 *msg; + u32 systab[32]; + u32 privmem[2]; + u32 privio[2]; + u32 *workspace; + + systab[0]=1; + systab[1]=0; + systab[2]=0; + systab[3]=0; + systab[4]=0; /* Organisation ID */ + systab[5]=2; /* Ident 2 for now */ + systab[6]=0<<24|0<<16|I2OVERSION<<12|1; /* Memory mapped, IOPState, v1.5, segment 1 */ + systab[7]=MSG_FRAME_SIZE>>2; /* Message size */ + systab[8]=0; /* LastChanged */ + systab[9]=0; /* Should be IOP capabilities */ + systab[10]=virt_to_phys(c->post_port); + systab[11]=0; + + privmem[0]=c->priv_mem; /* Private memory space base address */ + privmem[1]=c->priv_mem_size; + privio[0]=c->priv_io; /* Private I/O address */ + privio[1]=c->priv_io_size; + + m=i2o_wait_message(c, "SetSysTab"); + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + /* Now we build the systab */ + msg=(u32 *)(c->mem_offset+m); + + msg[0] = NINE_WORD_MSG_SIZE|SGL_OFFSET_6; + msg[1] = I2O_CMD_SYS_TAB_SET<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2] = 0; /* Context not needed */ + msg[3] = 0; + msg[4] = (1<<16)|(2<<12); /* Host 1 I2O 2 */ + msg[5] = 1; /* Segment 1 */ + + /* + * Scatter Gather List + */ + + msg[6] = 0x54000000|48; /* One table for now */ + msg[7] = virt_to_phys(systab); + msg[8] = 0xD4000000|48; /* One table for now */ + msg[9] = virt_to_phys(privmem); +/* msg[10] = virt_to_phys(privio); */ + + i2o_post_message(c,m); + + barrier(); + + /* + * Now wait for a reply + */ + + + m=i2o_wait_reply(c, "Systab read", 5); + + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)bus_to_virt(m); + + if(msg[4]>>24) + { + i2o_report_status(KERN_ERR, "i2o_core", + (msg[1]>>24)&0xFF, (msg[4]>>24)&0xFF, + msg[4]&0xFFFF); + } + I2O_REPLY_WRITE32(c,m); + + /* + * Finally we go online + */ + + m=i2o_wait_message(c, "No message for SysEnable"); + + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)(c->mem_offset+m); + + msg[0] = FOUR_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_SYS_ENABLE<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2] = 0; /* Context not needed */ + msg[3] = 0; + + i2o_post_message(c,m); + + barrier(); + + /* + * Now wait for a reply + */ + + + m=i2o_wait_reply(c, "Enable", 240); + + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)bus_to_virt(m); + + if(msg[4]>>24) + { + i2o_report_status(KERN_ERR, "i2o_core", + (msg[1]>>24)&0xFF, (msg[4]>>24)&0xFF, + msg[4]&0xFFFF); + } + I2O_REPLY_WRITE32(c,m); + + /* + * Grab the LCT, see what is attached + */ + + m=i2o_wait_message(c, "No message for LCT"); + + if(m==0xFFFFFFFF) + return -ETIMEDOUT; + + msg=(u32 *)(c->mem_offset+m); + + + workspace = kmalloc(8192, GFP_KERNEL); + if(workspace==NULL) + { + msg[0]=FOUR_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1]= HOST_TID<<12|ADAPTER_TID; /* NOP */ + i2o_post_message(c,m); + printk(KERN_ERR "No free memory for i2o controller buffer.\n"); + return -ENOMEM; + } + + memset(workspace, 0, 8192); + + msg[0] = FOUR_WORD_MSG_SIZE|SGL_OFFSET_6; + msg[1] = I2O_CMD_LCT_NOTIFY<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2] = 0; /* Context not needed */ + msg[3] = 0; + msg[4] = 0xFFFFFFFF; /* All devices */ + msg[5] = 0x00000000; /* Report now */ + msg[6] = 0xD0000000|8192; + msg[7] = virt_to_bus(workspace); + + i2o_post_message(c,m); + + barrier(); + + /* + * Now wait for a reply + */ + + m=i2o_wait_reply(c, "LCT", 5); + + if(m==0xFFFFFFFF) + { + kfree(workspace); + return -ETIMEDOUT; + } + + msg=(u32 *)bus_to_virt(m); + + if(msg[4]>>24) + { + i2o_report_status(KERN_ERR, "i2o_core", + (msg[1]>>24)&0xFF, (msg[4]>>24)&0xFF, + msg[4]&0xFFFF); + } + + i2o_parse_lct(c, workspace); + kfree(workspace); + + I2O_REPLY_WRITE32(c,m); + + return 0; +} + +/* + * Run time support routines + */ + +/* + * Generic "post and forget" helpers. This is less efficient - we do + * a memcpy for example that isnt strictly needed, but for most uses + * this is simply not worth optimising + */ + +int i2o_post_this(struct i2o_controller *c, int tid, u32 *data, int len) +{ + u32 m; + u32 *msg; + unsigned long t=jiffies; + + do + { + mb(); + m = I2O_POST_READ32(c); + } + while(m==0xFFFFFFFF && (jiffies-t)<HZ); + + + if(m==0xFFFFFFFF) + { + printk(KERN_ERR "i2o: controller not responding.\n"); + return -1; + } + msg = bus_to_virt(c->mem_offset + m); + memcpy(msg, data, len); + i2o_post_message(c,m); + return 0; +} + +/* + * Post a message and wait for a response flag to be set. This API will + * change to use wait_queue's one day + */ + +int i2o_post_wait(struct i2o_controller *c, int tid, u32 *data, int len, int *flag, int timeout) +{ + unsigned long t=jiffies; + + *flag = 0; + + if(i2o_post_this(c, tid, data, len)) + return -1; + + while(!*flag && (jiffies-t)<timeout*HZ) + { + schedule(); + mb(); + } + if(*flag <= 0) + return -1; + return 0; +} + +/* + * Issue UTIL_CLAIM messages + */ + +int i2o_issue_claim(struct i2o_controller *c, int tid, int context, int onoff, int *flag) +{ + u32 msg[6]; + + msg[0] = FIVE_WORD_MSG_SIZE | SGL_OFFSET_0; + if(onoff) + msg[1] = I2O_CMD_UTIL_CLAIM << 24 | HOST_TID<<12 | tid; + else + msg[1] = I2O_CMD_UTIL_RELEASE << 24 | HOST_TID << 12 | tid; + + /* The 0x80000000 convention for flagging is assumed by this helper */ + + msg[2] = 0x80000000|context; + msg[3] = (u32)flag; + msg[4] = 0x01<<24; /* Primary user */ + + return i2o_post_wait(c, tid, msg, 20, flag,2); +} + +/* + * Query a scalar value + */ + +int i2o_query_scalar(struct i2o_controller *c, int tid, int context, + int group, int field, void *buf, int buflen, int *flag) +{ + u16 *op; + u32 *bl; + u32 msg[9]; + + bl=kmalloc(buflen+64, GFP_KERNEL); /* Enough space for error replys */ + if(bl==NULL) + { + printk(KERN_ERR "i2o: no memory for query buffer.\n"); + return -ENOMEM; + } + + op = (u16*)bl; + op[0]=1; /* One Operation */ + op[1]=0; /* PAD */ + op[2]=1; /* FIELD_GET */ + op[3]=group; /* group number */ + op[4]=1; /* field count, default = 1 */ + op[5]=field; /* field index */ + + if(field == -1) + /* Single value or the whole group? */ + { + op[4]=-1; + op[5]=0; + } + + msg[0]=NINE_WORD_MSG_SIZE|SGL_OFFSET_5; + msg[1]=I2O_CMD_UTIL_PARAMS_GET<<24|HOST_TID<<12|tid; + msg[2]=context|0x80000000; /* So we can pick it out */ + msg[3]=(u32)flag; + msg[4]=0; + msg[5]=0x54000000|12; + msg[6]=virt_to_bus(bl); + /* + * There are 8 bytes of "overhead" required to pull in + * a Params ResultsList; 2 bytes for ResultCount + * (which should have value=1), plus 2 bytes for pad, + * plus 2 bytes for BlockSize, plus 1 byte BlockStatus, + * plus 1 byte ErrorInfoSize (8 bytes total overhead). + * This is followed finally by actual result value(s). + * + * Tell the IOP to return 8 + buflen bytes. + */ + msg[7]=0xD0000000|(8+buflen); + msg[8]=virt_to_bus(bl+3); + + bl[3]=0xFCFCFCFC; // Pad,ResultCount + bl[4]=0xFAFAFCFC; // ErrorInfoSize,BlockStatus,BlockSize + + /* + * Post the message and await a reply + */ + + if (i2o_post_wait(c, tid, msg, sizeof(msg), flag,2) < 0) + { + kfree(bl); + return -1; + } + + if(bl[4]&0x00FF00000) /* BlockStatus != SUCCESS */ + { + printk(KERN_WARNING "i2o_query_scalar - Error\n" + "ErrorInfoSize = 0x%02x, BlockStatus = 0x%02x, " + "BlockSize = 0x%04x\n", + bl[4]>>24, (bl[4]>>16)&0xFF, bl[4]&0xFFFF); + kfree(bl); + return -1; + } + if((bl[3] & 0xFFFF) != 1) + { + printk(KERN_ERR "i2o: query ResultCount = 0x%04x\n", bl[3]&0xFFFF); + } + + memcpy(buf, bl+5, buflen); + kfree(bl); + return 0; +} + + +#if 0 +/* + * Query a table field + * FIXME: NOT TESTED! + */ +int i2o_query_table(struct i2o_controller *c, int tid, int context, + void *buf, int buflen, + int table, + int *field, int fieldlen, + u32 *key, int keylen, + int *flag) +{ + static u16 op[32]; + u32 *bl; + u32 msg[9]; + int i; + + bl=kmalloc(buflen+64, GFP_KERNEL); + if(bl==NULL) + { + printk(KERN_ERR "i2o: no memory for query buffer.\n"); + return -ENOMEM; + } + + op[0]=1; /* Operation count */ + op[1]=0; /* Reserved */ + op[2]=I2O_PARAMS_LIST_GET; /* Operation */ + op[3]=table; /* Group */ + /* Specific fields or the whole group? */ + if(*field != -1) + { /* FIXME: Fields can be variable size */ + op[4]=fieldlen; + for (i=0; i < fieldlen; i++) + op[4+i]=field[i]; + } + else + { + op[4]=-1; + op[5]=0; + } + + memcpy(bl, op, 12); + + msg[0]=NINE_WORD_MSG_SIZE|SGL_OFFSET_5; + msg[1]=I2O_CMD_UTIL_PARAMS_GET<<24|HOST_TID<<12|tid; + msg[2]=context|0x80000000; /* So we can pick it out */ + msg[3]=(u32)flag; + msg[4]=0; + msg[5]=0x54000000|12; + msg[6]=virt_to_bus(bl); + + msg[7]=0xD0000000|(buflen+48); + msg[8]=virt_to_bus(bl+4); + + /* + * Post the message and await a reply + */ + + if(i2o_post_wait(c, tid, msg, sizeof(msg), flag,2)<0) + return -1; + + if(bl[5]&0x00FF00000) /* BlockStatus != SUCCESS */ + { + printk(KERN_WARNING "i2o_query_table - Error\n" + "ErrorInfoSize = 0x%02x, BlockStatus = 0x%02x, " + "BlockSize = 0x%04x\n", + bl[5]>>24, (bl[5]>>16)&0xFF, bl[5]&0xFFFF); + kfree(bl); + return -1; + } + + if((bl[4]&0xFFFF)!=1) + printk(KERN_ERR "i2o: query ResultCount = %0#4x\n", + bl[4]&0xFFFF); + + memcpy(buf, bl+6, buflen); + kfree(bl); + return 0; +} +#endif + +/* + * Set (for now) scalar value + * + * TODO: Add support for table groups + */ + +int i2o_params_set(struct i2o_controller *c, int tid, int context, int table, + int field, void *buf, int buflen, int *flag) +{ + static u16 opdata[]={1,0,6,0,1,4,0}; + u32 *bl; + u32 msg[9]; + + bl=kmalloc(buflen+64, GFP_KERNEL); + if(bl==NULL) + { + printk(KERN_ERR "i2o: no memory for set buffer.\n"); + return -ENOMEM; + } + + opdata[3]=table; + /* Single value or the whole group? */ + if(field != -1) { + opdata[4]=1; + opdata[5]=field; + opdata[6]=*(u16 *)buf; + } + else { + opdata[4]=-1; + opdata[5]=0; + } + + memcpy(bl, opdata, 14); + + msg[0]=NINE_WORD_MSG_SIZE|SGL_OFFSET_5; + msg[1]=I2O_CMD_UTIL_PARAMS_SET<<24|HOST_TID<<12|tid; + msg[2]=context|0x80000000; /* So we can pick it out */ + msg[3]=(u32)flag; + msg[4]=0; + msg[5]=0x54000000|14; + msg[6]=virt_to_bus(bl); + msg[7]=0xD0000000|(buflen+48); + msg[8]=virt_to_bus(bl+4); + + /* Post the message and wait for a reply */ + if(i2o_post_wait(c, tid, msg, 36, flag, 5)<0) + { + kfree(bl); + return -1; + } + + /* Perhaps we should check errors, eh? */ + if(bl[5]&0x00FF00000) /* BlockStatus != SUCCESS */ + { + printk(KERN_WARNING "i2o_params_set - Error\n" + "ErrorInfoSize = %0#2x, BlockStatus = %0#2x, " + "BlockSize = %0#4x\n", + bl[5]>>24, (bl[5]>>16)&0xFF, bl[5]&0xFFFF); + kfree(bl); + return -1; + } + + if((bl[4] & 0xFFFF) != 1) + { + printk(KERN_ERR "i2o: params set ResultCount = %0#4x\n", + bl[4]&0xFFFF); + } + + kfree(bl); + return 0; +} + + +void report_common_status(u8 req_status) +{ + /* the following reply status strings are common to all classes */ + + static char *REPLY_STATUS[] = { + "SUCCESS", + "ABORT_DIRTY", + "ABORT_NO_DATA_TRANSFER", + "ABORT_PARTIAL_TRANSFER", + "ERROR_DIRTY", + "ERROR_NO_DATA_TRANSFER", + "ERROR_PARTIAL_TRANSFER", + "PROCESS_ABORT_DIRTY", + "PROCESS_ABORT_NO_DATA_TRANSFER", + "PROCESS_ABORT_PARTIAL_TRANSFER", + "TRANSACTION_ERROR", + "PROGRESS_REPORT" + }; + + if (req_status > I2O_REPLY_STATUS_PROGRESS_REPORT) + printk("%0#4x / ", req_status); + else + printk("%s / ", REPLY_STATUS[req_status]); + + return; +} + +static void report_common_dsc(u16 detailed_status) +{ + /* The following detailed statuscodes are valid + - for executive class, utility class, DDM class and + - for transaction error replies + */ + + static char *COMMON_DSC[] = { + "SUCCESS", + "0x01", // not used + "BAD_KEY", + "TCL_ERROR", + "REPLY_BUFFER_FULL", + "NO_SUCH_PAGE", + "INSUFFICIENT_RESOURCE_SOFT", + "INSUFFICIENT_RESOURCE_HARD", + "0x08", // not used + "CHAIN_BUFFER_TOO_LARGE", + "UNSUPPORTED_FUNCTION", + "DEVICE_LOCKED", + "DEVICE_RESET", + "INAPPROPRIATE_FUNCTION", + "INVALID_INITIATOR_ADDRESS", + "INVALID_MESSAGE_FLAGS", + "INVALID_OFFSET", + "INVALID_PARAMETER", + "INVALID_REQUEST", + "INVALID_TARGET_ADDRESS", + "MESSAGE_TOO_LARGE", + "MESSAGE_TOO_SMALL", + "MISSING_PARAMETER", + "TIMEOUT", + "UNKNOWN_ERROR", + "UNKNOWN_FUNCTION", + "UNSUPPORTED_VERSION", + "DEVICE_BUSY", + "DEVICE_NOT_AVAILABLE" + }; + + if (detailed_status > I2O_DSC_DEVICE_NOT_AVAILABLE) + printk("%0#4x.\n", detailed_status); + else + printk("%s.\n", COMMON_DSC[detailed_status]); + + return; +} + +void report_lan_dsc(u16 detailed_status) +{ + static char *LAN_DSC[] = { // Lan detailed status code strings + "SUCCESS", + "DEVICE_FAILURE", + "DESTINATION_NOT_FOUND", + "TRANSMIT_ERROR", + "TRANSMIT_ABORTED", + "RECEIVE_ERROR", + "RECEIVE_ABORTED", + "DMA_ERROR", + "BAD_PACKET_DETECTED", + "OUT_OF_MEMORY", + "BUCKET_OVERRUN", + "IOP_INTERNAL_ERROR", + "CANCELED", + "INVALID_TRANSACTION_CONTEXT", + "DEST_ADDRESS_DETECTED", + "DEST_ADDRESS_OMITTED", + "PARTIAL_PACKET_RETURNED", + "TEMP_SUSPENDED_STATE" + }; + + if (detailed_status > I2O_LAN_DSC_TEMP_SUSPENDED_STATE) + printk("%0#4x.\n", detailed_status); + else + printk("%s.\n", LAN_DSC[detailed_status]); + + return; +} + +static void report_util_cmd(u8 cmd) +{ + switch (cmd) { + case I2O_CMD_UTIL_NOP: + printk("UTIL_NOP, "); + break; + case I2O_CMD_UTIL_ABORT: + printk("UTIL_ABORT, "); + break; + case I2O_CMD_UTIL_CLAIM: + printk("UTIL_CLAIM, "); + break; + case I2O_CMD_UTIL_RELEASE: + printk("UTIL_CLAIM_RELEASE, "); + break; + case I2O_CMD_UTIL_CONFIG_DIALOG: + printk("UTIL_CONFIG_DIALOG, "); + break; + case I2O_CMD_UTIL_DEVICE_RESERVE: + printk("UTIL_DEVICE_RESERVE, "); + break; + case I2O_CMD_UTIL_DEVICE_RELEASE: + printk("UTIL_DEVICE_RELEASE, "); + break; + case I2O_CMD_UTIL_ACK: + printk("UTIL_EVENT_ACKNOWLEDGE, "); + break; + case I2O_CMD_UTIL_EVT_REGISTER: + printk("UTIL_EVENT_REGISTER, "); + break; + case I2O_CMD_UTIL_LOCK: + printk("UTIL_LOCK, "); + break; + case I2O_CMD_UTIL_LOCK_RELEASE: + printk("UTIL_LOCK_RELEASE, "); + break; + case I2O_CMD_UTIL_PARAMS_GET: + printk("UTIL_PARAMS_GET, "); + break; + case I2O_CMD_UTIL_PARAMS_SET: + printk("UTIL_PARAMS_SET, "); + break; + case I2O_CMD_UTIL_REPLY_FAULT_NOTIFY: + printk("UTIL_REPLY_FAULT_NOTIFY, "); + break; + default: + printk("%0#2x, ",cmd); + } + + return; +} + + +static void report_exec_cmd(u8 cmd) +{ + switch (cmd) { + case I2O_CMD_ADAPTER_ASSIGN: + printk("EXEC_ADAPTER_ASSIGN, "); + break; + case I2O_CMD_ADAPTER_READ: + printk("EXEC_ADAPTER_READ, "); + break; + case I2O_CMD_ADAPTER_RELEASE: + printk("EXEC_ADAPTER_RELEASE, "); + break; + case I2O_CMD_BIOS_INFO_SET: + printk("EXEC_BIOS_INFO_SET, "); + break; + case I2O_CMD_BOOT_DEVICE_SET: + printk("EXEC_BOOT_DEVICE_SET, "); + break; + case I2O_CMD_CONFIG_VALIDATE: + printk("EXEC_CONFIG_VALIDATE, "); + break; + case I2O_CMD_CONN_SETUP: + printk("EXEC_CONN_SETUP, "); + break; + case I2O_CMD_DDM_DESTROY: + printk("EXEC_DDM_DESTROY, "); + break; + case I2O_CMD_DDM_ENABLE: + printk("EXEC_DDM_ENABLE, "); + break; + case I2O_CMD_DDM_QUIESCE: + printk("EXEC_DDM_QUIESCE, "); + break; + case I2O_CMD_DDM_RESET: + printk("EXEC_DDM_RESET, "); + break; + case I2O_CMD_DDM_SUSPEND: + printk("EXEC_DDM_SUSPEND, "); + break; + case I2O_CMD_DEVICE_ASSIGN: + printk("EXEC_DEVICE_ASSIGN, "); + break; + case I2O_CMD_DEVICE_RELEASE: + printk("EXEC_DEVICE_RELEASE, "); + break; + case I2O_CMD_HRT_GET: + printk("EXEC_HRT_GET, "); + break; + case I2O_CMD_ADAPTER_CLEAR: + printk("EXEC_IOP_CLEAR, "); + break; + case I2O_CMD_ADAPTER_CONNECT: + printk("EXEC_IOP_CONNECT, "); + break; + case I2O_CMD_ADAPTER_RESET: + printk("EXEC_IOP_RESET, "); + break; + case I2O_CMD_LCT_NOTIFY: + printk("EXEC_LCT_NOTIFY, "); + break; + case I2O_CMD_OUTBOUND_INIT: + printk("EXEC_OUTBOUND_INIT, "); + break; + case I2O_CMD_PATH_ENABLE: + printk("EXEC_PATH_ENABLE, "); + break; + case I2O_CMD_PATH_QUIESCE: + printk("EXEC_PATH_QUIESCE, "); + break; + case I2O_CMD_PATH_RESET: + printk("EXEC_PATH_RESET, "); + break; + case I2O_CMD_STATIC_MF_CREATE: + printk("EXEC_STATIC_MF_CREATE, "); + break; + case I2O_CMD_STATIC_MF_RELEASE: + printk("EXEC_STATIC_MF_RELEASE, "); + break; + case I2O_CMD_STATUS_GET: + printk("EXEC_STATUS_GET, "); + break; + case I2O_CMD_SW_DOWNLOAD: + printk("EXEC_SW_DOWNLOAD, "); + break; + case I2O_CMD_SW_UPLOAD: + printk("EXEC_SW_UPLOAD, "); + break; + case I2O_CMD_SW_REMOVE: + printk("EXEC_SW_REMOVE, "); + break; + case I2O_CMD_SYS_ENABLE: + printk("EXEC_SYS_ENABLE, "); + break; + case I2O_CMD_SYS_MODIFY: + printk("EXEC_SYS_MODIFY, "); + break; + case I2O_CMD_SYS_QUIESCE: + printk("EXEC_SYS_QUIESCE, "); + break; + case I2O_CMD_SYS_TAB_SET: + printk("EXEC_SYS_TAB_SET, "); + break; + default: + printk("%02x, ",cmd); + } + + return; +} + +static void report_lan_cmd(u8 cmd) +{ + switch (cmd) { + case LAN_PACKET_SEND: + printk("LAN_PACKET_SEND, "); + break; + case LAN_SDU_SEND: + printk("LAN_SDU_SEND, "); + break; + case LAN_RECEIVE_POST: + printk("LAN_RECEIVE_POST, "); + break; + case LAN_RESET: + printk("LAN_RESET, "); + break; + case LAN_SUSPEND: + printk("LAN_SUSPEND, "); + break; + default: + printk("%02x, ",cmd); + } + + return; +} + +/* TODO: Add support for other classes */ +void i2o_report_status(const char *severity, const char *module, u8 cmd, + u8 req_status, u16 detailed_status) +{ + printk("%s", severity); + printk("%s: ", module); + + if (cmd < 0x1F) { // Utility Class + report_util_cmd(cmd); + report_common_status(req_status); + report_common_dsc(detailed_status); + return; + } + + if (cmd >= 0x30 && cmd <= 0x3F) { // LAN class + report_lan_cmd(cmd); + report_common_status(req_status); + report_lan_dsc(detailed_status); + return; + } + + if (cmd >= 0xA0 && cmd <= 0xEF) { // Executive class + report_exec_cmd(cmd); + report_common_status(req_status); + report_common_dsc(detailed_status); + return; + } + + printk("%02x, %02x / %04x.\n", cmd, req_status, detailed_status); + return; +} + + +EXPORT_SYMBOL(i2o_install_handler); +EXPORT_SYMBOL(i2o_remove_handler); +EXPORT_SYMBOL(i2o_install_device); +EXPORT_SYMBOL(i2o_delete_device); +EXPORT_SYMBOL(i2o_quiesce_controller); +EXPORT_SYMBOL(i2o_clear_controller); +EXPORT_SYMBOL(i2o_install_controller); +EXPORT_SYMBOL(i2o_delete_controller); +EXPORT_SYMBOL(i2o_unlock_controller); +EXPORT_SYMBOL(i2o_find_controller); +EXPORT_SYMBOL(i2o_num_controllers); +EXPORT_SYMBOL(i2o_claim_device); +EXPORT_SYMBOL(i2o_release_device); +EXPORT_SYMBOL(i2o_run_queue); +EXPORT_SYMBOL(i2o_report_controller_unit); +EXPORT_SYMBOL(i2o_activate_controller); +EXPORT_SYMBOL(i2o_online_controller); +EXPORT_SYMBOL(i2o_get_class_name); + +EXPORT_SYMBOL(i2o_query_scalar); +EXPORT_SYMBOL(i2o_params_set); +EXPORT_SYMBOL(i2o_post_this); +EXPORT_SYMBOL(i2o_post_wait); +EXPORT_SYMBOL(i2o_issue_claim); + +EXPORT_SYMBOL(i2o_report_status); +EXPORT_SYMBOL(report_common_status); +EXPORT_SYMBOL(report_lan_dsc); + +EXPORT_SYMBOL(i2o_wait_message); + +MODULE_AUTHOR("Red Hat Software"); +MODULE_DESCRIPTION("I2O Core"); diff --git a/drivers/i2o/i2o_lan.c b/drivers/i2o/i2o_lan.c new file mode 100644 index 000000000..1ebbe0b49 --- /dev/null +++ b/drivers/i2o/i2o_lan.c @@ -0,0 +1,853 @@ +/* + * linux/drivers/i2o/i2o_lan.c + * + * I2O LAN CLASS OSM Prototyping, May 7th 1999 + * + * (C) Copyright 1999 University of Helsinki, + * Department of Computer Science + * + * This code is still under development / test. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Author: Auvo Häkkinen <Auvo.Hakkinen@cs.Helsinki.FI> + * + * Tested: in FDDI environment (using SysKonnect's DDM) + * in ETH environment (using Intel 82558 DDM proto) + * + * TODO: batch mode networking + * - this one assumes that we always get one packet in a bucket + * - we've not been able to test batch replies and batch receives + * error checking / timeouts + * - code/test for other LAN classes + */ + +#include <linux/module.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/fddidevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/malloc.h> +#include <linux/trdevice.h> +#include <asm/io.h> + +#include <linux/errno.h> + +#include <linux/i2o.h> +#include "i2o_lan.h" + +//#define DRIVERDEBUG +#ifdef DRIVERDEBUG +#define dprintk(s, args...) printk(s, ## args) +#else +#define dprintk(s, args...) +#endif + +#define MAX_LAN_CARDS 4 +static struct device *i2o_landevs[MAX_LAN_CARDS+1]; +static int unit = -1; /* device unit number */ + +struct i2o_lan_local { + u8 unit; + struct i2o_device *i2o_dev; + int reply_flag; // needed by scalar/table queries + struct fddi_statistics stats; +/* first fields are same as in struct net_device_stats stats; */ + unsigned short (*type_trans)(struct sk_buff *, struct device *); +}; + +/* function prototypes */ +static int i2o_lan_receive_post(struct device *dev); +static int i2o_lan_receive_post_reply(struct device *dev, struct i2o_message *m); + + +static void i2o_lan_reply(struct i2o_handler *h, struct i2o_controller *iop, + struct i2o_message *m) +{ + u32 *msg = (u32 *)m; + u8 unit = (u8)(msg[2]>>16); // InitiatorContext + struct device *dev = i2o_landevs[unit]; + +#ifdef DRIVERDEBUG + i2o_report_status(KERN_INFO, "i2o_lan", msg[1]>>24, msg[4]>>24, + msg[4]&0xFFFF); +#endif + if (msg[0] & (1<<13)) // Fail bit is set + { + printk(KERN_INFO "IOP failed to process the msg\n"); + printk("From tid=%d to tid=%d",(msg[1]>>12)&0xFFF,msg[1]&0xFFF); + return; + } + + switch (msg[1] >> 24) { + case LAN_RECEIVE_POST: + if (dev->start) + i2o_lan_receive_post_reply(dev,m); + else { + // we are getting unused buckets back + u8 trl_count = msg[3] & 0x000000FF; + struct i2o_bucket_descriptor *bucket = + (struct i2o_bucket_descriptor *)&msg[6]; + struct sk_buff *skb; + do { + dprintk("Releasing unused bucket\n"); + skb = (struct sk_buff *)bucket->context; + dev_kfree_skb(skb); + bucket++; + } while (--trl_count); + } + break; + + case LAN_PACKET_SEND: + case LAN_SDU_SEND: + { + u8 trl_count = msg[3] & 0x000000FF; + + if (msg[4] >> 24) // ReqStatus != SUCCESS + { + printk(KERN_WARNING "%s: ",dev->name); + report_common_status(msg[4]>>24); + report_lan_dsc(msg[4]&0xFFFF); + } + + do { // The HDM has handled the outgoing packet + dev_kfree_skb((struct sk_buff *)msg[4 + trl_count]); + dprintk(KERN_INFO "%s: Request skb freed (trl_count=%d).\n", + dev->name,trl_count); + } while (--trl_count); + + dev->tbusy = 0; + mark_bh(NET_BH); /* inform upper layers */ + } + break; + + default: + if (msg[2] & 0x80000000) // reply to a util get/set + { // flag for the i2o_post_wait + int *flag = (int *)msg[3]; + // ReqStatus != I2O_REPLY_STATUS_SUCCESS + *flag = (msg[4] >> 24) ? I2O_POST_WAIT_TIMEOUT + : I2O_POST_WAIT_OK ; + } + } +} + +static struct i2o_handler i2o_lan_handler = +{ + i2o_lan_reply, + "I2O Lan OSM", + 0 // context +}; +static int lan_context; + + +static int i2o_lan_receive_post_reply(struct device *dev, struct i2o_message *m) +{ + u32 *msg = (u32 *)m; + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_bucket_descriptor *bucket = (struct i2o_bucket_descriptor *)&msg[6]; + struct i2o_packet_info *packet; + + u8 trl_count = msg[3] & 0x000000FF; + struct sk_buff *skb; + +#ifdef 0 + dprintk(KERN_INFO "TrlFlags = 0x%02X, TrlElementSize = %d, TrlCount = %d\n" + "msgsize = %d, buckets_remaining = %d\n", + msg[3]>>24, msg[3]&0x0000FF00, trl_count, msg[0]>>16, msg[5]); +#endif + +/* + * NOTE: here we assume that also in batch mode we will get only + * one packet per bucket. This can be ensured by setting the + * PacketOrphanLimit to MaxPacketSize, as well as the bucket size. + */ + do { + /* packet is not at all needed here */ + packet = (struct i2o_packet_info *)bucket->packet_info; +#ifdef 0 + dprintk(KERN_INFO "flags = 0x%02X, offset = 0x%06X, status = 0x%02X, length = %d\n", + packet->flags, packet->offset, packet->status, packet->len); +#endif + skb = (struct sk_buff *)(bucket->context); + skb_put(skb,packet->len); + skb->dev = dev; + skb->protocol = priv->type_trans(skb, dev); + netif_rx(skb); + + dprintk(KERN_INFO "%s: Incoming packet (%d bytes) delivered " + "to upper level.\n",dev->name,packet->len); + + bucket++; // to next Packet Descriptor Block + + } while (--trl_count); + + if (msg[5] <= I2O_BUCKET_THRESH) // BucketsRemaining + i2o_lan_receive_post(dev); + + return 0; +} + +/* ==================================================== + * Interface to i2o: functions to send lan class request + */ + +/* + * i2o_lan_receive_post(): Post buckets to receive packets. + */ +static int i2o_lan_receive_post(struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + struct sk_buff *skb; + u32 m; u32 *msg; + + u32 bucket_len = (dev->mtu + dev->hard_header_len); + u32 bucket_count; + int n_elems = (iop->inbound_size - 16 ) / 12; // msg header + SGLs + u32 total = 0; + int i; + + dprintk(KERN_INFO "%s: Allocating %d buckets (size %d).\n", + dev->name, I2O_BUCKET_COUNT, bucket_len); + + while (total < I2O_BUCKET_COUNT) + { + m = I2O_POST_READ32(iop); + if (m == 0xFFFFFFFF) + return -ETIMEDOUT; + msg = bus_to_virt(iop->mem_offset + m); + + bucket_count = (total + n_elems < I2O_BUCKET_COUNT) + ? n_elems + : I2O_BUCKET_COUNT - total; + + msg[0] = I2O_MESSAGE_SIZE(4 + 3 * bucket_count) | 1<<12 | SGL_OFFSET_4; + msg[1] = LAN_RECEIVE_POST<<24 | HOST_TID<<12 | i2o_dev->id; + msg[2] = priv->unit << 16 | lan_context; // InitiatorContext + msg[3] = bucket_count; // BucketCount + + for (i = 0; i < bucket_count; i++) + { + skb = dev_alloc_skb(bucket_len + 2); + if (skb == NULL) + return -ENOMEM; + skb_reserve(skb, 2); + msg[4 + 3*i] = 0x51000000 | bucket_len; + msg[5 + 3*i] = (u32)skb; + msg[6 + 3*i] = virt_to_bus(skb->data); + } + msg[4 + 3*i - 3] |= 0x80000000; // set LE flag + i2o_post_message(iop,m); + + dprintk(KERN_INFO "%s: Sending %d buckets (size %d) to LAN HDM.\n", + dev->name,bucket_count,bucket_len); + + total += bucket_count; + } + return 0; +} + +/* + * i2o_lan_reset(): Reset the LAN adapter into the operational state and + * restore it to full operation. + */ +static int i2o_lan_reset(struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + u32 m; u32 *msg; + + m = I2O_POST_READ32(iop); + if (m == 0xFFFFFFFF) + return -ETIMEDOUT; + msg = bus_to_virt(iop->mem_offset + m); + + msg[0] = FIVE_WORD_MSG_SIZE | SGL_OFFSET_0; + msg[1] = LAN_RESET<<24 | HOST_TID<<12 | i2o_dev->id; + msg[2] = priv->unit << 16 | lan_context; // InitiatorContext + msg[3] = 0; // TransactionContext + msg[4] = 1 << 16; // return posted buckets + + i2o_post_message(iop,m); + + return 0; +} + +/* + * i2o_lan_suspend(): Put LAN adapter into a safe, non-active state. + * Reply to any LAN class message with status error_no_data_transfer + * / suspended. + */ +static int i2o_lan_suspend(struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + u32 m; u32 *msg; + + m = I2O_POST_READ32(iop); + if (m == 0xFFFFFFFF) + return -ETIMEDOUT; + msg = bus_to_virt(iop->mem_offset + m); + + msg[0] = FIVE_WORD_MSG_SIZE | SGL_OFFSET_0; + msg[1] = LAN_SUSPEND<<24 | HOST_TID<<12 | i2o_dev->id; + msg[2] = priv->unit << 16 | lan_context; // InitiatorContext + msg[3] = 0; // TransactionContext + msg[4] = 1 << 16; // return posted buckets + + i2o_post_message(iop,m); + + return 0; +} + +/* + * Set DDM into batch mode. + */ +static void i2o_set_batch_mode(struct device *dev) +{ + +/* + * NOTE: we have not been able to test batch mode + * since HDMs we have, don't implement it + */ + + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + u32 val; + + /* set LAN_BATCH_CONTROL attributes */ + + // enable batch mode, toggle automatically + val = 0x00000000; + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0003, 0, + &val, 4, &priv->reply_flag) <0) + printk(KERN_WARNING "Unable to enter I2O LAN batch mode.\n"); + else + dprintk(KERN_INFO "%s: I2O LAN batch mode enabled.\n",dev->name); + + /* + * When PacketOrphanlimit is same as the maximum packet length, + * the packets will never be split into two separate buckets + */ + + /* set LAN_OPERATION attributes */ + + val = dev->mtu + dev->hard_header_len; // PacketOrphanLimit + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0004, 2, + &val, 4, &priv->reply_flag) < 0) + printk(KERN_WARNING "i2o_lan: Unable to set PacketOrphanLimit.\n"); + else + dprintk(KERN_INFO "PacketOrphanLimit set to %d\n",val); + +#ifdef 0 +/* + * I2O spec 2.0: there should be proper default values for other attributes + * used in batch mode. + */ + + /* set LAN_RECEIVE_INFO attributes */ + + val = 10; // RxMaxBucketsReply + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0008, 3, + &val, 4, &priv->reply_flag) < 0) + printk(KERN_WARNING "%s: Unable to set RxMaxBucketsReply.\n", + dev->name); + + val = 10; // RxMaxPacketsBuckets + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0008, 4, + &val, 4, &priv->reply_flag) < 0) + printk(KERN_WARNING "%s: Unable to set RxMaxPacketsBucket.\n", + dev->name); + + /* set LAN_BATCH_CONTROL attributes */ + + val = 10; // MaxRxBatchCount + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0003, 5, + &val, 4, &priv->reply_flag) < 0) + printk(KERN_WARNING "%s: Unable to set MaxRxBatchCount.\n", + dev->name); + + val = 10; // MaxTxBatchCount + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0003, 8, + &val, 4, &priv->reply_flag) < 0) + printk(KERN_WARNING "%s Unable to set MaxTxBatchCount.\n", + dev->name); +#endif + + return; +} + +/* + * i2o_lan_open(): Open the device to send/receive packets via + * the network device. + */ +static int i2o_lan_open(struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + + i2o_lan_reset(dev); + + if (i2o_issue_claim(iop, i2o_dev->id, lan_context, 1, + &priv->reply_flag) < 0) + { + printk(KERN_WARNING "%s: Unable to claim the I2O LAN device.\n", dev->name); + return -EAGAIN; + } + dprintk(KERN_INFO "%s: I2O LAN device claimed (tid=%d).\n", dev->name, i2o_dev->id); + + dev->tbusy = 0; + dev->start = 1; + + i2o_set_batch_mode(dev); + i2o_lan_receive_post(dev); + + MOD_INC_USE_COUNT; + + return 0; +} + +/* + * i2o_lan_close(): End the transfering. + */ +static int i2o_lan_close(struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + + dev->tbusy = 1; + dev->start = 0; + + if (i2o_issue_claim(iop, i2o_dev->id, lan_context, 0, + &priv->reply_flag) < 0) + { + printk(KERN_WARNING "%s: Unable to unclaim I2O LAN device (tid=%d)\n", + dev->name, i2o_dev->id); + } + + i2o_lan_suspend(dev); + + MOD_DEC_USE_COUNT; + + return 0; +} + +/* + * i2o_lan_sdu_send(): Send a packet, MAC header added by the HDM. + * Must be supported by Fibre Channel, optional for Ethernet/802.3, + * Token Ring, FDDI + */ +static int i2o_lan_sdu_send(struct sk_buff *skb, struct device *dev) +{ +#ifdef 0 +/* not yet tested */ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + u32 m; u32 *msg; + + dprintk(KERN_INFO "LanSDUSend called, skb->len = %d\n", skb->len); + + m = *iop->post_port; + if (m == 0xFFFFFFFF) + { + dev_kfree_skb(skb); + return -1; + } + msg = bus_to_virt(iop->mem_offset + m); + + msg[0] = NINE_WORD_MSG_SIZE | SGL_OFFSET_4; + msg[1] = LAN_SDU_SEND<<24 | HOST_TID<<12 | i2o_dev->id; + msg[2] = priv->unit << 16 | lan_context; // IntiatorContext + msg[3] = 1<<4; // TransmitControlWord: suppress CRC generation + + // create a simple SGL, see fig. 3-26 + // D7 = 1101 0111 = LE eob 0 1 LA dir bc1 bc0 + + msg[4] = 0xD7000000 | (skb->len); // no MAC hdr included + msg[5] = (u32)skb; // TransactionContext + memcpy(&msg[6], skb->data, 8); // Destination MAC Addr ?? + msg[7] &= 0x0000FFFF; // followed by two bytes zeros + msg[8] = virt_to_bus(skb->data); + dev->trans_start = jiffies; + i2o_post_message(iop,m); + + dprintk(KERN_INFO "%s: Packet (%d bytes) sent to network.\n", + dev->name,skb->len); +#endif + return 0; +} + +/* + * i2o_lan_packet_send(): Send a packet as is, including the MAC header. + * + * Must be supported by Ethernet/802.3, Token Ring, FDDI, optional for + * Fibre Channel + */ +static int i2o_lan_packet_send(struct sk_buff *skb, struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + u32 m; u32 *msg; + + m = *iop->post_port; + if (m == 0xFFFFFFFF) { + dev_kfree_skb(skb); + return -1; + } + + msg = bus_to_virt(iop->mem_offset + m); + + msg[0] = SEVEN_WORD_MSG_SIZE | 1<<12 | SGL_OFFSET_4; + msg[1] = LAN_PACKET_SEND<<24 | HOST_TID<<12 | i2o_dev->id; + msg[2] = priv->unit << 16 | lan_context; // IntiatorContext + msg[3] = 1 << 4; // TransmitControlWord + + // create a simple SGL, see fig. 3-26 + // D5 = 1101 0101 = LE eob 0 1 LA dir bc1 bc0 + + msg[4] = 0xD5000000 | skb->len; // MAC hdr included + msg[5] = (u32)skb; // TransactionContext + msg[6] = virt_to_bus(skb->data); + + i2o_post_message(iop,m); + + dprintk(KERN_INFO "%s: Packet (%d bytes) sent to network.\n", + dev->name, skb->len); + + return 0; +} + +/* + * net_device_stats(): Return statistical information. + */ +static struct net_device_stats *i2o_lan_get_stats(struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + u64 val[16]; + + /* query LAN_HISTORICAL_STATS scalar parameter group 0x0100 */ + + i2o_query_scalar(iop, i2o_dev->id, lan_context, 0x0100, -1, + &val, 16*8, &priv->reply_flag); + priv->stats.tx_packets = val[0]; + priv->stats.tx_bytes = val[1]; + priv->stats.rx_packets = val[2]; + priv->stats.rx_bytes = val[3]; + priv->stats.tx_errors = val[4]; + priv->stats.rx_errors = val[5]; + priv->stats.rx_dropped = val[6]; + + // other net_device_stats and FDDI class specific fields follow ... + + return (struct net_device_stats *)&priv->stats; +} + +/* + * i2o_lan_set_multicast_list(): Enable a network device to receive packets + * not send to the protocol address. + */ +static void i2o_lan_set_multicast_list(struct device *dev) +{ + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + struct i2o_controller *iop = i2o_dev->controller; + u32 filter_mask; + + dprintk(KERN_INFO "Entered i2o_lan_set_multicast_list().\n"); + +return; + +/* + * FIXME: For some reason this kills interrupt handler in i2o_post_wait :-( + * + */ + dprintk(KERN_INFO "dev->flags = 0x%08X, dev->mc_count = 0x%08X\n", + dev->flags,dev->mc_count); + + if (i2o_query_scalar(iop, i2o_dev->id, lan_context, 0x0001, 3, + &filter_mask, 4, &priv->reply_flag) < 0 ) + printk(KERN_WARNING "i2o_lan: Unable to query filter mask.\n"); + + dprintk(KERN_INFO "filter_mask = 0x%08X\n",filter_mask); + + if (dev->flags & IFF_PROMISC) + { + // Enable promiscuous mode + + filter_mask |= 0x00000002; + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0001, 3, + &filter_mask, 4, &priv->reply_flag) <0) + printk(KERN_WARNING "i2o_lan: Unable to enable promiscuous multicast mode.\n"); + else + dprintk(KERN_INFO "i2o_lan: Promiscuous multicast mode enabled.\n"); + + return; + } + +// if ((dev->flags & IFF_ALLMULTI) || dev->mc_count > HW_MAX_ADDRS) +// { +// // Disable promiscuous mode, use normal mode. +// hardware_set_filter(NULL); +// +// dprintk(KERN_INFO "i2o_lan: Disabled promiscuous mode, uses normal mode\n"); +// +// filter_mask = 0x00000000; +// i2o_params_set(iop, i2o_dev->id, lan_context, 0x0001, 3, +// &filter_mask, 4, &priv->reply_flag); +// +// return; +// } + + if (dev->mc_count) + { + // Walk the address list, and load the filter +// hardware_set_filter(dev->mc_list); + + filter_mask = 0x00000004; + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0001, 3, + &filter_mask, 4, &priv->reply_flag) <0) + printk(KERN_WARNING "i2o_lan: Unable to enable Promiscuous multicast mode.\n"); + else + dprintk(KERN_INFO "i2o_lan: Promiscuous multicast mode enabled.\n"); + + return; + } + + // Unicast + + filter_mask |= 0x00000300; // Broadcast, Multicast disabled + if (i2o_params_set(iop, i2o_dev->id, lan_context, 0x0001, 3, + &filter_mask, 4, &priv->reply_flag) <0) + printk(KERN_WARNING "i2o_lan: Unable to enable unicast mode.\n"); + else + dprintk(KERN_INFO "i2o_lan: Unicast mode enabled.\n"); + + return; +} + +struct device *i2o_lan_register_device(struct i2o_device *i2o_dev) +{ + struct device *dev = NULL; + struct i2o_lan_local *priv = NULL; + u8 hw_addr[8]; + unsigned short (*type_trans)(struct sk_buff *, struct device *); + + switch (i2o_dev->subclass) + { + case I2O_LAN_ETHERNET: + /* Note: init_etherdev calls + ether_setup() and register_netdevice() + and allocates the priv structure */ + + dev = init_etherdev(NULL, sizeof(struct i2o_lan_local)); + if (dev == NULL) + return NULL; + type_trans = eth_type_trans; + break; + +/* +#ifdef CONFIG_ANYLAN + case I2O_LAN_100VG: + printk(KERN_WARNING "i2o_lan: 100base VG not yet supported\n"); + break; +#endif +*/ + +#ifdef CONFIG_TR + case I2O_LAN_TR: + dev = init_trdev(NULL, sizeof(struct i2o_lan_local)); + if(dev==NULL) + return NULL; + type_trans = tr_type_trans; + break; +#endif + +#ifdef CONFIG_FDDI + case I2O_LAN_FDDI: + { + int size = sizeof(struct device) + sizeof(struct i2o_lan_local) + + sizeof("fddi%d "); + + dev = (struct device *) kmalloc(size, GFP_KERNEL); + memset((char *)dev, 0, size); + dev->priv = (void *)(dev + 1); + dev->name = (char *)(dev + 1) + sizeof(struct i2o_lan_local); + + if (dev_alloc_name(dev,"fddi%d") < 0) + { + printk(KERN_WARNING "i2o_lan: Too many FDDI devices.\n"); + kfree(dev); + return NULL; + } + type_trans = fddi_type_trans; + + fddi_setup(dev); + register_netdev(dev); + } + break; +#endif + +/* +#ifdef CONFIG_FIBRE_CHANNEL + case I2O_LAN_FIBRE_CHANNEL: + printk(KERN_WARNING "i2o_lan: Fibre Channel not yet supported\n"); + break; +#endif +*/ + case I2O_LAN_UNKNOWN: + default: + printk(KERN_WARNING "i2o_lan: LAN type 0x%08X not supported\n", + i2o_dev->subclass); + return NULL; + } + + priv = (struct i2o_lan_local *)dev->priv; + priv->i2o_dev = i2o_dev; + priv->type_trans = type_trans; + + if (i2o_query_scalar(i2o_dev->controller, i2o_dev->id, lan_context, + 0x0001, 0, &hw_addr, 8, &priv->reply_flag) < 0) + { + printk("%s: Unable to query hardware address.\n", + dev->name); + return NULL; + } + + dprintk("%s hwaddr = %02X:%02X:%02X:%02X:%02X:%02X\n", + dev->name,hw_addr[0], hw_addr[1], hw_addr[2], hw_addr[3], + hw_addr[4], hw_addr[5]); + + dev->addr_len = 6; + memcpy(dev->dev_addr, hw_addr, 6); + + dev->open = i2o_lan_open; + dev->stop = i2o_lan_close; + dev->hard_start_xmit = i2o_lan_packet_send; + dev->get_stats = i2o_lan_get_stats; + dev->set_multicast_list = i2o_lan_set_multicast_list; + + return dev; +} + +#ifdef MODULE + +int init_module(void) +{ + struct device *dev; + struct i2o_lan_local *priv; + int i; + + if (i2o_install_handler(&i2o_lan_handler) < 0) + { + printk(KERN_ERR "Unable to register I2O LAN OSM.\n"); + return -EINVAL; + } + + lan_context = i2o_lan_handler.context; + + for (i=0; i < MAX_I2O_CONTROLLERS; i++) + { + struct i2o_controller *iop = i2o_find_controller(i); + struct i2o_device *i2o_dev; + + if (iop==NULL) + continue; + + for (i2o_dev=iop->devices;i2o_dev != NULL;i2o_dev=i2o_dev->next) + { + int class = i2o_dev->class; + + if (class != 0x020) /* not I2O_CLASS_LAN device*/ + continue; + + if (unit == MAX_LAN_CARDS) + { + printk(KERN_WARNING "Too many I2O LAN devices.\n"); + return -EINVAL; + } + + dev = i2o_lan_register_device(i2o_dev); + if (dev == NULL) + { + printk(KERN_WARNING "Unable to register I2O LAN device\n"); + continue; // try next one + } + priv = (struct i2o_lan_local *)dev->priv; + + unit++; + i2o_landevs[unit] = dev; + priv->unit = unit; + + printk(KERN_INFO "%s: I2O LAN device registered, tid = %d," + " subclass = 0x%08X, unit = %d.\n", + dev->name, i2o_dev->id, i2o_dev->subclass, + priv->unit); + } + } + + dprintk(KERN_INFO "%d I2O LAN devices found and registered.\n", unit+1); + + return 0; +} + +void cleanup_module(void) +{ + int i; + + for (i = 0; i <= unit; i++) + { + struct device *dev = i2o_landevs[i]; + struct i2o_lan_local *priv = (struct i2o_lan_local *)dev->priv; + struct i2o_device *i2o_dev = priv->i2o_dev; + + switch (i2o_dev->subclass) + { + case I2O_LAN_ETHERNET: + unregister_netdev(dev); + kfree(dev); + break; +#ifdef CONFIG_FDDI + case I2O_LAN_FDDI: + unregister_netdevice(dev); + kfree(dev); + break; +#endif +#ifdef CONFIG_TR + case I2O_LAN_TR: + unregister_netdev(dev); + kfree(dev); + break; +#endif + default: + printk(KERN_WARNING "i2o_lan: Spurious I2O LAN subclass 0x%08X.\n", + i2o_dev->subclass); + } + + dprintk(KERN_INFO "%s: I2O LAN device unregistered.\n", + dev->name); + } + + i2o_remove_handler(&i2o_lan_handler); +} + +EXPORT_NO_SYMBOLS; +MODULE_AUTHOR("Univ of Helsinki, CS Department"); +MODULE_DESCRIPTION("I2O Lan OSM"); + +#endif diff --git a/drivers/i2o/i2o_lan.h b/drivers/i2o/i2o_lan.h new file mode 100644 index 000000000..c8e82bf41 --- /dev/null +++ b/drivers/i2o/i2o_lan.h @@ -0,0 +1,112 @@ +/* + * i2o_lan.h LAN Class specific definitions + * + * I2O LAN CLASS OSM Prototyping, May 7th 1999 + * + * (C) Copyright 1999 University of Helsinki, + * Department of Computer Science + * + * This code is still under development / test. + * + * Author: Auvo Häkkinen <Auvo.Hakkinen@cs.Helsinki.FI> + * + */ + +#ifndef I2O_LAN_H +#define I2O_LAN_H + +/* Tunable parameters first */ + +#define I2O_BUCKET_COUNT 64 +#define I2O_BUCKET_THRESH 5 + +/* LAN types */ +#define I2O_LAN_ETHERNET 0x0030 +#define I2O_LAN_100VG 0x0040 +#define I2O_LAN_TR 0x0050 +#define I2O_LAN_FDDI 0x0060 +#define I2O_LAN_FIBRE_CHANNEL 0x0070 +#define I2O_LAN_UNKNOWN 0x00000000 + +/* Connector types */ + +/* Ethernet */ +#define I2O_LAN_AUI (I2O_LAN_ETHERNET << 4) + 0x00000001 +#define I2O_LAN_10BASE5 (I2O_LAN_ETHERNET << 4) + 0x00000002 +#define I2O_LAN_FIORL (I2O_LAN_ETHERNET << 4) + 0x00000003 +#define I2O_LAN_10BASE2 (I2O_LAN_ETHERNET << 4) + 0x00000004 +#define I2O_LAN_10BROAD36 (I2O_LAN_ETHERNET << 4) + 0x00000005 +#define I2O_LAN_10BASE_T (I2O_LAN_ETHERNET << 4) + 0x00000006 +#define I2O_LAN_10BASE_FP (I2O_LAN_ETHERNET << 4) + 0x00000007 +#define I2O_LAN_10BASE_FB (I2O_LAN_ETHERNET << 4) + 0x00000008 +#define I2O_LAN_10BASE_FL (I2O_LAN_ETHERNET << 4) + 0x00000009 +#define I2O_LAN_100BASE_TX (I2O_LAN_ETHERNET << 4) + 0x0000000A +#define I2O_LAN_100BASE_FX (I2O_LAN_ETHERNET << 4) + 0x0000000B +#define I2O_LAN_100BASE_T4 (I2O_LAN_ETHERNET << 4) + 0x0000000C +#define I2O_LAN_1000BASE_SX (I2O_LAN_ETHERNET << 4) + 0x0000000D +#define I2O_LAN_1000BASE_LX (I2O_LAN_ETHERNET << 4) + 0x0000000E +#define I2O_LAN_1000BASE_CX (I2O_LAN_ETHERNET << 4) + 0x0000000F +#define I2O_LAN_1000BASE_T (I2O_LAN_ETHERNET << 4) + 0x00000010 + +/* AnyLAN */ +#define I2O_LAN_100VG_ETHERNET (I2O_LAN_100VG << 4) + 0x00000001 +#define I2O_LAN_100VG_TR (I2O_LAN_100VG << 4) + 0x00000002 + +/* Token Ring */ +#define I2O_LAN_4MBIT (I2O_LAN_TR << 4) + 0x00000001 +#define I2O_LAN_16MBIT (I2O_LAN_TR << 4) + 0x00000002 + +/* FDDI */ +#define I2O_LAN_125MBAUD (I2O_LAN_FDDI << 4) + 0x00000001 + +/* Fibre Channel */ +#define I2O_LAN_POINT_POINT (I2O_LAN_FIBRE_CHANNEL << 4) + 0x00000001 +#define I2O_LAN_ARB_LOOP (I2O_LAN_FIBRE_CHANNEL << 4) + 0x00000002 +#define I2O_LAN_PUBLIC_LOOP (I2O_LAN_FIBRE_CHANNEL << 4) + 0x00000003 +#define I2O_LAN_FABRIC (I2O_LAN_FIBRE_CHANNEL << 4) + 0x00000004 + +#define I2O_LAN_EMULATION 0x00000F00 +#define I2O_LAN_OTHER 0x00000F01 +#define I2O_LAN_DEFAULT 0xFFFFFFFF + +/* LAN class functions */ + +#define LAN_PACKET_SEND 0x3B +#define LAN_SDU_SEND 0x3D +#define LAN_RECEIVE_POST 0x3E +#define LAN_RESET 0x35 +#define LAN_SUSPEND 0x37 + +/* LAN DetailedStatusCode defines */ +#define I2O_LAN_DSC_SUCCESS 0x00 +#define I2O_LAN_DSC_DEVICE_FAILURE 0x01 +#define I2O_LAN_DSC_DESTINATION_NOT_FOUND 0x02 +#define I2O_LAN_DSC_TRANSMIT_ERROR 0x03 +#define I2O_LAN_DSC_TRANSMIT_ABORTED 0x04 +#define I2O_LAN_DSC_RECEIVE_ERROR 0x05 +#define I2O_LAN_DSC_RECEIVE_ABORTED 0x06 +#define I2O_LAN_DSC_DMA_ERROR 0x07 +#define I2O_LAN_DSC_BAD_PACKET_DETECTED 0x08 +#define I2O_LAN_DSC_OUT_OF_MEMORY 0x09 +#define I2O_LAN_DSC_BUCKET_OVERRUN 0x0A +#define I2O_LAN_DSC_IOP_INTERNAL_ERROR 0x0B +#define I2O_LAN_DSC_CANCELED 0x0C +#define I2O_LAN_DSC_INVALID_TRANSACTION_CONTEXT 0x0D +#define I2O_LAN_DSC_DEST_ADDRESS_DETECTED 0x0E +#define I2O_LAN_DSC_DEST_ADDRESS_OMITTED 0x0F +#define I2O_LAN_DSC_PARTIAL_PACKET_RETURNED 0x10 +#define I2O_LAN_DSC_TEMP_SUSPENDED_STATE 0x11 + +struct i2o_packet_info { + u32 offset : 24; + u32 flags : 8; + u32 len : 24; + u32 status : 8; +}; + +struct i2o_bucket_descriptor { + u32 context; /* FIXME: 64bit support */ + struct i2o_packet_info packet_info[1]; +}; + +#endif /* I2O_LAN_H */ diff --git a/drivers/i2o/i2o_pci.c b/drivers/i2o/i2o_pci.c new file mode 100644 index 000000000..596d9f953 --- /dev/null +++ b/drivers/i2o/i2o_pci.c @@ -0,0 +1,243 @@ +/* + * Find I2O capable controllers on the PCI bus, and register/install + * them with the I2O layer + * + * (C) Copyright 1999 Red Hat Software + * + * Written by Alan Cox, Building Number Three Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/i2o.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/malloc.h> +#include <asm/io.h> + +/* + * Free bus specific resources + */ + +static void i2o_pci_dispose(struct i2o_controller *c) +{ + I2O_IRQ_WRITE32(c,0xFFFFFFFF); + if(c->bus.pci.irq > 0) + free_irq(c->bus.pci.irq, c); + iounmap(((u8 *)c->post_port)-0x40); +} + +/* + * No real bus specific handling yet (note that later we will + * need to 'steal' PCI devices on i960 mainboards) + */ + +static int i2o_pci_bind(struct i2o_controller *c, struct i2o_device *dev) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static int i2o_pci_unbind(struct i2o_controller *c, struct i2o_device *dev) +{ + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * Bus specific interrupt handler + */ + +static void i2o_pci_interrupt(int irq, void *dev_id, struct pt_regs *r) +{ + struct i2o_controller *c = dev_id; + i2o_run_queue(c); +} + +/* + * Install a PCI (or in theory AGP) i2o controller + */ + +int __init i2o_pci_install(struct pci_dev *dev) +{ + struct i2o_controller *c=kmalloc(sizeof(struct i2o_controller), + GFP_KERNEL); + u8 *mem; + u32 memptr = 0; + u32 size; + + int i; + + if(c==NULL) + { + printk(KERN_ERR "i2o_pci: insufficient memory to add controller.\n"); + return -ENOMEM; + } + memset(c, 0, sizeof(*c)); + + for(i=0; i<6; i++) + { + /* Skip I/O spaces */ + if(!(dev->base_address[i]&PCI_BASE_ADDRESS_SPACE)) + { + memptr=PCI_BASE_ADDRESS_MEM_MASK&dev->base_address[i]; + break; + } + } + + if(i==6) + { + printk(KERN_ERR "i2o_pci: I2O controller has no memory regions defined.\n"); + return -ENOMEM; + } + + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0+4*i, 0xFFFFFFFF); + pci_read_config_dword(dev, PCI_BASE_ADDRESS_0+4*i, &size); + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0+4*i, dev->base_address[i]); + + /* Map the I2O controller */ + + printk(KERN_INFO "PCI I2O controller at 0x%08X size=%d\n", memptr, -size); + mem = ioremap(memptr, -size); + + c->bus.pci.irq = -1; + + c->irq_mask = (volatile u32 *)(mem+0x34); + c->post_port = (volatile u32 *)(mem+0x40); + c->reply_port = (volatile u32 *)(mem+0x44); + + c->mem_phys = memptr; + c->mem_offset = (u32)mem; + c->destructor = i2o_pci_dispose; + + c->bind = i2o_pci_bind; + c->unbind = i2o_pci_unbind; + + c->type = I2O_TYPE_PCI; + + I2O_IRQ_WRITE32(c,0xFFFFFFFF); + + i = i2o_install_controller(c); + + if(i<0) + { + printk(KERN_ERR "i2o: unable to install controller.\n"); + return i; + } + + c->bus.pci.irq = dev->irq; + if(c->bus.pci.irq) + { + i=request_irq(dev->irq, i2o_pci_interrupt, SA_SHIRQ, + c->name, c); + if(i<0) + { + printk(KERN_ERR "%s: unable to allocate interrupt %d.\n", + c->name, dev->irq); + c->bus.pci.irq = -1; + i2o_delete_controller(c); + return -EBUSY; + } + } + return 0; +} + +int __init i2o_pci_scan(void) +{ + struct pci_dev *dev; + int count=0; + + printk(KERN_INFO "Checking for PCI I2O controllers...\n"); + + for(dev=pci_devices; dev!=NULL; dev=dev->next) + { + if((dev->class>>8)!=PCI_CLASS_INTELLIGENT_I2O) + continue; + if((dev->class&0xFF)>1) + { + printk(KERN_INFO "I2O controller found but does not support I2O 1.5 (skipping).\n"); + continue; + } + printk(KERN_INFO "I2O controller on bus %d at %d.\n", + dev->bus->number, dev->devfn); + if(!dev->master) + printk(KERN_WARNING "Controller not master enabled.\n"); + if(i2o_pci_install(dev)==0) + count++; + } + if(count) + printk(KERN_INFO "%d I2O controller%s found and installed.\n", count, + count==1?"":"s"); + return count?count:-ENODEV; +} + +static void i2o_pci_unload(void) +{ + int i=0; + struct i2o_controller *c; + + for(i = 0; i < MAX_I2O_CONTROLLERS; i++) + { + c=i2o_find_controller(i); + if(c==NULL) + continue; + if(c->type == I2O_TYPE_PCI) + i2o_delete_controller(c); + i2o_unlock_controller(c); + } +} + +static void i2o_pci_activate(void) +{ + int i=0; + struct i2o_controller *c; + + for(i = 0; i < MAX_I2O_CONTROLLERS; i++) + { + c=i2o_find_controller(i); + if(c==NULL) + continue; + if(c->type == I2O_TYPE_PCI) + { + if(i2o_activate_controller(c)) + { + printk("I2O: Failed to initialize iop%d\n", c->unit); + i2o_unlock_controller(c); + free_irq(c->bus.pci.irq, c); + i2o_delete_controller(c); + continue; + } + + I2O_IRQ_WRITE32(c,0); + } + i2o_unlock_controller(c); + } +} + +#ifdef MODULE + +EXPORT_NO_SYMBOLS; +MODULE_AUTHOR("Red Hat Software"); +MODULE_DESCRIPTION("I2O PCI Interface"); + +int init_module(void) +{ + if(i2o_pci_scan()<0) + return -ENODEV; + i2o_pci_activate(); + return 0; +} + +void cleanup_module(void) +{ + i2o_pci_unload(); +} + +#endif diff --git a/drivers/i2o/i2o_proc.c b/drivers/i2o/i2o_proc.c new file mode 100644 index 000000000..ce38b3914 --- /dev/null +++ b/drivers/i2o/i2o_proc.c @@ -0,0 +1,2382 @@ +/* + * procfs handler for Linux I2O subsystem + * + * Copyright (c) 1999 Intel Corporation + * + * Originally written by Deepak Saxena(deepak.saxena@intel.com) + * + * This program is free software. You can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This is an initial test release. The code is based on the design + * of the ide procfs system (drivers/block/ide-proc.c). Some code + * taken from i2o-core module by Alan Cox. + * + * DISCLAIMER: This code is still under development/test and may cause + * your system to behave unpredictably. Use at your own discretion. + * + * LAN entries by Juha Sievänen(Juha.Sievanen@cs.Helsinki.FI), + * University of Helsinki, Department of Computer Science + * + */ + +/* + * set tabstop=3 + */ + +/* + * TODO List + * + * - Add support for any version 2.0 spec changes once 2.0 IRTOS is + * is available to test with + * - Clean up code to use official structure definitions + */ + +// FIXME! +#define FMT_U64_HEX "0x%08x%08x" +#define U64_VAL(pu64) *((u32*)(pu64)+1), *((u32*)(pu64)) + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/i2o.h> +#include <linux/proc_fs.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/errno.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/byteorder.h> +#include <asm/spinlock.h> + + +#include "i2o_proc.h" + +#include "i2o_lan.h" + +/* + * Structure used to define /proc entries + */ +typedef struct _i2o_proc_entry_t +{ + char *name; /* entry name */ + mode_t mode; /* mode */ + read_proc_t *read_proc; /* read func */ + write_proc_t *write_proc; /* write func */ +} i2o_proc_entry; + +static int proc_context = 0; + + +static int i2o_proc_read_lct(char *, char **, off_t, int, int *, void *); +static int i2o_proc_read_hrt(char *, char **, off_t, int, int *, void *); +static int i2o_proc_read_stat(char *, char **, off_t, int, int *, void *); +static int i2o_proc_read_hw(char *, char **, off_t, int, int *, void *); +static int i2o_proc_read_dev(char *, char **, off_t, int, int *, void *); +static int i2o_proc_read_dev_name(char *, char **, off_t, int, int *, void *); +static int i2o_proc_read_ddm(char *, char **, off_t, int, int *, void *); +static int i2o_proc_read_uinfo(char *, char **, off_t, int, int *, void *); +static int print_serial_number(char *, int, u8 *, int); +static int i2o_proc_create_entries(void *, + i2o_proc_entry *p, struct proc_dir_entry *); +static void i2o_proc_remove_entries(i2o_proc_entry *p, + struct proc_dir_entry *); +static int i2o_proc_add_controller(struct i2o_controller *, + struct proc_dir_entry * ); +static void i2o_proc_remove_controller(struct i2o_controller *, + struct proc_dir_entry * ); +static int create_i2o_procfs(void); +static int destroy_i2o_procfs(void); +static void i2o_proc_reply(struct i2o_handler *, struct i2o_controller *, + struct i2o_message *); + +static int i2o_proc_read_lan_dev_info(char *, char **, off_t, int, int *, + void *); +static int i2o_proc_read_lan_mac_addr(char *, char **, off_t, int, int *, + void *); +static int i2o_proc_read_lan_curr_addr(char *, char **, off_t, int, int *, + void *); +#if 0 +static int i2o_proc_read_lan_mcast_addr(char *, char **, off_t, int, int *, + void *); +#endif +static int i2o_proc_read_lan_batch_control(char *, char **, off_t, int, int *, + void *); +static int i2o_proc_read_lan_operation(char *, char **, off_t, int, int *, + void *); +static int i2o_proc_read_lan_media_operation(char *, char **, off_t, int, + int *, void *); +#if 0 +static int i2o_proc_read_lan_alt_addr(char *, char **, off_t, int, int *, + void *); +#endif +static int i2o_proc_read_lan_tx_info(char *, char **, off_t, int, int *, + void *); +static int i2o_proc_read_lan_rx_info(char *, char **, off_t, int, int *, + void *); +static int i2o_proc_read_lan_hist_stats(char *, char **, off_t, int, int *, + void *); +static int i2o_proc_read_lan_opt_tx_hist_stats(char *, char **, off_t, int, + int *, void *); +static int i2o_proc_read_lan_opt_rx_hist_stats(char *, char **, off_t, int, + int *, void *); +static int i2o_proc_read_lan_fddi_stats(char *, char **, off_t, int, int *, + void *); + +#if 0 +/* Do we really need this??? */ + +static loff_t i2o_proc_lseek(struct file *file, loff_t off, int whence) +{ + return 0; +} +#endif + +static struct proc_dir_entry *i2o_proc_dir_root; + +/* + * Message handler + */ +static struct i2o_handler i2o_proc_handler = +{ + (void *)i2o_proc_reply, + "I2O procfs Layer", + 0 +}; + +/* + * IOP specific entries...write field just in case someone + * ever wants one. + */ +static i2o_proc_entry generic_iop_entries[] = +{ + {"hrt", S_IFREG|S_IRUGO, i2o_proc_read_hrt, NULL}, + {"lct", S_IFREG|S_IRUGO, i2o_proc_read_lct, NULL}, + {"stat", S_IFREG|S_IRUGO, i2o_proc_read_stat, NULL}, + {"hw", S_IFREG|S_IRUGO, i2o_proc_read_hw, NULL}, + {NULL, 0, NULL, NULL} +}; + +/* + * Device specific entries + */ +static i2o_proc_entry generic_dev_entries[] = +{ + {"dev_identity", S_IFREG|S_IRUGO, i2o_proc_read_dev, NULL}, + {"ddm_identity", S_IFREG|S_IRUGO, i2o_proc_read_ddm, NULL}, + {"user_info", S_IFREG|S_IRUGO, i2o_proc_read_uinfo, NULL}, + {NULL, 0, NULL, NULL} +}; + +/* + * Storage unit specific entries (SCSI Periph, BS) with device names + */ +static i2o_proc_entry rbs_dev_entries[] = +{ + {"dev_name", S_IFREG|S_IRUGO, i2o_proc_read_dev_name, NULL}, + {NULL, 0, NULL, NULL} +}; + +#define SCSI_TABLE_SIZE 13 + static char *scsi_devices[] = + { + "Direct-Access Read/Write", + "Sequential-Access Storage", + "Printer", + "Processor", + "WORM Device", + "CD-ROM Device", + "Scanner Device", + "Optical Memory Device", + "Medium Changer Device", + "Communications Device", + "Graphics Art Pre-Press Device", + "Graphics Art Pre-Press Device", + "Array Controller Device" + }; + +/* private */ + +/* + * LAN specific entries + * + * Should groups with r/w entries have their own subdirectory? + * + */ +static i2o_proc_entry lan_entries[] = +{ + /* LAN param groups 0000h-0008h */ + {"lan_dev_info", S_IFREG|S_IRUGO, i2o_proc_read_lan_dev_info, NULL}, + {"lan_mac_addr", S_IFREG|S_IRUGO, i2o_proc_read_lan_mac_addr, NULL}, +#if 0 + {"lan_mcast_addr", S_IFREG|S_IRUGO|S_IWUSR, + i2o_proc_read_lan_mcast_addr, NULL}, +#endif + {"lan_batch_ctrl", S_IFREG|S_IRUGO|S_IWUSR, + i2o_proc_read_lan_batch_control, NULL}, + {"lan_operation", S_IFREG|S_IRUGO, i2o_proc_read_lan_operation, NULL}, + {"lan_media_operation", S_IFREG|S_IRUGO, + i2o_proc_read_lan_media_operation, NULL}, +#if 0 + {"lan_alt_addr", S_IFREG|S_IRUGO, i2o_proc_read_lan_alt_addr, NULL}, +#endif + {"lan_tx_info", S_IFREG|S_IRUGO, i2o_proc_read_lan_tx_info, NULL}, + {"lan_rx_info", S_IFREG|S_IRUGO, i2o_proc_read_lan_rx_info, NULL}, + {"lan_stats", S_IFREG|S_IRUGO, i2o_proc_read_lan_hist_stats, NULL}, + {"lan_opt_tx_stats", S_IFREG|S_IRUGO, + i2o_proc_read_lan_opt_tx_hist_stats, NULL}, + {"lan_opt_rx_stats", S_IFREG|S_IRUGO, + i2o_proc_read_lan_opt_rx_hist_stats, NULL}, + {"lan_fddi_stats", S_IFREG|S_IRUGO, i2o_proc_read_lan_fddi_stats, NULL}, + /* some useful r/w entries, no write yet */ + {"lan_curr_addr", S_IFREG|S_IRUGO|S_IWUSR, + i2o_proc_read_lan_curr_addr, NULL}, + {NULL, 0, NULL, NULL} +}; + +static u32 i2o_proc_token = 0; + +static char* bus_strings[] = +{ + "Local Bus", + "ISA", + "EISA", + "MCA", + "PCI", + "PCMCIA", + "NUBUS", + "CARDBUS" +}; + +static spinlock_t i2o_proc_lock = SPIN_LOCK_UNLOCKED; + +void i2o_proc_reply(struct i2o_handler *phdlr, struct i2o_controller *pctrl, + struct i2o_message *pmsg) +{ + i2o_proc_token = I2O_POST_WAIT_OK; +} + +int i2o_proc_read_hrt(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_controller *c = (struct i2o_controller *)data; + pi2o_hrt hrt; + u32 msg[6]; + u32 *workspace; + u32 bus; + int count; + int i; + int token; + + spin_lock(&i2o_proc_lock); + + len = 0; + + workspace = kmalloc(2048, GFP_KERNEL); + hrt = (pi2o_hrt)workspace; + if(workspace==NULL) + { + len += sprintf(buf, "No free memory for HRT buffer\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + memset(workspace, 0, 2048); + + msg[0]= SIX_WORD_MSG_SIZE| SGL_OFFSET_4; + msg[1]= I2O_CMD_HRT_GET<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2]= (u32)proc_context; + msg[3]= 0; + msg[4]= (0xD0000000 | 2048); + msg[5]= virt_to_phys(workspace); + + token = i2o_post_wait(c, ADAPTER_TID, msg, 6*4, &i2o_proc_token,2); + if(token == I2O_POST_WAIT_TIMEOUT) + { + kfree(workspace); + len += sprintf(buf, "Timeout waiting for HRT\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + if(hrt->hrt_version) + { + len += sprintf(buf+len, + "HRT table for controller is too new a version.\n"); + return len; + } + + count = hrt->num_entries; + + if((count * hrt->entry_len + 8) > 2048) { + printk(KERN_WARNING "i2o_proc: HRT does not fit into buffer\n"); + len += sprintf(buf+len, + "HRT table too big to fit in buffer.\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf+len, "HRT has %d entries of %d bytes each.\n", + count, hrt->entry_len); + + for(i = 0; i < count; i++) + { + len += sprintf(buf+len, "Entry %d:\n", i); + len += sprintf(buf+len, " Adapter ID: %0#10x\n", + hrt->hrt_entry[i].adapter_id); + len += sprintf(buf+len, " Controlled by: %0#6x\n", + hrt->hrt_entry[i].parent_tid); + len += sprintf(buf+len, " Bus#%d\n", + hrt->hrt_entry[i].bus_num); + + if(hrt->hrt_entry[i].bus_type != 0x80) + { + bus = hrt->hrt_entry[i].bus_type; + len += sprintf(buf+len, " %s Information\n", bus_strings[bus]); + + switch(bus) + { + case I2O_BUS_LOCAL: + len += sprintf(buf+len, " IOBase: %0#6x,", + hrt->hrt_entry[i].bus.local_bus.LbBaseIOPort); + len += sprintf(buf+len, " MemoryBase: %0#10x\n", + hrt->hrt_entry[i].bus.local_bus.LbBaseMemoryAddress); + break; + + case I2O_BUS_ISA: + len += sprintf(buf+len, " IOBase: %0#6x,", + hrt->hrt_entry[i].bus.isa_bus.IsaBaseIOPort); + len += sprintf(buf+len, " MemoryBase: %0#10x,", + hrt->hrt_entry[i].bus.isa_bus.IsaBaseMemoryAddress); + len += sprintf(buf+len, " CSN: %0#4x,", + hrt->hrt_entry[i].bus.isa_bus.CSN); + break; + + case I2O_BUS_EISA: + len += sprintf(buf+len, " IOBase: %0#6x,", + hrt->hrt_entry[i].bus.eisa_bus.EisaBaseIOPort); + len += sprintf(buf+len, " MemoryBase: %0#10x,", + hrt->hrt_entry[i].bus.eisa_bus.EisaBaseMemoryAddress); + len += sprintf(buf+len, " Slot: %0#4x,", + hrt->hrt_entry[i].bus.eisa_bus.EisaSlotNumber); + break; + + case I2O_BUS_MCA: + len += sprintf(buf+len, " IOBase: %0#6x,", + hrt->hrt_entry[i].bus.mca_bus.McaBaseIOPort); + len += sprintf(buf+len, " MemoryBase: %0#10x,", + hrt->hrt_entry[i].bus.mca_bus.McaBaseMemoryAddress); + len += sprintf(buf+len, " Slot: %0#4x,", + hrt->hrt_entry[i].bus.mca_bus.McaSlotNumber); + break; + + case I2O_BUS_PCI: + len += sprintf(buf+len, " Bus: %0#4x", + hrt->hrt_entry[i].bus.pci_bus.PciBusNumber); + len += sprintf(buf+len, " Dev: %0#4x", + hrt->hrt_entry[i].bus.pci_bus.PciDeviceNumber); + len += sprintf(buf+len, " Func: %0#4x", + hrt->hrt_entry[i].bus.pci_bus.PciFunctionNumber); + len += sprintf(buf+len, " Vendor: %0#6x", + hrt->hrt_entry[i].bus.pci_bus.PciVendorID); + len += sprintf(buf+len, " Device: %0#6x\n", + hrt->hrt_entry[i].bus.pci_bus.PciDeviceID); + break; + + default: + len += sprintf(buf+len, " Unsupported Bus Type\n"); + } + } + else + len += sprintf(buf+len, " Unknown Bus Type\n"); + } + + kfree(workspace); + + spin_unlock(&i2o_proc_lock); + + return len; +} + +int i2o_proc_read_lct(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_controller *c = (struct i2o_controller*)data; + u32 msg[8]; + u32 *workspace; + pi2o_lct lct; /* = (pi2o_lct)c->lct; */ + int entries; + int token; + int i; + +#define BUS_TABLE_SIZE 3 + static char *bus_ports[] = + { + "Generic Bus", + "SCSI Bus", + "Fibre Channel Bus" + }; + + spin_lock(&i2o_proc_lock); + + len = 0; + + workspace = kmalloc(8192, GFP_KERNEL); + lct = (pi2o_lct)workspace; + if(workspace==NULL) + { + len += sprintf(buf, "No free memory for LCT buffer\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + memset(workspace, 0, 8192); + + msg[0] = FOUR_WORD_MSG_SIZE|SGL_OFFSET_6; + msg[1] = I2O_CMD_LCT_NOTIFY<<24 | HOST_TID<<12 | ADAPTER_TID; + msg[2] = (u32)proc_context; + msg[3] = 0; + msg[4] = 0xFFFFFFFF; /* All devices */ + msg[5] = 0x00000000; /* Report now */ + msg[6] = 0xD0000000|8192; + msg[7] = virt_to_bus(workspace); + + token = i2o_post_wait(c, ADAPTER_TID, msg, 8*4, &i2o_proc_token,2); + if(token == I2O_POST_WAIT_TIMEOUT) + { + kfree(workspace); + len += sprintf(buf, "Timeout waiting for LCT\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + entries = (lct->table_size - 3)/9; + + len += sprintf(buf, "LCT contains %d %s\n", entries, + entries == 1 ? "entry" : "entries"); + if(lct->boot_tid) + len += sprintf(buf+len, "Boot Device @ ID %d\n", lct->boot_tid); + + for(i = 0; i < entries; i++) + { + len += sprintf(buf+len, "Entry %d\n", i); + + len += sprintf(buf+len, " %s", i2o_get_class_name(lct->lct_entry[i].class_id)); + + /* + * Classes which we'll print subclass info for + */ + switch(lct->lct_entry[i].class_id & 0xFFF) + { + case I2O_CLASS_RANDOM_BLOCK_STORAGE: + switch(lct->lct_entry[i].sub_class) + { + case 0x00: + len += sprintf(buf+len, ": Direct-Access Read/Write"); + break; + + case 0x04: + len += sprintf(buf+len, ": WORM Drive"); + break; + + case 0x05: + len += sprintf(buf+len, ": CD-ROM Drive"); + break; + + case 0x07: + len += sprintf(buf+len, ": Optical Memory Device"); + break; + + default: + len += sprintf(buf+len, ": Unknown"); + break; + } + break; + + case I2O_CLASS_LAN: + switch(lct->lct_entry[i].sub_class & 0xFF) + { + case 0x30: + len += sprintf(buf+len, ": Ethernet"); + break; + + case 0x40: + len += sprintf(buf+len, ": 100base VG"); + break; + + case 0x50: + len += sprintf(buf+len, ": IEEE 802.5/Token-Ring"); + break; + + case 0x60: + len += sprintf(buf+len, ": ANSI X3T9.5 FDDI"); + break; + + case 0x70: + len += sprintf(buf+len, ": Fibre Channel"); + break; + + default: + len += sprintf(buf+len, ": Unknown Sub-Class"); + break; + } + break; + + case I2O_CLASS_SCSI_PERIPHERAL: + if(lct->lct_entry[i].sub_class < SCSI_TABLE_SIZE) + len += sprintf(buf+len, ": %s", + scsi_devices[lct->lct_entry[i].sub_class]); + else + len += sprintf(buf+len, ": Unknown Device Type"); + break; + + case I2O_CLASS_BUS_ADAPTER_PORT: + if(lct->lct_entry[i].sub_class < BUS_TABLE_SIZE) + len += sprintf(buf+len, ": %s", + bus_ports[lct->lct_entry[i].sub_class]); + else + len += sprintf(buf+len, ": Unknown Bus Type"); + break; + } + len += sprintf(buf+len, "\n"); + + len += sprintf(buf+len, " Local TID: 0x%03x\n", lct->lct_entry[i].tid); + len += sprintf(buf+len, " User TID: 0x%03x\n", lct->lct_entry[i].user_tid); + len += sprintf(buf+len, " Parent TID: 0x%03x\n", + lct->lct_entry[i].parent_tid); + len += sprintf(buf+len, " Identity Tag: 0x%x%x%x%x%x%x%x%x\n", + lct->lct_entry[i].identity_tag[0], + lct->lct_entry[i].identity_tag[1], + lct->lct_entry[i].identity_tag[2], + lct->lct_entry[i].identity_tag[3], + lct->lct_entry[i].identity_tag[4], + lct->lct_entry[i].identity_tag[5], + lct->lct_entry[i].identity_tag[6], + lct->lct_entry[i].identity_tag[7]); + len += sprintf(buf+len, " Change Indicator: %0#10x\n", + lct->lct_entry[i].change_ind); + len += sprintf(buf+len, " Device Flags: %0#10x\n", + lct->lct_entry[i].device_flags); + } + + kfree(workspace); + spin_unlock(&i2o_proc_lock); + + return len; +} + +int i2o_proc_read_stat(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_controller *c = (struct i2o_controller*)data; + u32 *msg; + u32 m; + u8 *workspace; + u16 *work16; + u32 *work32; + long time; + char prodstr[25]; + int version; + + spin_lock(&i2o_proc_lock); + + len = 0; + + workspace = (u8*)kmalloc(88, GFP_KERNEL); + if(!workspace) + { + len += sprintf(buf, "No memory for status transfer\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + m = I2O_POST_READ32(c); + if(m == 0xFFFFFFFF) + { + len += sprintf(buf, "Could not get inbound message frame from IOP!\n"); + kfree(workspace); + spin_unlock(&i2o_proc_lock); + return len; + } + + msg = (u32 *)(m+c->mem_offset); + + memset(workspace, 0, 88); + work32 = (u32*)workspace; + work16 = (u16*)workspace; + + msg[0] = NINE_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_STATUS_GET<<24|HOST_TID<<12|ADAPTER_TID; + msg[2] = msg[3] = msg[4] = msg[5] = 0; + msg[6] = virt_to_phys(workspace); + msg[7] = 0; /* FIXME: 64-bit */ + msg[8] = 88; + + /* + * hmm...i2o_post_message should just take ptr to message, and + * determine offset on it's own...less work for OSM developers + */ + i2o_post_message(c, m); + + time = jiffies; + + while(workspace[87] != 0xFF) + { + if(jiffies-time >= 2*HZ) + { + len += sprintf(buf, "Timeout waiting for status reply\n"); + kfree(workspace); + spin_unlock(&i2o_proc_lock); + return len; + } + schedule(); + barrier(); + } + + len += sprintf(buf+len, "Organization ID: %0#6x\n", work16[0]); + + version = workspace[9]&0xF0>>4; + if(version == 0x02) { + len += sprintf(buf+len, "Lowest I2O version supported: "); + switch(workspace[2]) { + case 0x00: + case 0x01: + len += sprintf(buf+len, "1.5\n"); + break; + case 0x02: + len += sprintf(buf+len, "2.0\n"); + break; + } + + len += sprintf(buf+len, "Highest I2O version supported: "); + switch(workspace[3]) { + case 0x00: + case 0x01: + len += sprintf(buf+len, "1.5\n"); + break; + case 0x02: + len += sprintf(buf+len, "2.0\n"); + break; + } + } + + len += sprintf(buf+len, "IOP ID: %0#5x\n", work16[2]&0xFFF); + len += sprintf(buf+len, "Host Unit ID: %0#6x\n", work16[3]); + len += sprintf(buf+len, "Segment Number: %0#5x\n", work16[4]&0XFFF); + + len += sprintf(buf+len, "I2O Version: "); + switch(version) + { + case 0x00: + case 0x01: + len += sprintf(buf+len, "1.5\n"); + break; + case 0x02: + len += sprintf(buf+len, "2.0\n"); + break; + default: + len += sprintf(buf+len, "Unknown version\n"); + } + + len += sprintf(buf+len, "IOP State: "); + switch(workspace[10]) + { + case 0x01: + len += sprintf(buf+len, "Init\n"); + break; + + case 0x02: + len += sprintf(buf+len, "Reset\n"); + break; + + case 0x04: + len += sprintf(buf+len, "Hold\n"); + break; + + case 0x05: + len += sprintf(buf+len, "Hold\n"); + break; + + case 0x08: + len += sprintf(buf+len, "Operational\n"); + break; + + case 0x10: + len += sprintf(buf+len, "FAILED\n"); + break; + + case 0x11: + len += sprintf(buf+len, "FAULTED\n"); + break; + + default: + len += sprintf(buf+len, "Unknown\n"); + break; + } + + /* 0x00 is the only type supported w/spec 1.5 */ + /* Added 2.0 types */ + len += sprintf(buf+len, "Messenger Type: "); + switch (workspace[11]) + { + case 0x00: + len += sprintf(buf+len, "Memory Mapped\n"); + break; + case 0x01: + len += sprintf(buf+len, "Memory mapped only\n"); + break; + case 0x02: + len += sprintf(buf+len, "Remote only\n"); + break; + case 0x03: + len += sprintf(buf+len, "Memory mapped and remote\n"); + break; + default: + len += sprintf(buf+len, "Unknown\n"); + break; + } + len += sprintf(buf+len, "Inbound Frame Size: %d bytes\n", work16[6]*4); + len += sprintf(buf+len, "Max Inbound Frames: %d\n", work32[4]); + len += sprintf(buf+len, "Current Inbound Frames: %d\n", work32[5]); + len += sprintf(buf+len, "Max Outbound Frames: %d\n", work32[6]); + + /* Spec doesn't say if NULL terminated or not... */ + memcpy(prodstr, work32+7, 24); + prodstr[24] = '\0'; + len += sprintf(buf+len, "Product ID: %s\n", prodstr); + + len += sprintf(buf+len, "LCT Size: %d\n", work32[13]); + + len += sprintf(buf+len, "Desired Private Memory Space: %d kB\n", + work32[15]>>10); + len += sprintf(buf+len, "Allocated Private Memory Space: %d kB\n", + work32[16]>>10); + len += sprintf(buf+len, "Private Memory Base Address: %0#10x\n", + work32[17]); + len += sprintf(buf+len, "Desired Private I/O Space: %d kB\n", + work32[18]>>10); + len += sprintf(buf+len, "Allocated Private I/O Space: %d kB\n", + work32[19]>>10); + len += sprintf(buf+len, "Private I/O Base Address: %0#10x\n", + work32[20]); + + spin_unlock(&i2o_proc_lock); + + return len; +} + +int i2o_proc_read_hw(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_controller *c = (struct i2o_controller*)data; + static u32 work32[5]; + static u8 *work8 = (u8*)work32; + static u16 *work16 = (u16*)work32; + int token; + u32 hwcap; + + static char *cpu_table[] = + { + "Intel 80960 Series", + "AMD2900 Series", + "Motorola 68000 Series", + "ARM Series", + "MIPS Series", + "Sparc Series", + "PowerPC Series", + "Intel x86 Series" + }; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(c, ADAPTER_TID, proc_context, + 0, // ParamGroup 0x0000h + -1, // all fields + &work32, + sizeof(work32), + &i2o_proc_token); + + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "IOP Hardware Information Table\n"); + + len += sprintf(buf+len, "I2O Vendor ID: %0#6x\n", work16[0]); + len += sprintf(buf+len, "Product ID: %0#6x\n", work16[1]); + len += sprintf(buf+len, "RAM: %dkB\n", work32[1]>>10); + len += sprintf(buf+len, "Non-Volatile Storage: %dkB\n", work32[2]>>10); + + hwcap = work32[3]; + len += sprintf(buf+len, "Capabilities:\n"); + if(hwcap&0x00000001) + len += sprintf(buf+len, " Self-booting\n"); + if(hwcap&0x00000002) + len += sprintf(buf+len, " Upgradable IRTOS\n"); + if(hwcap&0x00000004) + len += sprintf(buf+len, " Supports downloading DDMs\n"); + if(hwcap&0x00000008) + len += sprintf(buf+len, " Supports installing DDMs\n"); + if(hwcap&0x00000010) + len += sprintf(buf+len, " Battery-backed RAM\n"); + + len += sprintf(buf+len, "CPU: "); + if(work8[16] > 8) + len += sprintf(buf+len, "Unknown\n"); + else + len += sprintf(buf+len, "%s\n", cpu_table[work8[16]]); + /* Anyone using ProcessorVersion? */ + + spin_unlock(&i2o_proc_lock); + + return len; +} + +int i2o_proc_read_dev(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[128]; // allow for "stuff" + up to 256 byte (max) serial number + // == (allow) 512d bytes (max) + static u16 *work16 = (u16*)work32; + char sz[17]; + int token; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0xF100, // ParamGroup F100h (Device Identity) + -1, // all fields + &work32, + sizeof(work32), + &i2o_proc_token); + + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Device Class: %s\n", i2o_get_class_name(work16[0])); + + len += sprintf(buf+len, "Owner TID: %0#5x\n", work16[2]); + len += sprintf(buf+len, "Parent TID: %0#5x\n", work16[3]); + + memcpy(sz, work32+2, 16); + sz[16] = '\0'; + len += sprintf(buf+len, "Vendor Info: %s\n", sz); + + memcpy(sz, work32+6, 16); + sz[16] = '\0'; + len += sprintf(buf+len, "Product Info: %s\n", sz); + + memcpy(sz, work32+10, 16); + sz[16] = '\0'; + len += sprintf(buf+len, "Description: %s\n", sz); + + memcpy(sz, work32+14, 8); + sz[8] = '\0'; + len += sprintf(buf+len, "Product Revision: %s\n", sz); + + len += sprintf(buf+len, "Serial Number: "); + len = print_serial_number(buf, len, + (u8*)(work32+16), + /* allow for SNLen plus + * possible trailing '\0' + */ + sizeof(work32)-(16*sizeof(u32))-2 + ); + len += sprintf(buf+len, "\n"); + + spin_unlock(&i2o_proc_lock); + + return len; +} + + +int i2o_proc_read_dev_name(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + + if ( d->dev_name[0] == '\0' ) + return 0; + + len = sprintf(buf, "%s\n", d->dev_name); + + return len; +} + + + +int i2o_proc_read_ddm(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[128]; + static u16 *work16 = (u16*)work32; + int token; + char mod[25]; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0xF101, // ParamGroup F101h (DDM Identity) + -1, // all fields + &work32, + sizeof(work32), + &i2o_proc_token); + + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Registering DDM TID: 0x%03x\n", work16[0]&0xFFF); + + memcpy(mod, (char*)(work16+1), 24); + mod[24] = '\0'; + len += sprintf(buf+len, "Module Name: %s\n", mod); + + memcpy(mod, (char*)(work16+13), 8); + mod[8] = '\0'; + len += sprintf(buf+len, "Module Rev: %s\n", mod); + + len += sprintf(buf+len, "Serial Number: "); + len = print_serial_number(buf, len, + (u8*)(work16+17), + /* allow for SNLen plus + * possible trailing '\0' + */ + sizeof(work32)-(17*sizeof(u16))-2 + ); + len += sprintf(buf+len, "\n"); + + spin_unlock(&i2o_proc_lock); + + return len; +} + +int i2o_proc_read_uinfo(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[128]; + int token; + char sz[65]; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0xF102, // ParamGroup F102h (User Information) + -1, // all fields + &work32, + sizeof(work32), + &i2o_proc_token); + + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + memcpy(sz, (char*)work32, 64); + sz[64] = '\0'; + len += sprintf(buf, "Device Name: %s\n", sz); + + memcpy(sz, (char*)(work32+16), 64); + sz[64] = '\0'; + len += sprintf(buf+len, "Service Name: %s\n", sz); + + memcpy(sz, (char*)(work32+32), 64); + sz[64] = '\0'; + len += sprintf(buf+len, "Physical Name: %s\n", sz); + + memcpy(sz, (char*)(work32+48), 4); + sz[4] = '\0'; + len += sprintf(buf+len, "Instance Number: %s\n", sz); + + spin_unlock(&i2o_proc_lock); + + return len; +} + +static int print_serial_number(char *buff, int pos, u8 *serialno, int max_len) +{ + int i; + + /* 19990419 -sralston + * The I2O v1.5 (and v2.0 so far) "official specification" + * got serial numbers WRONG! + * Apparently, and despite what Section 3.4.4 says and + * Figure 3-35 shows (pg 3-39 in the pdf doc), + * the convention / consensus seems to be: + * + First byte is SNFormat + * + Second byte is SNLen (but only if SNFormat==7 (?)) + * + (v2.0) SCSI+BS may use IEEE Registered (64 or 128 bit) format + */ + switch(serialno[0]) + { + case I2O_SNFORMAT_BINARY: /* Binary */ + pos += sprintf(buff+pos, "0x"); + for(i = 0; i < serialno[1]; i++) + { + pos += sprintf(buff+pos, "%02X", serialno[2+i]); + } + break; + + case I2O_SNFORMAT_ASCII: /* ASCII */ + if ( serialno[1] < ' ' ) /* printable or SNLen? */ + { + /* sanity */ + max_len = (max_len < serialno[1]) ? max_len : serialno[1]; + serialno[1+max_len] = '\0'; + + /* just print it */ + pos += sprintf(buff+pos, "%s", &serialno[2]); + } + else + { + /* print chars for specified length */ + for(i = 0; i < serialno[1]; i++) + { + pos += sprintf(buff+pos, "%c", serialno[2+i]); + } + } + break; + + case I2O_SNFORMAT_UNICODE: /* UNICODE */ + pos += sprintf(buff+pos, "UNICODE Format. Can't Display\n"); + break; + + case I2O_SNFORMAT_LAN48_MAC: /* LAN-48 MAC Address */ + pos += sprintf(buff+pos, + "LAN-48 MAC Address @ %02X:%02X:%02X:%02X:%02X:%02X", + serialno[2], serialno[3], + serialno[4], serialno[5], + serialno[6], serialno[7]); + + case I2O_SNFORMAT_WAN: /* WAN MAC Address */ + /* FIXME: Figure out what a WAN access address looks like?? */ + pos += sprintf(buff+pos, "WAN Access Address"); + break; + + +/* plus new in v2.0 */ + case I2O_SNFORMAT_LAN64_MAC: /* LAN-64 MAC Address */ + /* FIXME: Figure out what a LAN-64 address really looks like?? */ + pos += sprintf(buff+pos, + "LAN-64 MAC Address @ [?:%02X:%02X:?] %02X:%02X:%02X:%02X:%02X:%02X", + serialno[8], serialno[9], + serialno[2], serialno[3], + serialno[4], serialno[5], + serialno[6], serialno[7]); + break; + + + case I2O_SNFORMAT_DDM: /* I2O DDM */ + pos += sprintf(buff+pos, + "DDM: Tid=%03Xh, Rsvd=%04Xh, OrgId=%04Xh", + *(u16*)&serialno[2], + *(u16*)&serialno[4], + *(u16*)&serialno[6]); + break; + + case I2O_SNFORMAT_IEEE_REG64: /* IEEE Registered (64-bit) */ + case I2O_SNFORMAT_IEEE_REG128: /* IEEE Registered (128-bit) */ + /* FIXME: Figure if this is even close?? */ + pos += sprintf(buff+pos, + "IEEE NodeName(hi,lo)=(%08Xh:%08Xh), PortName(hi,lo)=(%08Xh:%08Xh)\n", + *(u32*)&serialno[2], + *(u32*)&serialno[6], + *(u32*)&serialno[10], + *(u32*)&serialno[14]); + break; + + + case I2O_SNFORMAT_UNKNOWN: /* Unknown 0 */ + case I2O_SNFORMAT_UNKNOWN2: /* Unknown 0xff */ + default: + pos += sprintf(buff+pos, "Unknown Data Format"); + break; + } + + return pos; +} + +/* LAN group 0000h - Device info (scalar) */ +int i2o_proc_read_lan_dev_info(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[56]; + static u8 *work8 = (u8*)work32; + static u16 *work16 = (u16*)work32; + static u64 *work64 = (u64*)work32; + int token; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0000, -1, &work32, 56*4, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "LAN Type ........... "); + switch (work16[0]) + { + case 0x0030: + len += sprintf(buf+len, "Ethernet, "); + break; + case 0x0040: + len += sprintf(buf+len, "100Base VG, "); + break; + case 0x0050: + len += sprintf(buf+len, "Token Ring, "); + break; + case 0x0060: + len += sprintf(buf+len, "FDDI, "); + break; + case 0x0070: + len += sprintf(buf+len, "Fibre Channel, "); + break; + default: + len += sprintf(buf+len, "Unknown type, "); + break; + } + + if (work16[1]&0x00000001) + len += sprintf(buf+len, "emulated LAN, "); + else + len += sprintf(buf+len, "physical LAN port, "); + + if (work16[1]&0x00000002) + len += sprintf(buf+len, "full duplex\n"); + else + len += sprintf(buf+len, "simplex\n"); + + len += sprintf(buf+len, "Address format: "); + switch(work8[4]) { + case 0x00: + len += sprintf(buf+len, "IEEE 48bit\n"); + break; + case 0x01: + len += sprintf(buf+len, "FC IEEE\n"); + break; + default: + len += sprintf(buf+len, "Unknown\n"); + break; + } + + len += sprintf(buf+len, "State: "); + switch(work8[5]) + { + case 0x00: + len += sprintf(buf+len, "Unknown\n"); + break; + case 0x01: + len += sprintf(buf+len, "Unclaimed\n"); + break; + case 0x02: + len += sprintf(buf+len, "Operational\n"); + break; + case 0x03: + len += sprintf(buf+len, "Suspended\n"); + break; + case 0x04: + len += sprintf(buf+len, "Resetting\n"); + break; + case 0x05: + len += sprintf(buf+len, "Error\n"); + break; + case 0x06: + len += sprintf(buf+len, "Operational no Rx\n"); + break; + case 0x07: + len += sprintf(buf+len, "Suspended no Rx\n"); + break; + default: + len += sprintf(buf+len, "Unspecified\n"); + break; + } + + len += sprintf(buf+len, "Error status: "); + if(work16[3]&0x0001) + len += sprintf(buf+len, "Transmit Control Unit Inoperative "); + if(work16[3]&0x0002) + len += sprintf(buf+len, "Receive Control Unit Inoperative\n"); + if(work16[3]&0x0004) + len += sprintf(buf+len, "Local memory Allocation Error\n"); + len += sprintf(buf+len, "\n"); + + len += sprintf(buf+len, "Min Packet size: %d\n", work32[2]); + len += sprintf(buf+len, "Max Packet size: %d\n", work32[3]); + len += sprintf(buf+len, "HW Address: " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + work8[16],work8[17],work8[18],work8[19], + work8[20],work8[21],work8[22],work8[23]); + + len += sprintf(buf+len, "Max Tx Wire Speed: " FMT_U64_HEX " bps\n", U64_VAL(&work64[3])); + len += sprintf(buf+len, "Max Rx Wire Speed: " FMT_U64_HEX " bps\n", U64_VAL(&work64[4])); + + len += sprintf(buf+len, "Min SDU packet size: 0x%08x\n", work32[10]); + len += sprintf(buf+len, "Max SDU packet size: 0x%08x\n", work32[11]); + + spin_unlock(&i2o_proc_lock); + return len; +} + +/* LAN group 0001h - MAC address table (scalar) */ +int i2o_proc_read_lan_mac_addr(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[48]; + static u8 *work8 = (u8*)work32; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0001, -1, &work32, 48*4, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Active address: " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + work8[0],work8[1],work8[2],work8[3], + work8[4],work8[5],work8[6],work8[7]); + len += sprintf(buf+len, "Current address: " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + work8[8],work8[9],work8[10],work8[11], + work8[12],work8[13],work8[14],work8[15]); + len += sprintf(buf+len, "Functional address mask: " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + work8[16],work8[17],work8[18],work8[19], + work8[20],work8[21],work8[22],work8[23]); + + len += sprintf(buf+len, "Filter mask: 0x%08x\n", work32[6]); + len += sprintf(buf+len, "HW/DDM capabilities: 0x%08x\n", work32[7]); + len += sprintf(buf+len, " Unicast packets %ssupported (%sabled)\n", + (work32[7]&0x00000001)?"":"not ", + (work32[6]&0x00000001)?"en":"dis"); + len += sprintf(buf+len, " Promiscuous mode %ssupported (%sabled)\n", + (work32[7]&0x00000002)?"":"not", + (work32[6]&0x00000002)?"en":"dis"); + len += sprintf(buf+len, + " Multicast promiscuous mode %ssupported (%sabled)\n", + (work32[7]&0x00000004)?"":"not ", + (work32[6]&0x00000004)?"en":"dis"); + len += sprintf(buf+len, + " Broadcast Reception disabling %ssupported (%sabled)\n", + (work32[7]&0x00000100)?"":"not ", + (work32[6]&0x00000100)?"en":"dis"); + len += sprintf(buf+len, + " Multicast Reception disabling %ssupported (%sabled)\n", + (work32[7]&0x00000200)?"":"not ", + (work32[6]&0x00000200)?"en":"dis"); + len += sprintf(buf+len, + " Functional address disabling %ssupported (%sabled)\n", + (work32[7]&0x00000400)?"":"not ", + (work32[6]&0x00000400)?"en":"dis"); + len += sprintf(buf+len, " MAC reporting %ssupported\n", + (work32[7]&0x00000800)?"":"not "); + + len += sprintf(buf+len, " MAC Reporting mode: "); + if (work32[6]&0x00000800) + len += sprintf(buf+len, "Pass only priority MAC packets\n"); + else if (work32[6]&0x00001000) + len += sprintf(buf+len, "Pass all MAC packets\n"); + else if (work32[6]&0x00001800) + len += sprintf(buf+len, "Pass all MAC packets (promiscuous)\n"); + else + len += sprintf(buf+len, "Do not pass MAC packets\n"); + + len += sprintf(buf+len, "Number of multicast addesses: %d\n", work32[8]); + len += sprintf(buf+len, "Perfect filtering for max %d multicast addesses\n", + work32[9]); + len += sprintf(buf+len, "Imperfect filtering for max %d multicast addesses\n", + work32[10]); + + spin_unlock(&i2o_proc_lock); + + return len; +} + +/* LAN group 0001h, field 1 - Current MAC (scalar) */ +int i2o_proc_read_lan_curr_addr(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[2]; + static u8 *work8 = (u8*)work32; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0001, 2, &work32, 8, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Current address: " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + work8[0],work8[1],work8[2],work8[3], + work8[4],work8[5],work8[6],work8[7]); + + spin_unlock(&i2o_proc_lock); + return len; +} + + +#if 0 +/* LAN group 0002h - Multicast MAC address table (table) */ +int i2o_proc_read_lan_mcast_addr(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u8 work8[32]; + static u32 field32[8]; + static u8 *field8 = (u8 *)field32; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_table_polled(d->controller, d->id, &work8, 32, + 0x0002, 0, field32, 8); + + switch (token) { + case -ETIMEDOUT: + len += sprintf(buf, "Timeout reading table.\n"); + spin_unlock(&i2o_proc_lock); + return len; + break; + case -ENOMEM: + len += sprintf(buf, "No free memory to read the table.\n"); + spin_unlock(&i2o_proc_lock); + return len; + break; + case -EBADR: + len += sprintf(buf, "Error reading field.\n"); + spin_unlock(&i2o_proc_lock); + return len; + break; + default: + break; + } + + len += sprintf(buf, "Multicast MAC address: " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + field8[0],field8[1],field8[2],field8[3], + field8[4],field8[5],field8[6],field8[7]); + + spin_unlock(&i2o_proc_lock); + return len; +} +#endif + +/* LAN group 0003h - Batch Control (scalar) */ +int i2o_proc_read_lan_batch_control(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[18]; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0003, -1, &work32, 72, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Batch mode "); + if (work32[0]&0x00000001) + len += sprintf(buf+len, "disabled"); + else + len += sprintf(buf+len, "enabled"); + if (work32[0]&0x00000002) + len += sprintf(buf+len, " (current setting)"); + if (work32[0]&0x00000004) + len += sprintf(buf+len, ", forced"); + else + len += sprintf(buf+len, ", toggle"); + len += sprintf(buf+len, "\n"); + + if(d->i2oversion == 0x00) { /* Reserved in 1.53 and 2.0 */ + len += sprintf(buf+len, "Rising Load Delay: %d ms\n", + work32[1]/10); + len += sprintf(buf+len, "Rising Load Threshold: %d ms\n", + work32[2]/10); + len += sprintf(buf+len, "Falling Load Delay: %d ms\n", + work32[3]/10); + len += sprintf(buf+len, "Falling Load Threshold: %d ms\n", + work32[4]/10); + } + + len += sprintf(buf+len, "Max Rx Batch Count: %d\n", work32[5]); + len += sprintf(buf+len, "Max Rx Batch Delay: %d\n", work32[6]); + + if(d->i2oversion == 0x00) { + len += sprintf(buf+len, + "Transmission Completion Reporting Delay: %d ms\n", + work32[7]); + } else { + len += sprintf(buf+len, "Max Tx Batch Delay: %d\n", work32[7]); + len += sprintf(buf+len, "Max Tx Batch Count: %d\n", work32[8]); + } + + spin_unlock(&i2o_proc_lock); + return len; +} + +/* LAN group 0004h - LAN Operation (scalar) */ +int i2o_proc_read_lan_operation(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[5]; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0004, -1, &work32, 20, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Packet prepadding (32b words): %d\n", work32[0]); + len += sprintf(buf+len, "Transmission error reporting: %s\n", + (work32[1]&1)?"on":"off"); + len += sprintf(buf+len, "Bad packet handling: %s\n", + (work32[1]&0x2)?"by host":"by DDM"); + len += sprintf(buf+len, "Packet orphan limit: %d\n", work32[2]); + + len += sprintf(buf+len, "Tx modes:\n"); + if (work32[3]&0x00000004) + len += sprintf(buf+len, " HW CRC supressed\n"); + else + len += sprintf(buf+len, " HW CRC\n"); + if (work32[3]&0x00000100) + len += sprintf(buf+len, " HW IPv4 checksumming\n"); + if (work32[3]&0x00000200) + len += sprintf(buf+len, " HW TCP checksumming\n"); + if (work32[3]&0x00000400) + len += sprintf(buf+len, " HW UDP checksumming\n"); + if (work32[3]&0x00000800) + len += sprintf(buf+len, " HW RSVP checksumming\n"); + if (work32[3]&0x00001000) + len += sprintf(buf+len, " HW ICMP checksumming\n"); + if (work32[3]&0x00002000) + len += sprintf(buf+len, " Loopback packet not delivered\n"); + + len += sprintf(buf+len, "Rx modes:\n"); + if (work32[4]&0x00000004) + len += sprintf(buf+len, " FCS in payload\n"); + if (work32[4]&0x00000100) + len += sprintf(buf+len, " HW IPv4 checksum validation\n"); + if (work32[4]&0x00000200) + len += sprintf(buf+len, " HW TCP checksum validation\n"); + if (work32[4]&0x00000400) + len += sprintf(buf+len, " HW UDP checksum validation\n"); + if (work32[4]&0x00000800) + len += sprintf(buf+len, " HW RSVP checksum validation\n"); + if (work32[4]&0x00001000) + len += sprintf(buf+len, " HW ICMP checksum validation\n"); + + spin_unlock(&i2o_proc_lock); + return len; +} + +/* LAN group 0005h - Media operation (scalar) */ +int i2o_proc_read_lan_media_operation(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[9]; + static u8 *work8 = (u8*)work32; + static u64 *work64 = (u64*)work32; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0005, -1, &work32, 36, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Connector type: "); + switch(work32[0]) + { + case 0x00000000: + len += sprintf(buf+len, "OTHER\n"); + break; + case 0x00000001: + len += sprintf(buf+len, "UNKNOWN\n"); + break; + case 0x00000002: + len += sprintf(buf+len, "AUI\n"); + break; + case 0x00000003: + len += sprintf(buf+len, "UTP\n"); + break; + case 0x00000004: + len += sprintf(buf+len, "BNC\n"); + break; + case 0x00000005: + len += sprintf(buf+len, "RJ45\n"); + break; + case 0x00000006: + len += sprintf(buf+len, "STP DB9\n"); + break; + case 0x00000007: + len += sprintf(buf+len, "FIBER MIC\n"); + break; + case 0x00000008: + len += sprintf(buf+len, "APPLE AUI\n"); + break; + case 0x00000009: + len += sprintf(buf+len, "MII\n"); + break; + case 0x0000000A: + len += sprintf(buf+len, "DB9\n"); + break; + case 0x0000000B: + len += sprintf(buf+len, "HSSDC\n"); + break; + case 0x0000000C: + len += sprintf(buf+len, "DUPLEX SC FIBER\n"); + break; + case 0x0000000D: + len += sprintf(buf+len, "DUPLEX ST FIBER\n"); + break; + case 0x0000000E: + len += sprintf(buf+len, "TNC/BNC\n"); + break; + case 0xFFFFFFFF: + len += sprintf(buf+len, "HW DEFAULT\n"); + break; + } + + len += sprintf(buf+len, "Connection type: "); + switch(work32[1]) + { + case I2O_LAN_UNKNOWN: + len += sprintf(buf+len, "UNKNOWN\n"); + break; + case I2O_LAN_AUI: + len += sprintf(buf+len, "AUI\n"); + break; + case I2O_LAN_10BASE5: + len += sprintf(buf+len, "10BASE5\n"); + break; + case I2O_LAN_FIORL: + len += sprintf(buf+len, "FIORL\n"); + break; + case I2O_LAN_10BASE2: + len += sprintf(buf+len, "10BASE2\n"); + break; + case I2O_LAN_10BROAD36: + len += sprintf(buf+len, "10BROAD36\n"); + break; + case I2O_LAN_10BASE_T: + len += sprintf(buf+len, "10BASE-T\n"); + break; + case I2O_LAN_10BASE_FP: + len += sprintf(buf+len, "10BASE-FP\n"); + break; + case I2O_LAN_10BASE_FB: + len += sprintf(buf+len, "10BASE-FB\n"); + break; + case I2O_LAN_10BASE_FL: + len += sprintf(buf+len, "10BASE-FL\n"); + break; + case I2O_LAN_100BASE_TX: + len += sprintf(buf+len, "100BASE-TX\n"); + break; + case I2O_LAN_100BASE_FX: + len += sprintf(buf+len, "100BASE-FX\n"); + break; + case I2O_LAN_100BASE_T4: + len += sprintf(buf+len, "100BASE-T4\n"); + break; + case I2O_LAN_1000BASE_SX: + len += sprintf(buf+len, "1000BASE-SX\n"); + break; + case I2O_LAN_1000BASE_LX: + len += sprintf(buf+len, "1000BASE-LX\n"); + break; + case I2O_LAN_1000BASE_CX: + len += sprintf(buf+len, "1000BASE-CX\n"); + break; + case I2O_LAN_1000BASE_T: + len += sprintf(buf+len, "1000BASE-T\n"); + break; + case I2O_LAN_100VG_ETHERNET: + len += sprintf(buf+len, "100VG-ETHERNET\n"); + break; + case I2O_LAN_100VG_TR: + len += sprintf(buf+len, "100VG-TOKEN RING\n"); + break; + case I2O_LAN_4MBIT: + len += sprintf(buf+len, "4MBIT TOKEN RING\n"); + break; + case I2O_LAN_16MBIT: + len += sprintf(buf+len, "16 Mb Token Ring\n"); + break; + case I2O_LAN_125MBAUD: + len += sprintf(buf+len, "125 MBAUD FDDI\n"); + break; + case I2O_LAN_POINT_POINT: + len += sprintf(buf+len, "Point-to-point\n"); + break; + case I2O_LAN_ARB_LOOP: + len += sprintf(buf+len, "Arbitrated loop\n"); + break; + case I2O_LAN_PUBLIC_LOOP: + len += sprintf(buf+len, "Public loop\n"); + break; + case I2O_LAN_FABRIC: + len += sprintf(buf+len, "Fabric\n"); + break; + case I2O_LAN_EMULATION: + len += sprintf(buf+len, "Emulation\n"); + break; + case I2O_LAN_OTHER: + len += sprintf(buf+len, "Other\n"); + break; + case I2O_LAN_DEFAULT: + len += sprintf(buf+len, "HW default\n"); + break; + } + + len += sprintf(buf+len, "Current Tx Wire Speed: " FMT_U64_HEX " bps\n", + U64_VAL(&work64[1])); + len += sprintf(buf+len, "Current Rx Wire Speed: " FMT_U64_HEX " bps\n", + U64_VAL(&work64[2])); + + len += sprintf(buf+len, "%s duplex\n", (work8[24]&1)?"Full":"Half"); + + len += sprintf(buf+len, "Link status: "); + if(work8[25] == 0x00) + len += sprintf(buf+len, "Unknown\n"); + else if(work8[25] == 0x01) + len += sprintf(buf+len, "Normal\n"); + else if(work8[25] == 0x02) + len += sprintf(buf+len, "Failure\n"); + else if(work8[25] == 0x03) + len += sprintf(buf+len, "Reset\n"); + else + len += sprintf(buf+len, "Unspecified\n"); + + if (d->i2oversion == 0x00) { /* Reserved in 1.53 and 2.0 */ + len += sprintf(buf+len, "Bad packets handled by: %s\n", + (work8[26] == 0xFF)?"host":"DDM"); + } + if (d->i2oversion != 0x00) { + len += sprintf(buf+len, "Duplex mode target: "); + switch (work8[27]) { + case 0: + len += sprintf(buf+len, "Half Duplex\n"); + break; + case 1: + len += sprintf(buf+len, "Full Duplex\n"); + break; + default: + len += sprintf(buf+len, "\n"); + break; + } + + len += sprintf(buf+len, "Connector type target: "); + switch(work32[7]) + { + case 0x00000000: + len += sprintf(buf+len, "OTHER\n"); + break; + case 0x00000001: + len += sprintf(buf+len, "UNKNOWN\n"); + break; + case 0x00000002: + len += sprintf(buf+len, "AUI\n"); + break; + case 0x00000003: + len += sprintf(buf+len, "UTP\n"); + break; + case 0x00000004: + len += sprintf(buf+len, "BNC\n"); + break; + case 0x00000005: + len += sprintf(buf+len, "RJ45\n"); + break; + case 0x00000006: + len += sprintf(buf+len, "STP DB9\n"); + break; + case 0x00000007: + len += sprintf(buf+len, "FIBER MIC\n"); + break; + case 0x00000008: + len += sprintf(buf+len, "APPLE AUI\n"); + break; + case 0x00000009: + len += sprintf(buf+len, "MII\n"); + break; + case 0x0000000A: + len += sprintf(buf+len, "DB9\n"); + break; + case 0x0000000B: + len += sprintf(buf+len, "HSSDC\n"); + break; + case 0x0000000C: + len += sprintf(buf+len, "DUPLEX SC FIBER\n"); + break; + case 0x0000000D: + len += sprintf(buf+len, "DUPLEX ST FIBER\n"); + break; + case 0x0000000E: + len += sprintf(buf+len, "TNC/BNC\n"); + break; + case 0xFFFFFFFF: + len += sprintf(buf+len, "HW DEFAULT\n"); + break; + default: + len += sprintf(buf+len, "\n"); + break; + } + + len += sprintf(buf+len, "Connection type target: "); + switch(work32[8]) + { + case I2O_LAN_UNKNOWN: + len += sprintf(buf+len, "UNKNOWN\n"); + break; + case I2O_LAN_AUI: + len += sprintf(buf+len, "AUI\n"); + break; + case I2O_LAN_10BASE5: + len += sprintf(buf+len, "10BASE5\n"); + break; + case I2O_LAN_FIORL: + len += sprintf(buf+len, "FIORL\n"); + break; + case I2O_LAN_10BASE2: + len += sprintf(buf+len, "10BASE2\n"); + break; + case I2O_LAN_10BROAD36: + len += sprintf(buf+len, "10BROAD36\n"); + break; + case I2O_LAN_10BASE_T: + len += sprintf(buf+len, "10BASE-T\n"); + break; + case I2O_LAN_10BASE_FP: + len += sprintf(buf+len, "10BASE-FP\n"); + break; + case I2O_LAN_10BASE_FB: + len += sprintf(buf+len, "10BASE-FB\n"); + break; + case I2O_LAN_10BASE_FL: + len += sprintf(buf+len, "10BASE-FL\n"); + break; + case I2O_LAN_100BASE_TX: + len += sprintf(buf+len, "100BASE-TX\n"); + break; + case I2O_LAN_100BASE_FX: + len += sprintf(buf+len, "100BASE-FX\n"); + break; + case I2O_LAN_100BASE_T4: + len += sprintf(buf+len, "100BASE-T4\n"); + break; + case I2O_LAN_1000BASE_SX: + len += sprintf(buf+len, "1000BASE-SX\n"); + break; + case I2O_LAN_1000BASE_LX: + len += sprintf(buf+len, "1000BASE-LX\n"); + break; + case I2O_LAN_1000BASE_CX: + len += sprintf(buf+len, "1000BASE-CX\n"); + break; + case I2O_LAN_1000BASE_T: + len += sprintf(buf+len, "1000BASE-T\n"); + break; + case I2O_LAN_100VG_ETHERNET: + len += sprintf(buf+len, "100VG-ETHERNET\n"); + break; + case I2O_LAN_100VG_TR: + len += sprintf(buf+len, "100VG-TOKEN RING\n"); + break; + case I2O_LAN_4MBIT: + len += sprintf(buf+len, "4MBIT TOKEN RING\n"); + break; + case I2O_LAN_16MBIT: + len += sprintf(buf+len, "16 Mb Token Ring\n"); + break; + case I2O_LAN_125MBAUD: + len += sprintf(buf+len, "125 MBAUD FDDI\n"); + break; + case I2O_LAN_POINT_POINT: + len += sprintf(buf+len, "Point-to-point\n"); + break; + case I2O_LAN_ARB_LOOP: + len += sprintf(buf+len, "Arbitrated loop\n"); + break; + case I2O_LAN_PUBLIC_LOOP: + len += sprintf(buf+len, "Public loop\n"); + break; + case I2O_LAN_FABRIC: + len += sprintf(buf+len, "Fabric\n"); + break; + case I2O_LAN_EMULATION: + len += sprintf(buf+len, "Emulation\n"); + break; + case I2O_LAN_OTHER: + len += sprintf(buf+len, "Other\n"); + break; + case I2O_LAN_DEFAULT: + len += sprintf(buf+len, "HW default\n"); + break; + default: + len += sprintf(buf+len, "\n"); + break; + } + } + spin_unlock(&i2o_proc_lock); + return len; +} + +#if 0 +/* LAN group 0006h - Alternate address (table) */ +int i2o_proc_read_lan_alt_addr(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u8 work8[32]; + static u32 field32[2]; + static u8 *field8 = (u8 *)field32; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_table_polled(d->controller, d->id, &work8, 32, + 0x0006, 0, field32, 8); + switch (token) { + case -ETIMEDOUT: + len += sprintf(buf, "Timeout reading table.\n"); + spin_unlock(&i2o_proc_lock); + return len; + break; + case -ENOMEM: + len += sprintf(buf, "No free memory to read the table.\n"); + spin_unlock(&i2o_proc_lock); + return len; + break; + case -EBADR: + len += sprintf(buf, "Error reading field.\n"); + spin_unlock(&i2o_proc_lock); + return len; + break; + default: + break; + } + + len += sprintf(buf, "Alternate Address: " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", + field8[0],field8[1],field8[2],field8[3], + field8[4],field8[5],field8[6],field8[7]); + + spin_unlock(&i2o_proc_lock); + return len; +} +#endif + +/* LAN group 0007h - Transmit info (scalar) */ +int i2o_proc_read_lan_tx_info(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[10]; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0007, -1, &work32, 8, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Max SG Elements per packet: %d\n", work32[0]); + len += sprintf(buf+len, "Max SG Elements per chain: %d\n", work32[1]); + len += sprintf(buf+len, "Max outstanding packets: %d\n", work32[2]); + len += sprintf(buf+len, "Max packets per request: %d\n", work32[3]); + + len += sprintf(buf+len, "Tx modes:\n"); + if(work32[4]&0x00000002) + len += sprintf(buf+len, " No DA in SGL\n"); + if(work32[4]&0x00000004) + len += sprintf(buf+len, " CRC suppression\n"); + if(work32[4]&0x00000008) + len += sprintf(buf+len, " Loop suppression\n"); + if(work32[4]&0x00000010) + len += sprintf(buf+len, " MAC insertion\n"); + if(work32[4]&0x00000020) + len += sprintf(buf+len, " RIF insertion\n"); + if(work32[4]&0x00000100) + len += sprintf(buf+len, " IPv4 Checksum\n"); + if(work32[4]&0x00000200) + len += sprintf(buf+len, " TCP Checksum\n"); + if(work32[4]&0x00000400) + len += sprintf(buf+len, " UDP Checksum\n"); + if(work32[4]&0x00000800) + len += sprintf(buf+len, " RSVP Checksum\n"); + if(work32[4]&0x00001000) + len += sprintf(buf+len, " ICMP Checksum\n"); + if (d->i2oversion == 0x00) { + if(work32[4]&0x00008000) + len += sprintf(buf+len, " Loopback Enabled\n"); + if(work32[4]&0x00010000) + len += sprintf(buf+len, " Loopback Suppression Enabled\n"); + } else { + if(work32[4]&0x00010000) + len += sprintf(buf+len, " Loopback Enabled\n"); + if(work32[4]&0x00020000) + len += sprintf(buf+len, " Loopback Suppression Enabled\n"); + } + + spin_unlock(&i2o_proc_lock); + return len; +} + +/* LAN group 0008h - Receive info (scalar) */ +int i2o_proc_read_lan_rx_info(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u32 work32[10]; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0008, -1, &work32, 8, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Max size of chain element: %d\n", work32[0]); + len += sprintf(buf+len, "Max number of buckets: %d\n", work32[1]); + + if (d->i2oversion > 0x00) { /* not in 1.5 */ + len += sprintf(buf+len, "Rx modes: %d\n", work32[2]); + len += sprintf(buf+len, "RxMaxBucketsReply: %d\n", work32[3]); + len += sprintf(buf+len, "RxMaxPacketsPerBuckets: %d\n", work32[4]); + len += sprintf(buf+len, "RxMaxPostBuckets: %d\n", work32[5]); + } + + spin_unlock(&i2o_proc_lock); + return len; +} + + +/* LAN group 0100h - LAN Historical statistics (scalar) */ +int i2o_proc_read_lan_hist_stats(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u64 work64[9]; + int token; + + spin_lock(&i2o_proc_lock); + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0100, -1, &work64, 9*8, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "Tx packets: " FMT_U64_HEX "\n", U64_VAL(&work64[0])); + len += sprintf(buf+len, "Tx bytes: " FMT_U64_HEX "\n", U64_VAL(&work64[1])); + len += sprintf(buf+len, "Rx packets: " FMT_U64_HEX "\n", U64_VAL(&work64[2])); + len += sprintf(buf+len, "Rx bytes: " FMT_U64_HEX "\n", U64_VAL(&work64[3])); + len += sprintf(buf+len, "Tx errors: " FMT_U64_HEX "\n", U64_VAL(&work64[4])); + len += sprintf(buf+len, "Rx errors: " FMT_U64_HEX "\n", U64_VAL(&work64[5])); + len += sprintf(buf+len, "Rx dropped: " FMT_U64_HEX "\n", U64_VAL(&work64[6])); + len += sprintf(buf+len, "Adapter resets: " FMT_U64_HEX "\n", U64_VAL(&work64[7])); + len += sprintf(buf+len, "Adapter suspends: " FMT_U64_HEX "\n", U64_VAL(&work64[8])); + + spin_unlock(&i2o_proc_lock); + return len; +} + + +/* LAN group 0182h - Optional Non Media Specific Transmit Historical Statistics + * (scalar) */ +int i2o_proc_read_lan_opt_tx_hist_stats(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u64 work64[9]; + int token; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0182, -1, &work64, 9*8, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "TxRetryCount: " FMT_U64_HEX "\n", U64_VAL(&work64[0])); + len += sprintf(buf+len, "DirectedBytesTx: " FMT_U64_HEX "\n", U64_VAL(&work64[1])); + len += sprintf(buf+len, "DirectedPacketsTx: " FMT_U64_HEX "\n", U64_VAL(&work64[2])); + len += sprintf(buf+len, "MulticastBytesTx: " FMT_U64_HEX "\n", U64_VAL(&work64[3])); + len += sprintf(buf+len, "MulticastPacketsTx: " FMT_U64_HEX "\n", U64_VAL(&work64[4])); + len += sprintf(buf+len, "BroadcastBytesTx: " FMT_U64_HEX "\n", U64_VAL(&work64[5])); + len += sprintf(buf+len, "BroadcastPacketsTx: " FMT_U64_HEX "\n", U64_VAL(&work64[6])); + len += sprintf(buf+len, "TotalGroupAddrTxCount: " FMT_U64_HEX "\n", U64_VAL(&work64[7])); + len += sprintf(buf+len, "TotalTxPacketsTooShort: " FMT_U64_HEX "\n", U64_VAL(&work64[8])); + + spin_unlock(&i2o_proc_lock); + return len; +} + +/* LAN group 0183h - Optional Non Media Specific Receive Historical Statistics + * (scalar) */ +int i2o_proc_read_lan_opt_rx_hist_stats(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u64 work64[11]; + int token; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0183, -1, &work64, 11*8, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "ReceiveCRCErrorCount: " FMT_U64_HEX "\n", U64_VAL(&work64[0])); + len += sprintf(buf+len, "DirectedBytesRx: " FMT_U64_HEX "\n", U64_VAL(&work64[1])); + len += sprintf(buf+len, "DirectedPacketsRx: " FMT_U64_HEX "\n", U64_VAL(&work64[2])); + len += sprintf(buf+len, "MulticastBytesRx: " FMT_U64_HEX "\n", U64_VAL(&work64[3])); + len += sprintf(buf+len, "MulticastPacketsRx: " FMT_U64_HEX "\n", U64_VAL(&work64[4])); + len += sprintf(buf+len, "BroadcastBytesRx: " FMT_U64_HEX "\n", U64_VAL(&work64[5])); + len += sprintf(buf+len, "BroadcastPacketsRx: " FMT_U64_HEX "\n", U64_VAL(&work64[6])); + len += sprintf(buf+len, "TotalGroupAddrRxCount: " FMT_U64_HEX "\n", U64_VAL(&work64[7])); + len += sprintf(buf+len, "TotalRxPacketsTooShort: " FMT_U64_HEX "\n", U64_VAL(&work64[8])); + len += sprintf(buf+len, "TotalRxPacketsTooLong: " FMT_U64_HEX "\n", U64_VAL(&work64[9])); + len += sprintf(buf+len, "TotalRuntPacketsReceived: " FMT_U64_HEX "\n", U64_VAL(&work64[10])); + + spin_unlock(&i2o_proc_lock); + return len; +} + + +/* LAN group 0400h - Required FDDI Statistics (scalar) */ +int i2o_proc_read_lan_fddi_stats(char *buf, char **start, off_t offset, + int len, int *eof, void *data) +{ + struct i2o_device *d = (struct i2o_device*)data; + static u64 work64[11]; + int token; + + static char *conf_state[] = + { + "Isolated", + "Local a", + "Local b", + "Local ab", + "Local s", + "Wrap a", + "Wrap b", + "Wrap ab", + "Wrap s", + "C-Wrap a", + "C-Wrap b", + "C-Wrap s", + "Through", + }; + + static char *ring_state[] = + { + "Isolated", + "Non-op", + "Rind-op", + "Detect", + "Non-op-Dup", + "Ring-op-Dup", + "Directed", + "Trace" + }; + + static char *link_state[] = + { + "Off", + "Break", + "Trace", + "Connect", + "Next", + "Signal", + "Join", + "Verify", + "Active", + "Maintenance" + }; + + spin_lock(&i2o_proc_lock); + + len = 0; + + token = i2o_query_scalar(d->controller, d->id, proc_context, + 0x0400, -1, &work64, 11*8, &i2o_proc_token); + if(token < 0) + { + len += sprintf(buf, "Timeout waiting for reply from IOP\n"); + spin_unlock(&i2o_proc_lock); + return len; + } + + len += sprintf(buf, "ConfigurationState: %s\n", conf_state[work64[0]]); + len += sprintf(buf+len, "UpstreamNode: " FMT_U64_HEX "\n", U64_VAL(&work64[1])); + len += sprintf(buf+len, "DownStreamNode: " FMT_U64_HEX "\n", U64_VAL(&work64[2])); + len += sprintf(buf+len, "FrameErrors: " FMT_U64_HEX "\n", U64_VAL(&work64[3])); + len += sprintf(buf+len, "FramesLost: " FMT_U64_HEX "\n", U64_VAL(&work64[4])); + len += sprintf(buf+len, "RingMgmtState: %s\n", ring_state[work64[5]]); + len += sprintf(buf+len, "LCTFailures: " FMT_U64_HEX "\n", U64_VAL(&work64[6])); + len += sprintf(buf+len, "LEMRejects: " FMT_U64_HEX "\n", U64_VAL(&work64[7])); + len += sprintf(buf+len, "LEMCount: " FMT_U64_HEX "\n", U64_VAL(&work64[8])); + len += sprintf(buf+len, "LConnectionState: %s\n", link_state[work64[9]]); + + spin_unlock(&i2o_proc_lock); + return len; +} + +static int i2o_proc_create_entries(void *data, + i2o_proc_entry *pentry, struct proc_dir_entry *parent) +{ + struct proc_dir_entry *ent; + + while(pentry->name != NULL) + { + ent = create_proc_entry(pentry->name, pentry->mode, parent); + if(!ent) return -1; + + ent->data = data; + ent->read_proc = pentry->read_proc; + ent->write_proc = pentry->write_proc; + ent->nlink = 1; + + pentry++; + } + + return 0; +} + +static void i2o_proc_remove_entries(i2o_proc_entry *pentry, + struct proc_dir_entry *parent) +{ + while(pentry->name != NULL) + { + remove_proc_entry(pentry->name, parent); + pentry++; + } +} + +static int i2o_proc_add_controller(struct i2o_controller *pctrl, + struct proc_dir_entry *root ) +{ + struct proc_dir_entry *dir, *dir1; + struct i2o_device *dev; + char buff[10]; + + sprintf(buff, "iop%d", pctrl->unit); + + dir = create_proc_entry(buff, S_IFDIR, root); + if(!dir) + return -1; + + pctrl->proc_entry = dir; + + i2o_proc_create_entries(pctrl, generic_iop_entries, dir); + + for(dev = pctrl->devices; dev; dev = dev->next) + { + sprintf(buff, "%0#5x", dev->id); + + dir1 = create_proc_entry(buff, S_IFDIR, dir); + dev->proc_entry = dir1; + + if(!dir1) + printk(KERN_INFO "i2o_proc: Could not allocate proc dir\n"); + + i2o_proc_create_entries(dev, generic_dev_entries, dir1); + + switch(dev->class) + { + case I2O_CLASS_SCSI_PERIPHERAL: + case I2O_CLASS_RANDOM_BLOCK_STORAGE: + i2o_proc_create_entries(dev, rbs_dev_entries, dir1); + break; + case I2O_CLASS_LAN: + i2o_proc_create_entries(dev, lan_entries, dir1); + break; + default: + break; + } + } + + return 0; +} + +static void i2o_proc_remove_controller(struct i2o_controller *pctrl, + struct proc_dir_entry *parent) +{ + char buff[10]; + + sprintf(buff, "iop%d", pctrl->unit); + + i2o_proc_remove_entries(generic_iop_entries, pctrl->proc_entry); + + remove_proc_entry(buff, parent); + + pctrl->proc_entry = NULL; +} + +static int create_i2o_procfs(void) +{ + struct i2o_controller *pctrl = NULL; + int i; + + i2o_proc_dir_root = create_proc_entry("i2o", S_IFDIR, 0); + if(!i2o_proc_dir_root) + return -1; + + for(i = 0; i < MAX_I2O_CONTROLLERS; i++) + { + pctrl = i2o_find_controller(i); + if(pctrl) + i2o_proc_add_controller(pctrl, i2o_proc_dir_root); + }; + + return 0; +} + +static int destroy_i2o_procfs(void) +{ + struct i2o_controller *pctrl = NULL; + int i; + + if(!i2o_find_controller(0)) + return -1; + + for(i = 0; i < MAX_I2O_CONTROLLERS; i++) + { + pctrl = i2o_find_controller(i); + if(pctrl) + i2o_proc_remove_controller(pctrl, i2o_proc_dir_root); + }; + + remove_proc_entry("i2o", 0); + return 0; +} + +#ifdef MODULE + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("I2O procfs Handler"); + +int init_module(void) +{ + if(create_i2o_procfs()) + return -EBUSY; + + if (i2o_install_handler(&i2o_proc_handler) < 0) + { + printk(KERN_ERR "i2o_proc: Unable to install PROC handler.\n"); + return 0; + } + + proc_context = i2o_proc_handler.context; + + return 0; +} + +void cleanup_module(void) +{ + destroy_i2o_procfs(); + i2o_remove_handler(&i2o_proc_handler); +} +#endif diff --git a/drivers/i2o/i2o_proc.h b/drivers/i2o/i2o_proc.h new file mode 100644 index 000000000..fd659c396 --- /dev/null +++ b/drivers/i2o/i2o_proc.h @@ -0,0 +1,141 @@ +#ifndef i2oproc_h +#define i2oproc_h + +/* + * Fixme: make this dependent on architecture + * The official header files to this already...but we can't use them + */ +#define I2O_64BIT_CONTEXT 0 + +typedef struct _i2o_msg { + u8 ver_offset; + u8 msg_flags; + u16 msg_size; + u32 target_addr:12; + u32 initiator_addr:12; + u32 function:8; + u32 init_context; /* FIXME: 64-bit support! */ +} i2o_msg, *pi2o_msg; + +typedef struct _i2o_reply_message { + i2o_msg msg_frame; + u32 tctx; /* FIXME: 64-bit */ + u16 detailed_status_code; + u8 reserved; + u8 req_status; +} i2o_reply_msg, *pi2o_reply_msg; + +typedef struct _i2o_mult_reply_message { + i2o_msg msg_frame; + u32 tctx; /* FIXME: 64-bit */ + u16 detailed_status_code; + u8 reserved; + u8 req_status; +} i2o_mult_reply_msg, *pi2o_mult_reply_msg; + +/************************************************************************** + * HRT related constants and structures + **************************************************************************/ +#define I2O_BUS_LOCAL 0 +#define I2O_BUS_ISA 1 +#define I2O_BUS_EISA 2 +#define I2O_BUS_MCA 3 +#define I2O_BUS_PCI 4 +#define I2O_BUS_PCMCIA 5 +#define I2O_BUS_NUBUS 6 +#define I2O_BUS_CARDBUS 7 +#define I2O_BUS_UNKNOWN 0x80 + +typedef struct _i2o_pci_bus { + u8 PciFunctionNumber; + u8 PciDeviceNumber; + u8 PciBusNumber; + u8 reserved; + u16 PciVendorID; + u16 PciDeviceID; +} i2o_pci_bus, *pi2o_pci_bus; + +typedef struct _i2o_local_bus { + u16 LbBaseIOPort; + u16 reserved; + u32 LbBaseMemoryAddress; +} i2o_local_bus, *pi2o_local_bus; + +typedef struct _i2o_isa_bus { + u16 IsaBaseIOPort; + u8 CSN; + u8 reserved; + u32 IsaBaseMemoryAddress; +} i2o_isa_bus, *pi2o_isa_bus; + +/* I2O_EISA_BUS_INFO */ +typedef struct _i2o_eisa_bus_info { + u16 EisaBaseIOPort; + u8 reserved; + u8 EisaSlotNumber; + u32 EisaBaseMemoryAddress; +} i2o_eisa_bus, *pi2o_eisa_bus; + +typedef struct _i2o_mca_bus { + u16 McaBaseIOPort; + u8 reserved; + u8 McaSlotNumber; + u32 McaBaseMemoryAddress; +} i2o_mca_bus, *pi2o_mca_bus; + +typedef struct _i2o_other_bus { + u16 BaseIOPort; + u16 reserved; + u32 BaseMemoryAddress; +} i2o_other_bus, *pi2o_other_bus; + + +typedef struct _i2o_hrt_entry { + u32 adapter_id; + u32 parent_tid:12; + u32 state:4; + u32 bus_num:8; + u32 bus_type:8; + union { + i2o_pci_bus pci_bus; + i2o_local_bus local_bus; + i2o_isa_bus isa_bus; + i2o_eisa_bus eisa_bus; + i2o_mca_bus mca_bus; + i2o_other_bus other_bus; + } bus; +} i2o_hrt_entry, *pi2o_hrt_entry; + +typedef struct _i2o_hrt { + u16 num_entries; + u8 entry_len; + u8 hrt_version; + u32 change_ind; + i2o_hrt_entry hrt_entry[1]; +} i2o_hrt, *pi2o_hrt; + +typedef struct _i2o_lct_entry { + u32 entry_size:16; + u32 tid:12; + u32 reserved:4; + u32 change_ind; + u32 device_flags; + u32 class_id; + u32 sub_class; + u32 user_tid:12; + u32 parent_tid:12; + u32 bios_info:8; + u8 identity_tag[8]; + u32 event_capabilities; +} i2o_lct_entry, *pi2o_lct_entry; + +typedef struct _i2o_lct { + u32 table_size:16; + u32 boot_tid:12; + u32 lct_ver:4; + u32 iop_flags; + u32 current_change_ind; + i2o_lct_entry lct_entry[1]; +} i2o_lct, *pi2o_lct; + +#endif /* i2oproc_h */ diff --git a/drivers/i2o/i2o_scsi.c b/drivers/i2o/i2o_scsi.c new file mode 100644 index 000000000..505e3c22d --- /dev/null +++ b/drivers/i2o/i2o_scsi.c @@ -0,0 +1,871 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * Complications for I2O scsi + * + * o Each (bus,lun) is a logical device in I2O. We keep a map + * table. We spoof failed selection for unmapped units + * o Request sense buffers can come back for free. + * o Scatter gather is a bit dynamic. We have to investigate at + * setup time. + * o Some of our resources are dynamically shared. The i2o core + * needs a message reservation protocol to avoid swap v net + * deadlocking. We need to back off queue requests. + * + * In general the firmware wants to help. Where its help isn't performance + * useful we just ignore the aid. Its not worth the code in truth. + * + * Fixes: + * Steve Ralston : Scatter gather now works + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <asm/dma.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/atomic.h> +#include <linux/blk.h> +#include <linux/version.h> +#include <linux/i2o.h> +#include "../scsi/scsi.h" +#include "../scsi/hosts.h" +#include "../scsi/sd.h" +#include "i2o_scsi.h" + +#define VERSION_STRING "Version 0.0.1" + +#define dprintk(x) + +#define MAXHOSTS 32 + +struct proc_dir_entry proc_scsi_i2o_scsi = { + PROC_SCSI_I2O, 8, "i2o_scsi", S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +struct i2o_scsi_host +{ + struct i2o_controller *controller; + s16 task[16][8]; /* Allow 16 devices for now */ + unsigned long tagclock[16][8]; /* Tag clock for queueing */ + s16 bus_task; /* The adapter TID */ +}; + +static int scsi_context; +static int lun_done; +static int i2o_scsi_hosts; + +static u32 *retry[32]; +static struct i2o_controller *retry_ctrl[32]; +static struct timer_list retry_timer; +static int retry_ct = 0; + +static atomic_t queue_depth; + +/* + * SG Chain buffer support... + */ +#define SG_MAX_FRAGS 64 + +/* + * FIXME: we should allocate one of these per bus we find as we + * locate them not in a lump at boot. + */ + +typedef struct _chain_buf +{ + u32 sg_flags_cnt[SG_MAX_FRAGS]; + u32 sg_buf[SG_MAX_FRAGS]; +} chain_buf; + +#define SG_CHAIN_BUF_SZ sizeof(chain_buf) + +#define SG_MAX_BUFS (i2o_num_controllers * I2O_SCSI_CAN_QUEUE) +#define SG_CHAIN_POOL_SZ (SG_MAX_BUFS * SG_CHAIN_BUF_SZ) + +static int max_sg_len = 0; +static chain_buf *sg_chain_pool = NULL; +static int sg_chain_tag = 0; +static int sg_max_frags = SG_MAX_FRAGS; + +/* + * Retry congested frames. This actually needs pushing down into + * i2o core. We should only bother the OSM with this when we can't + * queue and retry the frame. Or perhaps we should call the OSM + * and its default handler should be this in the core, and this + * call a 2nd "I give up" handler in the OSM ? + */ + +static void i2o_retry_run(unsigned long f) +{ + int i; + unsigned long flags; + + save_flags(flags); + cli(); + + for(i=0;i<retry_ct;i++) + i2o_post_message(retry_ctrl[i], virt_to_bus(retry[i])); + retry_ct=0; + + restore_flags(flags); +} + +static void flush_pending(void) +{ + int i; + unsigned long flags; + + save_flags(flags); + cli(); + + for(i=0;i<retry_ct;i++) + { + retry[i][0]&=~0xFFFFFF; + retry[i][0]|=I2O_CMD_UTIL_NOP<<24; + i2o_post_message(retry_ctrl[i],virt_to_bus(retry[i])); + } + retry_ct=0; + + restore_flags(flags); +} + +static void i2o_scsi_reply(struct i2o_handler *h, struct i2o_controller *c, struct i2o_message *msg) +{ + Scsi_Cmnd *current_command; + u32 *m = (u32 *)msg; + u8 as,ds,st; + + if(m[0] & (1<<13)) + { + printk("IOP fail.\n"); + printk("From %d To %d Cmd %d.\n", + (m[1]>>12)&0xFFF, + m[1]&0xFFF, + m[1]>>24); + printk("Failure Code %d.\n", m[4]>>24); + if(m[4]&(1<<16)) + printk("Format error.\n"); + if(m[4]&(1<<17)) + printk("Path error.\n"); + if(m[4]&(1<<18)) + printk("Path State.\n"); + if(m[4]&(1<<18)) + printk("Congestion.\n"); + + m=(u32 *)bus_to_virt(m[7]); + printk("Failing message is %p.\n", m); + + if((m[4]&(1<<18)) && retry_ct < 32) + { + retry_ctrl[retry_ct]=c; + retry[retry_ct]=m; + if(!retry_ct++) + { + retry_timer.expires=jiffies+1; + add_timer(&retry_timer); + } + } + else + { + /* Create a scsi error for this */ + current_command = (Scsi_Cmnd *)m[3]; + printk("Aborted %ld\n", current_command->serial_number); + + spin_lock_irq(&io_request_lock); + current_command->result = DID_ERROR << 16; + current_command->scsi_done(current_command); + spin_unlock_irq(&io_request_lock); + + /* Now flush the message by making it a NOP */ + m[0]&=0x00FFFFFF; + m[0]|=(I2O_CMD_UTIL_NOP)<<24; + i2o_post_message(c,virt_to_bus(m)); + } + return; + } + + + /* Low byte is the adapter status, next is the device */ + as=(u8)m[4]; + ds=(u8)(m[4]>>8); + st=(u8)(m[4]>>24); + + dprintk(("i2o got a scsi reply %08X: ", m[0])); + dprintk(("m[2]=%08X: ", m[2])); + dprintk(("m[4]=%08X\n", m[4])); + + if(m[2]&0x80000000) + { + if(m[2]&0x40000000) + { + dprintk(("Event.\n")); + lun_done=1; + return; + } + printk(KERN_ERR "i2o_scsi: bus reset reply.\n"); + return; + } + + current_command = (Scsi_Cmnd *)m[3]; + + /* + * Is this a control request coming back - eg an abort ? + */ + + if(current_command==NULL) + { + if(st) + dprintk(("SCSI abort: %08X", m[4])); + dprintk(("SCSI abort completed.\n")); + return; + } + + dprintk(("Completed %ld\n", current_command->serial_number)); + + atomic_dec(&queue_depth); + + if(st == 0x06) + { + if(m[5] < current_command->underflow) + { + int i; + printk(KERN_ERR "SCSI: underflow 0x%08X 0x%08X\n", + m[5], current_command->underflow); + printk("Cmd: "); + for(i=0;i<15;i++) + printk("%02X ", current_command->cmnd[i]); + printk(".\n"); + } + else st=0; + } + + if(st) + { + /* An error has occured */ + + dprintk((KERN_DEBUG "SCSI error %08X", m[4])); + + if (ds == 0x0E) + /* SCSI Reset */ + current_command->result = DID_RESET << 16; + else if (ds == 0x0F) + current_command->result = DID_PARITY << 16; + else + current_command->result = DID_ERROR << 16; + } + else + /* + * It worked maybe ? + */ + current_command->result = DID_OK << 16 | ds; + spin_lock(&io_request_lock); + current_command->scsi_done(current_command); + spin_unlock(&io_request_lock); + return; +} + +struct i2o_handler i2o_scsi_handler= +{ + i2o_scsi_reply, + "I2O SCSI OSM", + 0 +}; + +static int i2o_find_lun(struct i2o_controller *c, struct i2o_device *d, int *target, int *lun) +{ + u8 reply[8]; + + if(i2o_query_scalar(c, d->id, scsi_context|0x40000000, + 0, 3, reply, 4, &lun_done)<0) + return -1; + + *target=reply[0]; + + if(i2o_query_scalar(c, d->id, scsi_context|0x40000000, + 0, 4, reply, 8, &lun_done)<0) + return -1; + + *lun=reply[1]; + + dprintk(("SCSI (%d,%d)\n", *target, *lun)); + return 0; +} + +static void i2o_scsi_init(struct i2o_controller *c, struct i2o_device *d, struct Scsi_Host *shpnt) +{ + struct i2o_device *unit; + struct i2o_scsi_host *h =(struct i2o_scsi_host *)shpnt->hostdata; + int lun; + int target; + + h->controller=c; + h->bus_task=d->id; + + for(target=0;target<16;target++) + for(lun=0;lun<8;lun++) + h->task[target][lun] = -1; + + for(unit=c->devices;unit!=NULL;unit=unit->next) + { + dprintk(("Class %03X, parent %d, want %d.\n", + unit->class, unit->parent, d->id)); + + /* Only look at scsi and fc devices */ + if ( (unit->class != I2O_CLASS_SCSI_PERIPHERAL) + && (unit->class != I2O_CLASS_FIBRE_CHANNEL_PERIPHERAL) + ) + continue; + + /* On our bus ? */ + dprintk(("Found a disk.\n")); + if ( (unit->parent == d->id) + || (unit->parent == d->parent) + ) + { + u16 limit; + dprintk(("Its ours.\n")); + if(i2o_find_lun(c, unit, &target, &lun)==-1) + { + printk(KERN_ERR "i2o_scsi: Unable to get lun for tid %d.\n", d->id); + continue; + } + dprintk(("Found disk %d %d.\n", target, lun)); + h->task[target][lun]=unit->id; + h->tagclock[target][lun]=jiffies; + + /* Get the max fragments/request */ + i2o_query_scalar(c, d->id, scsi_context|0x40000000, + 0xF103, 3, &limit, 2, &lun_done); + + /* sanity */ + if ( limit == 0 ) + { + printk(KERN_WARNING "i2o_scsi: Ignoring unreasonable SG limit of 0 from IOP!\n"); + limit = 1; + } + + shpnt->sg_tablesize = limit; + + dprintk(("i2o_scsi: set scatter-gather to %d.\n", + shpnt->sg_tablesize)); + } + } +} + +int i2o_scsi_detect(Scsi_Host_Template * tpnt) +{ + unsigned long flags; + struct Scsi_Host *shpnt = NULL; + int i; + int count; + + printk("i2o_scsi.c: %s\n", VERSION_STRING); + + if(i2o_install_handler(&i2o_scsi_handler)<0) + { + printk(KERN_ERR "i2o_scsi: Unable to install OSM handler.\n"); + return 0; + } + scsi_context = i2o_scsi_handler.context; + + if((sg_chain_pool = kmalloc(SG_CHAIN_POOL_SZ, GFP_KERNEL)) == NULL) + { + printk("i2o_scsi: Unable to alloc %d byte SG chain buffer pool.\n", SG_CHAIN_POOL_SZ); + printk("i2o_scsi: SG chaining DISABLED!\n"); + sg_max_frags = 11; + } + else + { + printk(" chain_pool: %d bytes @ %p\n", SG_CHAIN_POOL_SZ, sg_chain_pool); + printk(" (%d byte buffers X %d can_queue X %d i2o controllers)\n", + SG_CHAIN_BUF_SZ, I2O_SCSI_CAN_QUEUE, i2o_num_controllers); + sg_max_frags = SG_MAX_FRAGS; // 64 + } + + init_timer(&retry_timer); + retry_timer.data = 0UL; + retry_timer.function = i2o_retry_run; + +// printk("SCSI OSM at %d.\n", scsi_context); + + for (count = 0, i = 0; i < MAX_I2O_CONTROLLERS; i++) + { + struct i2o_controller *c=i2o_find_controller(i); + struct i2o_device *d; + /* + * This controller doesn't exist. + */ + + if(c==NULL) + continue; + + /* + * Fixme - we need some altered device locking. This + * is racing with device addition in theory. Easy to fix. + */ + + for(d=c->devices;d!=NULL;d=d->next) + { + /* + * bus_adapter, SCSI (obsolete), or FibreChannel busses only + */ + if( (d->class!=I2O_CLASS_BUS_ADAPTER_PORT) // bus_adapter + && (d->class!=I2O_CLASS_FIBRE_CHANNEL_PORT) // FC_PORT + ) + continue; + +// printk("Found a controller.\n"); + shpnt = scsi_register(tpnt, sizeof(struct i2o_scsi_host)); + save_flags(flags); + cli(); + shpnt->unique_id = (u32)d; + shpnt->io_port = 0; + shpnt->n_io_port = 0; + shpnt->irq = 0; + shpnt->this_id = /* Good question */15; + restore_flags(flags); +// printk("Scanning I2O port %d.\n", d->id); + i2o_scsi_init(c, d, shpnt); + count++; + } + } + i2o_scsi_hosts = count; + + if(count==0) + { + if(sg_chain_pool!=NULL) + { + kfree(sg_chain_pool); + sg_chain_pool = NULL; + } + flush_pending(); + del_timer(&retry_timer); + i2o_remove_handler(&i2o_scsi_handler); + } + + return count; +} + +int i2o_scsi_release(struct Scsi_Host *host) +{ + if(--i2o_scsi_hosts==0) + { + if(sg_chain_pool!=NULL) + { + kfree(sg_chain_pool); + sg_chain_pool = NULL; + } + flush_pending(); + del_timer(&retry_timer); + i2o_remove_handler(&i2o_scsi_handler); + } + return 0; +} + + +const char *i2o_scsi_info(struct Scsi_Host *SChost) +{ + struct i2o_scsi_host *hostdata; + + hostdata = (struct i2o_scsi_host *)SChost->hostdata; + + return(&hostdata->controller->name[0]); +} + + +/* + * From the wd93 driver: + * Returns true if there will be a DATA_OUT phase with this command, + * false otherwise. + * (Thanks to Joerg Dorchain for the research and suggestion.) + * + */ +static int is_dir_out(Scsi_Cmnd *cmd) +{ + switch (cmd->cmnd[0]) + { + case WRITE_6: case WRITE_10: case WRITE_12: + case WRITE_LONG: case WRITE_SAME: case WRITE_BUFFER: + case WRITE_VERIFY: case WRITE_VERIFY_12: + case COMPARE: case COPY: case COPY_VERIFY: + case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW: + case SEARCH_EQUAL_12: case SEARCH_HIGH_12: case SEARCH_LOW_12: + case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE: + case MODE_SELECT: case MODE_SELECT_10: case LOG_SELECT: + case SEND_DIAGNOSTIC: case CHANGE_DEFINITION: case UPDATE_BLOCK: + case SET_WINDOW: case MEDIUM_SCAN: case SEND_VOLUME_TAG: + case 0xea: + return 1; + default: + return 0; + } +} + +int i2o_scsi_queuecommand(Scsi_Cmnd * SCpnt, void (*done) (Scsi_Cmnd *)) +{ + int i; + int tid; + struct i2o_controller *c; + Scsi_Cmnd *current_command; + struct Scsi_Host *host; + struct i2o_scsi_host *hostdata; + u32 *msg, *mptr; + u32 m; + u32 *lenptr; + int direction; + int scsidir; + u32 len; + + static int max_qd = 1; + + /* + * The scsi layer should be handling this stuff + */ + + if(is_dir_out(SCpnt)) + { + direction=0x04000000; + scsidir=0x80000000; + } + else + { + scsidir=0x40000000; + direction=0x00000000; + } + + /* + * Do the incoming paperwork + */ + + host = SCpnt->host; + hostdata = (struct i2o_scsi_host *)host->hostdata; + SCpnt->scsi_done = done; + + if(SCpnt->target > 15) + { + printk(KERN_ERR "i2o_scsi: Wild target %d.\n", SCpnt->target); + return -1; + } + + tid = hostdata->task[SCpnt->target][SCpnt->lun]; + + dprintk(("qcmd: Tid = %d\n", tid)); + + current_command = SCpnt; /* set current command */ + current_command->scsi_done = done; /* set ptr to done function */ + + /* We don't have such a device. Pretend we did the command + and that selection timed out */ + + if(tid == -1) + { + SCpnt->result = DID_NO_CONNECT << 16; + done(SCpnt); + return 0; + } + + dprintk(("Real scsi messages.\n")); + + c = hostdata->controller; + + /* + * Obtain an I2O message. Right now we _have_ to obtain one + * until the scsi layer stuff is cleaned up. + */ + + do + { + mb(); + m = I2O_POST_READ32(c); + } + while(m==0xFFFFFFFF); + msg = bus_to_virt(c->mem_offset + m); + + /* + * Put together a scsi execscb message + */ + + msg[1] = I2O_CMD_SCSI_EXEC<<24|HOST_TID<<12|tid; + msg[2] = scsi_context; /* So the I2O layer passes to us */ + /* Sorry 64bit folks. FIXME */ + msg[3] = (u32)SCpnt; /* We want the SCSI control block back */ + /* Direction, disconnect ok, no tagging (yet) */ + msg[4] = scsidir|(1<<29)|SCpnt->cmd_len; + + /* + * Attach tags to the devices + */ + if(SCpnt->device->tagged_supported) + { + /* + * Some drives are too stupid to handle fairness issues + * with tagged queueing. We throw in the odd ordered + * tag to stop them starving themselves. + */ + if((jiffies - hostdata->tagclock[SCpnt->target][SCpnt->lun]) > (5*HZ)) + { + msg[4]|=(1<<23)|(1<<24); + hostdata->tagclock[SCpnt->target][SCpnt->lun]=jiffies; + } + else switch(SCpnt->tag) + { + case SIMPLE_QUEUE_TAG: + msg[4]|=(1<<23); + break; + case HEAD_OF_QUEUE_TAG: + msg[4]|=(1<<24); + break; + case ORDERED_QUEUE_TAG: + msg[4]|=(1<<23)|(1<<24); + break; + default: + msg[4]|=(1<<23); + } + } + + mptr=msg+5; + + /* + * Write SCSI command into the message - always 16 byte block + */ + + memcpy(mptr, SCpnt->cmnd, 16); + mptr+=4; + lenptr=mptr++; /* Remember me - fill in when we know */ + + + /* + * Now fill in the SGList and command + * + * FIXME: we need to set the sglist limits according to the + * message size of the I2O controller. We might only have room + * for 6 or so worst case + */ + + if(SCpnt->use_sg) + { + struct scatterlist *sg = (struct scatterlist *)SCpnt->request_buffer; + + if((sg_max_frags > 11) && (SCpnt->use_sg > 11)) + { + /* + * Need to chain! + */ + SCpnt->host_scribble = (void*)(sg_chain_pool + sg_chain_tag); + *mptr++=direction|0xB0000000|(SCpnt->use_sg*2*4); + *mptr=virt_to_bus(SCpnt->host_scribble); + mptr = (u32*)SCpnt->host_scribble; + if (SCpnt->use_sg > max_sg_len) + { + max_sg_len = SCpnt->use_sg; + printk("i2o_scsi: Chain SG! SCpnt=%p, SG_FragCnt=%d, SG_idx=%d\n", + SCpnt, SCpnt->use_sg, (chain_buf*)SCpnt->host_scribble-sg_chain_pool); + } + if ( ++sg_chain_tag == SG_MAX_BUFS ) + sg_chain_tag = 0; + } + + len = 0; + + for(i = 0 ; i < SCpnt->use_sg; i++) + { + *mptr++=direction|0x10000000|sg->length; + len+=sg->length; + *mptr++=virt_to_bus(sg->address); + sg++; + } + mptr[-2]|=0xC0000000; /* End of List and block */ + *lenptr=len; + if(len != SCpnt->underflow) + printk("Cmd len %08X Cmd underflow %08X\n", + len, SCpnt->underflow); + } + else + { + dprintk(("non sg for %p, %d\n", SCpnt->request_buffer, + SCpnt->request_bufflen)); + *mptr++=0xD0000000|direction|SCpnt->request_bufflen; + *mptr++=virt_to_bus(SCpnt->request_buffer); + *lenptr = len = SCpnt->request_bufflen; + /* No transfer ? - fix up the request */ + if(len == 0) + msg[4]&=~0xC0000000; + } + + /* + * Stick the headers on + */ + + msg[0] = (mptr-msg)<<16 | SGL_OFFSET_10; + + /* Queue the message */ + i2o_post_message(c,m); + + atomic_inc(&queue_depth); + + if(atomic_read(&queue_depth)> max_qd) + { + max_qd=atomic_read(&queue_depth); + printk("Queue depth now %d.\n", max_qd); + } + + mb(); + dprintk(("Issued %ld\n", current_command->serial_number)); + + return 0; +} + +static void internal_done(Scsi_Cmnd * SCpnt) +{ + SCpnt->SCp.Status++; +} + +int i2o_scsi_command(Scsi_Cmnd * SCpnt) +{ + i2o_scsi_queuecommand(SCpnt, internal_done); + SCpnt->SCp.Status = 0; + while (!SCpnt->SCp.Status) + barrier(); + return SCpnt->result; +} + +int i2o_scsi_abort(Scsi_Cmnd * SCpnt) +{ + struct i2o_controller *c; + struct Scsi_Host *host; + struct i2o_scsi_host *hostdata; + u32 *msg; + u32 m; + int tid; + + printk("i2o_scsi_abort\n"); + + host = SCpnt->host; + hostdata = (struct i2o_scsi_host *)host->hostdata; + tid = hostdata->task[SCpnt->target][SCpnt->lun]; + if(tid==-1) + { + printk(KERN_ERR "impossible command to abort.\n"); + return SCSI_ABORT_NOT_RUNNING; + } + c = hostdata->controller; + + /* + * Obtain an I2O message. Right now we _have_ to obtain one + * until the scsi layer stuff is cleaned up. + */ + + do + { + mb(); + m = I2O_POST_READ32(c); + } + while(m==0xFFFFFFFF); + msg = bus_to_virt(c->mem_offset + m); + + msg[0] = FIVE_WORD_MSG_SIZE; + msg[1] = I2O_CMD_SCSI_ABORT<<24|HOST_TID<<12|tid; + msg[2] = scsi_context; + msg[3] = 0; /* Not needed for an abort */ + msg[4] = (u32)SCpnt; + wmb(); + i2o_post_message(c,m); + wmb(); +// SCpnt->result = DID_RESET << 16; +// SCpnt->scsi_done(SCpnt); + return SCSI_ABORT_PENDING; +} + +int i2o_scsi_reset(Scsi_Cmnd * SCpnt, unsigned int reset_flags) +{ + int tid; + struct i2o_controller *c; + struct Scsi_Host *host; + struct i2o_scsi_host *hostdata; + u32 m; + u32 *msg; + + printk("i2o_scsi_reset\n"); + + /* + * Find the TID for the bus + */ + + host = SCpnt->host; + hostdata = (struct i2o_scsi_host *)host->hostdata; + tid = hostdata->bus_task; + c = hostdata->controller; + + /* + * Now send a SCSI reset request. Any remaining commands + * will be aborted by the IOP. We need to catch the reply + * possibly ? + */ + + m = I2O_POST_READ32(c); + + /* + * No free messages, try again next time - no big deal + */ + + if(m == 0xFFFFFFFF) + return SCSI_RESET_PUNT; + + msg = bus_to_virt(c->mem_offset + m); + msg[0] = FOUR_WORD_MSG_SIZE|SGL_OFFSET_0; + msg[1] = I2O_CMD_SCSI_BUSRESET<<24|HOST_TID<<12|tid; + msg[2] = scsi_context|0x80000000; + /* We use the top bit to split controller and unit transactions */ + /* Now store unit,tid so we can tie the completion back to a specific device */ + msg[3] = c->unit << 16 | tid; + i2o_post_message(c,m); + return SCSI_RESET_PENDING; +} + +/* + * This is anyones guess quite frankly. + */ + +int i2o_scsi_bios_param(Disk * disk, kdev_t dev, int *ip) +{ + int size; + + size = disk->capacity; + ip[0] = 64; /* heads */ + ip[1] = 32; /* sectors */ + if ((ip[2] = size >> 11) > 1024) { /* cylinders, test for big disk */ + ip[0] = 255; /* heads */ + ip[1] = 63; /* sectors */ + ip[2] = size / (255 * 63); /* cylinders */ + } + return 0; +} + +/* Loadable module support */ +#ifdef MODULE + +MODULE_AUTHOR("Red Hat Software"); + +Scsi_Host_Template driver_template = I2OSCSI; + +#include "../scsi/scsi_module.c" +#endif diff --git a/drivers/i2o/i2o_scsi.h b/drivers/i2o/i2o_scsi.h new file mode 100644 index 000000000..c6c88bc5e --- /dev/null +++ b/drivers/i2o/i2o_scsi.h @@ -0,0 +1,48 @@ +#ifndef _I2O_SCSI_H +#define _I2O_SCSI_H + +#if !defined(LINUX_VERSION_CODE) +#include <linux/version.h> +#endif + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) + +#include <linux/types.h> +#include <linux/kdev_t.h> + +#define I2O_SCSI_ID 15 +#define I2O_SCSI_CAN_QUEUE 8 +#define I2O_SCSI_CMD_PER_LUN 6 + +extern struct proc_dir_entry proc_scsi_i2o_scsi; + +extern int i2o_scsi_detect(Scsi_Host_Template *); +extern const char *i2o_scsi_info(struct Scsi_Host *); +extern int i2o_scsi_command(Scsi_Cmnd *); +extern int i2o_scsi_queuecommand(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +extern int i2o_scsi_abort(Scsi_Cmnd *); +extern int i2o_scsi_reset(Scsi_Cmnd *, unsigned int); +extern int i2o_scsi_bios_param(Disk *, kdev_t, int *); +extern void i2o_scsi_setup(char *str, int *ints); + +#define I2OSCSI { \ + next: NULL, \ + proc_dir: &proc_scsi_i2o_scsi, \ + name: "I2O SCSI Layer", \ + detect: i2o_scsi_detect, \ + release: i2o_scsi_release, \ + info: i2o_scsi_info, \ + command: i2o_scsi_command, \ + queuecommand: i2o_scsi_queuecommand, \ + abort: i2o_scsi_abort, \ + reset: i2o_scsi_reset, \ + bios_param: i2o_scsi_bios_param, \ + can_queue: I2O_SCSI_CAN_QUEUE, \ + this_id: I2O_SCSI_ID, \ + sg_tablesize: 8, \ + cmd_per_lun: I2O_SCSI_CMD_PER_LUN, \ + unchecked_isa_dma: 0, \ + use_clustering: ENABLE_CLUSTERING \ + } + +#endif diff --git a/drivers/isdn/eicon/Makefile b/drivers/isdn/eicon/Makefile new file mode 100644 index 000000000..306aac0e8 --- /dev/null +++ b/drivers/isdn/eicon/Makefile @@ -0,0 +1,13 @@ +L_OBJS := +M_OBJS := +O_OBJS := eicon_mod.o eicon_isa.o eicon_pci.o eicon_idi.o eicon_io.o + +O_TARGET := +ifeq ($(CONFIG_ISDN_DRV_EICON),y) + O_TARGET += eicon.o +else + O_TARGET += eicon.o + M_OBJS = eicon.o +endif + +include $(TOPDIR)/Rules.make diff --git a/drivers/isdn/eicon/eicon.h b/drivers/isdn/eicon/eicon.h new file mode 100644 index 000000000..9552d2b54 --- /dev/null +++ b/drivers/isdn/eicon/eicon.h @@ -0,0 +1,528 @@ +/* $Id: eicon.h,v 1.5 1999/03/29 11:19:41 armin Exp $ + * + * ISDN low-level module for Eicon.Diehl active ISDN-Cards. + * + * Copyright 1998 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon.h,v $ + * Revision 1.5 1999/03/29 11:19:41 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.4 1999/03/02 12:37:42 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.3 1999/01/24 20:14:07 armin + * Changed and added debug stuff. + * Better data sending. (still problems with tty's flip buffer) + * + * Revision 1.2 1999/01/10 18:46:04 armin + * Bug with wrong values in HLC fixed. + * Bytes to send are counted and limited now. + * + * Revision 1.1 1999/01/01 18:09:41 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + + +#ifndef eicon_h +#define eicon_h + +#define EICON_IOCTL_SETMMIO 0 +#define EICON_IOCTL_GETMMIO 1 +#define EICON_IOCTL_SETIRQ 2 +#define EICON_IOCTL_GETIRQ 3 +#define EICON_IOCTL_LOADBOOT 4 +#define EICON_IOCTL_ADDCARD 5 +#define EICON_IOCTL_GETTYPE 6 +#define EICON_IOCTL_LOADPCI 7 +#define EICON_IOCTL_LOADISA 8 +#define EICON_IOCTL_GETVER 9 + +#define EICON_IOCTL_MANIF 90 + +#define EICON_IOCTL_FREEIT 97 +#define EICON_IOCTL_TEST 98 +#define EICON_IOCTL_DEBUGVAR 99 + +/* Bus types */ +#define EICON_BUS_ISA 1 +#define EICON_BUS_MCA 2 +#define EICON_BUS_PCI 3 + +/* Constants for describing Card-Type */ +#define EICON_CTYPE_S 0 +#define EICON_CTYPE_SX 1 +#define EICON_CTYPE_SCOM 2 +#define EICON_CTYPE_QUADRO 3 +#define EICON_CTYPE_S2M 4 +#define EICON_CTYPE_MAESTRA 5 +#define EICON_CTYPE_MAESTRAQ 6 +#define EICON_CTYPE_MAESTRAQ_U 7 +#define EICON_CTYPE_MAESTRAP 8 +#define EICON_CTYPE_ISABRI 0x10 +#define EICON_CTYPE_ISAPRI 0x20 +#define EICON_CTYPE_MASK 0x0f +#define EICON_CTYPE_QUADRO_NR(n) (n<<4) + +#define MAX_HEADER_LEN 10 + +/* Struct for adding new cards */ +typedef struct eicon_cdef { + int membase; + int irq; + char id[10]; +} eicon_cdef; + +#define EICON_ISA_BOOT_MEMCHK 1 +#define EICON_ISA_BOOT_NORMAL 2 + +/* Struct for downloading protocol via ioctl for ISA cards */ +typedef struct { + /* start-up parameters */ + unsigned char tei; + unsigned char nt2; + unsigned char skip1; + unsigned char WatchDog; + unsigned char Permanent; + unsigned char XInterface; + unsigned char StableL2; + unsigned char NoOrderCheck; + unsigned char HandsetType; + unsigned char skip2; + unsigned char LowChannel; + unsigned char ProtVersion; + unsigned char Crc4; + unsigned char Loopback; + unsigned char oad[32]; + unsigned char osa[32]; + unsigned char spid[32]; + unsigned char boot_opt; + unsigned long bootstrap_len; + unsigned long firmware_len; + unsigned char code[1]; /* Rest (bootstrap- and firmware code) will be allocated */ +} eicon_isa_codebuf; + +/* Struct for downloading protocol via ioctl for PCI cards */ +typedef struct { + /* start-up parameters */ + unsigned char tei; + unsigned char nt2; + unsigned char WatchDog; + unsigned char Permanent; + unsigned char XInterface; + unsigned char StableL2; + unsigned char NoOrderCheck; + unsigned char HandsetType; + unsigned char LowChannel; + unsigned char ProtVersion; + unsigned char Crc4; + unsigned char NoHscx30Mode; /* switch PRI into No HSCX30 test mode */ + unsigned char Loopback; /* switch card into Loopback mode */ + struct q931_link_s + { + unsigned char oad[32]; + unsigned char osa[32]; + unsigned char spid[32]; + } l[2]; + unsigned long protocol_len; + unsigned int dsp_code_num; + unsigned long dsp_code_len[9]; + unsigned char code[1]; /* Rest (protocol- and dsp code) will be allocated */ +} eicon_pci_codebuf; + +/* Data for downloading protocol via ioctl */ +typedef union { + eicon_isa_codebuf isa; + eicon_pci_codebuf pci; +} eicon_codebuf; + +/* Data for Management interface */ +typedef struct { + int count; + int pos; + int length[50]; + unsigned char data[700]; +} eicon_manifbuf; + + +#ifdef __KERNEL__ + +/* Kernel includes */ +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/tqueue.h> +#include <linux/interrupt.h> +#include <linux/skbuff.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/segment.h> +#include <asm/io.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/ctype.h> + +#include <linux/isdnif.h> + +typedef struct { + __u16 length __attribute__ ((packed)); /* length of data/parameter field */ + __u8 P[1]; /* data/parameter field */ +} eicon_PBUFFER; + +#include "eicon_isa.h" + +/* Macro for delay via schedule() */ +#define SLEEP(j) { \ + current->state = TASK_INTERRUPTIBLE; \ + schedule_timeout(j); \ +} + +#endif /* KERNEL */ + + +#define DSP_COMBIFILE_FORMAT_IDENTIFICATION_SIZE 48 +#define DSP_COMBIFILE_FORMAT_VERSION_BCD 0x0100 + +#define DSP_FILE_FORMAT_IDENTIFICATION_SIZE 48 +#define DSP_FILE_FORMAT_VERSION_BCD 0x0100 + +typedef struct tag_dsp_combifile_header +{ + char format_identification[DSP_COMBIFILE_FORMAT_IDENTIFICATION_SIZE] __attribute__ ((packed)); + __u16 format_version_bcd __attribute__ ((packed)); + __u16 header_size __attribute__ ((packed)); + __u16 combifile_description_size __attribute__ ((packed)); + __u16 directory_entries __attribute__ ((packed)); + __u16 directory_size __attribute__ ((packed)); + __u16 download_count __attribute__ ((packed)); + __u16 usage_mask_size __attribute__ ((packed)); +} t_dsp_combifile_header; + +typedef struct tag_dsp_combifile_directory_entry +{ + __u16 card_type_number __attribute__ ((packed)); + __u16 file_set_number __attribute__ ((packed)); +} t_dsp_combifile_directory_entry; + +typedef struct tag_dsp_file_header +{ + char format_identification[DSP_FILE_FORMAT_IDENTIFICATION_SIZE] __attribute__ ((packed)); + __u16 format_version_bcd __attribute__ ((packed)); + __u16 download_id __attribute__ ((packed)); + __u16 download_flags __attribute__ ((packed)); + __u16 required_processing_power __attribute__ ((packed)); + __u16 interface_channel_count __attribute__ ((packed)); + __u16 header_size __attribute__ ((packed)); + __u16 download_description_size __attribute__ ((packed)); + __u16 memory_block_table_size __attribute__ ((packed)); + __u16 memory_block_count __attribute__ ((packed)); + __u16 segment_table_size __attribute__ ((packed)); + __u16 segment_count __attribute__ ((packed)); + __u16 symbol_table_size __attribute__ ((packed)); + __u16 symbol_count __attribute__ ((packed)); + __u16 total_data_size_dm __attribute__ ((packed)); + __u16 data_block_count_dm __attribute__ ((packed)); + __u16 total_data_size_pm __attribute__ ((packed)); + __u16 data_block_count_pm __attribute__ ((packed)); +} t_dsp_file_header; + +typedef struct tag_dsp_memory_block_desc +{ + __u16 alias_memory_block; + __u16 memory_type; + __u16 address; + __u16 size; /* DSP words */ +} t_dsp_memory_block_desc; + +typedef struct tag_dsp_segment_desc +{ + __u16 memory_block; + __u16 attributes; + __u16 base; + __u16 size; + __u16 alignment; /* ==0 -> no other legal start address than base */ +} t_dsp_segment_desc; + +typedef struct tag_dsp_symbol_desc +{ + __u16 symbol_id; + __u16 segment; + __u16 offset; + __u16 size; /* DSP words */ +} t_dsp_symbol_desc; + +typedef struct tag_dsp_data_block_header +{ + __u16 attributes; + __u16 segment; + __u16 offset; + __u16 size; /* DSP words */ +} t_dsp_data_block_header; + +typedef struct tag_dsp_download_desc /* be sure to keep native alignment for MAESTRA's */ +{ + __u16 download_id; + __u16 download_flags; + __u16 required_processing_power; + __u16 interface_channel_count; + __u16 excess_header_size; + __u16 memory_block_count; + __u16 segment_count; + __u16 symbol_count; + __u16 data_block_count_dm; + __u16 data_block_count_pm; + __u8 * p_excess_header_data __attribute__ ((packed)); + char * p_download_description __attribute__ ((packed)); + t_dsp_memory_block_desc *p_memory_block_table __attribute__ ((packed)); + t_dsp_segment_desc *p_segment_table __attribute__ ((packed)); + t_dsp_symbol_desc *p_symbol_table __attribute__ ((packed)); + __u16 * p_data_blocks_dm __attribute__ ((packed)); + __u16 * p_data_blocks_pm __attribute__ ((packed)); +} t_dsp_download_desc; + + +#ifdef __KERNEL__ + +typedef struct { + __u8 Req; /* pending request */ + __u8 Rc; /* return code received */ + __u8 Ind; /* indication received */ + __u8 ReqCh; /* channel of current Req */ + __u8 RcCh; /* channel of current Rc */ + __u8 IndCh; /* channel of current Ind */ + __u8 D3Id; /* ID used by this entity */ + __u8 B2Id; /* ID used by this entity */ + __u8 GlobalId; /* reserved field */ + __u8 XNum; /* number of X-buffers */ + __u8 RNum; /* number of R-buffers */ + struct sk_buff_head X; /* X-buffer queue */ + struct sk_buff_head R; /* R-buffer queue */ + __u8 RNR; /* receive not ready flag */ + __u8 complete; /* receive complete status */ + __u8 busy; /* busy flag */ + __u16 ref; /* saved reference */ +} entity; + + +typedef struct { + int No; /* Channel Number */ + unsigned short callref; /* Call Reference */ + unsigned short fsm_state; /* Current D-Channel state */ + unsigned short eazmask; /* EAZ-Mask for this Channel */ + unsigned int queued; /* User-Data Bytes in TX queue */ + unsigned int waitq; /* User-Data Bytes in wait queue */ + unsigned int waitpq; /* User-Data Bytes in packet queue */ + unsigned short plci; + unsigned short ncci; + unsigned char l2prot; /* Layer 2 protocol */ + unsigned char l3prot; /* Layer 3 protocol */ + entity e; /* Entity */ + char cpn[32]; /* remember cpn */ + char oad[32]; /* remember oad */ + unsigned char cause[2]; /* Last Cause */ + unsigned char si1; + unsigned char si2; +} eicon_chan; + +typedef struct { + eicon_chan *ptr; +} eicon_chan_ptr; + +#include "eicon_pci.h" + +#define EICON_FLAGS_RUNNING 1 /* Cards driver activated */ +#define EICON_FLAGS_PVALID 2 /* Cards port is valid */ +#define EICON_FLAGS_IVALID 4 /* Cards irq is valid */ +#define EICON_FLAGS_MVALID 8 /* Cards membase is valid */ +#define EICON_FLAGS_LOADED 8 /* Firmware loaded */ + +#define EICON_BCH 2 /* # of channels per card */ + +/* D-Channel states */ +#define EICON_STATE_NULL 0 +#define EICON_STATE_ICALL 1 +#define EICON_STATE_OCALL 2 +#define EICON_STATE_IWAIT 3 +#define EICON_STATE_OWAIT 4 +#define EICON_STATE_IBWAIT 5 +#define EICON_STATE_OBWAIT 6 +#define EICON_STATE_BWAIT 7 +#define EICON_STATE_BHWAIT 8 +#define EICON_STATE_BHWAIT2 9 +#define EICON_STATE_DHWAIT 10 +#define EICON_STATE_DHWAIT2 11 +#define EICON_STATE_BSETUP 12 +#define EICON_STATE_ACTIVE 13 +#define EICON_STATE_ICALLW 14 +#define EICON_STATE_LISTEN 15 +#define EICON_STATE_WMCONN 16 + +#define EICON_MAX_QUEUED 8000 /* 2 * maxbuff */ + +#define EICON_LOCK_TX 0 +#define EICON_LOCK_RX 1 + +typedef struct { + int dummy; +} eicon_mca_card; + +typedef union { + eicon_isa_card isa; + eicon_pci_card pci; + eicon_mca_card mca; +} eicon_hwif; + +typedef struct { + __u8 ret; + __u8 id; + __u8 ch; +} eicon_ack; + +typedef struct { + __u8 code; + __u8 id; + __u8 ch; +} eicon_req; + +typedef struct { + __u8 ret; + __u8 id; + __u8 ch; + __u8 more; +} eicon_indhdr; + +typedef struct msn_entry { + char eaz; + char msn[16]; + struct msn_entry * next; +} msn_entry; + +/* + * Per card driver data + */ +typedef struct eicon_card { + eicon_hwif hwif; /* Hardware dependant interface */ + u_char ptype; /* Protocol type (1TR6 or Euro) */ + u_char bus; /* Bustype (ISA, MCA, PCI) */ + u_char type; /* Cardtype (EICON_CTYPE_...) */ + struct eicon_card *qnext; /* Pointer to next quadro adapter */ + int Feature; /* Protocol Feature Value */ + struct eicon_card *next; /* Pointer to next device struct */ + int myid; /* Driver-Nr. assigned by linklevel */ + unsigned long flags; /* Statusflags */ + unsigned long ilock; /* Semaphores for IRQ-Routines */ + struct sk_buff_head rcvq; /* Receive-Message queue */ + struct sk_buff_head sndq; /* Send-Message queue */ + struct sk_buff_head rackq; /* Req-Ack-Message queue */ + struct sk_buff_head sackq; /* Data-Ack-Message queue */ + u_char *ack_msg; /* Ptr to User Data in User skb */ + __u16 need_b3ack; /* Flag: Need ACK for current skb */ + struct sk_buff *sbuf; /* skb which is currently sent */ + struct tq_struct snd_tq; /* Task struct for xmit bh */ + struct tq_struct rcv_tq; /* Task struct for rcv bh */ + struct tq_struct ack_tq; /* Task struct for ack bh */ + msn_entry *msn_list; + unsigned short msgnum; /* Message number for sending */ + eicon_chan* IdTable[256]; /* Table to find entity */ + __u16 ref_in; + __u16 ref_out; + int nchannels; /* Number of B-Channels */ + int ReadyInt; /* Ready Interrupt */ + eicon_chan *bch; /* B-Channel status/control */ + char status_buf[256]; /* Buffer for status messages */ + char *status_buf_read; + char *status_buf_write; + char *status_buf_end; + isdn_if interface; /* Interface to upper layer */ + char regname[35]; /* Name used for request_region */ +} eicon_card; + +/* -----------------------------------------------------------** +** The PROTOCOL_FEATURE_STRING ** +** defines capabilities and ** +** features of the actual protocol code. It's used as a bit ** +** mask. ** +** The following Bits are defined: ** +** -----------------------------------------------------------*/ +#define PROTCAP_TELINDUS 0x0001 /* Telindus Variant of protocol code */ +#define PROTCAP_MANIF 0x0002 /* Management interface implemented */ +#define PROTCAP_V_42 0x0004 /* V42 implemented */ +#define PROTCAP_V90D 0x0008 /* V.90D (implies up to 384k DSP code) */ +#define PROTCAP_EXTD_FAX 0x0010 /* Extended FAX (ECM, 2D, T6, Polling) */ +#define PROTCAP_FREE4 0x0020 /* not used */ +#define PROTCAP_FREE5 0x0040 /* not used */ +#define PROTCAP_FREE6 0x0080 /* not used */ +#define PROTCAP_FREE7 0x0100 /* not used */ +#define PROTCAP_FREE8 0x0200 /* not used */ +#define PROTCAP_FREE9 0x0400 /* not used */ +#define PROTCAP_FREE10 0x0800 /* not used */ +#define PROTCAP_FREE11 0x1000 /* not used */ +#define PROTCAP_FREE12 0x2000 /* not used */ +#define PROTCAP_FREE13 0x4000 /* not used */ +#define PROTCAP_EXTENSION 0x8000 /* used for future extentions */ + +#include "eicon_idi.h" + +extern eicon_card *cards; +extern char *eicon_ctype_name[]; + + +extern __inline__ void eicon_schedule_tx(eicon_card *card) +{ + queue_task(&card->snd_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +extern __inline__ void eicon_schedule_rx(eicon_card *card) +{ + queue_task(&card->rcv_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +extern __inline__ void eicon_schedule_ack(eicon_card *card) +{ + queue_task(&card->ack_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +extern char *eicon_find_eaz(eicon_card *, char); +extern int eicon_addcard(int, int, int, char *); +extern void eicon_io_transmit(eicon_card *card); +extern void eicon_irq(int irq, void *dev_id, struct pt_regs *regs); +extern void eicon_io_rcv_dispatch(eicon_card *ccard); +extern void eicon_io_ack_dispatch(eicon_card *ccard); +extern ulong DebugVar; + +#endif /* __KERNEL__ */ + +#endif /* eicon_h */ diff --git a/drivers/isdn/eicon/eicon_dsp.h b/drivers/isdn/eicon/eicon_dsp.h new file mode 100644 index 000000000..94a4595c8 --- /dev/null +++ b/drivers/isdn/eicon/eicon_dsp.h @@ -0,0 +1,304 @@ +/* $Id: eicon_dsp.h,v 1.2 1999/03/29 11:19:42 armin Exp $ + * + * ISDN lowlevel-module for Eicon.Diehl active cards. + * DSP definitions + * + * Copyright 1999 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_dsp.h,v $ + * Revision 1.2 1999/03/29 11:19:42 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.1 1999/03/02 12:18:54 armin + * First checkin of DSP defines for audio features. + * + * + */ + +#ifndef DSP_H +#define DSP_H + +#define DSP_UDATA_REQUEST_RECONFIGURE 0 +/* +parameters: + <word> reconfigure delay (in 8kHz samples) + <word> reconfigure code + <byte> reconfigure hdlc preamble flags +*/ + +#define DSP_RECONFIGURE_TX_FLAG 0x8000 +#define DSP_RECONFIGURE_SHORT_TRAIN_FLAG 0x4000 +#define DSP_RECONFIGURE_ECHO_PROTECT_FLAG 0x2000 +#define DSP_RECONFIGURE_HDLC_FLAG 0x1000 +#define DSP_RECONFIGURE_SYNC_FLAG 0x0800 +#define DSP_RECONFIGURE_PROTOCOL_MASK 0x00ff +#define DSP_RECONFIGURE_IDLE 0 +#define DSP_RECONFIGURE_V25 1 +#define DSP_RECONFIGURE_V21_CH2 2 +#define DSP_RECONFIGURE_V27_2400 3 +#define DSP_RECONFIGURE_V27_4800 4 +#define DSP_RECONFIGURE_V29_7200 5 +#define DSP_RECONFIGURE_V29_9600 6 +#define DSP_RECONFIGURE_V33_12000 7 +#define DSP_RECONFIGURE_V33_14400 8 +#define DSP_RECONFIGURE_V17_7200 9 +#define DSP_RECONFIGURE_V17_9600 10 +#define DSP_RECONFIGURE_V17_12000 11 +#define DSP_RECONFIGURE_V17_14400 12 + +/* +data indications if transparent framer + <byte> data 0 + <byte> data 1 + ... + +data indications if HDLC framer + <byte> data 0 + <byte> data 1 + ... + <byte> CRC 0 + <byte> CRC 1 + <byte> preamble flags +*/ + +#define DSP_UDATA_REQUEST_SWITCH_FRAMER 1 +/* +parameters: + <byte> transmit framer type + <byte> receive framer type +*/ + +#define DSP_REQUEST_SWITCH_FRAMER_HDLC 0 +#define DSP_REQUEST_SWITCH_FRAMER_TRANSPARENT 1 +#define DSP_REQUEST_SWITCH_FRAMER_ASYNC 2 + + +#define DSP_UDATA_REQUEST_CLEARDOWN 2 +/* +parameters: + - none - +*/ + + +#define DSP_UDATA_REQUEST_TX_CONFIRMATION_ON 3 +/* +parameters: + - none - +*/ + + +#define DSP_UDATA_REQUEST_TX_CONFIRMATION_OFF 4 +/* +parameters: + - none - +*/ + + +#define DSP_UDATA_INDICATION_SYNC 0 +/* +returns: + <word> time of sync (sampled from counter at 8kHz) +*/ + +#define DSP_UDATA_INDICATION_DCD_OFF 1 +/* +returns: + <word> time of DCD off (sampled from counter at 8kHz) +*/ + +#define DSP_UDATA_INDICATION_DCD_ON 2 +/* +returns: + <word> time of DCD on (sampled from counter at 8kHz) + <byte> connected norm + <word> connected options + <dword> connected speed (bit/s, max of tx and rx speed) + <word> roundtrip delay (ms) + <dword> connected speed tx (bit/s) + <dword> connected speed rx (bit/s) +*/ + +#define DSP_UDATA_INDICATION_CTS_OFF 3 +/* +returns: + <word> time of CTS off (sampled from counter at 8kHz) +*/ + +#define DSP_UDATA_INDICATION_CTS_ON 4 +/* +returns: + <word> time of CTS on (sampled from counter at 8kHz) + <byte> connected norm + <word> connected options + <dword> connected speed (bit/s, max of tx and rx speed) + <word> roundtrip delay (ms) + <dword> connected speed tx (bit/s) + <dword> connected speed rx (bit/s) +*/ + +typedef struct eicon_dsp_ind { + __u16 time __attribute__ ((packed)); + __u8 norm __attribute__ ((packed)); + __u16 options __attribute__ ((packed)); + __u32 speed __attribute__ ((packed)); + __u16 delay __attribute__ ((packed)); + __u32 txspeed __attribute__ ((packed)); + __u32 rxspeed __attribute__ ((packed)); +} eicon_dsp_ind; + +#define DSP_CONNECTED_NORM_UNSPECIFIED 0 +#define DSP_CONNECTED_NORM_V21 1 +#define DSP_CONNECTED_NORM_V23 2 +#define DSP_CONNECTED_NORM_V22 3 +#define DSP_CONNECTED_NORM_V22_BIS 4 +#define DSP_CONNECTED_NORM_V32_BIS 5 +#define DSP_CONNECTED_NORM_V34 6 +#define DSP_CONNECTED_NORM_V8 7 +#define DSP_CONNECTED_NORM_BELL_212A 8 +#define DSP_CONNECTED_NORM_BELL_103 9 +#define DSP_CONNECTED_NORM_V29_LEASED_LINE 10 +#define DSP_CONNECTED_NORM_V33_LEASED_LINE 11 +#define DSP_CONNECTED_NORM_V90 12 +#define DSP_CONNECTED_NORM_V21_CH2 13 +#define DSP_CONNECTED_NORM_V27_TER 14 +#define DSP_CONNECTED_NORM_V29 15 +#define DSP_CONNECTED_NORM_V33 16 +#define DSP_CONNECTED_NORM_V17 17 + +#define DSP_CONNECTED_OPTION_TRELLIS 0x0001 +#define DSP_CONNECTED_OPTION_V42_TRANS 0x0002 +#define DSP_CONNECTED_OPTION_V42_LAPM 0x0004 +#define DSP_CONNECTED_OPTION_SHORT_TRAIN 0x0008 +#define DSP_CONNECTED_OPTION_TALKER_ECHO_PROTECT 0x0010 + + +#define DSP_UDATA_INDICATION_DISCONNECT 5 +/* +returns: + <byte> cause +*/ + +#define DSP_DISCONNECT_CAUSE_NONE 0x00 +#define DSP_DISCONNECT_CAUSE_BUSY_TONE 0x01 +#define DSP_DISCONNECT_CAUSE_CONGESTION_TONE 0x02 +#define DSP_DISCONNECT_CAUSE_INCOMPATIBILITY 0x03 +#define DSP_DISCONNECT_CAUSE_CLEARDOWN 0x04 +#define DSP_DISCONNECT_CAUSE_TRAINING_TIMEOUT 0x05 + + +#define DSP_UDATA_INDICATION_TX_CONFIRMATION 6 +/* +returns: + <word> confirmation number +*/ + + +#define DSP_UDATA_REQUEST_SEND_DTMF_DIGITS 16 +/* +parameters: + <word> tone duration (ms) + <word> gap duration (ms) + <byte> digit 0 tone code + ... + <byte> digit n tone code +*/ + +#define DSP_SEND_DTMF_DIGITS_HEADER_LENGTH 5 + +#define DSP_DTMF_DIGIT_TONE_LOW_GROUP_697_HZ 0x00 +#define DSP_DTMF_DIGIT_TONE_LOW_GROUP_770_HZ 0x01 +#define DSP_DTMF_DIGIT_TONE_LOW_GROUP_852_HZ 0x02 +#define DSP_DTMF_DIGIT_TONE_LOW_GROUP_941_HZ 0x03 +#define DSP_DTMF_DIGIT_TONE_LOW_GROUP_MASK 0x03 +#define DSP_DTMF_DIGIT_TONE_HIGH_GROUP_1209_HZ 0x00 +#define DSP_DTMF_DIGIT_TONE_HIGH_GROUP_1336_HZ 0x04 +#define DSP_DTMF_DIGIT_TONE_HIGH_GROUP_1477_HZ 0x08 +#define DSP_DTMF_DIGIT_TONE_HIGH_GROUP_1633_HZ 0x0c +#define DSP_DTMF_DIGIT_TONE_HIGH_GROUP_MASK 0x0c + +#define DSP_DTMF_DIGIT_TONE_CODE_0 0x07 +#define DSP_DTMF_DIGIT_TONE_CODE_1 0x00 +#define DSP_DTMF_DIGIT_TONE_CODE_2 0x04 +#define DSP_DTMF_DIGIT_TONE_CODE_3 0x08 +#define DSP_DTMF_DIGIT_TONE_CODE_4 0x01 +#define DSP_DTMF_DIGIT_TONE_CODE_5 0x05 +#define DSP_DTMF_DIGIT_TONE_CODE_6 0x09 +#define DSP_DTMF_DIGIT_TONE_CODE_7 0x02 +#define DSP_DTMF_DIGIT_TONE_CODE_8 0x06 +#define DSP_DTMF_DIGIT_TONE_CODE_9 0x0a +#define DSP_DTMF_DIGIT_TONE_CODE_STAR 0x03 +#define DSP_DTMF_DIGIT_TONE_CODE_HASHMARK 0x0b +#define DSP_DTMF_DIGIT_TONE_CODE_A 0x0c +#define DSP_DTMF_DIGIT_TONE_CODE_B 0x0d +#define DSP_DTMF_DIGIT_TONE_CODE_C 0x0e +#define DSP_DTMF_DIGIT_TONE_CODE_D 0x0f + + +#define DSP_UDATA_INDICATION_DTMF_DIGITS_SENT 16 +/* +returns: + - none - + One indication will be sent for every request. +*/ + + +#define DSP_UDATA_REQUEST_ENABLE_DTMF_RECEIVER 17 +/* +parameters: + <word> tone duration (ms) + <word> gap duration (ms) +*/ + +#define DSP_UDATA_REQUEST_DISABLE_DTMF_RECEIVER 18 +/* +parameters: + - none - +*/ + +#define DSP_UDATA_INDICATION_DTMF_DIGITS_RECEIVED 17 +/* +returns: + <byte> digit 0 tone code + ... + <byte> digit n tone code +*/ + +#define DSP_DTMF_DIGITS_RECEIVED_HEADER_LENGTH 1 + + +#define DSP_UDATA_INDICATION_MODEM_CALLING_TONE 18 +/* +returns: + - none - +*/ + +#define DSP_UDATA_INDICATION_FAX_CALLING_TONE 19 +/* +returns: + - none - +*/ + +#define DSP_UDATA_INDICATION_ANSWER_TONE 20 +/* +returns: + - none - +*/ + +#endif /* DSP_H */ + diff --git a/drivers/isdn/eicon/eicon_idi.c b/drivers/isdn/eicon/eicon_idi.c new file mode 100644 index 000000000..a28f316c1 --- /dev/null +++ b/drivers/isdn/eicon/eicon_idi.c @@ -0,0 +1,1479 @@ +/* $Id: eicon_idi.c,v 1.9 1999/03/29 11:19:42 armin Exp $ + * + * ISDN lowlevel-module for Eicon.Diehl active cards. + * IDI interface + * + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_idi.c,v $ + * Revision 1.9 1999/03/29 11:19:42 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.8 1999/03/02 12:37:43 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.7 1999/02/03 18:34:35 armin + * Channel selection for outgoing calls w/o CHI. + * Added channel # in debug messages. + * L2 Transparent should work with 800 byte/packet now. + * + * Revision 1.6 1999/01/26 07:18:59 armin + * Bug with wrong added CPN fixed. + * + * Revision 1.5 1999/01/24 20:14:11 armin + * Changed and added debug stuff. + * Better data sending. (still problems with tty's flip buffer) + * + * Revision 1.4 1999/01/10 18:46:05 armin + * Bug with wrong values in HLC fixed. + * Bytes to send are counted and limited now. + * + * Revision 1.3 1999/01/05 14:49:34 armin + * Added experimental usage of full BC and HLC for + * speech, 3.1kHz audio, fax gr.2/3 + * + * Revision 1.2 1999/01/04 13:19:29 armin + * Channel status with listen-request wrong - fixed. + * + * Revision 1.1 1999/01/01 18:09:41 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + +#define __NO_VERSION__ +#include "eicon.h" +#include "eicon_idi.h" +#include "eicon_dsp.h" + +#undef EICON_FULL_SERVICE_OKTETT + +char *eicon_idi_revision = "$Revision: 1.9 $"; + +eicon_manifbuf *manbuf; + +static char BC_Speech[3] = { 0x80, 0x90, 0xa3 }; +static char BC_31khz[3] = { 0x90, 0x90, 0xa3 }; +static char BC_64k[2] = { 0x88, 0x90 }; +static char BC_video[3] = { 0x91, 0x90, 0xa5 }; + +#ifdef EICON_FULL_SERVICE_OKTETT +/* +static char HLC_telephony[2] = { 0x91, 0x81 }; +*/ +static char HLC_faxg3[2] = { 0x91, 0x84 }; +#endif + +int eicon_idi_manage_assign(eicon_card *card); +int eicon_idi_manage_remove(eicon_card *card); + +int +idi_assign_req(eicon_REQ *reqbuf, int signet, eicon_chan *chan) +{ + int l = 0; + if (!signet) { + /* Signal Layer */ + reqbuf->XBuffer.P[l++] = CAI; + reqbuf->XBuffer.P[l++] = 1; + reqbuf->XBuffer.P[l++] = 0; + reqbuf->XBuffer.P[l++] = KEY; + reqbuf->XBuffer.P[l++] = 3; + reqbuf->XBuffer.P[l++] = 'I'; + reqbuf->XBuffer.P[l++] = '4'; + reqbuf->XBuffer.P[l++] = 'L'; + reqbuf->XBuffer.P[l++] = SHIFT|6; + reqbuf->XBuffer.P[l++] = SIN; + reqbuf->XBuffer.P[l++] = 2; + reqbuf->XBuffer.P[l++] = 0; + reqbuf->XBuffer.P[l++] = 0; + reqbuf->XBuffer.P[l++] = 0; /* end */ + reqbuf->Req = ASSIGN; + reqbuf->ReqCh = 0; + reqbuf->ReqId = 0; + reqbuf->XBuffer.length = l; + reqbuf->Reference = 0; /* Sig Entity */ + } + else { + /* Network Layer */ + reqbuf->XBuffer.P[l++] = CAI; + reqbuf->XBuffer.P[l++] = 1; + reqbuf->XBuffer.P[l++] = chan->e.D3Id; + reqbuf->XBuffer.P[l++] = LLC; + reqbuf->XBuffer.P[l++] = 2; + switch(chan->l2prot) { + case ISDN_PROTO_L2_HDLC: + reqbuf->XBuffer.P[l++] = 2; + break; + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + reqbuf->XBuffer.P[l++] = 5; + break; + case ISDN_PROTO_L2_TRANS: + case ISDN_PROTO_L2_MODEM: + reqbuf->XBuffer.P[l++] = 2; + break; + default: + reqbuf->XBuffer.P[l++] = 1; + } + switch(chan->l3prot) { + case ISDN_PROTO_L3_TRANS: + default: + reqbuf->XBuffer.P[l++] = 4; + } + reqbuf->XBuffer.P[l++] = 0; /* end */ + reqbuf->Req = ASSIGN; + reqbuf->ReqCh = 0; + reqbuf->ReqId = 0x20; + reqbuf->XBuffer.length = l; + reqbuf->Reference = 1; /* Net Entity */ + } + return(0); +} + +int +idi_put_req(eicon_REQ *reqbuf, int rq, int signet) +{ + reqbuf->Req = rq; + reqbuf->ReqCh = 0; + reqbuf->ReqId = 1; + reqbuf->XBuffer.length = 1; + reqbuf->XBuffer.P[0] = 0; + reqbuf->Reference = signet; + return(0); +} + +int +idi_call_res_req(eicon_REQ *reqbuf, eicon_chan *chan) +{ + int l = 9; + reqbuf->Req = CALL_RES; + reqbuf->ReqCh = 0; + reqbuf->ReqId = 1; + reqbuf->XBuffer.P[0] = CAI; + reqbuf->XBuffer.P[1] = 6; + reqbuf->XBuffer.P[2] = 9; + reqbuf->XBuffer.P[3] = 0; + reqbuf->XBuffer.P[4] = 0; + reqbuf->XBuffer.P[5] = 0; + reqbuf->XBuffer.P[6] = 32; + reqbuf->XBuffer.P[7] = 3; + switch(chan->l2prot) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + case ISDN_PROTO_L2_HDLC: + reqbuf->XBuffer.P[1] = 1; + reqbuf->XBuffer.P[2] = 0x05; + l = 4; + break; + case ISDN_PROTO_L2_V11096: + reqbuf->XBuffer.P[2] = 0x0d; + reqbuf->XBuffer.P[3] = 5; + reqbuf->XBuffer.P[4] = 0; + break; + case ISDN_PROTO_L2_V11019: + reqbuf->XBuffer.P[2] = 0x0d; + reqbuf->XBuffer.P[3] = 6; + reqbuf->XBuffer.P[4] = 0; + break; + case ISDN_PROTO_L2_V11038: + reqbuf->XBuffer.P[2] = 0x0d; + reqbuf->XBuffer.P[3] = 7; + reqbuf->XBuffer.P[4] = 0; + break; + case ISDN_PROTO_L2_MODEM: + reqbuf->XBuffer.P[2] = 0x11; + reqbuf->XBuffer.P[3] = 7; + reqbuf->XBuffer.P[4] = 0; + reqbuf->XBuffer.P[5] = 0; + reqbuf->XBuffer.P[6] = 128; + reqbuf->XBuffer.P[7] = 0; + break; + } + reqbuf->XBuffer.P[8] = 0; + reqbuf->XBuffer.length = l; + reqbuf->Reference = 0; /* Sig Entity */ + if (DebugVar & 8) + printk(KERN_DEBUG"idi_req: Ch%d: Call_Res\n", chan->No); + return(0); +} + +int +idi_do_req(eicon_card *card, eicon_chan *chan, int cmd, int layer) +{ + struct sk_buff *skb; + struct sk_buff *skb2; + eicon_REQ *reqbuf; + eicon_chan_ptr *chan2; + + skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC); + skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC); + + if ((!skb) || (!skb2)) { + if (DebugVar & 1) + printk(KERN_WARNING "idi_err: Ch%d: alloc_skb failed\n", chan->No); + return -ENOMEM; + } + + chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr)); + chan2->ptr = chan; + + reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ)); + if (DebugVar & 8) + printk(KERN_DEBUG "idi_req: Ch%d: 0x%02x (%s)\n", chan->No, cmd, (layer)?"Net":"Sig"); + if (layer) cmd |= 0x700; + switch(cmd) { + case ASSIGN: + case ASSIGN|0x700: + idi_assign_req(reqbuf, layer, chan); + break; + case REMOVE: + case REMOVE|0x700: + idi_put_req(reqbuf, REMOVE, layer); + break; + case INDICATE_REQ: + idi_put_req(reqbuf, INDICATE_REQ, 0); + break; + case HANGUP: + idi_put_req(reqbuf, HANGUP, 0); + break; + case REJECT: + idi_put_req(reqbuf, REJECT, 0); + break; + case CALL_ALERT: + idi_put_req(reqbuf, CALL_ALERT, 0); + break; + case CALL_RES: + idi_call_res_req(reqbuf, chan); + break; + case IDI_N_CONNECT|0x700: + idi_put_req(reqbuf, IDI_N_CONNECT, 1); + break; + case IDI_N_CONNECT_ACK|0x700: + idi_put_req(reqbuf, IDI_N_CONNECT_ACK, 1); + break; + case IDI_N_DISC|0x700: + idi_put_req(reqbuf, IDI_N_DISC, 1); + break; + case IDI_N_DISC_ACK|0x700: + idi_put_req(reqbuf, IDI_N_DISC_ACK, 1); + break; + default: + if (DebugVar & 1) + printk(KERN_ERR "idi_req: Ch%d: Unknown request\n", chan->No); + return(-1); + } + + skb_queue_tail(&chan->e.X, skb); + skb_queue_tail(&card->sndq, skb2); + eicon_schedule_tx(card); + return(0); +} + +int +eicon_idi_listen_req(eicon_card *card, eicon_chan *chan) +{ + if (DebugVar & 16) + printk(KERN_DEBUG"idi_req: Ch%d: Listen_Req eazmask=0x%x\n",chan->No, chan->eazmask); + if (!chan->e.D3Id) { + idi_do_req(card, chan, ASSIGN, 0); + } + if (chan->fsm_state == EICON_STATE_NULL) { + idi_do_req(card, chan, INDICATE_REQ, 0); + chan->fsm_state = EICON_STATE_LISTEN; + } + return(0); +} + +unsigned char +idi_si2bc(int si1, int si2, char *bc, char *hlc) +{ + hlc[0] = 0; + switch(si1) { + case 1: + bc[0] = 0x90; /* 3,1 kHz audio */ + bc[1] = 0x90; /* 64 kbit/s */ + bc[2] = 0xa3; /* G.711 A-law */ +#ifdef EICON_FULL_SERVICE_OKTETT + if (si2 == 1) { + bc[0] = 0x80; /* Speech */ + hlc[0] = 0x02; /* hlc len */ + hlc[1] = 0x91; /* first hic */ + hlc[2] = 0x81; /* Telephony */ + } +#endif + return(3); + case 2: + bc[0] = 0x90; /* 3,1 kHz audio */ + bc[1] = 0x90; /* 64 kbit/s */ + bc[2] = 0xa3; /* G.711 A-law */ +#ifdef EICON_FULL_SERVICE_OKTETT + if (si2 == 2) { + hlc[0] = 0x02; /* hlc len */ + hlc[1] = 0x91; /* first hic */ + hlc[2] = 0x84; /* Fax Gr.2/3 */ + } +#endif + return(3); + case 5: + case 7: + default: + bc[0] = 0x88; + bc[1] = 0x90; + return(2); + } + return (0); +} + +int +idi_hangup(eicon_card *card, eicon_chan *chan) +{ + if ((chan->fsm_state == EICON_STATE_ACTIVE) || + (chan->fsm_state == EICON_STATE_WMCONN)) { + if (chan->e.B2Id) idi_do_req(card, chan, IDI_N_DISC, 1); + } + if (chan->e.B2Id) idi_do_req(card, chan, REMOVE, 1); + idi_do_req(card, chan, HANGUP, 0); + chan->fsm_state = EICON_STATE_NULL; + if (DebugVar & 8) + printk(KERN_DEBUG"idi_req: Ch%d: Hangup\n", chan->No); + return(0); +} + +int +idi_connect_res(eicon_card *card, eicon_chan *chan) +{ + chan->fsm_state = EICON_STATE_IWAIT; + idi_do_req(card, chan, CALL_RES, 0); + idi_do_req(card, chan, ASSIGN, 1); + return(0); +} + +int +idi_connect_req(eicon_card *card, eicon_chan *chan, char *phone, + char *eazmsn, int si1, int si2) +{ + int l = 0; + int i; + unsigned char tmp; + unsigned char bc[5]; + unsigned char hlc[5]; + struct sk_buff *skb; + struct sk_buff *skb2; + eicon_REQ *reqbuf; + eicon_chan_ptr *chan2; + + skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC); + skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC); + + if ((!skb) || (!skb2)) { + if (DebugVar & 1) + printk(KERN_WARNING "idi_err: Ch%d: alloc_skb failed\n", chan->No); + return -ENOMEM; + } + + chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr)); + chan2->ptr = chan; + + reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ)); + reqbuf->Req = CALL_REQ; + reqbuf->ReqCh = 0; + reqbuf->ReqId = 1; + + reqbuf->XBuffer.P[l++] = CPN; + reqbuf->XBuffer.P[l++] = strlen(phone) + 1; + reqbuf->XBuffer.P[l++] = 0xc1; + for(i=0; i<strlen(phone);i++) + reqbuf->XBuffer.P[l++] = phone[i]; + + reqbuf->XBuffer.P[l++] = OAD; + reqbuf->XBuffer.P[l++] = strlen(eazmsn) + 2; + reqbuf->XBuffer.P[l++] = 0x01; + reqbuf->XBuffer.P[l++] = 0x81; + for(i=0; i<strlen(eazmsn);i++) + reqbuf->XBuffer.P[l++] = eazmsn[i]; + + if ((tmp = idi_si2bc(si1, si2, bc, hlc)) > 0) { + reqbuf->XBuffer.P[l++] = BC; + reqbuf->XBuffer.P[l++] = tmp; + for(i=0; i<tmp;i++) + reqbuf->XBuffer.P[l++] = bc[i]; + if ((tmp=hlc[0])) { + reqbuf->XBuffer.P[l++] = HLC; + reqbuf->XBuffer.P[l++] = tmp; + for(i=1; i<=tmp;i++) + reqbuf->XBuffer.P[l++] = hlc[i]; + } + } + reqbuf->XBuffer.P[l++] = CAI; + reqbuf->XBuffer.P[l++] = 6; + reqbuf->XBuffer.P[l++] = 0x09; + reqbuf->XBuffer.P[l++] = 0; + reqbuf->XBuffer.P[l++] = 0; + reqbuf->XBuffer.P[l++] = 0; + reqbuf->XBuffer.P[l++] = 32; + reqbuf->XBuffer.P[l++] = 3; + switch(chan->l2prot) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + case ISDN_PROTO_L2_HDLC: + reqbuf->XBuffer.P[l-6] = 5; + reqbuf->XBuffer.P[l-7] = 1; + l -= 5; + break; + case ISDN_PROTO_L2_V11096: + reqbuf->XBuffer.P[l-7] = 3; + reqbuf->XBuffer.P[l-6] = 0x0d; + reqbuf->XBuffer.P[l-5] = 5; + reqbuf->XBuffer.P[l-4] = 0; + l -= 3; + break; + case ISDN_PROTO_L2_V11019: + reqbuf->XBuffer.P[l-7] = 3; + reqbuf->XBuffer.P[l-6] = 0x0d; + reqbuf->XBuffer.P[l-5] = 6; + reqbuf->XBuffer.P[l-4] = 0; + l -= 3; + break; + case ISDN_PROTO_L2_V11038: + reqbuf->XBuffer.P[l-7] = 3; + reqbuf->XBuffer.P[l-6] = 0x0d; + reqbuf->XBuffer.P[l-5] = 7; + reqbuf->XBuffer.P[l-4] = 0; + l -= 3; + break; + case ISDN_PROTO_L2_MODEM: + reqbuf->XBuffer.P[l-6] = 0x11; + reqbuf->XBuffer.P[l-5] = 7; + reqbuf->XBuffer.P[l-4] = 0; + reqbuf->XBuffer.P[l-3] = 0; + reqbuf->XBuffer.P[l-2] = 128; + reqbuf->XBuffer.P[l-1] = 0; + break; + } + + reqbuf->XBuffer.P[l++] = 0; /* end */ + reqbuf->XBuffer.length = l; + reqbuf->Reference = 0; /* Sig Entity */ + + skb_queue_tail(&chan->e.X, skb); + skb_queue_tail(&card->sndq, skb2); + eicon_schedule_tx(card); + + if (DebugVar & 8) + printk(KERN_DEBUG"idi_req: Ch%d: Conn_Req %s -> %s\n",chan->No, eazmsn, phone); + return(0); +} + + +void +idi_IndParse(eicon_card *ccard, eicon_chan *chan, idi_ind_message *message, unsigned char *buffer, int len) +{ + int i,j; + int pos = 0; + int codeset = 0; + int wlen = 0; + int lock = 0; + __u8 w; + __u16 code; + isdn_ctrl cmd; + + memset(message, 0, sizeof(idi_ind_message)); + + if ((!len) || (!buffer[pos])) return; + while(pos <= len) { + w = buffer[pos++]; + if (!w) return; + if (w & 0x80) { + wlen = 0; + } + else { + wlen = buffer[pos++]; + } + + if (pos > len) return; + + if (lock & 0x80) lock &= 0x7f; + else codeset = lock; + + if((w&0xf0) == SHIFT) { + codeset = w; + if(!(codeset & 0x08)) lock = codeset & 7; + codeset &= 7; + lock |= 0x80; + } + else { + if (w==ESC && wlen >=2) { + code = buffer[pos++]|0x800; + wlen--; + } + else code = w; + code |= (codeset<<8); + + switch(code) { + case OAD: + j = 1; + if (wlen) { + message->plan = buffer[pos++]; + if (message->plan &0x80) + message->screen = 0; + else { + message->screen = buffer[pos++]; + j = 2; + } + } + for(i=0; i < wlen-j; i++) + message->oad[i] = buffer[pos++]; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: OAD=(0x%02x,0x%02x) %s\n", chan->No, + message->plan, message->screen, message->oad); + break; + case RDN: + j = 1; + if (wlen) { + if (!(buffer[pos++] & 0x80)) { + pos++; + j = 2; + } + } + for(i=0; i < wlen-j; i++) + message->rdn[i] = buffer[pos++]; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: RDN= %s\n", chan->No, + message->rdn); + break; + case CPN: + for(i=0; i < wlen; i++) + message->cpn[i] = buffer[pos++]; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: CPN=(0x%02x) %s\n", chan->No, + (__u8)message->cpn[0], message->cpn + 1); + break; + case DSA: + pos++; + for(i=0; i < wlen-1; i++) + message->dsa[i] = buffer[pos++]; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: DSA=%s\n", chan->No, message->dsa); + break; + case OSA: + pos++; + for(i=0; i < wlen-1; i++) + message->osa[i] = buffer[pos++]; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: OSA=%s\n", chan->No, message->osa); + break; + case BC: + for(i=0; i < wlen; i++) + message->bc[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: BC = 0x%02x 0x%02x 0x%02x\n", chan->No, + message->bc[0],message->bc[1],message->bc[2]); + break; + case 0x800|BC: + for(i=0; i < wlen; i++) + message->e_bc[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: ESC/BC=%d\n", chan->No, message->bc[0]); + break; + case LLC: + for(i=0; i < wlen; i++) + message->llc[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: LLC=%d %d %d %d\n", chan->No, message->llc[0], + message->llc[1],message->llc[2],message->llc[3]); + break; + case HLC: + for(i=0; i < wlen; i++) + message->hlc[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: HLC=%x %x %x %x %x\n", chan->No, + message->hlc[0], message->hlc[1], + message->hlc[2], message->hlc[3], message->hlc[4]); + break; + case DSP: + case 0x600|DSP: + for(i=0; i < wlen; i++) + message->display[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: Display: %s\n", chan->No, + message->display); + break; + case 0x600|KEY: + for(i=0; i < wlen; i++) + message->keypad[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: Keypad: %s\n", chan->No, + message->keypad); + break; + case NI: + case 0x600|NI: + if (wlen) { + if (DebugVar & 4) { + switch(buffer[pos] & 127) { + case 0: + printk(KERN_DEBUG"idi_inf: Ch%d: User suspended.\n", chan->No); + break; + case 1: + printk(KERN_DEBUG"idi_inf: Ch%d: User resumed.\n", chan->No); + break; + case 2: + printk(KERN_DEBUG"idi_inf: Ch%d: Bearer service change.\n", chan->No); + break; + default: + printk(KERN_DEBUG"idi_inf: Ch%d: Unknown Notification %x.\n", + chan->No, buffer[pos] & 127); + } + } + pos += wlen; + } + break; + case PI: + case 0x600|PI: + if (wlen > 1) { + if (DebugVar & 4) { + switch(buffer[pos+1] & 127) { + case 1: + printk(KERN_DEBUG"idi_inf: Ch%d: Call is not end-to-end ISDN.\n", chan->No); + break; + case 2: + printk(KERN_DEBUG"idi_inf: Ch%d: Destination address is non ISDN.\n", chan->No); + break; + case 3: + printk(KERN_DEBUG"idi_inf: Ch%d: Origination address is non ISDN.\n", chan->No); + break; + case 4: + printk(KERN_DEBUG"idi_inf: Ch%d: Call has returned to the ISDN.\n", chan->No); + break; + case 5: + printk(KERN_DEBUG"idi_inf: Ch%d: Interworking has occurred.\n", chan->No); + break; + case 8: + printk(KERN_DEBUG"idi_inf: Ch%d: In-band information available.\n", chan->No); + break; + default: + printk(KERN_DEBUG"idi_inf: Ch%d: Unknown Progress %x.\n", + chan->No, buffer[pos+1] & 127); + } + } + } + pos += wlen; + break; + case CAU: + for(i=0; i < wlen; i++) + message->cau[i] = buffer[pos++]; + memcpy(&chan->cause, &message->cau, 2); + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: CAU=%d %d\n", chan->No, + message->cau[0],message->cau[1]); + break; + case 0x800|CAU: + for(i=0; i < wlen; i++) + message->e_cau[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: ECAU=%d %d\n", chan->No, + message->e_cau[0],message->e_cau[1]); + break; + case 0x800|CHI: + for(i=0; i < wlen; i++) + message->e_chi[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: ESC/CHI=%d\n", chan->No, + message->e_cau[0]); + break; + case 0x800|0x7a: + pos ++; + message->e_mt=buffer[pos++]; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: EMT=0x%x\n", chan->No, message->e_mt); + break; + case DT: + for(i=0; i < wlen; i++) + message->dt[i] = buffer[pos++]; + if (DebugVar & 4) + printk(KERN_DEBUG"idi_inf: Ch%d: DT: %02d.%02d.%02d %02d:%02d:%02d\n", chan->No, + message->dt[2], message->dt[1], message->dt[0], + message->dt[3], message->dt[4], message->dt[5]); + break; + case 0x600|SIN: + for(i=0; i < wlen; i++) + message->sin[i] = buffer[pos++]; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: SIN=%d %d\n", chan->No, + message->sin[0],message->sin[1]); + break; + case 0x600|CPS: + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: Called Party Status in ind\n", chan->No); + pos += wlen; + break; + case 0x600|CIF: + for (i = 0; i < wlen; i++) + if (buffer[pos + i] != '0') break; + memcpy(&cmd.parm.num, &buffer[pos + i], wlen - i); + cmd.parm.num[wlen - i] = 0; + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: CIF=%s\n", chan->No, cmd.parm.num); + pos += wlen; + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_CINF; + cmd.arg = chan->No; + ccard->interface.statcallb(&cmd); + break; + case 0x600|DATE: + if (DebugVar & 2) + printk(KERN_DEBUG"idi_inf: Ch%d: Date in ind\n", chan->No); + pos += wlen; + break; + case 0xe08: + case 0xe7a: + case 0xe04: + case 0xe00: + /* *** TODO *** */ + case CHA: + /* Charge advice */ + case FTY: + case 0x600|FTY: + case CHI: + case 0x800: + /* Not yet interested in this */ + pos += wlen; + break; + case 0x880: + /* Managment Information Element */ + if (!manbuf) { + if (DebugVar & 1) + printk(KERN_WARNING"idi_err: manbuf not allocated\n"); + } + else { + memcpy(&manbuf->data[manbuf->pos], &buffer[pos], wlen); + manbuf->length[manbuf->count] = wlen; + manbuf->count++; + manbuf->pos += wlen; + } + pos += wlen; + break; + default: + pos += wlen; + if (DebugVar & 6) + printk(KERN_WARNING"idi_inf: Ch%d: unknown information element 0x%x in ind, len:%x\n", + chan->No, code, wlen); + } + } + } +} + +void +idi_bc2si(unsigned char *bc, unsigned char *hlc, unsigned char *si1, unsigned char *si2) +{ + si1[0] = 0; + si2[0] = 0; + if (memcmp(bc, BC_Speech, 3) == 0) { /* Speech */ + si1[0] = 1; +#ifdef EICON_FULL_SERVICE_OKTETT + si2[0] = 1; +#endif + } + if (memcmp(bc, BC_31khz, 3) == 0) { /* 3.1kHz audio */ + si1[0] = 1; +#ifdef EICON_FULL_SERVICE_OKTETT + si2[0] = 2; + if (memcmp(hlc, HLC_faxg3, 2) == 0) { /* Fax Gr.2/3 */ + si1[0] = 2; + } +#endif + } + if (memcmp(bc, BC_64k, 2) == 0) { /* unrestricted 64 kbits */ + si1[0] = 7; + } + if (memcmp(bc, BC_video, 3) == 0) { /* video */ + si1[0] = 4; + } +} + +void +idi_parse_udata(eicon_card *ccard, eicon_chan *chan, unsigned char *buffer, int len) +{ + isdn_ctrl cmd; + eicon_dsp_ind *p = (eicon_dsp_ind *) (&buffer[1]); + static char *connmsg[] = + {"", "V.21", "V.23", "V.22", "V.22bis", "V.32bis", "V.34", + "V.8", "Bell 212A", "Bell 103", "V.29 Leased", "V.33 Leased", "V.90", + "V.21 CH2", "V.27ter", "V.29", "V.33", "V.17"}; + + switch (buffer[0]) { + case DSP_UDATA_INDICATION_SYNC: + if (DebugVar & 16) + printk(KERN_DEBUG"idi_ind: Ch%d: UDATA_SYNC time %d\n", chan->No, p->time); + break; + case DSP_UDATA_INDICATION_DCD_OFF: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: UDATA_DCD_OFF time %d\n", chan->No, p->time); + break; + case DSP_UDATA_INDICATION_DCD_ON: + if ((chan->l2prot == ISDN_PROTO_L2_MODEM) && + (chan->fsm_state == EICON_STATE_WMCONN)) { + chan->fsm_state = EICON_STATE_ACTIVE; + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_BCONN; + cmd.arg = chan->No; + sprintf(cmd.parm.num, "%d/%s", p->speed, connmsg[p->norm]); + ccard->interface.statcallb(&cmd); + } + if (DebugVar & 8) { + printk(KERN_DEBUG"idi_ind: Ch%d: UDATA_DCD_ON time %d\n", chan->No, p->time); + printk(KERN_DEBUG"idi_ind: Ch%d: %d %d %d %d\n", chan->No, + p->norm, p->options, p->speed, p->delay); + } + break; + case DSP_UDATA_INDICATION_CTS_OFF: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: UDATA_CTS_OFF time %d\n", chan->No, p->time); + break; + case DSP_UDATA_INDICATION_CTS_ON: + if (DebugVar & 8) { + printk(KERN_DEBUG"idi_ind: Ch%d: UDATA_CTS_ON time %d\n", chan->No, p->time); + printk(KERN_DEBUG"idi_ind: Ch%d: %d %d %d %d\n", chan->No, + p->norm, p->options, p->speed, p->delay); + } + break; + case DSP_UDATA_INDICATION_DISCONNECT: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: UDATA_DISCONNECT cause %d\n", chan->No, buffer[1]); + break; + default: + if (DebugVar & 8) + printk(KERN_WARNING "idi_ind: Ch%d: UNHANDLED UDATA Indication 0x%02x\n", chan->No, buffer[0]); + } +} + +void +idi_handle_ind(eicon_card *ccard, struct sk_buff *skb) +{ + int tmp; + int free_buff; + struct sk_buff *skb2; + eicon_IND *ind = (eicon_IND *)skb->data; + eicon_chan *chan; + idi_ind_message message; + isdn_ctrl cmd; + + if ((chan = ccard->IdTable[ind->IndId]) == NULL) { + dev_kfree_skb(skb); + return; + } + + if ((DebugVar & 128) || + ((DebugVar & 16) && (ind->Ind != 8))) { + printk(KERN_DEBUG "idi_hdl: Ch%d: Ind=%d Id=%d Ch=%d MInd=%d MLen=%d Len=%d\n", chan->No, + ind->Ind,ind->IndId,ind->IndCh,ind->MInd,ind->MLength,ind->RBuffer.length); + } + + free_buff = 1; + /* Signal Layer */ + if (chan->e.D3Id == ind->IndId) { + idi_IndParse(ccard, chan, &message, ind->RBuffer.P, ind->RBuffer.length); + switch(ind->Ind) { + case HANGUP: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: Hangup\n", chan->No); + while((skb2 = skb_dequeue(&chan->e.X))) { + dev_kfree_skb(skb2); + } + chan->e.busy = 0; + chan->queued = 0; + chan->waitq = 0; + chan->waitpq = 0; + chan->fsm_state = EICON_STATE_NULL; + if (message.e_cau[0] & 0x7f) { + cmd.driver = ccard->myid; + cmd.arg = chan->No; + sprintf(cmd.parm.num,"E%02x%02x", + chan->cause[0]&0x7f, message.e_cau[0]&0x7f); + cmd.command = ISDN_STAT_CAUSE; + ccard->interface.statcallb(&cmd); + } + chan->cause[0] = 0; + cmd.driver = ccard->myid; + cmd.arg = chan->No; + cmd.command = ISDN_STAT_DHUP; + ccard->interface.statcallb(&cmd); + eicon_idi_listen_req(ccard, chan); + break; + case INDICATE_IND: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: Indicate_Ind\n", chan->No); + chan->fsm_state = EICON_STATE_ICALL; + idi_bc2si(message.bc, message.hlc, &chan->si1, &chan->si2); + strcpy(chan->cpn, message.cpn + 1); + if (strlen(message.dsa)) { + strcat(chan->cpn, "."); + strcat(chan->cpn, message.dsa); + } + strcpy(chan->oad, message.oad); + try_stat_icall_again: + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_ICALL; + cmd.arg = chan->No; + cmd.parm.setup.si1 = chan->si1; + cmd.parm.setup.si2 = chan->si2; + strcpy(cmd.parm.setup.eazmsn, chan->cpn); + strcpy(cmd.parm.setup.phone, chan->oad); + cmd.parm.setup.plan = message.plan; + cmd.parm.setup.screen = message.screen; + tmp = ccard->interface.statcallb(&cmd); + switch(tmp) { + case 0: /* no user responding */ + idi_do_req(ccard, chan, HANGUP, 0); + break; + case 1: /* alert */ + if (DebugVar & 8) + printk(KERN_DEBUG"idi_req: Ch%d: Call Alert\n", chan->No); + if ((chan->fsm_state == EICON_STATE_ICALL) || (chan->fsm_state == EICON_STATE_ICALLW)) { + chan->fsm_state = EICON_STATE_ICALL; + idi_do_req(ccard, chan, CALL_ALERT, 0); + } + break; + case 2: /* reject */ + if (DebugVar & 8) + printk(KERN_DEBUG"idi_req: Ch%d: Call Reject\n", chan->No); + idi_do_req(ccard, chan, REJECT, 0); + break; + case 3: /* incomplete number */ + if (DebugVar & 8) + printk(KERN_DEBUG"idi_req: Ch%d: Incomplete Number\n", chan->No); + switch(ccard->type) { + case EICON_CTYPE_MAESTRAP: + case EICON_CTYPE_S2M: + /* TODO (other protocols) */ + chan->fsm_state = EICON_STATE_ICALLW; + break; + default: + idi_do_req(ccard, chan, HANGUP, 0); + } + break; + } + break; + case INFO_IND: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: Info_Ind\n", chan->No); + if ((chan->fsm_state == EICON_STATE_ICALLW) && + (message.cpn[0])) { + strcat(chan->cpn, message.cpn + 1); + goto try_stat_icall_again; + } + break; + case CALL_IND: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: Call_Ind\n", chan->No); + if ((chan->fsm_state == EICON_STATE_ICALL) || (chan->fsm_state == EICON_STATE_IWAIT)) { + chan->fsm_state = EICON_STATE_IBWAIT; + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_DCONN; + cmd.arg = chan->No; + ccard->interface.statcallb(&cmd); + idi_do_req(ccard, chan, IDI_N_CONNECT, 1); + } else + idi_hangup(ccard, chan); + break; + case CALL_CON: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: Call_Con\n", chan->No); + if (chan->fsm_state == EICON_STATE_OCALL) { + chan->fsm_state = EICON_STATE_OBWAIT; + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_DCONN; + cmd.arg = chan->No; + ccard->interface.statcallb(&cmd); + idi_do_req(ccard, chan, ASSIGN, 1); + idi_do_req(ccard, chan, IDI_N_CONNECT, 1); + } else + idi_hangup(ccard, chan); + break; + case AOC_IND: + if (DebugVar & 8) + printk(KERN_DEBUG"idi_ind: Ch%d: Advice of Charge\n", chan->No); + break; + default: + if (DebugVar & 8) + printk(KERN_WARNING "idi_ind: Ch%d: UNHANDLED SigIndication 0x%02x\n", chan->No, ind->Ind); + } + } + /* Network Layer */ + else if (chan->e.B2Id == ind->IndId) { + + if (chan->No == ccard->nchannels) { + /* Management Indication */ + idi_IndParse(ccard, chan, &message, ind->RBuffer.P, ind->RBuffer.length); + chan->fsm_state = 1; + } + else + switch(ind->Ind) { + case IDI_N_CONNECT_ACK: + if (DebugVar & 16) + printk(KERN_DEBUG"idi_ind: Ch%d: N_Connect_Ack\n", chan->No); + if (chan->l2prot == ISDN_PROTO_L2_MODEM) { + chan->fsm_state = EICON_STATE_WMCONN; + break; + } + chan->fsm_state = EICON_STATE_ACTIVE; + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_BCONN; + cmd.arg = chan->No; + ccard->interface.statcallb(&cmd); + break; + case IDI_N_CONNECT: + if (DebugVar & 16) + printk(KERN_DEBUG"idi_ind: Ch%d: N_Connect\n", chan->No); + if (chan->e.B2Id) idi_do_req(ccard, chan, IDI_N_CONNECT_ACK, 1); + if (chan->l2prot == ISDN_PROTO_L2_MODEM) { + chan->fsm_state = EICON_STATE_WMCONN; + break; + } + chan->fsm_state = EICON_STATE_ACTIVE; + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_BCONN; + cmd.arg = chan->No; + ccard->interface.statcallb(&cmd); + break; + case IDI_N_DISC: + if (DebugVar & 16) + printk(KERN_DEBUG"idi_ind: Ch%d: N_DISC\n", chan->No); + if (chan->e.B2Id) { + idi_do_req(ccard, chan, IDI_N_DISC_ACK, 1); + idi_do_req(ccard, chan, REMOVE, 1); + } + chan->queued = 0; + chan->waitq = 0; + chan->waitpq = 0; + if (chan->fsm_state == EICON_STATE_ACTIVE) { + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_BHUP; + cmd.arg = chan->No; + ccard->interface.statcallb(&cmd); + } + break; + case IDI_N_DISC_ACK: + if (DebugVar & 16) + printk(KERN_DEBUG"idi_ind: Ch%d: N_DISC_ACK\n", chan->No); + break; + case IDI_N_DATA_ACK: + if (DebugVar & 16) + printk(KERN_DEBUG"idi_ind: Ch%d: N_DATA_ACK\n", chan->No); + break; + case IDI_N_DATA: + skb_pull(skb, sizeof(eicon_IND) - 1); + if (DebugVar & 128) + printk(KERN_DEBUG"idi_rcv: Ch%d: %d bytes\n", chan->No, skb->len); + ccard->interface.rcvcallb_skb(ccard->myid, chan->No, skb); + free_buff = 0; + break; + case IDI_N_UDATA: + idi_parse_udata(ccard, chan, ind->RBuffer.P, ind->RBuffer.length); + break; + default: + if (DebugVar & 8) + printk(KERN_WARNING "idi_ind: Ch%d: UNHANDLED NetIndication 0x%02x\n", chan->No, ind->Ind); + } + } + else { + if (DebugVar & 1) + printk(KERN_ERR "idi_ind: Ch%d: Ind is neither SIG nor NET !\n", chan->No); + } + if (free_buff) dev_kfree_skb(skb); +} + +void +idi_handle_ack(eicon_card *ccard, struct sk_buff *skb) +{ + int j; + eicon_RC *ack = (eicon_RC *)skb->data; + eicon_chan *chan; + isdn_ctrl cmd; + + if ((ack->Rc != ASSIGN_OK) && (ack->Rc != OK)) { + if ((chan = ccard->IdTable[ack->RcId]) != NULL) { + chan->e.busy = 0; + if (DebugVar & 24) + printk(KERN_ERR "eicon_ack: Ch%d: Not OK: Rc=%d Id=%d Ch=%d\n", chan->No, + ack->Rc, ack->RcId, ack->RcCh); + if (chan->No == ccard->nchannels) { /* Management */ + chan->fsm_state = 2; + } else { /* any other channel */ + /* card reports error: we hangup */ + idi_hangup(ccard, chan); + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan->No; + ccard->interface.statcallb(&cmd); + } + } + } + else { + if ((chan = ccard->IdTable[ack->RcId]) != NULL) { + if (ack->RcId != ((chan->e.ReqCh) ? chan->e.B2Id : chan->e.D3Id)) { + if (DebugVar & 16) + printk(KERN_DEBUG "idi_ack: Ch%d: RcId %d not equal to last %d\n", chan->No, + ack->RcId, (chan->e.ReqCh) ? chan->e.B2Id : chan->e.D3Id); + } else { + if (chan->No == ccard->nchannels) { /* Management */ + if (chan->e.Req == 0x04) chan->fsm_state = 1; + } + if (chan->e.ReqCh) { + switch(chan->e.Req & 0x0f) { + case IDI_N_MDATA: + case IDI_N_DATA: + chan->queued -= chan->waitq; + if (chan->queued < 0) chan->queued = 0; + if ((chan->e.Req & 0x0f) == IDI_N_DATA) { + cmd.driver = ccard->myid; + cmd.command = ISDN_STAT_BSENT; + cmd.arg = chan->No; + cmd.parm.length = chan->waitpq; + chan->waitpq = 0; + ccard->interface.statcallb(&cmd); + } + break; + default: + if (DebugVar & 16) + printk(KERN_DEBUG "idi_ack: Ch%d: RC OK Id=%d Ch=%d (ref:%d)\n", chan->No, + ack->RcId, ack->RcCh, ack->Reference); + } + } + else { + if (DebugVar & 16) + printk(KERN_DEBUG "idi_ack: Ch%d: RC OK Id=%d Ch=%d (ref:%d)\n", chan->No, + ack->RcId, ack->RcCh, ack->Reference); + } + + if (chan->e.Req == REMOVE) { + if (ack->Reference == chan->e.ref) { + ccard->IdTable[ack->RcId] = NULL; + if (DebugVar & 16) + printk(KERN_DEBUG "idi_ack: Ch%d: Removed : Id=%d Ch=%d (%s)\n", chan->No, + ack->RcId, ack->RcCh, (chan->e.ReqCh)? "Net":"Sig"); + if (!chan->e.ReqCh) + chan->e.D3Id = 0; + else + chan->e.B2Id = 0; + } + else { + if (DebugVar & 16) + printk(KERN_DEBUG "idi_ack: Ch%d: Rc-Ref %d not equal to stored %d\n", chan->No, + ack->Reference, chan->e.ref); + } + } + chan->e.busy = 0; + } + } + else { + for(j = 0; j < ccard->nchannels + 1; j++) { + if (ccard->bch[j].e.ref == ack->Reference) { + if (!ccard->bch[j].e.ReqCh) + ccard->bch[j].e.D3Id = ack->RcId; + else + ccard->bch[j].e.B2Id = ack->RcId; + ccard->IdTable[ack->RcId] = &ccard->bch[j]; + ccard->bch[j].e.busy = 0; + ccard->bch[j].e.ref = 0; + if (DebugVar & 16) + printk(KERN_DEBUG"idi_ack: Ch%d: Id %d assigned (%s)\n", j, + ack->RcId, (ccard->bch[j].e.ReqCh)? "Net":"Sig"); + break; + } + } + if (j > ccard->nchannels) { + if (DebugVar & 24) + printk(KERN_DEBUG"idi_ack: Ch??: ref %d not found for Id %d\n", + ack->Reference, ack->RcId); + } + } + } + dev_kfree_skb(skb); + eicon_schedule_tx(ccard); +} + +int +idi_send_data(eicon_card *card, eicon_chan *chan, int ack, struct sk_buff *skb) +{ + struct sk_buff *xmit_skb; + struct sk_buff *skb2; + eicon_REQ *reqbuf; + eicon_chan_ptr *chan2; + int len, plen = 0, offset = 0; + unsigned long flags; + + if (chan->fsm_state != EICON_STATE_ACTIVE) { + if (DebugVar & 1) + printk(KERN_DEBUG"idi_snd: Ch%d: send bytes on state %d !\n", chan->No, chan->fsm_state); + return -ENODEV; + } + + len = skb->len; + if (len > 2138) /* too much for the shared memory */ + return -1; + if (!len) + return 0; + if (chan->queued + len > ((chan->l2prot == ISDN_PROTO_L2_TRANS) ? 4000 : EICON_MAX_QUEUED)) + return 0; + if (DebugVar & 128) + printk(KERN_DEBUG"idi_snd: Ch%d: %d bytes\n", chan->No, len); + save_flags(flags); + cli(); + while(offset < len) { + + plen = ((len - offset) > 270) ? 270 : len - offset; + + xmit_skb = alloc_skb(plen + sizeof(eicon_REQ), GFP_ATOMIC); + skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC); + + if ((!skb) || (!skb2)) { + restore_flags(flags); + if (DebugVar & 1) + printk(KERN_WARNING "idi_err: Ch%d: alloc_skb failed\n", chan->No); + return -ENOMEM; + } + + chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr)); + chan2->ptr = chan; + + reqbuf = (eicon_REQ *)skb_put(xmit_skb, plen + sizeof(eicon_REQ)); + if (((len - offset) > 270) && + (chan->l2prot != ISDN_PROTO_L2_TRANS)) { + reqbuf->Req = IDI_N_MDATA; + } else { + reqbuf->Req = IDI_N_DATA; + if (ack) reqbuf->Req |= N_D_BIT; + } + reqbuf->ReqCh = 0; + reqbuf->ReqId = 1; + memcpy(&reqbuf->XBuffer.P, skb->data + offset, plen); + reqbuf->XBuffer.length = plen; + reqbuf->Reference = 1; /* Net Entity */ + + skb_queue_tail(&chan->e.X, xmit_skb); + skb_queue_tail(&card->sndq, skb2); + + offset += plen; + } + chan->queued += len; + restore_flags(flags); + eicon_schedule_tx(card); + dev_kfree_skb(skb); + return len; +} + + + +int +eicon_idi_manage_assign(eicon_card *card) +{ + struct sk_buff *skb; + struct sk_buff *skb2; + eicon_REQ *reqbuf; + eicon_chan *chan; + eicon_chan_ptr *chan2; + + chan = &(card->bch[card->nchannels]); + + skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC); + skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC); + + if ((!skb) || (!skb2)) { + if (DebugVar & 1) + printk(KERN_WARNING "idi_err: alloc_skb failed\n"); + return -ENOMEM; + } + + chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr)); + chan2->ptr = chan; + + reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ)); + + reqbuf->XBuffer.P[0] = 0; + reqbuf->Req = ASSIGN; + reqbuf->ReqCh = 0; + reqbuf->ReqId = 0xe0; + reqbuf->XBuffer.length = 1; + reqbuf->Reference = 2; /* Man Entity */ + + skb_queue_tail(&chan->e.X, skb); + skb_queue_tail(&card->sndq, skb2); + eicon_schedule_tx(card); + return(0); +} + + +int +eicon_idi_manage_remove(eicon_card *card) +{ + struct sk_buff *skb; + struct sk_buff *skb2; + eicon_REQ *reqbuf; + eicon_chan *chan; + eicon_chan_ptr *chan2; + + chan = &(card->bch[card->nchannels]); + + skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC); + skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC); + + if ((!skb) || (!skb2)) { + if (DebugVar & 1) + printk(KERN_WARNING "idi_err: alloc_skb failed\n"); + return -ENOMEM; + } + + chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr)); + chan2->ptr = chan; + + reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ)); + + reqbuf->Req = REMOVE; + reqbuf->ReqCh = 0; + reqbuf->ReqId = 1; + reqbuf->XBuffer.length = 0; + reqbuf->Reference = 2; /* Man Entity */ + + skb_queue_tail(&chan->e.X, skb); + skb_queue_tail(&card->sndq, skb2); + eicon_schedule_tx(card); + return(0); +} + + +int +eicon_idi_manage(eicon_card *card, eicon_manifbuf *mb) +{ + int l = 0; + int ret = 0; + int timeout; + int i; + struct sk_buff *skb; + struct sk_buff *skb2; + eicon_REQ *reqbuf; + eicon_chan *chan; + eicon_chan_ptr *chan2; + + chan = &(card->bch[card->nchannels]); + + if (chan->e.D3Id) return -EBUSY; + chan->e.D3Id = 1; + while((skb2 = skb_dequeue(&chan->e.X))) + dev_kfree_skb(skb2); + chan->e.busy = 0; + + if ((ret = eicon_idi_manage_assign(card))) { + chan->e.D3Id = 0; + return(ret); + } + + timeout = jiffies + 50; + while (timeout > jiffies) { + if (chan->e.B2Id) break; + SLEEP(10); + } + if (!chan->e.B2Id) { + chan->e.D3Id = 0; + return -EIO; + } + + chan->fsm_state = 0; + + if (!(manbuf = kmalloc(sizeof(eicon_manifbuf), GFP_KERNEL))) { + if (DebugVar & 1) + printk(KERN_WARNING "idi_err: alloc_manifbuf failed\n"); + chan->e.D3Id = 0; + return -ENOMEM; + } + if (copy_from_user(manbuf, mb, sizeof(eicon_manifbuf))) { + chan->e.D3Id = 0; + return -EFAULT; + } + + skb = alloc_skb(270 + sizeof(eicon_REQ), GFP_ATOMIC); + skb2 = alloc_skb(sizeof(eicon_chan_ptr), GFP_ATOMIC); + + if ((!skb) || (!skb2)) { + if (DebugVar & 1) + printk(KERN_WARNING "idi_err_manif: alloc_skb failed\n"); + kfree(manbuf); + chan->e.D3Id = 0; + return -ENOMEM; + } + + chan2 = (eicon_chan_ptr *)skb_put(skb2, sizeof(eicon_chan_ptr)); + chan2->ptr = chan; + + reqbuf = (eicon_REQ *)skb_put(skb, 270 + sizeof(eicon_REQ)); + + reqbuf->XBuffer.P[l++] = ESC; + reqbuf->XBuffer.P[l++] = 6; + reqbuf->XBuffer.P[l++] = 0x80; + for (i = 0; i < manbuf->length[0]; i++) + reqbuf->XBuffer.P[l++] = manbuf->data[i]; + reqbuf->XBuffer.P[1] = manbuf->length[0] + 1; + + reqbuf->XBuffer.P[l++] = 0; + reqbuf->Req = (manbuf->count) ? manbuf->count : 0x02; /* Request */ + reqbuf->ReqCh = 0; + reqbuf->ReqId = 1; + reqbuf->XBuffer.length = l; + reqbuf->Reference = 2; /* Man Entity */ + + skb_queue_tail(&chan->e.X, skb); + skb_queue_tail(&card->sndq, skb2); + + manbuf->count = 0; + manbuf->pos = 0; + + eicon_schedule_tx(card); + + timeout = jiffies + 50; + while (timeout > jiffies) { + if (chan->fsm_state) break; + SLEEP(10); + } + if ((!chan->fsm_state) || (chan->fsm_state == 2)) { + eicon_idi_manage_remove(card); + kfree(manbuf); + chan->e.D3Id = 0; + return -EIO; + } + + if ((ret = eicon_idi_manage_remove(card))) { + chan->e.D3Id = 0; + return(ret); + } + + if (copy_to_user(mb, manbuf, sizeof(eicon_manifbuf))) { + chan->e.D3Id = 0; + return -EFAULT; + } + + kfree(manbuf); + chan->e.D3Id = 0; + return(0); +} diff --git a/drivers/isdn/eicon/eicon_idi.h b/drivers/isdn/eicon/eicon_idi.h new file mode 100644 index 000000000..a0605cdef --- /dev/null +++ b/drivers/isdn/eicon/eicon_idi.h @@ -0,0 +1,248 @@ +/* $Id: eicon_idi.h,v 1.4 1999/03/29 11:19:44 armin Exp $ + * + * ISDN lowlevel-module for the Eicon.Diehl active cards. + * IDI-Interface + * + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_idi.h,v $ + * Revision 1.4 1999/03/29 11:19:44 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.3 1999/03/02 12:37:45 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.2 1999/01/24 20:14:18 armin + * Changed and added debug stuff. + * Better data sending. (still problems with tty's flip buffer) + * + * Revision 1.1 1999/01/01 18:09:42 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + +#ifndef IDI_H +#define IDI_H + + +#define ASSIGN 0x01 +#define REMOVE 0xff + +#define CALL_REQ 1 /* call request */ +#define CALL_CON 1 /* call confirmation */ +#define CALL_IND 2 /* incoming call connected */ +#define LISTEN_REQ 2 /* listen request */ +#define HANGUP 3 /* hangup request/indication */ +#define SUSPEND 4 /* call suspend request/confirm */ +#define RESUME 5 /* call resume request/confirm */ +#define SUSPEND_REJ 6 /* suspend rejected indication */ +#define USER_DATA 8 /* user data for user to user signaling */ +#define CONGESTION 9 /* network congestion indication */ +#define INDICATE_REQ 10 /* request to indicate an incoming call */ +#define INDICATE_IND 10 /* indicates that there is an incoming call */ +#define CALL_RES 11 /* accept an incoming call */ +#define CALL_ALERT 12 /* send ALERT for incoming call */ +#define INFO_REQ 13 /* INFO request */ +#define INFO_IND 13 /* INFO indication */ +#define REJECT 14 /* reject an incoming call */ +#define RESOURCES 15 /* reserve B-Channel hardware resources */ +#define TEL_CTRL 16 /* Telephone control request/indication */ +#define STATUS_REQ 17 /* Request D-State (returned in INFO_IND) */ +#define FAC_REG_REQ 18 /* connection idependent fac registration */ +#define FAC_REG_ACK 19 /* fac registration acknowledge */ +#define FAC_REG_REJ 20 /* fac registration reject */ +#define CALL_COMPLETE 21/* send a CALL_PROC for incoming call */ +#define AOC_IND 26/* Advice of Charge */ + +#define IDI_N_MDATA (0x01) +#define IDI_N_CONNECT (0x02) +#define IDI_N_CONNECT_ACK (0x03) +#define IDI_N_DISC (0x04) +#define IDI_N_DISC_ACK (0x05) +#define IDI_N_RESET (0x06) +#define IDI_N_RESET_ACK (0x07) +#define IDI_N_DATA (0x08) +#define IDI_N_EDATA (0x09) +#define IDI_N_UDATA (0x0a) +#define IDI_N_BDATA (0x0b) +#define IDI_N_DATA_ACK (0x0c) +#define IDI_N_EDATA_ACK (0x0d) + +#define N_Q_BIT 0x10 /* Q-bit for req/ind */ +#define N_M_BIT 0x20 /* M-bit for req/ind */ +#define N_D_BIT 0x40 /* D-bit for req/ind */ + + +#define SHIFT 0x90 /* codeset shift */ +#define MORE 0xa0 /* more data */ +#define CL 0xb0 /* congestion level */ + + /* codeset 0 */ + +#define BC 0x04 /* Bearer Capability */ +#define CAU 0x08 /* cause */ +#define CAD 0x0c /* Connected address */ +#define CAI 0x10 /* call identity */ +#define CHI 0x18 /* channel identification */ +#define LLI 0x19 /* logical link id */ +#define CHA 0x1a /* charge advice */ +#define FTY 0x1c +#define PI 0x1e /* Progress Indicator */ +#define NI 0x27 /* Notification Indicator */ +#define DT 0x29 /* ETSI date/time */ +#define KEY 0x2c /* keypad information element */ +#define DSP 0x28 /* display */ +#define OAD 0x6c /* origination address */ +#define OSA 0x6d /* origination sub-address */ +#define CPN 0x70 /* called party number */ +#define DSA 0x71 /* destination sub-address */ +#define RDN 0x74 /* redirecting number */ +#define LLC 0x7c /* low layer compatibility */ +#define HLC 0x7d /* high layer compatibility */ +#define UUI 0x7e /* user user information */ +#define ESC 0x7f /* escape extension */ + +#define DLC 0x20 /* data link layer configuration */ +#define NLC 0x21 /* network layer configuration */ + + /* codeset 6 */ + +#define SIN 0x01 /* service indicator */ +#define CIF 0x02 /* charging information */ +#define DATE 0x03 /* date */ +#define CPS 0x07 /* called party status */ + +/*------------------------------------------------------------------*/ +/* return code coding */ +/*------------------------------------------------------------------*/ + +#define UNKNOWN_COMMAND 0x01 /* unknown command */ +#define WRONG_COMMAND 0x02 /* wrong command */ +#define WRONG_ID 0x03 /* unknown task/entity id */ +#define WRONG_CH 0x04 /* wrong task/entity id */ +#define UNKNOWN_IE 0x05 /* unknown information el. */ +#define WRONG_IE 0x06 /* wrong information el. */ +#define OUT_OF_RESOURCES 0x07 /* card out of res. */ +#define N_FLOW_CONTROL 0x10 /* Flow-Control, retry */ +#define ASSIGN_RC 0xe0 /* ASSIGN acknowledgement */ +#define ASSIGN_OK 0xef /* ASSIGN OK */ +#define OK_FC 0xfc /* Flow-Control RC */ +#define READY_INT 0xfd /* Ready interrupt */ +#define TIMER_INT 0xfe /* timer interrupt */ +#define OK 0xff /* command accepted */ + +/*------------------------------------------------------------------*/ + +typedef struct { + char cpn[32]; + char oad[32]; + char dsa[32]; + char osa[32]; + __u8 plan; + __u8 screen; + __u8 sin[4]; + __u8 chi[4]; + __u8 e_chi[4]; + __u8 bc[12]; + __u8 e_bc[12]; + __u8 llc[18]; + __u8 hlc[5]; + __u8 cau[4]; + __u8 e_cau[2]; + __u8 e_mt; + __u8 dt[6]; + char display[83]; + char keypad[35]; + char rdn[32]; +} idi_ind_message; + +typedef struct { + __u16 next __attribute__ ((packed)); + __u8 Req __attribute__ ((packed)); + __u8 ReqId __attribute__ ((packed)); + __u8 ReqCh __attribute__ ((packed)); + __u8 Reserved1 __attribute__ ((packed)); + __u16 Reference __attribute__ ((packed)); + __u8 Reserved[8] __attribute__ ((packed)); + eicon_PBUFFER XBuffer; +} eicon_REQ; + +typedef struct { + __u16 next __attribute__ ((packed)); + __u8 Rc __attribute__ ((packed)); + __u8 RcId __attribute__ ((packed)); + __u8 RcCh __attribute__ ((packed)); + __u8 Reserved1 __attribute__ ((packed)); + __u16 Reference __attribute__ ((packed)); + __u8 Reserved2[8] __attribute__ ((packed)); +} eicon_RC; + +typedef struct { + __u16 next __attribute__ ((packed)); + __u8 Ind __attribute__ ((packed)); + __u8 IndId __attribute__ ((packed)); + __u8 IndCh __attribute__ ((packed)); + __u8 MInd __attribute__ ((packed)); + __u16 MLength __attribute__ ((packed)); + __u16 Reference __attribute__ ((packed)); + __u8 RNR __attribute__ ((packed)); + __u8 Reserved __attribute__ ((packed)); + __u32 Ack __attribute__ ((packed)); + eicon_PBUFFER RBuffer; +} eicon_IND; + +typedef struct { + __u16 NextReq __attribute__ ((packed)); /* pointer to next Req Buffer */ + __u16 NextRc __attribute__ ((packed)); /* pointer to next Rc Buffer */ + __u16 NextInd __attribute__ ((packed)); /* pointer to next Ind Buffer */ + __u8 ReqInput __attribute__ ((packed)); /* number of Req Buffers sent */ + __u8 ReqOutput __attribute__ ((packed)); /* number of Req Buffers returned */ + __u8 ReqReserved __attribute__ ((packed));/*number of Req Buffers reserved */ + __u8 Int __attribute__ ((packed)); /* ISDN-P interrupt */ + __u8 XLock __attribute__ ((packed)); /* Lock field for arbitration */ + __u8 RcOutput __attribute__ ((packed)); /* number of Rc buffers received */ + __u8 IndOutput __attribute__ ((packed)); /* number of Ind buffers received */ + __u8 IMask __attribute__ ((packed)); /* Interrupt Mask Flag */ + __u8 Reserved1[2] __attribute__ ((packed)); /* reserved field, do not use */ + __u8 ReadyInt __attribute__ ((packed)); /* request field for ready int */ + __u8 Reserved2[12] __attribute__ ((packed)); /* reserved field, do not use */ + __u8 InterfaceType __attribute__ ((packed)); /* interface type 1=16K */ + __u16 Signature __attribute__ ((packed)); /* ISDN-P initialized ind */ + __u8 B[1]; /* buffer space for Req,Ind and Rc */ +} eicon_pr_ram; + + +extern int idi_do_req(eicon_card *card, eicon_chan *chan, int cmd, int layer); +extern int idi_hangup(eicon_card *card, eicon_chan *chan); +extern int idi_connect_res(eicon_card *card, eicon_chan *chan); +extern int eicon_idi_listen_req(eicon_card *card, eicon_chan *chan); +extern int idi_connect_req(eicon_card *card, eicon_chan *chan, char *phone, + char *eazmsn, int si1, int si2); + +extern void idi_handle_ack(eicon_card *card, struct sk_buff *skb); +extern void idi_handle_ind(eicon_card *card, struct sk_buff *skb); +extern int eicon_idi_manage(eicon_card *card, eicon_manifbuf *mb); +extern int idi_send_data(eicon_card *card, eicon_chan *chan, int ack, struct sk_buff *skb); + +#endif diff --git a/drivers/isdn/eicon/eicon_io.c b/drivers/isdn/eicon/eicon_io.c new file mode 100644 index 000000000..1c69d37cd --- /dev/null +++ b/drivers/isdn/eicon/eicon_io.c @@ -0,0 +1,755 @@ +/* $Id: eicon_io.c,v 1.1 1999/03/29 11:19:45 armin Exp $ + * + * ISDN low-level module for Eicon.Diehl active ISDN-Cards. + * Code for communicating with hardware. + * + * Copyright 1999 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * Thanks to Eicon Technology Diehl GmbH & Co. oHG for + * documents, informations and hardware. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_io.c,v $ + * Revision 1.1 1999/03/29 11:19:45 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * + */ + + +#include "eicon.h" + +void +eicon_io_rcv_dispatch(eicon_card *ccard) { + struct sk_buff *skb, *skb2, *skb_new; + eicon_IND *ind, *ind2, *ind_new; + eicon_chan *chan; + + if (!ccard) { + if (DebugVar & 1) + printk(KERN_WARNING "eicon_io_rcv_dispatch: NULL card!\n"); + return; + } + + while((skb = skb_dequeue(&ccard->rcvq))) { + ind = (eicon_IND *)skb->data; + + if ((chan = ccard->IdTable[ind->IndId]) == NULL) { + if (DebugVar & 1) { + switch(ind->Ind) { + case IDI_N_DISC_ACK: + /* doesn't matter if this happens */ + break; + default: + printk(KERN_ERR "idi: Indication for unknown channel Ind=%d Id=%d\n", ind->Ind, ind->IndId); + printk(KERN_DEBUG "idi_hdl: Ch??: Ind=%d Id=%d Ch=%d MInd=%d MLen=%d Len=%d\n", + ind->Ind,ind->IndId,ind->IndCh,ind->MInd,ind->MLength,ind->RBuffer.length); + } + } + dev_kfree_skb(skb); + continue; + } + + if (chan->e.complete) { /* check for rec-buffer chaining */ + if (ind->MLength == ind->RBuffer.length) { + chan->e.complete = 1; + idi_handle_ind(ccard, skb); + continue; + } + else { + chan->e.complete = 0; + ind->Ind = ind->MInd; + skb_queue_tail(&chan->e.R, skb); + continue; + } + } + else { + if (!(skb2 = skb_dequeue(&chan->e.R))) { + chan->e.complete = 1; + if (DebugVar & 1) + printk(KERN_ERR "eicon: buffer incomplete, but 0 in queue\n"); + dev_kfree_skb(skb); + dev_kfree_skb(skb2); + continue; + } + ind2 = (eicon_IND *)skb2->data; + skb_new = alloc_skb(((sizeof(eicon_IND)-1)+ind->RBuffer.length+ind2->RBuffer.length), + GFP_ATOMIC); + ind_new = (eicon_IND *)skb_put(skb_new, + ((sizeof(eicon_IND)-1)+ind->RBuffer.length+ind2->RBuffer.length)); + ind_new->Ind = ind2->Ind; + ind_new->IndId = ind2->IndId; + ind_new->IndCh = ind2->IndCh; + ind_new->MInd = ind2->MInd; + ind_new->MLength = ind2->MLength; + ind_new->RBuffer.length = ind2->RBuffer.length + ind->RBuffer.length; + memcpy(&ind_new->RBuffer.P, &ind2->RBuffer.P, ind2->RBuffer.length); + memcpy((&ind_new->RBuffer.P)+ind2->RBuffer.length, &ind->RBuffer.P, ind->RBuffer.length); + dev_kfree_skb(skb); + dev_kfree_skb(skb2); + if (ind->MLength == ind->RBuffer.length) { + chan->e.complete = 2; + idi_handle_ind(ccard, skb_new); + continue; + } + else { + chan->e.complete = 0; + skb_queue_tail(&chan->e.R, skb_new); + continue; + } + } + } +} + +void +eicon_io_ack_dispatch(eicon_card *ccard) { + struct sk_buff *skb; + + if (!ccard) { + if (DebugVar & 1) + printk(KERN_WARNING "eicon_io_ack_dispatch: NULL card!\n"); + return; + } + while((skb = skb_dequeue(&ccard->rackq))) { + idi_handle_ack(ccard, skb); + } +} + + +/* + * IO-Functions for different card-types + */ + +u8 ram_inb(eicon_card *card, void *adr) { + eicon_pci_card *pcard; + eicon_isa_card *icard; + u32 addr = (u32) adr; + + pcard = &card->hwif.pci; + icard = &card->hwif.isa; + + switch(card->type) { + case EICON_CTYPE_MAESTRA: + outw((u16)addr, (u16)pcard->PCIreg + M_ADDR); + return(inb((u16)pcard->PCIreg + M_DATA)); + case EICON_CTYPE_MAESTRAP: + case EICON_CTYPE_S2M: + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + return(readb(addr)); + } + return(0); +} + +u16 ram_inw(eicon_card *card, void *adr) { + eicon_pci_card *pcard; + eicon_isa_card *icard; + u32 addr = (u32) adr; + + pcard = &card->hwif.pci; + icard = &card->hwif.isa; + + switch(card->type) { + case EICON_CTYPE_MAESTRA: + outw((u16)addr, (u16)pcard->PCIreg + M_ADDR); + return(inw((u16)pcard->PCIreg + M_DATA)); + case EICON_CTYPE_MAESTRAP: + case EICON_CTYPE_S2M: + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + return(readw(addr)); + } + return(0); +} + +void ram_outb(eicon_card *card, void *adr, u8 data) { + eicon_pci_card *pcard; + eicon_isa_card *icard; + u32 addr = (u32) adr; + + pcard = &card->hwif.pci; + icard = &card->hwif.isa; + + switch(card->type) { + case EICON_CTYPE_MAESTRA: + outw((u16)addr, (u16)pcard->PCIreg + M_ADDR); + outb((u8)data, (u16)pcard->PCIreg + M_DATA); + break; + case EICON_CTYPE_MAESTRAP: + case EICON_CTYPE_S2M: + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + writeb(data, addr); + break; + } +} + +void ram_outw(eicon_card *card, void *adr , u16 data) { + eicon_pci_card *pcard; + eicon_isa_card *icard; + u32 addr = (u32) adr; + + pcard = &card->hwif.pci; + icard = &card->hwif.isa; + + switch(card->type) { + case EICON_CTYPE_MAESTRA: + outw((u16)addr, (u16)pcard->PCIreg + M_ADDR); + outw((u16)data, (u16)pcard->PCIreg + M_DATA); + break; + case EICON_CTYPE_MAESTRAP: + case EICON_CTYPE_S2M: + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + writew(data, addr); + break; + } +} + +void ram_copyfromcard(eicon_card *card, void *adrto, void *adr, int len) { + int i; + switch(card->type) { + case EICON_CTYPE_MAESTRA: + for(i = 0; i < len; i++) { + writeb(ram_inb(card, adr + i), adrto + i); + } + break; + case EICON_CTYPE_MAESTRAP: + memcpy(adrto, adr, len); + break; + case EICON_CTYPE_S2M: + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + memcpy_fromio(adrto, adr, len); + break; + } +} + +void ram_copytocard(eicon_card *card, void *adrto, void *adr, int len) { + int i; + switch(card->type) { + case EICON_CTYPE_MAESTRA: + for(i = 0; i < len; i++) { + ram_outb(card, adrto + i, readb(adr + i)); + } + break; + case EICON_CTYPE_MAESTRAP: + memcpy(adrto, adr, len); + break; + case EICON_CTYPE_S2M: + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + memcpy_toio(adrto, adr, len); + break; + } +} + +/* + * Transmit-Function + */ +void +eicon_io_transmit(eicon_card *ccard) { + eicon_pci_card *pci_card; + eicon_isa_card *isa_card; + struct sk_buff *skb; + struct sk_buff *skb2; + unsigned long flags; + char *ram, *reg, *cfg; + eicon_pr_ram *prram = 0; + eicon_isa_com *com = 0; + eicon_REQ *ReqOut = 0; + eicon_REQ *reqbuf = 0; + eicon_chan *chan; + eicon_chan_ptr *chan2; + int ReqCount; + int scom = 0; + int tmp = 0; + int quloop = 1; + + pci_card = &ccard->hwif.pci; + isa_card = &ccard->hwif.isa; + + if (!ccard) { + if (DebugVar & 1) + printk(KERN_WARNING "eicon_transmit: NULL card!\n"); + return; + } + + switch(ccard->type) { + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + scom = 1; + com = (eicon_isa_com *)isa_card->shmem; + break; + case EICON_CTYPE_S2M: + scom = 0; + prram = (eicon_pr_ram *)isa_card->shmem; + break; + case EICON_CTYPE_MAESTRAP: + scom = 0; + ram = (char *)pci_card->PCIram; + reg = (char *)pci_card->PCIreg; + cfg = (char *)pci_card->PCIcfg; + prram = (eicon_pr_ram *)ram; + break; + case EICON_CTYPE_MAESTRA: + scom = 0; + ram = (char *)pci_card->PCIram; + reg = (char *)pci_card->PCIreg; + cfg = (char *)pci_card->PCIcfg; + prram = 0; + break; + default: + printk(KERN_WARNING "eicon_transmit: unsupported card-type!\n"); + return; + } + + ReqCount = 0; + if (!(skb2 = skb_dequeue(&ccard->sndq))) + quloop = 0; + while(quloop) { + save_flags(flags); + cli(); + if (scom) { + if (ram_inb(ccard, &com->Req)) { + if (!ccard->ReadyInt) { + tmp = ram_inb(ccard, &com->ReadyInt) + 1; + ram_outb(ccard, &com->ReadyInt, tmp); + ccard->ReadyInt++; + } + restore_flags(flags); + skb_queue_head(&ccard->sndq, skb2); + if (DebugVar & 32) + printk(KERN_INFO "eicon: transmit: Card not ready\n"); + return; + } + } else { + if (!(ram_inb(ccard, &prram->ReqOutput) - ram_inb(ccard, &prram->ReqInput))) { + restore_flags(flags); + skb_queue_head(&ccard->sndq, skb2); + if (DebugVar & 32) + printk(KERN_INFO "eicon: transmit: Card not ready\n"); + return; + } + } + restore_flags(flags); + chan2 = (eicon_chan_ptr *)skb2->data; + chan = chan2->ptr; + if (!chan->e.busy) { + if((skb = skb_dequeue(&chan->e.X))) { + save_flags(flags); + cli(); + reqbuf = (eicon_REQ *)skb->data; + if (scom) { + ram_outw(ccard, &com->XBuffer.length, reqbuf->XBuffer.length); + ram_copytocard(ccard, &com->XBuffer.P, &reqbuf->XBuffer.P, reqbuf->XBuffer.length); + ram_outb(ccard, &com->ReqCh, reqbuf->ReqCh); + + } else { + /* get address of next available request buffer */ + ReqOut = (eicon_REQ *)&prram->B[ram_inw(ccard, &prram->NextReq)]; + ram_outw(ccard, &ReqOut->XBuffer.length, reqbuf->XBuffer.length); + ram_copytocard(ccard, &ReqOut->XBuffer.P, &reqbuf->XBuffer.P, reqbuf->XBuffer.length); + ram_outb(ccard, &ReqOut->ReqCh, reqbuf->ReqCh); + ram_outb(ccard, &ReqOut->Req, reqbuf->Req); + } + + if (reqbuf->ReqId &0x1f) { /* if this is no ASSIGN */ + + if (!reqbuf->Reference) { /* Signal Layer */ + if (scom) + ram_outb(ccard, &com->ReqId, chan->e.D3Id); + else + ram_outb(ccard, &ReqOut->ReqId, chan->e.D3Id); + + chan->e.ReqCh = 0; + } + else { /* Net Layer */ + if (scom) + ram_outb(ccard, &com->ReqId, chan->e.B2Id); + else + ram_outb(ccard, &ReqOut->ReqId, chan->e.B2Id); + + chan->e.ReqCh = 1; + if (((reqbuf->Req & 0x0f) == 0x08) || + ((reqbuf->Req & 0x0f) == 0x01)) { /* Send Data */ + chan->waitq = reqbuf->XBuffer.length; + chan->waitpq += reqbuf->XBuffer.length; + } + } + + } else { /* It is an ASSIGN */ + + if (scom) + ram_outb(ccard, &com->ReqId, reqbuf->ReqId); + else + ram_outb(ccard, &ReqOut->ReqId, reqbuf->ReqId); + + if (!reqbuf->Reference) + chan->e.ReqCh = 0; + else + chan->e.ReqCh = 1; + } + if (scom) + chan->e.ref = ccard->ref_out++; + else + chan->e.ref = ram_inw(ccard, &ReqOut->Reference); + + chan->e.Req = reqbuf->Req; + ReqCount++; + if (scom) + ram_outb(ccard, &com->Req, reqbuf->Req); + else + ram_outw(ccard, &prram->NextReq, ram_inw(ccard, &ReqOut->next)); + + chan->e.busy = 1; + restore_flags(flags); + if (DebugVar & 32) + printk(KERN_DEBUG "eicon: Req=%x Id=%x Ch=%x Len=%x Ref=%d\n", + reqbuf->Req, + ram_inb(ccard, &ReqOut->ReqId), + reqbuf->ReqCh, reqbuf->XBuffer.length, + chan->e.ref); + dev_kfree_skb(skb); + } + dev_kfree_skb(skb2); + } + else { + skb_queue_tail(&ccard->sackq, skb2); + if (DebugVar & 32) + printk(KERN_INFO "eicon: transmit: busy chan %d\n", chan->No); + } + + if (scom) + quloop = 0; + else + if (!(skb2 = skb_dequeue(&ccard->sndq))) + quloop = 0; + + } + if (!scom) + ram_outb(ccard, &prram->ReqInput, (__u8)(ram_inb(ccard, &prram->ReqInput) + ReqCount)); + + while((skb = skb_dequeue(&ccard->sackq))) { + skb_queue_tail(&ccard->sndq, skb); + } +} + + +/* + * IRQ handler + */ +void +eicon_irq(int irq, void *dev_id, struct pt_regs *regs) { + eicon_card *ccard = (eicon_card *)dev_id; + eicon_pci_card *pci_card; + eicon_isa_card *isa_card; + char *ram = 0; + char *reg = 0; + char *cfg = 0; + eicon_pr_ram *prram = 0; + eicon_isa_com *com = 0; + eicon_RC *RcIn; + eicon_IND *IndIn; + struct sk_buff *skb; + int Count = 0; + int Rc = 0; + int Ind = 0; + unsigned char *irqprobe = 0; + int scom = 0; + int tmp = 0; + + + if (!ccard) { + printk(KERN_WARNING "eicon_irq: spurious interrupt %d\n", irq); + return; + } + + if (ccard->type == EICON_CTYPE_QUADRO) { + tmp = 4; + while(tmp) { + com = (eicon_isa_com *)ccard->hwif.isa.shmem; + if ((readb(ccard->hwif.isa.intack))) { /* quadro found */ + break; + } + ccard = ccard->qnext; + tmp--; + } + } + + pci_card = &ccard->hwif.pci; + isa_card = &ccard->hwif.isa; + + switch(ccard->type) { + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + scom = 1; + com = (eicon_isa_com *)isa_card->shmem; + irqprobe = &isa_card->irqprobe; + break; + case EICON_CTYPE_S2M: + scom = 0; + prram = (eicon_pr_ram *)isa_card->shmem; + irqprobe = &isa_card->irqprobe; + break; + case EICON_CTYPE_MAESTRAP: + scom = 0; + ram = (char *)pci_card->PCIram; + reg = (char *)pci_card->PCIreg; + cfg = (char *)pci_card->PCIcfg; + irqprobe = &pci_card->irqprobe; + prram = (eicon_pr_ram *)ram; + break; + case EICON_CTYPE_MAESTRA: + scom = 0; + ram = (char *)pci_card->PCIram; + reg = (char *)pci_card->PCIreg; + cfg = (char *)pci_card->PCIcfg; + irqprobe = &pci_card->irqprobe; + prram = 0; + break; + default: + printk(KERN_WARNING "eicon_irq: unsupported card-type!\n"); + return; + } + + if (*irqprobe) { + switch(ccard->type) { + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + if (readb(isa_card->intack)) { + writeb(0, &com->Rc); + writeb(0, isa_card->intack); + } + (*irqprobe)++; + break; + case EICON_CTYPE_S2M: + if (readb(isa_card->intack)) { + writeb(0, &prram->RcOutput); + writeb(0, isa_card->intack); + } + (*irqprobe)++; + break; + case EICON_CTYPE_MAESTRAP: + if (readb(&ram[0x3fe])) { + writeb(0, &prram->RcOutput); + writew(MP_IRQ_RESET_VAL, &cfg[MP_IRQ_RESET]); + writew(0, &cfg[MP_IRQ_RESET + 2]); + writeb(0, &ram[0x3fe]); + } + *irqprobe = 0; + break; + case EICON_CTYPE_MAESTRA: + outb(0x08, pci_card->PCIreg + M_RESET); + *irqprobe = 0; + break; + } + return; + } + + switch(ccard->type) { + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + case EICON_CTYPE_S2M: + if (!(readb(isa_card->intack))) { /* card did not interrupt */ + if (DebugVar & 1) + printk(KERN_DEBUG "eicon: IRQ: card tells no interrupt!\n"); + return; + } + break; + case EICON_CTYPE_MAESTRAP: + if (!(readb(&ram[0x3fe]))) { /* card did not interrupt */ + if (DebugVar & 1) + printk(KERN_DEBUG "eicon: IRQ: card tells no interrupt!\n"); + return; + } + break; + case EICON_CTYPE_MAESTRA: + outw(0x3fe, pci_card->PCIreg + M_ADDR); + if (!(inb(pci_card->PCIreg + M_DATA))) { /* card did not interrupt */ + if (DebugVar & 1) + printk(KERN_DEBUG "eicon: IRQ: card tells no interrupt!\n"); + return; + } + break; + } + + if (scom) { + + /* if a return code is available ... */ + if ((tmp = ram_inb(ccard, &com->Rc))) { + eicon_RC *ack; + if (tmp == READY_INT) { + if (DebugVar & 64) + printk(KERN_INFO "eicon: IRQ Rc=READY_INT\n"); + if (ccard->ReadyInt) { + ccard->ReadyInt--; + ram_outb(ccard, &com->Rc, 0); + } + } else { + skb = alloc_skb(sizeof(eicon_RC), GFP_ATOMIC); + ack = (eicon_RC *)skb_put(skb, sizeof(eicon_RC)); + ack->Rc = tmp; + ack->RcId = ram_inb(ccard, &com->RcId); + ack->RcCh = ram_inb(ccard, &com->RcCh); + ack->Reference = ccard->ref_in++; + if (DebugVar & 64) + printk(KERN_INFO "eicon: IRQ Rc=%d Id=%d Ch=%d Ref=%d\n", + tmp,ack->RcId,ack->RcCh,ack->Reference); + skb_queue_tail(&ccard->rackq, skb); + eicon_schedule_ack(ccard); + ram_outb(ccard, &com->Req, 0); + ram_outb(ccard, &com->Rc, 0); + } + + } else { + + /* if an indication is available ... */ + if ((tmp = ram_inb(ccard, &com->Ind))) { + eicon_IND *ind; + int len = ram_inw(ccard, &com->RBuffer.length); + skb = alloc_skb((sizeof(eicon_IND) + len - 1), GFP_ATOMIC); + ind = (eicon_IND *)skb_put(skb, (sizeof(eicon_IND) + len - 1)); + ind->Ind = tmp; + ind->IndId = ram_inb(ccard, &com->IndId); + ind->IndCh = ram_inb(ccard, &com->IndCh); + ind->MInd = ram_inb(ccard, &com->MInd); + ind->MLength = ram_inw(ccard, &com->MLength); + ind->RBuffer.length = len; + if (DebugVar & 64) + printk(KERN_INFO "eicon: IRQ Ind=%d Id=%d Ch=%d MInd=%d MLen=%d Len=%d\n", + tmp,ind->IndId,ind->IndCh,ind->MInd,ind->MLength,len); + ram_copyfromcard(ccard, &ind->RBuffer.P, &com->RBuffer.P, len); + skb_queue_tail(&ccard->rcvq, skb); + eicon_schedule_rx(ccard); + ram_outb(ccard, &com->Ind, 0); + } + } + + } else { + + /* if return codes are available ... */ + if((Count = ram_inb(ccard, &prram->RcOutput))) { + eicon_RC *ack; + /* get the buffer address of the first return code */ + RcIn = (eicon_RC *)&prram->B[ram_inw(ccard, &prram->NextRc)]; + /* for all return codes do ... */ + while(Count--) { + + if((Rc=ram_inb(ccard, &RcIn->Rc))) { + skb = alloc_skb(sizeof(eicon_RC), GFP_ATOMIC); + ack = (eicon_RC *)skb_put(skb, sizeof(eicon_RC)); + ack->Rc = Rc; + ack->RcId = ram_inb(ccard, &RcIn->RcId); + ack->RcCh = ram_inb(ccard, &RcIn->RcCh); + ack->Reference = ram_inw(ccard, &RcIn->Reference); + if (DebugVar & 64) + printk(KERN_INFO "eicon: IRQ Rc=%d Id=%d Ch=%d Ref=%d\n", + Rc,ack->RcId,ack->RcCh,ack->Reference); + ram_outb(ccard, &RcIn->Rc, 0); + skb_queue_tail(&ccard->rackq, skb); + eicon_schedule_ack(ccard); + } + /* get buffer address of next return code */ + RcIn = (eicon_RC *)&prram->B[ram_inw(ccard, &RcIn->next)]; + } + /* clear all return codes (no chaining!) */ + ram_outb(ccard, &prram->RcOutput, 0); + } + + /* if indications are available ... */ + if((Count = ram_inb(ccard, &prram->IndOutput))) { + eicon_IND *ind; + /* get the buffer address of the first indication */ + IndIn = (eicon_IND *)&prram->B[ram_inw(ccard, &prram->NextInd)]; + /* for all indications do ... */ + while(Count--) { + Ind = ram_inb(ccard, &IndIn->Ind); + if(Ind) { + int len = ram_inw(ccard, &IndIn->RBuffer.length); + skb = alloc_skb((sizeof(eicon_IND) + len - 1), GFP_ATOMIC); + ind = (eicon_IND *)skb_put(skb, (sizeof(eicon_IND) + len - 1)); + ind->Ind = Ind; + ind->IndId = ram_inb(ccard, &IndIn->IndId); + ind->IndCh = ram_inb(ccard, &IndIn->IndCh); + ind->MInd = ram_inb(ccard, &IndIn->MInd); + ind->MLength = ram_inw(ccard, &IndIn->MLength); + ind->RBuffer.length = len; + if (DebugVar & 64) + printk(KERN_INFO "eicon: IRQ Ind=%d Id=%d Ch=%d MInd=%d MLen=%d Len=%d\n", + Ind,ind->IndId,ind->IndCh,ind->MInd,ind->MLength,len); + ram_copyfromcard(ccard, &ind->RBuffer.P, &IndIn->RBuffer.P, len); + skb_queue_tail(&ccard->rcvq, skb); + eicon_schedule_rx(ccard); + ram_outb(ccard, &IndIn->Ind, 0); + } + /* get buffer address of next indication */ + IndIn = (eicon_IND *)&prram->B[ram_inw(ccard, &IndIn->next)]; + } + ram_outb(ccard, &prram->IndOutput, 0); + } + + } + + /* clear interrupt */ + switch(ccard->type) { + case EICON_CTYPE_QUADRO: + writeb(0, isa_card->intack); + writeb(0, &com[0x401]); + break; + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_S2M: + writeb(0, isa_card->intack); + break; + case EICON_CTYPE_MAESTRAP: + writew(MP_IRQ_RESET_VAL, &cfg[MP_IRQ_RESET]); + writew(0, &cfg[MP_IRQ_RESET + 2]); + writeb(0, &ram[0x3fe]); + break; + case EICON_CTYPE_MAESTRA: + outb(0x08, pci_card->PCIreg + M_RESET); + outw(0x3fe, pci_card->PCIreg + M_ADDR); + outb(0, pci_card->PCIreg + M_DATA); + break; + } + + return; +} + diff --git a/drivers/isdn/eicon/eicon_isa.c b/drivers/isdn/eicon/eicon_isa.c new file mode 100644 index 000000000..184f1c394 --- /dev/null +++ b/drivers/isdn/eicon/eicon_isa.c @@ -0,0 +1,432 @@ +/* $Id: eicon_isa.c,v 1.5 1999/04/01 12:48:33 armin Exp $ + * + * ISDN low-level module for Eicon.Diehl active ISDN-Cards. + * Hardware-specific code for old ISA cards. + * + * Copyright 1998 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_isa.c,v $ + * Revision 1.5 1999/04/01 12:48:33 armin + * Changed some log outputs. + * + * Revision 1.4 1999/03/29 11:19:46 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.3 1999/03/02 12:37:45 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.2 1999/01/24 20:14:19 armin + * Changed and added debug stuff. + * Better data sending. (still problems with tty's flip buffer) + * + * Revision 1.1 1999/01/01 18:09:43 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + +#include "eicon.h" +#include "eicon_isa.h" + +#define check_shmem check_region +#define release_shmem release_region +#define request_shmem request_region + +char *eicon_isa_revision = "$Revision: 1.5 $"; + +/* Mask for detecting invalid IRQ parameter */ +static int eicon_isa_valid_irq[] = { + 0x1c1c, /* 2, 3, 4, 10, 11, 12 (S)*/ + 0x1c1c, /* 2, 3, 4, 10, 11, 12 (SX) */ + 0x1cbc, /* 2, 3, 4, 5, 7, 10, 11, 12 (SCOM) */ + 0x1cbc, /* 2, 3, 4, 5, 6, 10, 11, 12 (Quadro) */ + 0x1cbc /* 2, 3, 4, 5, 7, 10, 11, 12 (S2M) */ +}; + +static void +eicon_isa_release_shmem(eicon_isa_card *card) { + if (card->mvalid) + release_shmem((unsigned long)card->shmem, card->ramsize); + card->mvalid = 0; +} + +static void +eicon_isa_release_irq(eicon_isa_card *card) { + if (!card->master) + return; + if (card->ivalid) + free_irq(card->irq, card); + card->ivalid = 0; +} + +void +eicon_isa_release(eicon_isa_card *card) { + eicon_isa_release_irq(card); + eicon_isa_release_shmem(card); +} + +void +eicon_isa_printpar(eicon_isa_card *card) { + switch (card->type) { + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + case EICON_CTYPE_S2M: + printk(KERN_INFO "Eicon %s at 0x%lx, irq %d\n", + eicon_ctype_name[card->type], + (unsigned long)card->shmem, + card->irq); + } +} + +int +eicon_isa_find_card(int Mem, int Irq, char * Id) +{ + int primary = 1; + + if (!strlen(Id)) + return -1; + + /* Check for valid membase address */ + if ((Mem < 0x0c0000) || + (Mem > 0x0fc000) || + (Mem & 0xfff)) { + printk(KERN_WARNING "eicon_isa: illegal membase 0x%x for %s\n", + Mem, Id); + return -1; + } + if (check_shmem(Mem, RAMSIZE)) { + printk(KERN_WARNING "eicon_isa_boot: memory at 0x%x already in use.\n", Mem); + return -1; + } + + writew(0x55aa, Mem + 0x402); + if (readw(Mem + 0x402) != 0x55aa) primary = 0; + writew(0, Mem + 0x402); + if (readw(Mem + 0x402) != 0) primary = 0; + + printk(KERN_INFO "Eicon: Driver-ID: %s\n", Id); + if (primary) { + printk(KERN_INFO "Eicon: assuming pri card at 0x%x\n", Mem); + writeb(0, Mem + 0x3ffe); + return EICON_CTYPE_ISAPRI; + } else { + printk(KERN_INFO "Eicon: assuming bri card at 0x%x\n", Mem); + writeb(0, Mem + 0x400); + return EICON_CTYPE_ISABRI; + } + return -1; +} + +int +eicon_isa_bootload(eicon_isa_card *card, eicon_isa_codebuf *cb) { + int tmp; + int timeout; + eicon_isa_codebuf cbuf; + unsigned char *code; + eicon_isa_boot *boot; + + if (copy_from_user(&cbuf, cb, sizeof(eicon_isa_codebuf))) + return -EFAULT; + + /* Allocate code-buffer and copy code from userspace */ + if (cbuf.bootstrap_len > 1024) { + printk(KERN_WARNING "eicon_isa_boot: Invalid startup-code size %ld\n", + cbuf.bootstrap_len); + return -EINVAL; + } + if (!(code = kmalloc(cbuf.bootstrap_len, GFP_KERNEL))) { + printk(KERN_WARNING "eicon_isa_boot: Couldn't allocate code buffer\n"); + return -ENOMEM; + } + if (copy_from_user(code, &cb->code, cbuf.bootstrap_len)) { + kfree(code); + return -EFAULT; + } + + switch(card->type) { + case EICON_CTYPE_S: + case EICON_CTYPE_SX: + case EICON_CTYPE_SCOM: + case EICON_CTYPE_QUADRO: + case EICON_CTYPE_ISABRI: + card->ramsize = RAMSIZE; + card->intack = (__u8 *)card->shmem + INTACK; + card->startcpu = (__u8 *)card->shmem + STARTCPU; + card->stopcpu = (__u8 *)card->shmem + STOPCPU; + break; + case EICON_CTYPE_S2M: + case EICON_CTYPE_ISAPRI: + card->ramsize = RAMSIZE_P; + card->intack = (__u8 *)card->shmem + INTACK_P; + card->startcpu = (__u8 *)card->shmem + STARTCPU_P; + card->stopcpu = (__u8 *)card->shmem + STOPCPU_P; + break; + default: + printk(KERN_WARNING "eicon_isa_boot: Invalid card type %d\n", card->type); + return -EINVAL; + } + + /* Register shmem */ + if (check_shmem((unsigned long)card->shmem, card->ramsize)) { + printk(KERN_WARNING "eicon_isa_boot: memory at 0x%lx already in use.\n", + (unsigned long)card->shmem); + kfree(code); + return -EBUSY; + } + request_shmem((unsigned long)card->shmem, card->ramsize, "Eicon ISA ISDN"); + card->mvalid = 1; + + /* clear any pending irq's */ + readb(card->intack); + /* set reset-line active */ + writeb(0, card->stopcpu); + /* clear irq-requests */ + writeb(0, card->intack); + readb(card->intack); + + /* Copy code into card */ + memcpy_toio(&card->shmem->c, code, cbuf.bootstrap_len); + + /* Check for properly loaded code */ + if (!check_signature((unsigned long)&card->shmem->c, code, 1020)) { + printk(KERN_WARNING "eicon_isa_boot: Could not load startup-code\n"); + eicon_isa_release_shmem(card); + kfree(code); + return -EIO; + } + /* if 16k-ramsize, duplicate the reset-jump-code */ + if (card->ramsize == RAMSIZE_P) + memcpy_toio((__u8 *)card->shmem + 0x3ff0, &code[0x3f0], 12); + + kfree(code); + boot = &card->shmem->boot; + + /* Delay 0.2 sec. */ + SLEEP(20); + + /* Start CPU */ + writeb(cbuf.boot_opt, &boot->ctrl); + writeb(0, card->startcpu); + + /* Delay 0.2 sec. */ + SLEEP(20); + + timeout = jiffies + (HZ * 22); + while (timeout > jiffies) { + if (readb(&boot->ctrl) == 0) + break; + SLEEP(10); + } + if (readb(&boot->ctrl) != 0) { + printk(KERN_WARNING "eicon_isa_boot: CPU test failed\n"); + eicon_isa_release_shmem(card); + return -EIO; + } + + /* Check for memory-test errors */ + if (readw(&boot->ebit)) { + printk(KERN_WARNING "eicon_isa_boot: memory test failed (bit 0x%04x at 0x%08x)\n", + readw(&boot->ebit), readl(&boot->eloc)); + eicon_isa_release_shmem(card); + return -EIO; + } + + /* Check card type and memory size */ + tmp = readb(&boot->card); + if ((tmp < 0) || (tmp > 4)) { + printk(KERN_WARNING "eicon_isa_boot: Type detect failed\n"); + eicon_isa_release_shmem(card); + return -EIO; + } + card->type = tmp; + ((eicon_card *)card->card)->type = tmp; + + tmp = readb(&boot->msize); + if (tmp != 8 && tmp != 16 && tmp != 24 && + tmp != 32 && tmp != 48 && tmp != 60) { + printk(KERN_WARNING "eicon_isa_boot: invalid memsize\n"); + eicon_isa_release_shmem(card); + return -EIO; + } + printk(KERN_INFO "%s: startup-code loaded\n", eicon_ctype_name[card->type]); + if ((card->type == EICON_CTYPE_QUADRO) && (card->master)) { + tmp = eicon_addcard(card->type, (unsigned long)card->shmem, card->irq, + ((eicon_card *)card->card)->regname); + printk(KERN_INFO "Eicon: %d adapters added\n", tmp); + } + return 0; +} + +int +eicon_isa_load(eicon_isa_card *card, eicon_isa_codebuf *cb) { + eicon_isa_boot *boot; + int tmp; + int timeout; + int j; + eicon_isa_codebuf cbuf; + unsigned char *code; + unsigned char *p; + + if (copy_from_user(&cbuf, cb, sizeof(eicon_isa_codebuf))) + return -EFAULT; + + if (!(code = kmalloc(cbuf.firmware_len, GFP_KERNEL))) { + printk(KERN_WARNING "eicon_isa_boot: Couldn't allocate code buffer\n"); + return -ENOMEM; + } + + if (copy_from_user(code, &cb->code, cbuf.firmware_len)) { + kfree(code); + return -EFAULT; + } + + boot = &card->shmem->boot; + + if ((!card->ivalid) && card->master) { + card->irqprobe = 1; + /* Check for valid IRQ */ + if ((card->irq < 0) || (card->irq > 15) || + (!((1 << card->irq) & eicon_isa_valid_irq[card->type & 0x0f]))) { + printk(KERN_WARNING "eicon_isa_boot: illegal irq: %d\n", card->irq); + eicon_isa_release_shmem(card); + kfree(code); + return -EINVAL; + } + /* Register irq */ + if (!request_irq(card->irq, &eicon_irq, 0, "Eicon ISA ISDN", card)) + card->ivalid = 1; + else { + printk(KERN_WARNING "eicon_isa_boot: irq %d already in use.\n", + card->irq); + eicon_isa_release_shmem(card); + kfree(code); + return -EBUSY; + } + } + + tmp = readb(&boot->msize); + if (tmp != 8 && tmp != 16 && tmp != 24 && + tmp != 32 && tmp != 48 && tmp != 60) { + printk(KERN_WARNING "eicon_isa_boot: invalid memsize\n"); + eicon_isa_release_shmem(card); + return -EIO; + } + + eicon_isa_printpar(card); + + /* Download firmware */ + printk(KERN_INFO "%s %dkB, loading firmware ...\n", + eicon_ctype_name[card->type], + tmp * 16); + tmp = cbuf.firmware_len >> 8; + p = code; + while (tmp--) { + memcpy_toio(&boot->b, p, 256); + writeb(1, &boot->ctrl); + timeout = jiffies + 10; + while (timeout > jiffies) { + if (readb(&boot->ctrl) == 0) + break; + SLEEP(2); + } + if (readb(&boot->ctrl)) { + printk(KERN_WARNING "eicon_isa_boot: download timeout at 0x%x\n", p-code); + eicon_isa_release(card); + kfree(code); + return -EIO; + } + p += 256; + } + kfree(code); + + /* Initialize firmware parameters */ + memcpy_toio(&card->shmem->c[8], &cbuf.tei, 14); + memcpy_toio(&card->shmem->c[32], &cbuf.oad, 96); + memcpy_toio(&card->shmem->c[128], &cbuf.oad, 96); + + /* Start firmware, wait for signature */ + writeb(2, &boot->ctrl); + timeout = jiffies + (5*HZ); + while (timeout > jiffies) { + if (readw(&boot->signature) == 0x4447) + break; + SLEEP(2); + } + if (readw(&boot->signature) != 0x4447) { + printk(KERN_WARNING "eicon_isa_boot: firmware selftest failed %04x\n", + readw(&boot->signature)); + eicon_isa_release(card); + return -EIO; + } + + card->channels = readb(&card->shmem->c[0x3f6]); + + /* clear irq-requests, reset irq-count */ + readb(card->intack); + writeb(0, card->intack); + + if (card->master) { + card->irqprobe = 1; + /* Trigger an interrupt and check if it is delivered */ + tmp = readb(&card->shmem->com.ReadyInt); + tmp ++; + writeb(tmp, &card->shmem->com.ReadyInt); + timeout = jiffies + 20; + while (timeout > jiffies) { + if (card->irqprobe > 1) + break; + SLEEP(2); + } + if (card->irqprobe == 1) { + printk(KERN_WARNING "eicon_isa_boot: IRQ test failed\n"); + eicon_isa_release(card); + return -EIO; + } + } + writeb(card->irq, &card->shmem->com.Int); + + /* initializing some variables */ + ((eicon_card *)card->card)->ReadyInt = 0; + ((eicon_card *)card->card)->ref_in = 1; + ((eicon_card *)card->card)->ref_out = 1; + for(j=0; j<256; j++) ((eicon_card *)card->card)->IdTable[j] = NULL; + for(j=0; j< (card->channels + 1); j++) { + ((eicon_card *)card->card)->bch[j].e.busy = 0; + ((eicon_card *)card->card)->bch[j].e.D3Id = 0; + ((eicon_card *)card->card)->bch[j].e.B2Id = 0; + ((eicon_card *)card->card)->bch[j].e.ref = 0; + ((eicon_card *)card->card)->bch[j].e.Req = 0; + ((eicon_card *)card->card)->bch[j].e.complete = 1; + ((eicon_card *)card->card)->bch[j].fsm_state = EICON_STATE_NULL; + } + + printk(KERN_INFO "Eicon: Supported channels: %d\n", card->channels); + printk(KERN_INFO "%s successfully started\n", eicon_ctype_name[card->type]); + + /* Enable normal IRQ processing */ + card->irqprobe = 0; + return 0; +} diff --git a/drivers/isdn/eicon/eicon_isa.h b/drivers/isdn/eicon/eicon_isa.h new file mode 100644 index 000000000..5b0dc1a6a --- /dev/null +++ b/drivers/isdn/eicon/eicon_isa.h @@ -0,0 +1,144 @@ +/* $Id: eicon_isa.h,v 1.3 1999/03/29 11:19:47 armin Exp $ + * + * ISDN low-level module for Eicon.Diehl active ISDN-Cards. + * + * Copyright 1998 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_isa.h,v $ + * Revision 1.3 1999/03/29 11:19:47 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.2 1999/03/02 12:37:46 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.1 1999/01/01 18:09:44 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + +#ifndef eicon_isa_h +#define eicon_isa_h + +#ifdef __KERNEL__ + +/* Factory defaults for ISA-Cards */ +#define EICON_ISA_MEMBASE 0xd0000 +#define EICON_ISA_IRQ 3 +/* shmem offset for Quadro parts */ +#define EICON_ISA_QOFFSET 0x0800 + +typedef struct { + __u16 length __attribute__ ((packed)); /* length of data/parameter field */ + __u8 P[270]; /* data/parameter field */ +} eicon_scom_PBUFFER; + +/* General communication buffer */ +typedef struct { + __u8 Req; /* request register */ + __u8 ReqId; /* request task/entity identification */ + __u8 Rc; /* return code register */ + __u8 RcId; /* return code task/entity identification */ + __u8 Ind; /* Indication register */ + __u8 IndId; /* Indication task/entity identification */ + __u8 IMask; /* Interrupt Mask Flag */ + __u8 RNR; /* Receiver Not Ready (set by PC) */ + __u8 XLock; /* XBuffer locked Flag */ + __u8 Int; /* ISDN interrupt */ + __u8 ReqCh; /* Channel field for layer-3 Requests */ + __u8 RcCh; /* Channel field for layer-3 Returncodes */ + __u8 IndCh; /* Channel field for layer-3 Indications */ + __u8 MInd; /* more data indication field */ + __u16 MLength; /* more data total packet length */ + __u8 ReadyInt; /* request field for ready interrupt */ + __u8 Reserved[12]; /* reserved space */ + __u8 IfType; /* 1 = 16k-Interface */ + __u16 Signature __attribute__ ((packed)); /* ISDN adapter Signature */ + eicon_scom_PBUFFER XBuffer; /* Transmit Buffer */ + eicon_scom_PBUFFER RBuffer; /* Receive Buffer */ +} eicon_isa_com; + +/* struct for downloading firmware */ +typedef struct { + __u8 ctrl; + __u8 card; + __u8 msize; + __u8 fill0; + __u16 ebit __attribute__ ((packed)); + __u32 eloc __attribute__ ((packed)); + __u8 reserved[20]; + __u16 signature __attribute__ ((packed)); + __u8 fill[224]; + __u8 b[256]; +} eicon_isa_boot; + +/* Shared memory */ +typedef union { + unsigned char c[0x400]; + eicon_isa_com com; + eicon_isa_boot boot; +} eicon_isa_shmem; + +/* + * card's description + */ +typedef struct { + int ramsize; + int irq; /* IRQ */ + void* card; + eicon_isa_shmem* shmem; /* Shared-memory area */ + unsigned char* intack; /* Int-Acknowledge */ + unsigned char* stopcpu; /* Writing here stops CPU */ + unsigned char* startcpu; /* Writing here starts CPU */ + unsigned char type; /* card type */ + int channels; /* No. of channels */ + unsigned char irqprobe; /* Flag: IRQ-probing */ + unsigned char mvalid; /* Flag: Memory is valid */ + unsigned char ivalid; /* Flag: IRQ is valid */ + unsigned char master; /* Flag: Card ist Quadro 1/4 */ + void* generic; /* Ptr to generic card struct */ +} eicon_isa_card; + +/* Offsets for special locations on standard cards */ +#define INTACK 0x03fe +#define STOPCPU 0x0400 +#define STARTCPU 0x0401 +#define RAMSIZE 0x0400 +/* Offsets for special location on PRI card */ +#define INTACK_P 0x3ffc +#define STOPCPU_P 0x3ffe +#define STARTCPU_P 0x3fff +#define RAMSIZE_P 0x4000 + + +extern int eicon_isa_load(eicon_isa_card *card, eicon_isa_codebuf *cb); +extern int eicon_isa_bootload(eicon_isa_card *card, eicon_isa_codebuf *cb); +extern void eicon_isa_release(eicon_isa_card *card); +extern void eicon_isa_printpar(eicon_isa_card *card); +extern void eicon_isa_transmit(eicon_isa_card *card); +extern int eicon_isa_find_card(int Mem, int Irq, char * Id); + +#endif /* __KERNEL__ */ + +#endif /* eicon_isa_h */ diff --git a/drivers/isdn/eicon/eicon_mod.c b/drivers/isdn/eicon/eicon_mod.c new file mode 100644 index 000000000..c14d91d7e --- /dev/null +++ b/drivers/isdn/eicon/eicon_mod.c @@ -0,0 +1,1210 @@ +/* $Id: eicon_mod.c,v 1.5 1999/04/01 12:48:35 armin Exp $ + * + * ISDN lowlevel-module for Eicon.Diehl active cards. + * + * Copyright 1997 by Fritz Elfert (fritz@wuemaus.franken.de) + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * Thanks to Eicon Technology Diehl GmbH & Co. oHG for + * documents, informations and hardware. + * + * Deutsche Telekom AG for S2M support. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_mod.c,v $ + * Revision 1.5 1999/04/01 12:48:35 armin + * Changed some log outputs. + * + * Revision 1.4 1999/03/29 11:19:47 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.3 1999/03/02 12:37:47 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.2 1999/01/24 20:14:21 armin + * Changed and added debug stuff. + * Better data sending. (still problems with tty's flip buffer) + * + * Revision 1.1 1999/01/01 18:09:44 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> + +#include "eicon.h" + +#define INCLUDE_INLINE_FUNCS + +static eicon_card *cards = (eicon_card *) NULL; + +static char *eicon_revision = "$Revision: 1.5 $"; + +extern char *eicon_pci_revision; +extern char *eicon_isa_revision; +extern char *eicon_idi_revision; + +#ifdef MODULE +#define MOD_USE_COUNT (GET_USE_COUNT (&__this_module)) +#endif + +#define EICON_CTRL_VERSION 1 + +ulong DebugVar; + +/* Parameters to be set by insmod */ +static int membase = -1; +static int irq = -1; +static char *id = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +MODULE_DESCRIPTION( "Driver for Eicon.Diehl active ISDN cards"); +MODULE_AUTHOR( "Armin Schindler"); +MODULE_SUPPORTED_DEVICE( "ISDN subsystem"); +MODULE_PARM_DESC(membase, "Base address of first ISA card"); +MODULE_PARM_DESC(irq, "IRQ of first card"); +MODULE_PARM_DESC(id, "ID-String of first card"); +MODULE_PARM(membase, "i"); +MODULE_PARM(irq, "i"); +MODULE_PARM(id, "s"); + +char *eicon_ctype_name[] = { + "ISDN-S", + "ISDN-SX", + "ISDN-SCOM", + "ISDN-QUADRO", + "ISDN-S2M", + "DIVA Server BRI/PCI", + "DIVA Server 4BRI/PCI", + "DIVA Server 4BRI/PCI", + "DIVA Server PRI/PCI" +}; + +static int +getrel(char *p) +{ + int v = 0; + char *tmp = 0; + + if ((tmp = strchr(p, '.'))) + p = tmp + 1; + while (p[0] >= '0' && p[0] <= '9') { + v = ((v < 0) ? 0 : (v * 10)) + (int) (p[0] - '0'); + p++; + } + return v; + + +} + +static char * +eicon_getrev(const char *revision) +{ + char *rev; + char *p; + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else rev = "?.??"; + return rev; + +} + +static eicon_chan * +find_channel(eicon_card *card, int channel) +{ + if ((channel >= 0) && (channel < card->nchannels)) + return &(card->bch[channel]); + if (DebugVar & 1) + printk(KERN_WARNING "eicon: Invalid channel %d\n", channel); + return NULL; +} + +/* + * Free MSN list + */ +static void +eicon_clear_msn(eicon_card *card) +{ + struct msn_entry *p = card->msn_list; + struct msn_entry *q; + unsigned long flags; + + save_flags(flags); + cli(); + card->msn_list = NULL; + restore_flags(flags); + while (p) { + q = p->next; + kfree(p); + p = q; + } +} + +/* + * Find an MSN entry in the list. + * If ia5 != 0, return IA5-encoded EAZ, else + * return a bitmask with corresponding bit set. + */ +static __u16 +eicon_find_msn(eicon_card *card, char *msn, int ia5) +{ + struct msn_entry *p = card->msn_list; + __u8 eaz = '0'; + + while (p) { + if (!strcmp(p->msn, msn)) { + eaz = p->eaz; + break; + } + p = p->next; + } + if (!ia5) + return (1 << (eaz - '0')); + else + return eaz; +} + +/* + * Find an EAZ entry in the list. + * return a string with corresponding msn. + */ +char * +eicon_find_eaz(eicon_card *card, char eaz) +{ + struct msn_entry *p = card->msn_list; + + while (p) { + if (p->eaz == eaz) + return(p->msn); + p = p->next; + } + return("\0"); +} + +#if 0 +/* + * Add or delete an MSN to the MSN list + * + * First character of msneaz is EAZ, rest is MSN. + * If length of eazmsn is 1, delete that entry. + */ +static int +eicon_set_msn(eicon_card *card, char *eazmsn) +{ + struct msn_entry *p = card->msn_list; + struct msn_entry *q = NULL; + unsigned long flags; + int i; + + if (!strlen(eazmsn)) + return 0; + if (strlen(eazmsn) > 16) + return -EINVAL; + for (i = 0; i < strlen(eazmsn); i++) + if (!isdigit(eazmsn[i])) + return -EINVAL; + if (strlen(eazmsn) == 1) { + /* Delete a single MSN */ + while (p) { + if (p->eaz == eazmsn[0]) { + save_flags(flags); + cli(); + if (q) + q->next = p->next; + else + card->msn_list = p->next; + restore_flags(flags); + kfree(p); + if (DebugVar & 8) + printk(KERN_DEBUG + "Mapping for EAZ %c deleted\n", + eazmsn[0]); + return 0; + } + q = p; + p = p->next; + } + return 0; + } + /* Add a single MSN */ + while (p) { + /* Found in list, replace MSN */ + if (p->eaz == eazmsn[0]) { + save_flags(flags); + cli(); + strcpy(p->msn, &eazmsn[1]); + restore_flags(flags); + if (DebugVar & 8) + printk(KERN_DEBUG + "Mapping for EAZ %c changed to %s\n", + eazmsn[0], + &eazmsn[1]); + return 0; + } + p = p->next; + } + /* Not found in list, add new entry */ + p = kmalloc(sizeof(msn_entry), GFP_KERNEL); + if (!p) + return -ENOMEM; + p->eaz = eazmsn[0]; + strcpy(p->msn, &eazmsn[1]); + p->next = card->msn_list; + save_flags(flags); + cli(); + card->msn_list = p; + restore_flags(flags); + if (DebugVar & 8) + printk(KERN_DEBUG + "Mapping %c -> %s added\n", + eazmsn[0], + &eazmsn[1]); + return 0; +} +#endif + +static void +eicon_rcv_dispatch(struct eicon_card *card) +{ + switch (card->bus) { + case EICON_BUS_ISA: + case EICON_BUS_PCI: + eicon_io_rcv_dispatch(card); + break; + case EICON_BUS_MCA: + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon_ack_dispatch: Illegal bustype %d\n", card->bus); + } +} + +static void +eicon_ack_dispatch(struct eicon_card *card) +{ + switch (card->bus) { + case EICON_BUS_ISA: + case EICON_BUS_PCI: + eicon_io_ack_dispatch(card); + break; + case EICON_BUS_MCA: + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon_ack_dispatch: Illegal bustype %d\n", card->bus); + } +} + +static void +eicon_transmit(struct eicon_card *card) +{ + switch (card->bus) { + case EICON_BUS_ISA: + case EICON_BUS_PCI: + eicon_io_transmit(card); + break; + case EICON_BUS_MCA: + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon_transmit: Illegal bustype %d\n", card->bus); + } +} + +static int +eicon_command(eicon_card * card, isdn_ctrl * c) +{ + ulong a; + eicon_chan *chan; + eicon_cdef cdef; + isdn_ctrl cmd; + char tmp[17]; + int ret = 0; + unsigned long flags; + + switch (c->command) { + case ISDN_CMD_IOCTL: + memcpy(&a, c->parm.num, sizeof(ulong)); + switch (c->arg) { + case EICON_IOCTL_GETVER: + return(EICON_CTRL_VERSION); + case EICON_IOCTL_GETTYPE: + return(card->type); + case EICON_IOCTL_GETMMIO: + switch (card->bus) { + case EICON_BUS_ISA: + return (int)card->hwif.isa.shmem; +#if CONFIG_PCI + case EICON_BUS_PCI: + return card->hwif.pci.PCIram; +#endif + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: Illegal BUS type %d\n", + card->bus); + ret = -ENODEV; + } + case EICON_IOCTL_SETMMIO: + if (card->flags & EICON_FLAGS_LOADED) + return -EBUSY; + switch (card->bus) { + case EICON_BUS_ISA: + if (eicon_isa_find_card(a, + card->hwif.isa.irq, + card->regname) < 0) + return -EFAULT; + card->hwif.isa.shmem = (eicon_isa_shmem *)a; + return 0; + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: Illegal BUS type %d\n", + card->bus); + ret = -ENODEV; + } + case EICON_IOCTL_GETIRQ: + switch (card->bus) { + case EICON_BUS_ISA: + return card->hwif.isa.irq; +#if CONFIG_PCI + case EICON_BUS_PCI: + return card->hwif.pci.irq; +#endif + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: Illegal BUS type %d\n", + card->bus); + ret = -ENODEV; + } + case EICON_IOCTL_SETIRQ: + if (card->flags & EICON_FLAGS_LOADED) + return -EBUSY; + if ((a < 2) || (a > 15)) + return -EFAULT; + switch (card->bus) { + case EICON_BUS_ISA: + card->hwif.isa.irq = a; + return 0; + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: Illegal BUS type %d\n", + card->bus); + ret = -ENODEV; + } + case EICON_IOCTL_LOADBOOT: + if (card->flags & EICON_FLAGS_RUNNING) + return -EBUSY; + switch (card->bus) { + case EICON_BUS_ISA: + ret = eicon_isa_bootload( + &(card->hwif.isa), + &(((eicon_codebuf *)a)->isa)); + break; + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: Illegal BUS type %d\n", + card->bus); + ret = -ENODEV; + } + return ret; + case EICON_IOCTL_LOADISA: + if (card->flags & EICON_FLAGS_RUNNING) + return -EBUSY; + switch (card->bus) { + case EICON_BUS_ISA: + ret = eicon_isa_load( + &(card->hwif.isa), + &(((eicon_codebuf *)a)->isa)); + if (!ret) { + card->flags |= EICON_FLAGS_LOADED; + card->flags |= EICON_FLAGS_RUNNING; + if (card->hwif.isa.channels > 1) { + cmd.command = ISDN_STAT_ADDCH; + cmd.driver = card->myid; + cmd.arg = card->hwif.isa.channels - 1; + card->interface.statcallb(&cmd); + } + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + card->interface.statcallb(&cmd); + } + break; + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: Illegal BUS type %d\n", + card->bus); + ret = -ENODEV; + } + return ret; + + case EICON_IOCTL_MANIF: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!card->Feature & PROTCAP_MANIF) + return -ENODEV; + ret = eicon_idi_manage( + card, + (eicon_manifbuf *)a); + return ret; +#if CONFIG_PCI + case EICON_IOCTL_LOADPCI: + if (card->flags & EICON_FLAGS_RUNNING) + return -EBUSY; + if (card->bus == EICON_BUS_PCI) { + switch(card->type) { + case EICON_CTYPE_MAESTRA: + ret = eicon_pci_load_bri( + &(card->hwif.pci), + &(((eicon_codebuf *)a)->pci)); + break; + + case EICON_CTYPE_MAESTRAP: + ret = eicon_pci_load_pri( + &(card->hwif.pci), + &(((eicon_codebuf *)a)->pci)); + break; + } + if (!ret) { + card->flags |= EICON_FLAGS_LOADED; + card->flags |= EICON_FLAGS_RUNNING; + if (card->hwif.pci.channels > 1) { + cmd.command = ISDN_STAT_ADDCH; + cmd.driver = card->myid; + cmd.arg = card->hwif.pci.channels - 1; + card->interface.statcallb(&cmd); + } + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + card->interface.statcallb(&cmd); + } + return ret; + } else return -ENODEV; +#endif +#if 0 + case EICON_IOCTL_SETMSN: + if ((ret = copy_from_user(tmp, (char *)a, sizeof(tmp)))) + return -EFAULT; + if ((ret = eicon_set_msn(card, tmp))) + return ret; +#if 0 + if (card->flags & EICON_FLAGS_RUNNING) + return(eicon_capi_manufacturer_req_msn(card)); +#endif + return 0; +#endif + case EICON_IOCTL_ADDCARD: + if ((ret = copy_from_user(&cdef, (char *)a, sizeof(cdef)))) + return -EFAULT; + if (!(eicon_addcard(0, cdef.membase, cdef.irq, cdef.id))) + return -EIO; + return 0; + case EICON_IOCTL_DEBUGVAR: + DebugVar = a; + printk(KERN_DEBUG"Eicon: Debug Value set to %ld\n", DebugVar); + return 0; +#ifdef MODULE + case EICON_IOCTL_FREEIT: + while (MOD_USE_COUNT > 0) MOD_DEC_USE_COUNT; + MOD_INC_USE_COUNT; + return 0; +#endif + default: + return -EINVAL; + } + break; + case ISDN_CMD_DIAL: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + save_flags(flags); + cli(); + if ((chan->fsm_state != EICON_STATE_NULL) && (chan->fsm_state != EICON_STATE_LISTEN)) { + restore_flags(flags); + if (DebugVar & 1) + printk(KERN_WARNING "Dial on channel %d with state %d\n", + chan->No, chan->fsm_state); + return -EBUSY; + } + if (card->ptype == ISDN_PTYPE_EURO) + tmp[0] = eicon_find_msn(card, c->parm.setup.eazmsn, 1); + else + tmp[0] = c->parm.setup.eazmsn[0]; + chan->fsm_state = EICON_STATE_OCALL; + chan->callref = 0xffff; + restore_flags(flags); + + ret = idi_connect_req(card, chan, c->parm.setup.phone, + c->parm.setup.eazmsn, + c->parm.setup.si1, + c->parm.setup.si2); + if (ret) { + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg &= 0x1f; + card->interface.statcallb(&cmd); + } + return ret; + case ISDN_CMD_ACCEPTD: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + if (chan->fsm_state == EICON_STATE_ICALL) { + idi_connect_res(card, chan); + } + return 0; + case ISDN_CMD_ACCEPTB: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + return 0; + case ISDN_CMD_HANGUP: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + idi_hangup(card, chan); + return 0; + case ISDN_CMD_SETEAZ: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + if (strlen(c->parm.num)) { + if (card->ptype == ISDN_PTYPE_EURO) { + chan->eazmask = eicon_find_msn(card, c->parm.num, 0); + } + if (card->ptype == ISDN_PTYPE_1TR6) { + int i; + chan->eazmask = 0; + for (i = 0; i < strlen(c->parm.num); i++) + if (isdigit(c->parm.num[i])) + chan->eazmask |= (1 << (c->parm.num[i] - '0')); + } + } else + chan->eazmask = 0x3ff; + eicon_idi_listen_req(card, chan); + return 0; + case ISDN_CMD_CLREAZ: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + chan->eazmask = 0; + eicon_idi_listen_req(card, chan); + return 0; + case ISDN_CMD_SETL2: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + chan->l2prot = (c->arg >> 8); + return 0; + case ISDN_CMD_GETL2: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + return chan->l2prot; + case ISDN_CMD_SETL3: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if ((c->arg >> 8) != ISDN_PROTO_L3_TRANS) { + if (DebugVar & 1) + printk(KERN_WARNING "L3 protocol unknown\n"); + return -1; + } + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + chan->l3prot = (c->arg >> 8); + return 0; + case ISDN_CMD_GETL3: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x1f))) + break; + return chan->l3prot; + case ISDN_CMD_GETEAZ: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (DebugVar & 1) + printk(KERN_DEBUG "eicon CMD_GETEAZ not implemented\n"); + return 0; + case ISDN_CMD_SETSIL: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (DebugVar & 1) + printk(KERN_DEBUG "eicon CMD_SETSIL not implemented\n"); + return 0; + case ISDN_CMD_GETSIL: + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + if (DebugVar & 1) + printk(KERN_DEBUG "eicon CMD_GETSIL not implemented\n"); + return 0; + case ISDN_CMD_LOCK: + MOD_INC_USE_COUNT; + return 0; + case ISDN_CMD_UNLOCK: + MOD_DEC_USE_COUNT; + return 0; + } + + return -EINVAL; +} + +/* + * Find card with given driverId + */ +static inline eicon_card * +eicon_findcard(int driverid) +{ + eicon_card *p = cards; + + while (p) { + if (p->myid == driverid) + return p; + p = p->next; + } + return (eicon_card *) 0; +} + +/* + * Wrapper functions for interface to linklevel + */ +static int +if_command(isdn_ctrl * c) +{ + eicon_card *card = eicon_findcard(c->driver); + + if (card) + return (eicon_command(card, c)); + printk(KERN_ERR + "eicon: if_command %d called with invalid driverId %d!\n", + c->command, c->driver); + return -ENODEV; +} + +static int +if_writecmd(const u_char * buf, int len, int user, int id, int channel) +{ + eicon_card *card = eicon_findcard(id); + + if (card) { + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + return (len); + } + printk(KERN_ERR + "eicon: if_writecmd called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_readstatus(u_char * buf, int len, int user, int id, int channel) +{ +#if 0 + /* Not yet used */ + eicon_card *card = eicon_findcard(id); + + if (card) { + if (!card->flags & EICON_FLAGS_RUNNING) + return -ENODEV; + return (eicon_readstatus(buf, len, user, card)); + } + printk(KERN_ERR + "eicon: if_readstatus called with invalid driverId!\n"); +#endif + return -ENODEV; +} + +static int +if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) +{ + eicon_card *card = eicon_findcard(id); + eicon_chan *chan; + + if (card) { + if (!card->flags & EICON_FLAGS_RUNNING) { + dev_kfree_skb(skb); + return -ENODEV; + } + if (!(chan = find_channel(card, channel))) { + dev_kfree_skb(skb); + return -ENODEV; + } + if (chan->fsm_state == EICON_STATE_ACTIVE) + return (idi_send_data(card, chan, ack, skb)); + else { + dev_kfree_skb(skb); + return -ENODEV; + } + } + printk(KERN_ERR + "eicon: if_sendbuf called with invalid driverId!\n"); + dev_kfree_skb(skb); + return -ENODEV; +} + + +/* + * Allocate a new card-struct, initialize it + * link it into cards-list. + */ +static void +eicon_alloccard(int Type, int membase, int irq, char *id) +{ + int i; + int j; + int qloop; + char qid[5]; + eicon_card *card; +#if CONFIG_PCI + eicon_pci_card *pcic; +#endif + + qloop = (Type == EICON_CTYPE_QUADRO)?2:0; + for (i = 0; i <= qloop; i++) { + if (!(card = (eicon_card *) kmalloc(sizeof(eicon_card), GFP_KERNEL))) { + printk(KERN_WARNING + "eicon: (%s) Could not allocate card-struct.\n", id); + return; + } + memset((char *) card, 0, sizeof(eicon_card)); + skb_queue_head_init(&card->sndq); + skb_queue_head_init(&card->rcvq); + skb_queue_head_init(&card->rackq); + skb_queue_head_init(&card->sackq); + card->snd_tq.routine = (void *) (void *) eicon_transmit; + card->snd_tq.data = card; + card->rcv_tq.routine = (void *) (void *) eicon_rcv_dispatch; + card->rcv_tq.data = card; + card->ack_tq.routine = (void *) (void *) eicon_ack_dispatch; + card->ack_tq.data = card; + card->interface.maxbufsize = 4000; + card->interface.command = if_command; + card->interface.writebuf_skb = if_sendbuf; + card->interface.writecmd = if_writecmd; + card->interface.readstat = if_readstatus; + card->interface.features = + ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L2_TRANS | + ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_P_UNKNOWN; + card->interface.hl_hdrlen = 20; + card->ptype = ISDN_PTYPE_UNKNOWN; + strncpy(card->interface.id, id, sizeof(card->interface.id) - 1); + card->myid = -1; + card->type = Type; + switch (Type) { + case EICON_CTYPE_QUADRO: + if (membase == -1) + membase = EICON_ISA_MEMBASE; + if (irq == -1) + irq = EICON_ISA_IRQ; + card->bus = EICON_BUS_ISA; + card->hwif.isa.card = (void *)card; + card->hwif.isa.shmem = (eicon_isa_shmem *)(membase + (i+1) * EICON_ISA_QOFFSET); + card->hwif.isa.master = 0; + strcpy(card->interface.id, id); + if (id[strlen(id) - 1] == 'a') { + card->interface.id[strlen(id) - 1] = 'a' + i + 1; + } else { + sprintf(qid, "_%c",'2' + i); + strcat(card->interface.id, qid); + } + printk(KERN_INFO "Eicon: Quadro: Driver-Id %s added.\n", + card->interface.id); + if (i == 0) { + eicon_card *p = cards; + while(p) { + if ((p->hwif.isa.master) && (p->hwif.isa.irq == irq)) { + p->qnext = card; + break; + } + p = p->next; + } + if (!p) { + printk(KERN_WARNING "eicon_alloccard: Quadro Master not found.\n"); + kfree(card); + return; + } + } else { + cards->qnext = card; + } + card->hwif.isa.irq = irq; + card->hwif.isa.type = Type; + card->nchannels = 2; + card->interface.channels = 1; + break; +#if CONFIG_PCI + case EICON_CTYPE_MAESTRA: + (eicon_pci_card *)pcic = (eicon_pci_card *)membase; + card->bus = EICON_BUS_PCI; + card->interface.features |= + ISDN_FEATURE_L2_V11096 | + ISDN_FEATURE_L2_V11019 | + ISDN_FEATURE_L2_V11038 | + ISDN_FEATURE_L2_MODEM; + card->hwif.pci.card = (void *)card; + card->hwif.pci.PCIreg = pcic->PCIreg; + card->hwif.pci.PCIcfg = pcic->PCIcfg; + card->hwif.pci.master = 1; + card->hwif.pci.mvalid = pcic->mvalid; + card->hwif.pci.ivalid = 0; + card->hwif.pci.irq = irq; + card->hwif.pci.type = Type; + card->flags = 0; + card->nchannels = 2; + card->interface.channels = 1; + break; + + case EICON_CTYPE_MAESTRAP: + (eicon_pci_card *)pcic = (eicon_pci_card *)membase; + card->bus = EICON_BUS_PCI; + card->interface.features |= + ISDN_FEATURE_L2_V11096 | + ISDN_FEATURE_L2_V11019 | + ISDN_FEATURE_L2_V11038 | + ISDN_FEATURE_L2_MODEM; + card->hwif.pci.card = (void *)card; + card->hwif.pci.shmem = (eicon_pci_shmem *)pcic->shmem; + card->hwif.pci.PCIreg = pcic->PCIreg; + card->hwif.pci.PCIram = pcic->PCIram; + card->hwif.pci.PCIcfg = pcic->PCIcfg; + card->hwif.pci.master = 1; + card->hwif.pci.mvalid = pcic->mvalid; + card->hwif.pci.ivalid = 0; + card->hwif.pci.irq = irq; + card->hwif.pci.type = Type; + card->flags = 0; + card->nchannels = 30; + card->interface.channels = 1; + break; +#endif + case EICON_CTYPE_ISABRI: + if (membase == -1) + membase = EICON_ISA_MEMBASE; + if (irq == -1) + irq = EICON_ISA_IRQ; + card->bus = EICON_BUS_ISA; + card->hwif.isa.card = (void *)card; + card->hwif.isa.shmem = (eicon_isa_shmem *)membase; + card->hwif.isa.master = 1; + card->hwif.isa.irq = irq; + card->hwif.isa.type = Type; + card->nchannels = 2; + card->interface.channels = 1; + break; + case EICON_CTYPE_ISAPRI: + if (membase == -1) + membase = EICON_ISA_MEMBASE; + if (irq == -1) + irq = EICON_ISA_IRQ; + card->bus = EICON_BUS_ISA; + card->hwif.isa.card = (void *)card; + card->hwif.isa.shmem = (eicon_isa_shmem *)membase; + card->hwif.isa.master = 1; + card->hwif.isa.irq = irq; + card->hwif.isa.type = Type; + card->nchannels = 30; + card->interface.channels = 1; + break; + default: + printk(KERN_WARNING "eicon_alloccard: Invalid type %d\n", Type); + kfree(card); + return; + } + if (!(card->bch = (eicon_chan *) kmalloc(sizeof(eicon_chan) * (card->nchannels + 1) + , GFP_KERNEL))) { + printk(KERN_WARNING + "eicon: (%s) Could not allocate bch-struct.\n", id); + kfree(card); + return; + } + for (j=0; j< (card->nchannels + 1); j++) { + memset((char *)&card->bch[j], 0, sizeof(eicon_chan)); + card->bch[j].plci = 0x8000; + card->bch[j].ncci = 0x8000; + card->bch[j].l2prot = ISDN_PROTO_L2_X75I; + card->bch[j].l3prot = ISDN_PROTO_L3_TRANS; + card->bch[j].e.D3Id = 0; + card->bch[j].e.B2Id = 0; + card->bch[j].e.Req = 0; + card->bch[j].No = j; + skb_queue_head_init(&card->bch[j].e.X); + skb_queue_head_init(&card->bch[j].e.R); + } + card->next = cards; + cards = card; + } +} + +/* + * register card at linklevel + */ +static int +eicon_registercard(eicon_card * card) +{ + switch (card->bus) { + case EICON_BUS_ISA: + /* TODO something to print */ + break; + case EICON_BUS_PCI: +#if CONFIG_PCI + eicon_pci_printpar(&card->hwif.pci); + break; +#endif + case EICON_BUS_MCA: + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon_registercard: Illegal BUS type %d\n", + card->bus); + return -1; + } + if (!register_isdn(&card->interface)) { + printk(KERN_WARNING + "eicon_registercard: Unable to register %s\n", + card->interface.id); + return -1; + } + card->myid = card->interface.channels; + sprintf(card->regname, "%s", card->interface.id); + return 0; +} + +#ifdef MODULE +static void +unregister_card(eicon_card * card) +{ + isdn_ctrl cmd; + + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + switch (card->bus) { + case EICON_BUS_ISA: + eicon_isa_release(&card->hwif.isa); + break; + case EICON_BUS_PCI: +#if CONFIG_PCI + eicon_pci_release(&card->hwif.pci); + break; +#endif + case EICON_BUS_MCA: + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: Invalid BUS type %d\n", + card->bus); + break; + } +} +#endif /* MODULE */ + +static void +eicon_freecard(eicon_card *card) { + eicon_clear_msn(card); + kfree(card->bch); + kfree(card); +} + +int +eicon_addcard(int Type, int membase, int irq, char *id) +{ + eicon_card *p; + eicon_card *q = NULL; + int registered; + int added = 0; + int failed = 0; + + if (!Type) /* ISA */ + if ((Type = eicon_isa_find_card(membase, irq, id)) < 0) + return 0; + eicon_alloccard(Type, membase, irq, id); + p = cards; + while (p) { + registered = 0; + if (!p->interface.statcallb) { + /* Not yet registered. + * Try to register and activate it. + */ + added++; + switch (p->bus) { + case EICON_BUS_ISA: + if (eicon_registercard(p)) + break; + registered = 1; + break; + case EICON_BUS_PCI: +#if CONFIG_PCI + if (eicon_registercard(p)) + break; + registered = 1; + break; +#endif + case EICON_BUS_MCA: + default: + if (DebugVar & 1) + printk(KERN_WARNING + "eicon: addcard: Invalid BUS type %d\n", + p->bus); + } + } else + /* Card already registered */ + registered = 1; + if (registered) { + /* Init OK, next card ... */ + q = p; + p = p->next; + } else { + /* registering failed, remove card from list, free memory */ + printk(KERN_WARNING + "eicon: Initialization of %s failed\n", + p->interface.id); + if (q) { + q->next = p->next; + eicon_freecard(p); + p = q->next; + } else { + cards = p->next; + eicon_freecard(p); + p = cards; + } + failed++; + } + } + return (added - failed); +} + +#define DRIVERNAME "Eicon active ISDN driver" +#define DRIVERRELEASE "1" + +#ifdef MODULE +#define eicon_init init_module +#endif + +__initfunc(int +eicon_init(void)) +{ + int tmp = 0; + int release = 0; + char tmprev[50]; + + DebugVar = 1; + + printk(KERN_INFO "%s Rev: ", DRIVERNAME); + strcpy(tmprev, eicon_revision); + printk("%s/", eicon_getrev(tmprev)); + release += getrel(tmprev); + strcpy(tmprev, eicon_pci_revision); + printk("%s/", eicon_getrev(tmprev)); + release += getrel(tmprev); + strcpy(tmprev, eicon_isa_revision); + printk("%s/", eicon_getrev(tmprev)); + release += getrel(tmprev); + strcpy(tmprev, eicon_idi_revision); + printk("%s\n", eicon_getrev(tmprev)); + release += getrel(tmprev); + sprintf(tmprev,"%d", release); + printk(KERN_INFO "%s Release: %s.%s\n", DRIVERNAME, + DRIVERRELEASE, tmprev); + + tmp = eicon_addcard(0, membase, irq, id); +#if CONFIG_PCI + tmp += eicon_pci_find_card(id); +#endif + if (!cards) { +#ifdef MODULE + printk(KERN_INFO "Eicon: No cards defined, driver not loaded !\n"); +#endif + return -ENODEV; + + } else + printk(KERN_INFO "Eicon: %d card%s added\n", tmp, (tmp>1)?"s":""); + /* No symbols to export, hide all symbols */ + EXPORT_NO_SYMBOLS; + return 0; +} + +#ifdef MODULE +void +cleanup_module(void) +{ + eicon_card *card = cards; + eicon_card *last; + while (card) { + unregister_card(card); + card = card->next; + } + card = cards; + while (card) { + last = card; + card = card->next; + eicon_freecard(last); + } + printk(KERN_INFO "%s unloaded\n", DRIVERNAME); +} + +#else +__initfunc(void +eicon_setup(char *str, int *ints)) +{ + int i, argc; + + argc = ints[0]; + i = 1; + if (argc) { + membase = irq = -1; + if (argc) { + membase = ints[i]; + i++; + argc--; + } + if (argc) { + irq = ints[i]; + i++; + argc--; + } + if (strlen(str)) { + strcpy(id, str); + } else { + strcpy(id, "eicon"); + } + /* eicon_addcard(0, membase, irq, id); */ + printk(KERN_INFO "eicon: membase=0x%x irq=%d id=%s\n", membase, irq, id); + } +} +#endif diff --git a/drivers/isdn/eicon/eicon_pci.c b/drivers/isdn/eicon/eicon_pci.c new file mode 100644 index 000000000..3d07167ce --- /dev/null +++ b/drivers/isdn/eicon/eicon_pci.c @@ -0,0 +1,952 @@ +/* $Id: eicon_pci.c,v 1.6 1999/04/01 12:48:37 armin Exp $ + * + * ISDN low-level module for Eicon.Diehl active ISDN-Cards. + * Hardware-specific code for PCI cards. + * + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * Thanks to Eicon Technology Diehl GmbH & Co. oHG for + * documents, informations and hardware. + * + * Deutsche Telekom AG for S2M support. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_pci.c,v $ + * Revision 1.6 1999/04/01 12:48:37 armin + * Changed some log outputs. + * + * Revision 1.5 1999/03/29 11:19:49 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.4 1999/03/02 12:37:48 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.3 1999/01/24 20:14:24 armin + * Changed and added debug stuff. + * Better data sending. (still problems with tty's flip buffer) + * + * Revision 1.2 1999/01/10 18:46:06 armin + * Bug with wrong values in HLC fixed. + * Bytes to send are counted and limited now. + * + * Revision 1.1 1999/01/01 18:09:45 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + +#include <linux/config.h> +#include <linux/pci.h> + +#include "eicon.h" +#include "eicon_pci.h" + + +char *eicon_pci_revision = "$Revision: 1.6 $"; + +#if CONFIG_PCI /* intire stuff is only for PCI */ + +#undef EICON_PCI_DEBUG + +int eicon_pci_find_card(char *ID) +{ + if (pci_present()) { + struct pci_dev *pdev = NULL; + int pci_nextindex=0, pci_cards=0, pci_akt=0; + int pci_type = PCI_MAESTRA; + int NoMorePCICards = FALSE; + char *ram, *reg, *cfg; + unsigned int pram=0, preg=0, pcfg=0; + char did[12]; + eicon_pci_card *aparms; + + if (!(aparms = (eicon_pci_card *) kmalloc(sizeof(eicon_pci_card), GFP_KERNEL))) { + printk(KERN_WARNING + "eicon_pci: Could not allocate card-struct.\n"); + return 0; + } + + for (pci_cards = 0; pci_cards < 0x0f; pci_cards++) + { + do { + if ((pdev = pci_find_device(PCI_VENDOR_EICON, + pci_type, + pdev))) + { + pci_nextindex++; + break; + } + else { + pci_nextindex = 0; + switch (pci_type) /* switch to next card type */ + { + case PCI_MAESTRA: + pci_type = PCI_MAESTRAQ; break; + case PCI_MAESTRAQ: + pci_type = PCI_MAESTRAQ_U; break; + case PCI_MAESTRAQ_U: + pci_type = PCI_MAESTRAP; break; + default: + case PCI_MAESTRAP: + NoMorePCICards = TRUE; + } + } + } + while (!NoMorePCICards); + if (NoMorePCICards) + { + if (pci_cards < 1) { + printk(KERN_INFO "Eicon: No supported PCI cards found.\n"); + kfree(aparms); + return 0; + } + else + { + printk(KERN_INFO "Eicon: %d PCI card%s registered.\n", + pci_cards, (pci_cards > 1) ? "s":""); + kfree(aparms); + return (pci_cards); + } + } + + pci_akt = 0; + switch(pci_type) + { + case PCI_MAESTRA: + printk(KERN_INFO "Eicon: DIVA Server BRI/PCI detected !\n"); + aparms->type = EICON_CTYPE_MAESTRA; + + aparms->irq = pdev->irq; + preg = pdev->base_address[2] & 0xfffffffc; + pcfg = pdev->base_address[1] & 0xffffff80; + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: irq=%d\n", aparms->irq); + printk(KERN_DEBUG "eicon_pci: reg=0x%x\n", preg); + printk(KERN_DEBUG "eicon_pci: cfg=0x%x\n", pcfg); +#endif + pci_akt = 1; + break; + + case PCI_MAESTRAQ: + case PCI_MAESTRAQ_U: + printk(KERN_ERR "Eicon: DIVA Server 4BRI/PCI detected but not supported !\n"); + pci_cards--; + pci_akt = 0; + break; + + case PCI_MAESTRAP: + printk(KERN_INFO "Eicon: DIVA Server PRI/PCI detected !\n"); + aparms->type = EICON_CTYPE_MAESTRAP; /*includes 9M,30M*/ + aparms->irq = pdev->irq; + pram = pdev->base_address[0] & 0xfffff000; + preg = pdev->base_address[2] & 0xfffff000; + pcfg = pdev->base_address[4] & 0xfffff000; + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: irq=%d\n", aparms->irq); + printk(KERN_DEBUG "eicon_pci: ram=0x%x\n", + (pram)); + printk(KERN_DEBUG "eicon_pci: reg=0x%x\n", + (preg)); + printk(KERN_DEBUG "eicon_pci: cfg=0x%x\n", + (pcfg)); +#endif + pci_akt = 1; + break; + default: + printk(KERN_ERR "eicon_pci: Unknown PCI card detected !\n"); + pci_cards--; + pci_akt = 0; + break; + } + + if (pci_akt) { + /* remapping memory */ + switch(pci_type) + { + case PCI_MAESTRA: + aparms->PCIreg = (unsigned int) preg; + aparms->PCIcfg = (unsigned int) pcfg; + if (check_region((aparms->PCIreg), 0x20)) { + printk(KERN_WARNING "eicon_pci: reg port already in use !\n"); + aparms->PCIreg = 0; + break; + } else { + request_region(aparms->PCIreg, 0x20, "eicon reg"); + } + if (check_region((aparms->PCIcfg), 0x100)) { + printk(KERN_WARNING "eicon_pci: cfg port already in use !\n"); + aparms->PCIcfg = 0; + break; + } else { + request_region(aparms->PCIcfg, 0x100, "eicon cfg"); + } + break; + case PCI_MAESTRAQ: + case PCI_MAESTRAQ_U: + case PCI_MAESTRAP: + aparms->shmem = (eicon_pci_shmem *) ioremap(pram, 0x10000); + ram = (u8 *) ((u32)aparms->shmem + MP_SHARED_RAM_OFFSET); + reg = ioremap(preg, 0x4000); + cfg = ioremap(pcfg, 0x1000); + aparms->PCIram = (unsigned int) ram; + aparms->PCIreg = (unsigned int) reg; + aparms->PCIcfg = (unsigned int) cfg; + break; + } + if ((!aparms->PCIreg) || (!aparms->PCIcfg)) { + printk(KERN_ERR "eicon_pci: Card could not be added !\n"); + pci_cards--; + } else { + aparms->mvalid = 1; + + sprintf(did, "%s%d", (strlen(ID) < 1) ? "eicon":ID, pci_cards); + + printk(KERN_INFO "%s: DriverID: '%s'\n",eicon_ctype_name[aparms->type] , did); + + if (!(eicon_addcard(aparms->type, (int) aparms, aparms->irq, did))) { + printk(KERN_ERR "eicon_pci: Card could not be added !\n"); + pci_cards--; + } + } + } + + } + } else + printk(KERN_ERR "eicon_pci: Kernel compiled with PCI but no PCI-bios found !\n"); + return 0; +} + +/* + * Checks protocol file id for "F#xxxx" string fragment to + * extract the features, supported by this protocol version. + * binary representation of the feature string value is returned + * in *value. The function returns 0 if feature string was not + * found or has a wrong format, else 1. + */ +static int GetProtFeatureValue(char *sw_id, int *value) +{ + __u8 i, offset; + + while (*sw_id) + { + if ((sw_id[0] == 'F') && (sw_id[1] == '#')) + { + sw_id = &sw_id[2]; + for (i=0, *value=0; i<4; i++, sw_id++) + { + if ((*sw_id >= '0') && (*sw_id <= '9')) + { + offset = '0'; + } + else if ((*sw_id >= 'A') && (*sw_id <= 'F')) + { + offset = 'A' + 10; + } + else if ((*sw_id >= 'a') && (*sw_id <= 'f')) + { + offset = 'a' + 10; + } + else + { + return 0; + } + *value |= (*sw_id - offset) << (4*(3-i)); + } + return 1; + } + else + { + sw_id++; + } + } + return 0; +} + + +void +eicon_pci_printpar(eicon_pci_card *card) { + switch (card->type) { + case EICON_CTYPE_MAESTRA: + printk(KERN_INFO "%s at 0x%x / 0x%x, irq %d\n", + eicon_ctype_name[card->type], + (unsigned int)card->PCIreg, + (unsigned int)card->PCIcfg, + card->irq); + break; + case EICON_CTYPE_MAESTRAQ: + case EICON_CTYPE_MAESTRAQ_U: + case EICON_CTYPE_MAESTRAP: + printk(KERN_INFO "%s at 0x%x, irq %d\n", + eicon_ctype_name[card->type], + (unsigned int)card->shmem, + card->irq); +#ifdef EICON_PCI_DEBUG + printk(KERN_INFO "eicon_pci: remapped ram= 0x%x\n",(unsigned int)card->PCIram); + printk(KERN_INFO "eicon_pci: remapped reg= 0x%x\n",(unsigned int)card->PCIreg); + printk(KERN_INFO "eicon_pci: remapped cfg= 0x%x\n",(unsigned int)card->PCIcfg); +#endif + break; + } +} + + +static void +eicon_pci_release_shmem(eicon_pci_card *card) { + if (!card->master) + return; + if (card->mvalid) { + switch (card->type) { + case EICON_CTYPE_MAESTRA: + /* reset board */ + outb(0, card->PCIcfg + 0x4c); /* disable interrupts from PLX */ + outb(0, card->PCIreg + M_RESET); + SLEEP(20); + outb(0, card->PCIreg + M_ADDRH); + outw(0, card->PCIreg + M_ADDR); + outw(0, card->PCIreg + M_DATA); + + release_region(card->PCIreg, 0x20); + release_region(card->PCIcfg, 0x100); + break; + case EICON_CTYPE_MAESTRAQ: + case EICON_CTYPE_MAESTRAQ_U: + case EICON_CTYPE_MAESTRAP: + /* reset board */ + writeb(_MP_RISC_RESET | _MP_LED1 | _MP_LED2, card->PCIreg + MP_RESET); + SLEEP(20); + writeb(0, card->PCIreg + MP_RESET); + SLEEP(20); + + iounmap((void *)card->shmem); + iounmap((void *)card->PCIreg); + iounmap((void *)card->PCIcfg); + break; + } + } + card->mvalid = 0; +} + +static void +eicon_pci_release_irq(eicon_pci_card *card) { + if (!card->master) + return; + if (card->ivalid) + free_irq(card->irq, card); + card->ivalid = 0; +} + +void +eicon_pci_release(eicon_pci_card *card) { + eicon_pci_release_irq(card); + eicon_pci_release_shmem(card); +} + +/* + * Upload buffer content to adapters shared memory + * on verify error, 1 is returned and a message is printed on screen + * else 0 is returned + * Can serve IO-Type and Memory type adapters + */ +int eicon_upload(t_dsp_download_space *p_para, + __u16 length, /* byte count */ + __u8 *buffer, + int verify) +{ + __u32 i, dwdata = 0, val = 0, timeout; + __u16 data; + eicon_pci_boot *boot = 0; + + switch (p_para->type) /* actions depend on type of union */ + { + case DL_PARA_IO_TYPE: + for (i=0; i<length; i+=2) + { + outb ((u8) ((p_para->dat.io.r3addr + i) >> 16), p_para->dat.io.ioADDRH); + outw ((u16) (p_para->dat.io.r3addr + i), p_para->dat.io.ioADDR); + /* outw (((u16 *)code)[i >> 1], p_para->dat.io.ioDATA); */ + outw (*(u16 *)&buffer[i], p_para->dat.io.ioDATA); + } + if (verify) /* check written block */ + { + for (i=0; i<length; i+=2) + { + outb ((u8) ((p_para->dat.io.r3addr + i) >> 16), p_para->dat.io.ioADDRH); + outw ((u16) (p_para->dat.io.r3addr + i), p_para->dat.io.ioADDR); + data = inw(p_para->dat.io.ioDATA); + if (data != *(u16 *)&buffer[i]) + { + p_para->dat.io.r3addr += i; + p_para->dat.io.BadData = data; + p_para->dat.io.GoodData = *(u16 *)&buffer[i]; + return 1; + } + } + } + break; + + case DL_PARA_MEM_TYPE: + boot = p_para->dat.mem.boot; + writel(p_para->dat.mem.r3addr, &boot->addr); + for (i=0; i<length; i+=4) + { + writel(((u32 *)buffer)[i >> 2], &boot->data[i]); + } + if (verify) /* check written block */ + { + for (i=0; i<length; i+=4) + { + dwdata = readl(&boot->data[i]); + if (((u32 *)buffer)[i >> 2] != dwdata) + { + p_para->dat.mem.r3addr += i; + p_para->dat.mem.BadData = dwdata; + p_para->dat.mem.GoodData = ((u32 *)buffer)[i >> 2]; + return 1; + } + } + } + writel(((length + 3) / 4), &boot->len); /* len in dwords */ + writel(2, &boot->cmd); + + timeout = jiffies + 20; + while (timeout > jiffies) { + val = readl(&boot->cmd); + if (!val) break; + SLEEP(2); + } + if (val) + { + p_para->dat.mem.timeout = 1; + return 1; + } + break; + } + return 0; +} + + +/* show header information of code file */ +static +int eicon_pci_print_hdr(unsigned char *code, int offset) +{ + unsigned char hdr[80]; + int i, fvalue = 0; + + i = 0; + while ((i < (sizeof(hdr) -1)) + && (code[offset + i] != '\0') + && (code[offset + i] != '\r') + && (code[offset + i] != '\n')) + { + hdr[i] = code[offset + i]; + i++; + } + hdr[i] = '\0'; + printk(KERN_DEBUG "Eicon: loading %s\n", hdr); + if (GetProtFeatureValue(hdr, &fvalue)) return(fvalue); + else return(0); +} + + +/* + * Configure a card, download code into BRI card, + * check if we get interrupts and return 0 on succes. + * Return -ERRNO on failure. + */ +int +eicon_pci_load_bri(eicon_pci_card *card, eicon_pci_codebuf *cb) { + int i,j; + int timeout; + unsigned int offset, offp=0, size, length; + int signature = 0; + int FeatureValue = 0; + eicon_pci_codebuf cbuf; + t_dsp_download_space dl_para; + t_dsp_download_desc dsp_download_table; + unsigned char *code; + unsigned int reg; + unsigned int cfg; + + if (copy_from_user(&cbuf, cb, sizeof(eicon_pci_codebuf))) + return -EFAULT; + + reg = card->PCIreg; + cfg = card->PCIcfg; + + /* reset board */ + outb(0, reg + M_RESET); + SLEEP(10); + outb(0, reg + M_ADDRH); + outw(0, reg + M_ADDR); + outw(0, reg + M_DATA); + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: reset card\n"); +#endif + + /* clear shared memory */ + outb(0xff, reg + M_ADDRH); + outw(0, reg + M_ADDR); + for(i = 0; i < 0xffff; i++) outw(0, reg + M_DATA); + SLEEP(10); + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: clear shared memory\n"); +#endif + + /* download protocol and dsp file */ + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: downloading firmware...\n"); +#endif + + /* Allocate code-buffer */ + if (!(code = kmalloc(400, GFP_KERNEL))) { + printk(KERN_WARNING "eicon_pci_boot: Couldn't allocate code buffer\n"); + return -ENOMEM; + } + + /* prepare protocol upload */ + dl_para.type = DL_PARA_IO_TYPE; + dl_para.dat.io.ioADDR = reg + M_ADDR; + dl_para.dat.io.ioADDRH = reg + M_ADDRH; + dl_para.dat.io.ioDATA = reg + M_DATA; + + for (j = 0; j <= cbuf.dsp_code_num; j++) + { + if (j == 0) size = cbuf.protocol_len; + else size = cbuf.dsp_code_len[j]; + + offset = 0; + + if (j == 0) dl_para.dat.io.r3addr = 0; + if (j == 1) dl_para.dat.io.r3addr = M_DSP_CODE_BASE + + ((sizeof(__u32) + (sizeof(dsp_download_table) * 35) + 3) &0xfffffffc); + if (j == 2) dl_para.dat.io.r3addr = M_DSP_CODE_BASE; + if (j == 3) dl_para.dat.io.r3addr = M_DSP_CODE_BASE + sizeof(__u32); + + do /* download block of up to 400 bytes */ + { + length = ((size - offset) >= 400) ? 400 : (size - offset); + + if (copy_from_user(code, (&cb->code) + offp + offset, length)) { + kfree(code); + return -EFAULT; + } + + if ((offset == 0) && (j < 2)) { + FeatureValue = eicon_pci_print_hdr(code, j ? 0x00 : 0x80); +#ifdef EICON_PCI_DEBUG + if (FeatureValue) printk(KERN_DEBUG "eicon_pci: Feature Value : 0x%04x.\n", FeatureValue); +#endif + if ((j==0) && (!(FeatureValue & PROTCAP_TELINDUS))) { + printk(KERN_ERR "eicon_pci: Protocol Code cannot handle Telindus\n"); + kfree(code); + return -EFAULT; + } + ((eicon_card *)card->card)->Feature = FeatureValue; + } + + if (eicon_upload(&dl_para, length, code, 1)) + { + printk(KERN_ERR "eicon_pci: code block check failed at 0x%x !\n",dl_para.dat.io.r3addr); + kfree(code); + return -EIO; + } + /* move onto next block */ + offset += length; + dl_para.dat.io.r3addr += length; + } while (offset < size); + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "Eicon: %d bytes loaded.\n", offset); +#endif + offp += size; + } + kfree(code); + + /* clear signature */ + outb(0xff, reg + M_ADDRH); + outw(0x1e, reg + M_ADDR); + outw(0, reg + M_DATA); + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: copy configuration data into shared memory...\n"); +#endif + /* copy configuration data into shared memory */ + outw(8, reg + M_ADDR); outb(cbuf.tei, reg + M_DATA); + outw(9, reg + M_ADDR); outb(cbuf.nt2, reg + M_DATA); + outw(10,reg + M_ADDR); outb(0, reg + M_DATA); + outw(11,reg + M_ADDR); outb(cbuf.WatchDog, reg + M_DATA); + outw(12,reg + M_ADDR); outb(cbuf.Permanent, reg + M_DATA); + outw(13,reg + M_ADDR); outb(0, reg + M_DATA); /* XInterface */ + outw(14,reg + M_ADDR); outb(cbuf.StableL2, reg + M_DATA); + outw(15,reg + M_ADDR); outb(cbuf.NoOrderCheck, reg + M_DATA); + outw(16,reg + M_ADDR); outb(0, reg + M_DATA); /* HandsetType */ + outw(17,reg + M_ADDR); outb(0, reg + M_DATA); /* SigFlags */ + outw(18,reg + M_ADDR); outb(cbuf.LowChannel, reg + M_DATA); + outw(19,reg + M_ADDR); outb(cbuf.ProtVersion, reg + M_DATA); + outw(20,reg + M_ADDR); outb(cbuf.Crc4, reg + M_DATA); + outw(21,reg + M_ADDR); outb((cbuf.Loopback) ? 2:0, reg + M_DATA); + + for (i=0;i<32;i++) + { + outw( 32+i, reg + M_ADDR); outb(cbuf.l[0].oad[i], reg + M_DATA); + outw( 64+i, reg + M_ADDR); outb(cbuf.l[0].osa[i], reg + M_DATA); + outw( 96+i, reg + M_ADDR); outb(cbuf.l[0].spid[i], reg + M_DATA); + outw(128+i, reg + M_ADDR); outb(cbuf.l[1].oad[i], reg + M_DATA); + outw(160+i, reg + M_ADDR); outb(cbuf.l[1].osa[i], reg + M_DATA); + outw(192+i, reg + M_ADDR); outb(cbuf.l[1].spid[i], reg + M_DATA); + } + +#ifdef EICON_PCI_DEBUG + printk(KERN_ERR "eicon_pci: starting CPU...\n"); +#endif + /* let the CPU run */ + outw(0x08, reg + M_RESET); + + timeout = jiffies + (5*HZ); + while (timeout > jiffies) { + outw(0x1e, reg + M_ADDR); + signature = inw(reg + M_DATA); + if (signature == DIVAS_SIGNATURE) break; + SLEEP(2); + } + if (signature != DIVAS_SIGNATURE) + { +#ifdef EICON_PCI_DEBUG + printk(KERN_ERR "eicon_pci: signature 0x%x expected 0x%x\n",signature,DIVAS_SIGNATURE); +#endif + printk(KERN_ERR "eicon_pci: Timeout, protocol code not running !\n"); + return -EIO; + } +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: Protocol code running, signature OK\n"); +#endif + + /* get serial number and number of channels supported by card */ + outb(0xff, reg + M_ADDRH); + outw(0x3f6, reg + M_ADDR); + card->channels = inw(reg + M_DATA); + card->serial = (u32)inw(cfg + 0x22) << 16 | (u32)inw(cfg + 0x26); + printk(KERN_INFO "Eicon: Supported channels : %d\n", card->channels); + printk(KERN_INFO "Eicon: Card serial no. = %lu\n", card->serial); + + /* test interrupt */ + card->irqprobe = 1; + + if (!card->ivalid) { + if (request_irq(card->irq, &eicon_irq, 0, "Eicon PCI ISDN", card->card)) + { + printk(KERN_ERR "eicon_pci: Couldn't request irq %d\n", card->irq); + return -EIO; + } + } + card->ivalid = 1; + +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: testing interrupt\n"); +#endif + /* Trigger an interrupt and check if it is delivered */ + outb(0x41, cfg + 0x4c); /* enable PLX for interrupts */ + outb(0x89, reg + M_RESET); /* place int request */ + + timeout = jiffies + 20; + while (timeout > jiffies) { + if (card->irqprobe != 1) break; + SLEEP(5); + } + if (card->irqprobe == 1) { + free_irq(card->irq, card); + card->ivalid = 0; + printk(KERN_ERR "eicon_pci: Getting no interrupts !\n"); + return -EIO; + } + + /* initializing some variables */ + ((eicon_card *)card->card)->ReadyInt = 0; + for(j=0; j<256; j++) ((eicon_card *)card->card)->IdTable[j] = NULL; + for(j=0; j< (card->channels + 1); j++) { + ((eicon_card *)card->card)->bch[j].e.busy = 0; + ((eicon_card *)card->card)->bch[j].e.D3Id = 0; + ((eicon_card *)card->card)->bch[j].e.B2Id = 0; + ((eicon_card *)card->card)->bch[j].e.ref = 0; + ((eicon_card *)card->card)->bch[j].e.Req = 0; + ((eicon_card *)card->card)->bch[j].e.complete = 1; + ((eicon_card *)card->card)->bch[j].fsm_state = EICON_STATE_NULL; + } + + printk(KERN_INFO "Eicon: Card successfully started\n"); + + return 0; +} + + +/* + * Configure a card, download code into PRI card, + * check if we get interrupts and return 0 on succes. + * Return -ERRNO on failure. + */ +int +eicon_pci_load_pri(eicon_pci_card *card, eicon_pci_codebuf *cb) { + eicon_pci_boot *boot; + eicon_pr_ram *prram; + int i,j; + int timeout; + int FeatureValue = 0; + unsigned int offset, offp=0, size, length; + unsigned long int signature = 0; + t_dsp_download_space dl_para; + t_dsp_download_desc dsp_download_table; + eicon_pci_codebuf cbuf; + unsigned char *code; + unsigned char req_int; + char *ram, *reg, *cfg; + + if (copy_from_user(&cbuf, cb, sizeof(eicon_pci_codebuf))) + return -EFAULT; + + boot = &card->shmem->boot; + ram = (char *)card->PCIram; + reg = (char *)card->PCIreg; + cfg = (char *)card->PCIcfg; + prram = (eicon_pr_ram *)ram; + + /* reset board */ + writeb(_MP_RISC_RESET | _MP_LED1 | _MP_LED2, card->PCIreg + MP_RESET); + SLEEP(20); + writeb(0, card->PCIreg + MP_RESET); + SLEEP(20); + + /* set command count to 0 */ + writel(0, &boot->reserved); + + /* check if CPU increments the life word */ + i = readw(&boot->live); + SLEEP(20); + if (i == readw(&boot->live)) { + printk(KERN_ERR "eicon_pci: card is reset, but CPU not running !\n"); + return -EIO; + } +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: reset card OK (CPU running)\n"); +#endif + + /* download firmware : DSP and Protocol */ +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: downloading firmware...\n"); +#endif + + /* Allocate code-buffer */ + if (!(code = kmalloc(400, GFP_KERNEL))) { + printk(KERN_WARNING "eicon_pci_boot: Couldn't allocate code buffer\n"); + return -ENOMEM; + } + + /* prepare protocol upload */ + dl_para.type = DL_PARA_MEM_TYPE; + dl_para.dat.mem.boot = boot; + + for (j = 0; j <= cbuf.dsp_code_num; j++) + { + if (j==0) size = cbuf.protocol_len; + else size = cbuf.dsp_code_len[j]; + + if (j==1) writel(MP_DSP_ADDR, &boot->addr); /* DSP code entry point */ + + if (j == 0) dl_para.dat.io.r3addr = MP_PROTOCOL_ADDR; + if (j == 1) dl_para.dat.io.r3addr = MP_DSP_CODE_BASE + + ((sizeof(__u32) + (sizeof(dsp_download_table) * 35) + 3) &0xfffffffc); + if (j == 2) dl_para.dat.io.r3addr = MP_DSP_CODE_BASE; + if (j == 3) dl_para.dat.io.r3addr = MP_DSP_CODE_BASE + sizeof(__u32); + + offset = 0; + do /* download block of up to 400 bytes */ + { + length = ((size - offset) >= 400) ? 400 : (size - offset); + + if (copy_from_user(code, (&cb->code) + offp + offset, length)) { + kfree(code); + return -EFAULT; + } + + if ((offset == 0) && (j < 2)) { + FeatureValue = eicon_pci_print_hdr(code, j ? 0x00 : 0x80); +#ifdef EICON_PCI_DEBUG + if (FeatureValue) printk(KERN_DEBUG "eicon_pci: Feature Value : 0x%x.\n", FeatureValue); +#endif + if ((j==0) && (!(FeatureValue & PROTCAP_TELINDUS))) { + printk(KERN_ERR "eicon_pci: Protocol Code cannot handle Telindus\n"); + kfree(code); + return -EFAULT; + } + ((eicon_card *)card->card)->Feature = FeatureValue; + } + + if (eicon_upload(&dl_para, length, code, 1)) + { + if (dl_para.dat.mem.timeout == 0) + printk(KERN_ERR "eicon_pci: code block check failed at 0x%x !\n",dl_para.dat.io.r3addr); + else + printk(KERN_ERR "eicon_pci: timeout, no ACK to load !\n"); + kfree(code); + return -EIO; + } + + /* move onto next block */ + offset += length; + dl_para.dat.mem.r3addr += length; + } while (offset < size); +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: %d bytes loaded.\n", offset); +#endif + offp += size; + } + kfree(code); + + /* initialize the adapter data structure */ +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: copy configuration data into shared memory...\n"); +#endif + /* clear out config space */ + for (i = 0; i < 256; i++) writeb(0, &ram[i]); + + /* copy configuration down to the card */ + writeb(cbuf.tei, &ram[8]); + writeb(cbuf.nt2, &ram[9]); + writeb(0, &ram[10]); + writeb(cbuf.WatchDog, &ram[11]); + writeb(cbuf.Permanent, &ram[12]); + writeb(cbuf.XInterface, &ram[13]); + writeb(cbuf.StableL2, &ram[14]); + writeb(cbuf.NoOrderCheck, &ram[15]); + writeb(cbuf.HandsetType, &ram[16]); + writeb(0, &ram[17]); + writeb(cbuf.LowChannel, &ram[18]); + writeb(cbuf.ProtVersion, &ram[19]); + writeb(cbuf.Crc4, &ram[20]); + for (i = 0; i < 32; i++) + { + writeb(cbuf.l[0].oad[i], &ram[32 + i]); + writeb(cbuf.l[0].osa[i], &ram[64 + i]); + writeb(cbuf.l[0].spid[i], &ram[96 + i]); + writeb(cbuf.l[1].oad[i], &ram[128 + i]); + writeb(cbuf.l[1].osa[i], &ram[160 + i]); + writeb(cbuf.l[1].spid[i], &ram[192 + i]); + } +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: configured card OK\n"); +#endif + + /* start adapter */ +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: tell card to start...\n"); +#endif + writel(MP_PROTOCOL_ADDR, &boot->addr); /* RISC code entry point */ + writel(3, &boot->cmd); /* DIVAS_START_CMD */ + + /* wait till card ACKs */ + timeout = jiffies + (5*HZ); + while (timeout > jiffies) { + signature = readl(&boot->signature); + if ((signature >> 16) == DIVAS_SIGNATURE) break; + SLEEP(2); + } + if ((signature >> 16) != DIVAS_SIGNATURE) + { +#ifdef EICON_PCI_DEBUG + printk(KERN_ERR "eicon_pci: signature 0x%lx expected 0x%x\n",(signature >> 16),DIVAS_SIGNATURE); +#endif + printk(KERN_ERR "eicon_pci: timeout, protocol code not running !\n"); + return -EIO; + } +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: Protocol code running, signature OK\n"); +#endif + + /* get serial number and number of channels supported by card */ + card->channels = readb(&ram[0x3f6]); + card->serial = readl(&ram[0x3f0]); + printk(KERN_INFO "Eicon: Supported channels : %d\n", card->channels); + printk(KERN_INFO "Eicon: Card serial no. = %lu\n", card->serial); + + /* test interrupt */ + readb(&ram[0x3fe]); + writeb(0, &ram[0x3fe]); /* reset any pending interrupt */ + readb(&ram[0x3fe]); + + writew(MP_IRQ_RESET_VAL, &cfg[MP_IRQ_RESET]); + writew(0, &cfg[MP_IRQ_RESET + 2]); + + card->irqprobe = 1; + + if (!card->ivalid) { + if (request_irq(card->irq, &eicon_irq, 0, "Eicon PCI ISDN", card->card)) + { + printk(KERN_ERR "eicon_pci: Couldn't request irq %d\n", card->irq); + return -EIO; + } + } + card->ivalid = 1; + + req_int = readb(&prram->ReadyInt); +#ifdef EICON_PCI_DEBUG + printk(KERN_DEBUG "eicon_pci: testing interrupt\n"); +#endif + req_int++; + /* Trigger an interrupt and check if it is delivered */ + writeb(req_int, &prram->ReadyInt); + + timeout = jiffies + 20; + while (timeout > jiffies) { + if (card->irqprobe != 1) break; + SLEEP(2); + } + if (card->irqprobe == 1) { + free_irq(card->irq, card); + card->ivalid = 0; + printk(KERN_ERR "eicon_pci: Getting no interrupts !\n"); + return -EIO; + } + + /* initializing some variables */ + ((eicon_card *)card->card)->ReadyInt = 0; + for(j=0; j<256; j++) ((eicon_card *)card->card)->IdTable[j] = NULL; + for(j=0; j< (card->channels + 1); j++) { + ((eicon_card *)card->card)->bch[j].e.busy = 0; + ((eicon_card *)card->card)->bch[j].e.D3Id = 0; + ((eicon_card *)card->card)->bch[j].e.B2Id = 0; + ((eicon_card *)card->card)->bch[j].e.ref = 0; + ((eicon_card *)card->card)->bch[j].e.Req = 0; + ((eicon_card *)card->card)->bch[j].e.complete = 1; + ((eicon_card *)card->card)->bch[j].fsm_state = EICON_STATE_NULL; + } + + printk(KERN_INFO "Eicon: Card successfully started\n"); + + return 0; +} + +#endif /* CONFIG_PCI */ + diff --git a/drivers/isdn/eicon/eicon_pci.h b/drivers/isdn/eicon/eicon_pci.h new file mode 100644 index 000000000..a23faade2 --- /dev/null +++ b/drivers/isdn/eicon/eicon_pci.h @@ -0,0 +1,188 @@ +/* $Id: eicon_pci.h,v 1.3 1999/03/29 11:19:51 armin Exp $ + * + * ISDN low-level module for Eicon.Diehl active ISDN-Cards (PCI part). + * + * Copyright 1998,99 by Armin Schindler (mac@melware.de) + * Copyright 1999 Cytronics & Melware (info@melware.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: eicon_pci.h,v $ + * Revision 1.3 1999/03/29 11:19:51 armin + * I/O stuff now in seperate file (eicon_io.c) + * Old ISA type cards (S,SX,SCOM,Quadro,S2M) implemented. + * + * Revision 1.2 1999/03/02 12:37:50 armin + * Added some important checks. + * Analog Modem with DSP. + * Channels will be added to Link-Level after loading firmware. + * + * Revision 1.1 1999/01/01 18:09:46 armin + * First checkin of new eicon driver. + * DIVA-Server BRI/PCI and PRI/PCI are supported. + * Old diehl code is obsolete. + * + * + */ + +#ifndef eicon_pci_h +#define eicon_pci_h + +#ifdef __KERNEL__ + + +#define PCI_VENDOR_EICON 0x1133 +#define PCI_DIVA_PRO20 0xe001 /* Not supported */ +#define PCI_DIVA20 0xe002 /* Not supported */ +#define PCI_DIVA_PRO20_U 0xe003 /* Not supported */ +#define PCI_DIVA20_U 0xe004 /* Not supported */ +#define PCI_MAESTRA 0xe010 +#define PCI_MAESTRAQ 0xe012 +#define PCI_MAESTRAQ_U 0xe013 +#define PCI_MAESTRAP 0xe014 + +#define DIVA_PRO20 1 +#define DIVA20 2 +#define DIVA_PRO20_U 3 +#define DIVA20_U 4 +#define MAESTRA 5 +#define MAESTRAQ 6 +#define MAESTRAQ_U 7 +#define MAESTRAP 8 + +#define TRUE 1 +#define FALSE 0 + +#define DIVAS_SIGNATURE 0x4447 + + +/* MAESTRA BRI PCI */ + +#define M_RESET 0x10 /* offset of reset register */ +#define M_DATA 0x00 /* offset of data register */ +#define M_ADDR 0x04 /* offset of address register */ +#define M_ADDRH 0x0c /* offset of high address register */ + +#define M_DSP_CODE_LEN 0xbf7d0000 +#define M_DSP_CODE 0xbf7d0004 /* max 128K DSP-Code */ +#define M_DSP_CODE_BASE 0xbf7a0000 +#define M_MAX_DSP_CODE_SIZE 0x00050000 /* max 320K DSP-Code (Telindus) */ + + + +/* MAESTRA PRI PCI */ + +#define MP_SHARED_RAM_OFFSET 0x1000 /* offset of shared RAM base in the DRAM memory bar */ + +#define MP_IRQ_RESET 0xc18 /* offset of interrupt status register in the CONFIG memory bar */ +#define MP_IRQ_RESET_VAL 0xfe /* value to clear an interrupt */ + +#define MP_PROTOCOL_ADDR 0xa0011000 /* load address of protocol code */ +#define MP_DSP_ADDR 0xa03c0000 /* load address of DSP code */ +#define MP_MAX_PROTOCOL_CODE_SIZE 0x000a0000 /* max 640K Protocol-Code */ +#define MP_DSP_CODE_BASE 0xa03a0000 +#define MP_MAX_DSP_CODE_SIZE 0x00060000 /* max 384K DSP-Code */ + +#define MP_RESET 0x20 /* offset of RESET register in the DEVICES memory bar */ + +/* RESET register bits */ +#define _MP_S2M_RESET 0x10 /* active lo */ +#define _MP_LED2 0x08 /* 1 = on */ +#define _MP_LED1 0x04 /* 1 = on */ +#define _MP_DSP_RESET 0x02 /* active lo */ +#define _MP_RISC_RESET 0x81 /* active hi, bit 7 for compatibility with old boards */ + +/* boot interface structure */ +typedef struct { + __u32 cmd __attribute__ ((packed)); + __u32 addr __attribute__ ((packed)); + __u32 len __attribute__ ((packed)); + __u32 err __attribute__ ((packed)); + __u32 live __attribute__ ((packed)); + __u32 reserved[(0x1020>>2)-6] __attribute__ ((packed)); + __u32 signature __attribute__ ((packed)); + __u8 data[1]; /* real interface description */ +} eicon_pci_boot; + + +#define DL_PARA_IO_TYPE 0 +#define DL_PARA_MEM_TYPE 1 + +typedef struct tag_dsp_download_space +{ + __u16 type; /* see definitions above to differ union elements */ + union + { + struct + { + __u32 r3addr; + __u16 ioADDR; + __u16 ioADDRH; + __u16 ioDATA; + __u16 BadData; /* in case of verify error */ + __u16 GoodData; + } io; /* for io based adapters */ + struct + { + __u32 r3addr; + eicon_pci_boot *boot; + __u32 BadData; /* in case of verify error */ + __u32 GoodData; + __u16 timeout; + } mem; /* for memory based adapters */ + } dat; +} t_dsp_download_space; + + +/* Shared memory */ +typedef union { + eicon_pci_boot boot; +} eicon_pci_shmem; + +/* + * card's description + */ +typedef struct { + int ramsize; + int irq; /* IRQ */ + unsigned int PCIram; + unsigned int PCIreg; + unsigned int PCIcfg; + long int serial; /* Serial No. */ + int channels; /* No. of supported channels */ + void* card; + eicon_pci_shmem* shmem; /* Shared-memory area */ + unsigned char* intack; /* Int-Acknowledge */ + unsigned char* stopcpu; /* Writing here stops CPU */ + unsigned char* startcpu; /* Writing here starts CPU */ + unsigned char type; /* card type */ + unsigned char irqprobe; /* Flag: IRQ-probing */ + unsigned char mvalid; /* Flag: Memory is valid */ + unsigned char ivalid; /* Flag: IRQ is valid */ + unsigned char master; /* Flag: Card is Quadro 1/4 */ + void* generic; /* Ptr to generic card struct */ +} eicon_pci_card; + + + +extern int eicon_pci_load_pri(eicon_pci_card *card, eicon_pci_codebuf *cb); +extern int eicon_pci_load_bri(eicon_pci_card *card, eicon_pci_codebuf *cb); +extern void eicon_pci_release(eicon_pci_card *card); +extern void eicon_pci_printpar(eicon_pci_card *card); +extern int eicon_pci_find_card(char *ID); + +#endif /* __KERNEL__ */ + +#endif /* eicon_pci_h */ diff --git a/drivers/isdn/hisax/avm_a1p.c b/drivers/isdn/hisax/avm_a1p.c new file mode 100644 index 000000000..c11ac41e4 --- /dev/null +++ b/drivers/isdn/hisax/avm_a1p.c @@ -0,0 +1,334 @@ +/* $Id: avm_a1p.c,v 2.3 1998/11/15 23:54:22 keil Exp $ + * + * avm_a1p.c low level stuff for the following AVM cards: + * A1 PCMCIA + * FRITZ!Card PCMCIA + * FRITZ!Card PCMCIA 2.0 + * + * Author Carsten Paeth (calle@calle.in-berlin.de) + * + * $Log: avm_a1p.c,v $ + * Revision 2.3 1998/11/15 23:54:22 keil + * changes from 2.0 + * + * Revision 2.2 1998/08/13 23:36:13 keil + * HiSax 3.1 - don't work stable with current LinkLevel + * + * Revision 2.1 1998/07/15 15:01:23 calle + * Support for AVM passive PCMCIA cards: + * A1 PCMCIA, FRITZ!Card PCMCIA and FRITZ!Card PCMCIA 2.0 + * + * Revision 1.1.2.1 1998/07/15 14:43:26 calle + * Support for AVM passive PCMCIA cards: + * A1 PCMCIA, FRITZ!Card PCMCIA and FRITZ!Card PCMCIA 2.0 + * + * + */ +#define __NO_VERSION__ +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +/* register offsets */ +#define ADDRREG_OFFSET 0x02 +#define DATAREG_OFFSET 0x03 +#define ASL0_OFFSET 0x04 +#define ASL1_OFFSET 0x05 +#define MODREG_OFFSET 0x06 +#define VERREG_OFFSET 0x07 + +/* address offsets */ +#define ISAC_FIFO_OFFSET 0x00 +#define ISAC_REG_OFFSET 0x20 +#define HSCX_CH_DIFF 0x40 +#define HSCX_FIFO_OFFSET 0x80 +#define HSCX_REG_OFFSET 0xa0 + +/* read bits ASL0 */ +#define ASL0_R_TIMER 0x10 /* active low */ +#define ASL0_R_ISAC 0x20 /* active low */ +#define ASL0_R_HSCX 0x40 /* active low */ +#define ASL0_R_TESTBIT 0x80 +#define ASL0_R_IRQPENDING (ASL0_R_ISAC|ASL0_R_HSCX|ASL0_R_TIMER) + +/* write bits ASL0 */ +#define ASL0_W_RESET 0x01 +#define ASL0_W_TDISABLE 0x02 +#define ASL0_W_TRESET 0x04 +#define ASL0_W_IRQENABLE 0x08 +#define ASL0_W_TESTBIT 0x80 + +/* write bits ASL1 */ +#define ASL1_W_LED0 0x10 +#define ASL1_W_LED1 0x20 +#define ASL1_W_ENABLE_S0 0xC0 + +#define byteout(addr,val) outb(val,addr) +#define bytein(addr) inb(addr) + +static const char *avm_revision = "$Revision: 2.3 $"; + +static inline u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + long flags; + u_char ret; + + offset -= 0x20; + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_REG_OFFSET+offset); + ret = bytein(cs->hw.avm.cfg_reg+DATAREG_OFFSET); + restore_flags(flags); + return ret; +} + +static inline void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + long flags; + + offset -= 0x20; + + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_REG_OFFSET+offset); + byteout(cs->hw.avm.cfg_reg+DATAREG_OFFSET, value); + restore_flags(flags); +} + +static inline void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + long flags; + + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_FIFO_OFFSET); + insb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); + restore_flags(flags); +} + +static inline void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + long flags; + + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET,ISAC_FIFO_OFFSET); + outsb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); + restore_flags(flags); +} + +static inline u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + u_char ret; + long flags; + + offset -= 0x20; + + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_REG_OFFSET+hscx*HSCX_CH_DIFF+offset); + ret = bytein(cs->hw.avm.cfg_reg+DATAREG_OFFSET); + restore_flags(flags); + return ret; +} + +static inline void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + long flags; + + offset -= 0x20; + + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_REG_OFFSET+hscx*HSCX_CH_DIFF+offset); + byteout(cs->hw.avm.cfg_reg+DATAREG_OFFSET, value); + restore_flags(flags); +} + +static inline void +ReadHSCXfifo(struct IsdnCardState *cs, int hscx, u_char * data, int size) +{ + long flags; + + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_FIFO_OFFSET+hscx*HSCX_CH_DIFF); + insb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); + restore_flags(flags); +} + +static inline void +WriteHSCXfifo(struct IsdnCardState *cs, int hscx, u_char * data, int size) +{ + long flags; + + save_flags(flags); + cli(); + byteout(cs->hw.avm.cfg_reg+ADDRREG_OFFSET, + HSCX_FIFO_OFFSET+hscx*HSCX_CH_DIFF); + outsb(cs->hw.avm.cfg_reg+DATAREG_OFFSET, data, size); + restore_flags(flags); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) ReadHSCX(cs, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) WriteHSCX(cs, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) ReadHSCXfifo(cs, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) WriteHSCXfifo(cs, nr, ptr, cnt) + +#include "hscx_irq.c" + +static void +avm_a1p_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, sval, stat = 0; + + if (!cs) { + printk(KERN_WARNING "AVM A1 PCMCIA: Spurious interrupt!\n"); + return; + } + while ((sval = (~bytein(cs->hw.avm.cfg_reg+ASL0_OFFSET) & ASL0_R_IRQPENDING))) { + if (cs->debug & L1_DEB_INTSTAT) + debugl1(cs, "avm IntStatus %x", sval); + if (sval & ASL0_R_HSCX) { + val = ReadHSCX(cs, 1, HSCX_ISTA); + if (val) { + hscx_int_main(cs, val); + stat |= 1; + } + } + if (sval & ASL0_R_ISAC) { + val = ReadISAC(cs, ISAC_ISTA); + if (val) { + isac_interrupt(cs, val); + stat |= 2; + } + } + } + if (stat & 1) { + WriteHSCX(cs, 0, HSCX_MASK, 0xff); + WriteHSCX(cs, 1, HSCX_MASK, 0xff); + WriteHSCX(cs, 0, HSCX_MASK, 0x00); + WriteHSCX(cs, 1, HSCX_MASK, 0x00); + } + if (stat & 2) { + WriteISAC(cs, ISAC_MASK, 0xff); + WriteISAC(cs, ISAC_MASK, 0x00); + } +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + int ret; + switch (mt) { + case CARD_RESET: + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,ASL0_W_RESET); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + return 0; + + case CARD_RELEASE: + /* free_irq is done in HiSax_closecard(). */ + /* free_irq(cs->irq, cs); */ + return 0; + + case CARD_SETIRQ: + ret = request_irq(cs->irq, &avm_a1p_interrupt, + I4L_IRQ_FLAG, "HiSax", cs); + if (ret) + return ret; + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET, + ASL0_W_TDISABLE|ASL0_W_TRESET|ASL0_W_IRQENABLE); + return 0; + + case CARD_INIT: + clear_pending_isac_ints(cs); + clear_pending_hscx_ints(cs); + inithscxisac(cs, 1); + inithscxisac(cs, 2); + return 0; + + case CARD_TEST: + /* we really don't need it for the PCMCIA Version */ + return 0; + + default: + /* all card drivers ignore others, so we do the same */ + return 0; + } + return 0; +} + +__initfunc(int +setup_avm_a1_pcmcia(struct IsdnCard *card)) +{ + u_char model, vers; + struct IsdnCardState *cs = card->cs; + long flags; + char tmp[64]; + + + strcpy(tmp, avm_revision); + printk(KERN_INFO "HiSax: AVM A1 PCMCIA driver Rev. %s\n", + HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_A1_PCMCIA) + return (0); + + cs->hw.avm.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + + + save_flags(flags); + outb(cs->hw.avm.cfg_reg+ASL1_OFFSET, ASL1_W_ENABLE_S0); + sti(); + + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,ASL0_W_RESET); + HZDELAY(HZ / 5 + 1); + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET,0x00); + + byteout(cs->hw.avm.cfg_reg+ASL0_OFFSET, ASL0_W_TDISABLE|ASL0_W_TRESET); + + restore_flags(flags); + + model = bytein(cs->hw.avm.cfg_reg+MODREG_OFFSET); + vers = bytein(cs->hw.avm.cfg_reg+VERREG_OFFSET); + + printk(KERN_INFO "AVM A1 PCMCIA: io 0x%x irq %d model %d version %d\n", + cs->hw.avm.cfg_reg, cs->irq, model, vers); + + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &AVM_card_msg; + + ISACVersion(cs, "AVM A1 PCMCIA:"); + if (HscxVersion(cs, "AVM A1 PCMCIA:")) { + printk(KERN_WARNING + "AVM A1 PCMCIA: wrong HSCX versions check IO address\n"); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/avm_pci.c b/drivers/isdn/hisax/avm_pci.c new file mode 100644 index 000000000..c0f04f91c --- /dev/null +++ b/drivers/isdn/hisax/avm_pci.c @@ -0,0 +1,865 @@ +/* $Id: avm_pci.c,v 1.7 1999/02/22 18:26:30 keil Exp $ + + * avm_pci.c low level stuff for AVM Fritz!PCI and ISA PnP isdn cards + * Thanks to AVM, Berlin for informations + * + * Author Karsten Keil (keil@isdn4linux.de) + * + * + * $Log: avm_pci.c,v $ + * Revision 1.7 1999/02/22 18:26:30 keil + * Argh ! ISAC address was only set with PCI + * + * Revision 1.6 1998/11/27 19:59:28 keil + * set subtype for Fritz!PCI + * + * Revision 1.5 1998/11/27 12:56:45 keil + * forgot to update setup function name + * + * Revision 1.4 1998/11/15 23:53:19 keil + * Fritz!PnP; changes from 2.0 + * + * Revision 1.3 1998/09/27 23:53:39 keil + * Fix error handling + * + * Revision 1.2 1998/09/27 12:54:55 keil + * bcs assign was lost in setstack, very bad results + * + * Revision 1.1 1998/08/20 13:47:30 keil + * first version + * + * + * + */ +#define __NO_VERSION__ +#include <linux/config.h> +#include "hisax.h" +#include "isac.h" +#include "isdnl1.h" +#include <linux/pci.h> +#include <linux/interrupt.h> + +extern const char *CardType[]; +static const char *avm_pci_rev = "$Revision: 1.7 $"; + +#define AVM_FRITZ_PCI 1 +#define AVM_FRITZ_PNP 2 + +#define PCI_VENDOR_AVM 0x1244 +#define PCI_FRITZPCI_ID 0xa00 + +#define HDLC_FIFO 0x0 +#define HDLC_STATUS 0x4 + +#define AVM_HDLC_1 0x00 +#define AVM_HDLC_2 0x01 +#define AVM_ISAC_FIFO 0x02 +#define AVM_ISAC_REG_LOW 0x04 +#define AVM_ISAC_REG_HIGH 0x06 + +#define AVM_STATUS0_IRQ_ISAC 0x01 +#define AVM_STATUS0_IRQ_HDLC 0x02 +#define AVM_STATUS0_IRQ_TIMER 0x04 +#define AVM_STATUS0_IRQ_MASK 0x07 + +#define AVM_STATUS0_RESET 0x01 +#define AVM_STATUS0_DIS_TIMER 0x02 +#define AVM_STATUS0_RES_TIMER 0x04 +#define AVM_STATUS0_ENA_IRQ 0x08 +#define AVM_STATUS0_TESTBIT 0x10 + +#define AVM_STATUS1_INT_SEL 0x0f +#define AVM_STATUS1_ENA_IOM 0x80 + +#define HDLC_MODE_ITF_FLG 0x01 +#define HDLC_MODE_TRANS 0x02 +#define HDLC_MODE_CCR_7 0x04 +#define HDLC_MODE_CCR_16 0x08 +#define HDLC_MODE_TESTLOOP 0x80 + +#define HDLC_INT_XPR 0x80 +#define HDLC_INT_XDU 0x40 +#define HDLC_INT_RPR 0x20 +#define HDLC_INT_MASK 0xE0 + +#define HDLC_STAT_RME 0x01 +#define HDLC_STAT_RDO 0x10 +#define HDLC_STAT_CRCVFRRAB 0x0E +#define HDLC_STAT_CRCVFR 0x06 +#define HDLC_STAT_RML_MASK 0x3f00 + +#define HDLC_CMD_XRS 0x80 +#define HDLC_CMD_XME 0x01 +#define HDLC_CMD_RRS 0x20 +#define HDLC_CMD_XML_MASK 0x3f00 + + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + register u_char idx = (offset > 0x2f) ? AVM_ISAC_REG_HIGH : AVM_ISAC_REG_LOW; + register u_char val; + register long flags; + + save_flags(flags); + cli(); + outb(idx, cs->hw.avm.cfg_reg + 4); + val = inb(cs->hw.avm.isac + (offset & 0xf)); + restore_flags(flags); + return (val); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + register u_char idx = (offset > 0x2f) ? AVM_ISAC_REG_HIGH : AVM_ISAC_REG_LOW; + register long flags; + + save_flags(flags); + cli(); + outb(idx, cs->hw.avm.cfg_reg + 4); + outb(value, cs->hw.avm.isac + (offset & 0xf)); + restore_flags(flags); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + outb(AVM_ISAC_FIFO, cs->hw.avm.cfg_reg + 4); + insb(cs->hw.avm.isac, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + outb(AVM_ISAC_FIFO, cs->hw.avm.cfg_reg + 4); + outsb(cs->hw.avm.isac, data, size); +} + +static inline u_int +ReadHDLCPCI(struct IsdnCardState *cs, int chan, u_char offset) +{ + register u_int idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register u_int val; + register long flags; + + save_flags(flags); + cli(); + outl(idx, cs->hw.avm.cfg_reg + 4); + val = inl(cs->hw.avm.isac + offset); + restore_flags(flags); + return (val); +} + +static inline void +WriteHDLCPCI(struct IsdnCardState *cs, int chan, u_char offset, u_int value) +{ + register u_int idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register long flags; + + save_flags(flags); + cli(); + outl(idx, cs->hw.avm.cfg_reg + 4); + outl(value, cs->hw.avm.isac + offset); + restore_flags(flags); +} + +static inline u_char +ReadHDLCPnP(struct IsdnCardState *cs, int chan, u_char offset) +{ + register u_char idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register u_char val; + register long flags; + + save_flags(flags); + cli(); + outb(idx, cs->hw.avm.cfg_reg + 4); + val = inb(cs->hw.avm.isac + offset); + restore_flags(flags); + return (val); +} + +static inline void +WriteHDLCPnP(struct IsdnCardState *cs, int chan, u_char offset, u_char value) +{ + register u_char idx = chan ? AVM_HDLC_2 : AVM_HDLC_1; + register long flags; + + save_flags(flags); + cli(); + outb(idx, cs->hw.avm.cfg_reg + 4); + outb(value, cs->hw.avm.isac + offset); + restore_flags(flags); +} + +static u_char +ReadHDLC_s(struct IsdnCardState *cs, int chan, u_char offset) +{ + return(0xff & ReadHDLCPCI(cs, chan, offset)); +} + +static void +WriteHDLC_s(struct IsdnCardState *cs, int chan, u_char offset, u_char value) +{ + WriteHDLCPCI(cs, chan, offset, value); +} + +static inline +struct BCState *Sel_BCS(struct IsdnCardState *cs, int channel) +{ + if (cs->bcs[0].mode && (cs->bcs[0].channel == channel)) + return(&cs->bcs[0]); + else if (cs->bcs[1].mode && (cs->bcs[1].channel == channel)) + return(&cs->bcs[1]); + else + return(NULL); +} + +void inline +hdlc_sched_event(struct BCState *bcs, int event) +{ + bcs->event |= 1 << event; + queue_task(&bcs->tqueue, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +void +write_ctrl(struct BCState *bcs, int which) { + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "hdlc %c wr%x ctrl %x", + 'A' + bcs->channel, which, bcs->hw.hdlc.ctrl.ctrl); + if (bcs->cs->subtyp == AVM_FRITZ_PCI) { + WriteHDLCPCI(bcs->cs, bcs->channel, HDLC_STATUS, bcs->hw.hdlc.ctrl.ctrl); + } else { + if (which & 4) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS + 2, + bcs->hw.hdlc.ctrl.sr.mode); + if (which & 2) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS + 1, + bcs->hw.hdlc.ctrl.sr.xml); + if (which & 1) + WriteHDLCPnP(bcs->cs, bcs->channel, HDLC_STATUS, + bcs->hw.hdlc.ctrl.sr.cmd); + } +} + +void +modehdlc(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + int hdlc = bcs->channel; + + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "hdlc %c mode %d ichan %d", + 'A' + hdlc, mode, bc); + bcs->mode = mode; + bcs->channel = bc; + bcs->hw.hdlc.ctrl.ctrl = 0; + switch (mode) { + case (L1_MODE_NULL): + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_TRANS; + write_ctrl(bcs, 5); + break; + case (L1_MODE_TRANS): + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_TRANS; + write_ctrl(bcs, 5); + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd = 0; + hdlc_sched_event(bcs, B_XMTBUFREADY); + break; + case (L1_MODE_HDLC): + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS | HDLC_CMD_RRS; + bcs->hw.hdlc.ctrl.sr.mode = HDLC_MODE_ITF_FLG; + write_ctrl(bcs, 5); + bcs->hw.hdlc.ctrl.sr.cmd = HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd = 0; + hdlc_sched_event(bcs, B_XMTBUFREADY); + break; + } +} + +static inline void +hdlc_empty_fifo(struct BCState *bcs, int count) +{ + register u_int *ptr; + u_char *p; + u_char idx = bcs->channel ? AVM_HDLC_2 : AVM_HDLC_1; + int cnt=0; + struct IsdnCardState *cs = bcs->cs; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_empty_fifo %d", count); + if (bcs->hw.hdlc.rcvidx + count > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "hdlc_empty_fifo: incoming packet too large"); + return; + } + ptr = (u_int *) p = bcs->hw.hdlc.rcvbuf + bcs->hw.hdlc.rcvidx; + bcs->hw.hdlc.rcvidx += count; + if (cs->subtyp == AVM_FRITZ_PCI) { + outl(idx, cs->hw.avm.cfg_reg + 4); + while (cnt < count) { + *ptr++ = inl(cs->hw.avm.isac); + cnt += 4; + } + } else { + outb(idx, cs->hw.avm.cfg_reg + 4); + while (cnt < count) { + *p++ = inb(cs->hw.avm.isac); + cnt++; + } + } + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + if (cs->subtyp == AVM_FRITZ_PNP) + p = (u_char *) ptr; + t += sprintf(t, "hdlc_empty_fifo %c cnt %d", + bcs->channel ? 'B' : 'A', count); + QuickHex(t, p, count); + debugl1(cs, bcs->blog); + } +} + +static inline void +hdlc_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count, cnt =0; + int fifo_size = 32; + u_char *p; + u_int *ptr; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_fill_fifo"); + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_XME; + if (bcs->tx_skb->len > fifo_size) { + count = fifo_size; + } else { + count = bcs->tx_skb->len; + if (bcs->mode != L1_MODE_TRANS) + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_XME; + } + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "hdlc_fill_fifo %d/%ld", count, bcs->tx_skb->len); + ptr = (u_int *) p = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.hdlc.count += count; + bcs->hw.hdlc.ctrl.sr.xml = ((count == fifo_size) ? 0 : count); + write_ctrl(bcs, 3); /* sets the correct index too */ + if (cs->subtyp == AVM_FRITZ_PCI) { + while (cnt<count) { + outl(*ptr++, cs->hw.avm.isac); + cnt += 4; + } + } else { + while (cnt<count) { + outb(*p++, cs->hw.avm.isac); + cnt++; + } + } + if (cs->debug & L1_DEB_HSCX_FIFO) { + char *t = bcs->blog; + + if (cs->subtyp == AVM_FRITZ_PNP) + p = (u_char *) ptr; + t += sprintf(t, "hdlc_fill_fifo %c cnt %d", + bcs->channel ? 'B' : 'A', count); + QuickHex(t, p, count); + debugl1(cs, bcs->blog); + } +} + +static void +fill_hdlc(struct BCState *bcs) +{ + long flags; + save_flags(flags); + cli(); + hdlc_fill_fifo(bcs); + restore_flags(flags); +} + +static inline void +HDLC_irq(struct BCState *bcs, u_int stat) { + int len; + struct sk_buff *skb; + + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "ch%d stat %#x", bcs->channel, stat); + if (stat & HDLC_INT_RPR) { + if (stat & HDLC_STAT_RDO) { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "RDO"); + else + debugl1(bcs->cs, "ch%d stat %#x", bcs->channel, stat); + bcs->hw.hdlc.ctrl.sr.xml = 0; + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_RRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_RRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.rcvidx = 0; + } else { + if (!(len = (stat & HDLC_STAT_RML_MASK)>>8)) + len = 32; + hdlc_empty_fifo(bcs, len); + if ((stat & HDLC_STAT_RME) || (bcs->mode == L1_MODE_TRANS)) { + if (((stat & HDLC_STAT_CRCVFRRAB)==HDLC_STAT_CRCVFR) || + (bcs->mode == L1_MODE_TRANS)) { + if (!(skb = dev_alloc_skb(bcs->hw.hdlc.rcvidx))) + printk(KERN_WARNING "HDLC: receive out of memory\n"); + else { + memcpy(skb_put(skb, bcs->hw.hdlc.rcvidx), + bcs->hw.hdlc.rcvbuf, bcs->hw.hdlc.rcvidx); + skb_queue_tail(&bcs->rqueue, skb); + } + bcs->hw.hdlc.rcvidx = 0; + hdlc_sched_event(bcs, B_RCVBUFREADY); + } else { + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "invalid frame"); + else + debugl1(bcs->cs, "ch%d invalid frame %#x", bcs->channel, stat); + bcs->hw.hdlc.rcvidx = 0; + } + } + } + } + if (stat & HDLC_INT_XDU) { + /* Here we lost an TX interrupt, so + * restart transmitting the whole frame. + */ + if (bcs->tx_skb) { + skb_push(bcs->tx_skb, bcs->hw.hdlc.count); + bcs->tx_cnt += bcs->hw.hdlc.count; + bcs->hw.hdlc.count = 0; +// hdlc_sched_event(bcs, B_XMTBUFREADY); + if (bcs->cs->debug & L1_DEB_WARN) + debugl1(bcs->cs, "ch%d XDU", bcs->channel); + } else if (bcs->cs->debug & L1_DEB_WARN) + debugl1(bcs->cs, "ch%d XDU without skb", bcs->channel); + bcs->hw.hdlc.ctrl.sr.xml = 0; + bcs->hw.hdlc.ctrl.sr.cmd |= HDLC_CMD_XRS; + write_ctrl(bcs, 1); + bcs->hw.hdlc.ctrl.sr.cmd &= ~HDLC_CMD_XRS; + write_ctrl(bcs, 1); + hdlc_fill_fifo(bcs); + } else if (stat & HDLC_INT_XPR) { + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + hdlc_fill_fifo(bcs); + return; + } else { + if (bcs->st->lli.l1writewakeup && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) + bcs->st->lli.l1writewakeup(bcs->st, bcs->hw.hdlc.count); + dev_kfree_skb(bcs->tx_skb); + bcs->hw.hdlc.count = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hdlc.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + hdlc_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + hdlc_sched_event(bcs, B_XMTBUFREADY); + } + } +} + +inline void +HDLC_irq_main(struct IsdnCardState *cs) +{ + u_int stat; + long flags; + struct BCState *bcs; + + save_flags(flags); + cli(); + if (cs->subtyp == AVM_FRITZ_PCI) { + stat = ReadHDLCPCI(cs, 0, HDLC_STATUS); + } else { + stat = ReadHDLCPnP(cs, 0, HDLC_STATUS); + if (stat & HDLC_INT_RPR) + stat |= (ReadHDLCPnP(cs, 0, HDLC_STATUS+1))<<8; + } + if (stat & HDLC_INT_MASK) { + if (!(bcs = Sel_BCS(cs, 0))) { + if (cs->debug) + debugl1(cs, "hdlc spurious channel 0 IRQ"); + } else + HDLC_irq(bcs, stat); + } + if (cs->subtyp == AVM_FRITZ_PCI) { + stat = ReadHDLCPCI(cs, 1, HDLC_STATUS); + } else { + stat = ReadHDLCPnP(cs, 1, HDLC_STATUS); + if (stat & HDLC_INT_RPR) + stat |= (ReadHDLCPnP(cs, 1, HDLC_STATUS+1))<<8; + } + if (stat & HDLC_INT_MASK) { + if (!(bcs = Sel_BCS(cs, 1))) { + if (cs->debug) + debugl1(cs, "hdlc spurious channel 1 IRQ"); + } else + HDLC_irq(bcs, stat); + } + restore_flags(flags); +} + +void +hdlc_l2l1(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + save_flags(flags); + cli(); + if (st->l1.bcs->tx_skb) { + skb_queue_tail(&st->l1.bcs->squeue, skb); + restore_flags(flags); + } else { + st->l1.bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &st->l1.bcs->Flag); + st->l1.bcs->hw.hdlc.count = 0; + restore_flags(flags); + st->l1.bcs->cs->BC_Send_Data(st->l1.bcs); + } + break; + case (PH_PULL | INDICATION): + if (st->l1.bcs->tx_skb) { + printk(KERN_WARNING "hdlc_l2l1: this shouldn't happen\n"); + break; + } + test_and_set_bit(BC_FLG_BUSY, &st->l1.bcs->Flag); + st->l1.bcs->tx_skb = skb; + st->l1.bcs->hw.hdlc.count = 0; + st->l1.bcs->cs->BC_Send_Data(st->l1.bcs); + break; + case (PH_PULL | REQUEST): + if (!st->l1.bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + test_and_set_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag); + modehdlc(st->l1.bcs, st->l1.mode, st->l1.bc); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + test_and_clear_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &st->l1.bcs->Flag); + modehdlc(st->l1.bcs, 0, st->l1.bc); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +void +close_hdlcstate(struct BCState *bcs) +{ + modehdlc(bcs, 0, 0); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hdlc.rcvbuf) { + kfree(bcs->hw.hdlc.rcvbuf); + bcs->hw.hdlc.rcvbuf = NULL; + } + if (bcs->blog) { + kfree(bcs->blog); + bcs->blog = NULL; + } + discard_queue(&bcs->rqueue); + discard_queue(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +int +open_hdlcstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.hdlc.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for hdlc.rcvbuf\n"); + return (1); + } + if (!(bcs->blog = kmalloc(MAX_BLOG_SPACE, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for bcs->blog\n"); + test_and_clear_bit(BC_FLG_INIT, &bcs->Flag); + kfree(bcs->hw.hdlc.rcvbuf); + bcs->hw.hdlc.rcvbuf = NULL; + return (2); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hdlc.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_hdlc(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_hdlcstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = hdlc_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +HISAX_INITFUNC(void +clear_pending_hdlc_ints(struct IsdnCardState *cs)) +{ + u_int val; + + if (cs->subtyp == AVM_FRITZ_PCI) { + val = ReadHDLCPCI(cs, 0, HDLC_STATUS); + debugl1(cs, "HDLC 1 STA %x", val); + val = ReadHDLCPCI(cs, 1, HDLC_STATUS); + debugl1(cs, "HDLC 2 STA %x", val); + } else { + val = ReadHDLCPnP(cs, 0, HDLC_STATUS); + debugl1(cs, "HDLC 1 STA %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 1); + debugl1(cs, "HDLC 1 RML %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 2); + debugl1(cs, "HDLC 1 MODE %x", val); + val = ReadHDLCPnP(cs, 0, HDLC_STATUS + 3); + debugl1(cs, "HDLC 1 VIN %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS); + debugl1(cs, "HDLC 2 STA %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 1); + debugl1(cs, "HDLC 2 RML %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 2); + debugl1(cs, "HDLC 2 MODE %x", val); + val = ReadHDLCPnP(cs, 1, HDLC_STATUS + 3); + debugl1(cs, "HDLC 2 VIN %x", val); + } +} + +HISAX_INITFUNC(void +inithdlc(struct IsdnCardState *cs)) +{ + cs->bcs[0].BC_SetStack = setstack_hdlc; + cs->bcs[1].BC_SetStack = setstack_hdlc; + cs->bcs[0].BC_Close = close_hdlcstate; + cs->bcs[1].BC_Close = close_hdlcstate; + modehdlc(cs->bcs, 0, 0); + modehdlc(cs->bcs + 1, 0, 0); +} + +static void +avm_pcipnp_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ + struct IsdnCardState *cs = dev_id; + u_char val, stat = 0; + u_char sval; + + if (!cs) { + printk(KERN_WARNING "AVM PCI: Spurious interrupt!\n"); + return; + } + sval = inb(cs->hw.avm.cfg_reg + 2); + if ((sval & AVM_STATUS0_IRQ_MASK) == AVM_STATUS0_IRQ_MASK) + /* possible a shared IRQ reqest */ + return; + if (!(sval & AVM_STATUS0_IRQ_ISAC)) { + val = ReadISAC(cs, ISAC_ISTA); + isac_interrupt(cs, val); + stat |= 2; + } + if (!(sval & AVM_STATUS0_IRQ_HDLC)) { + HDLC_irq_main(cs); + } + if (stat & 2) { + WriteISAC(cs, ISAC_MASK, 0xFF); + WriteISAC(cs, ISAC_MASK, 0x0); + } +} + +static void +reset_avmpcipnp(struct IsdnCardState *cs) +{ + long flags; + + printk(KERN_INFO "AVM PCI/PnP: reset\n"); + save_flags(flags); + sti(); + outb(AVM_STATUS0_RESET | AVM_STATUS0_DIS_TIMER, cs->hw.avm.cfg_reg + 2); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout((10*HZ)/1000); /* Timeout 10ms */ + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | AVM_STATUS0_ENA_IRQ, cs->hw.avm.cfg_reg + 2); + outb(AVM_STATUS1_ENA_IOM | cs->irq, cs->hw.avm.cfg_reg + 3); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout((10*HZ)/1000); /* Timeout 10ms */ + printk(KERN_INFO "AVM PCI/PnP: S1 %x\n", inb(cs->hw.avm.cfg_reg + 3)); +} + +static int +AVM_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + u_int irq_flag; + + switch (mt) { + case CARD_RESET: + reset_avmpcipnp(cs); + return(0); + case CARD_RELEASE: + outb(0, cs->hw.avm.cfg_reg + 2); + release_region(cs->hw.avm.cfg_reg, 32); + return(0); + case CARD_SETIRQ: + if (cs->subtyp == AVM_FRITZ_PCI) + irq_flag = I4L_IRQ_FLAG | SA_SHIRQ; + else + irq_flag = I4L_IRQ_FLAG; + return(request_irq(cs->irq, &avm_pcipnp_interrupt, + irq_flag, "HiSax", cs)); + case CARD_INIT: + clear_pending_isac_ints(cs); + initisac(cs); + clear_pending_hdlc_ints(cs); + inithdlc(cs); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER, + cs->hw.avm.cfg_reg + 2); + WriteISAC(cs, ISAC_MASK, 0); + outb(AVM_STATUS0_DIS_TIMER | AVM_STATUS0_RES_TIMER | + AVM_STATUS0_ENA_IRQ, cs->hw.avm.cfg_reg + 2); + /* RESET Receiver and Transmitter */ + WriteISAC(cs, ISAC_CMDR, 0x41); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static struct pci_dev *dev_avm __initdata = NULL; + +__initfunc(int +setup_avm_pcipnp(struct IsdnCard *card)) +{ + u_int val, ver; + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, avm_pci_rev); + printk(KERN_INFO "HiSax: AVM PCI driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_FRITZPCI) + return (0); + if (card->para[1]) { + cs->hw.avm.cfg_reg = card->para[1]; + cs->irq = card->para[0]; + cs->subtyp = AVM_FRITZ_PNP; + } else { +#if CONFIG_PCI + if (!pci_present()) { + printk(KERN_ERR "FritzPCI: no PCI bus present\n"); + return(0); + } + if ((dev_avm = pci_find_device(PCI_VENDOR_AVM, + PCI_FRITZPCI_ID, dev_avm))) { + cs->irq = dev_avm->irq; + if (!cs->irq) { + printk(KERN_WARNING "FritzPCI: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.avm.cfg_reg = dev_avm->base_address[1] & + PCI_BASE_ADDRESS_IO_MASK; + if (!cs->hw.avm.cfg_reg) { + printk(KERN_WARNING "FritzPCI: No IO-Adr for PCI card found\n"); + return(0); + } + cs->subtyp = AVM_FRITZ_PCI; + } else { + printk(KERN_WARNING "FritzPCI: No PCI card found\n"); + return(0); + } +#else + printk(KERN_WARNING "FritzPCI: NO_PCI_BIOS\n"); + return (0); +#endif /* CONFIG_PCI */ + } + cs->hw.avm.isac = cs->hw.avm.cfg_reg + 0x10; + if (check_region((cs->hw.avm.cfg_reg), 32)) { + printk(KERN_WARNING + "HiSax: %s config port %x-%x already in use\n", + CardType[card->typ], + cs->hw.avm.cfg_reg, + cs->hw.avm.cfg_reg + 31); + return (0); + } else { + request_region(cs->hw.avm.cfg_reg, 32, + (cs->subtyp == AVM_FRITZ_PCI) ? "avm PCI" : "avm PnP"); + } + switch (cs->subtyp) { + case AVM_FRITZ_PCI: + val = inl(cs->hw.avm.cfg_reg); + printk(KERN_INFO "AVM PCI: stat %#x\n", val); + printk(KERN_INFO "AVM PCI: Class %X Rev %d\n", + val & 0xff, (val>>8) & 0xff); + cs->BC_Read_Reg = &ReadHDLC_s; + cs->BC_Write_Reg = &WriteHDLC_s; + break; + case AVM_FRITZ_PNP: + val = inb(cs->hw.avm.cfg_reg); + ver = inb(cs->hw.avm.cfg_reg + 1); + printk(KERN_INFO "AVM PnP: Class %X Rev %d\n", val, ver); + reset_avmpcipnp(cs); + cs->BC_Read_Reg = &ReadHDLCPnP; + cs->BC_Write_Reg = &WriteHDLCPnP; + break; + default: + printk(KERN_WARNING "AVM unknown subtype %d\n", cs->subtyp); + outb(0, cs->hw.avm.cfg_reg + 2); + release_region(cs->hw.avm.cfg_reg, 32); + return(0); + } + printk(KERN_INFO "HiSax: %s config irq:%d base:0x%X\n", + (cs->subtyp == AVM_FRITZ_PCI) ? "AVM Fritz!PCI" : "AVM Fritz!PnP", + cs->irq, cs->hw.avm.cfg_reg); + + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Send_Data = &fill_hdlc; + cs->cardmsg = &AVM_card_msg; + ISACVersion(cs, (cs->subtyp == AVM_FRITZ_PCI) ? "AVM PCI:" : "AVM PnP:"); + return (1); +} diff --git a/drivers/isdn/hisax/cert.c b/drivers/isdn/hisax/cert.c new file mode 100644 index 000000000..a76736b60 --- /dev/null +++ b/drivers/isdn/hisax/cert.c @@ -0,0 +1,55 @@ +/* $Id: cert.c,v 2.1 1998/11/15 23:51:15 keil Exp $ + + * Author Karsten Keil (keil@isdn4linux.de) + * + * This file is (c) under GNU PUBLIC LICENSE + * For changes and modifications please read + * ../../../Documentation/isdn/HiSax.cert + * + * $Log: cert.c,v $ + * Revision 2.1 1998/11/15 23:51:15 keil + * certification stuff + * + * Revision 1.2.2.1 1998/11/03 21:46:37 keil + * first version + * + * + */ + +#include <linux/kernel.h> + +int +certification_check(int output) { + +#ifdef CERTIFICATION +#if CERTIFICATION == 0 + if (output) { + printk(KERN_INFO "HiSax: Approval certification valid\n"); + printk(KERN_INFO "HiSax: Approved with ELSA Quickstep series cards\n"); + printk(KERN_INFO "HiSax: Approval registration numbers:\n"); + printk(KERN_INFO "HiSax: German D133361J CETECOM ICT Services GmbH\n"); + printk(KERN_INFO "HiSax: EU (D133362J) CETECOM ICT Services GmbH\n"); + } + return(0); +#endif +#if CERTIFICATION == 1 + if (output) { + printk(KERN_INFO "HiSax: Approval certification failed because of\n"); + printk(KERN_INFO "HiSax: unauthorized source code changes\n"); + } + return(1); +#endif +#if CERTIFICATION == 127 + if (output) { + printk(KERN_INFO "HiSax: Approval certification not possible\n"); + printk(KERN_INFO "HiSax: because \"md5sum\" is not available\n"); + } + return(2); +#endif +#else + if (output) { + printk(KERN_INFO "HiSax: Certification not verified\n"); + } + return(3); +#endif +} diff --git a/drivers/isdn/hisax/elsa_ser.c b/drivers/isdn/hisax/elsa_ser.c new file mode 100644 index 000000000..f5ec29839 --- /dev/null +++ b/drivers/isdn/hisax/elsa_ser.c @@ -0,0 +1,749 @@ +#include <linux/config.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> + +#define MAX_MODEM_BUF 256 +#define WAKEUP_CHARS (MAX_MODEM_BUF/2) +#define RS_ISR_PASS_LIMIT 256 +#define BASE_BAUD ( 1843200 / 16 ) + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +//#define SERIAL_DEBUG_OPEN 1 +//#define SERIAL_DEBUG_INTR 1 +//#define SERIAL_DEBUG_FLOW 1 +#undef SERIAL_DEBUG_OPEN +#undef SERIAL_DEBUG_INTR +#undef SERIAL_DEBUG_FLOW +#undef SERIAL_DEBUG_REG +//#define SERIAL_DEBUG_REG 1 + +#ifdef SERIAL_DEBUG_REG +static u_char deb[32]; +const char *ModemIn[] = {"RBR","IER","IIR","LCR","MCR","LSR","MSR","SCR"}; +const char *ModemOut[] = {"THR","IER","FCR","LCR","MCR","LSR","MSR","SCR"}; +#endif + +static char *MInit_1 = "AT&F&C1E0&D2\r\0"; +static char *MInit_2 = "ATL2M1S64=13\r\0"; +static char *MInit_3 = "AT+FCLASS=0\r\0"; +static char *MInit_4 = "ATV1S2=128X1\r\0"; +static char *MInit_5 = "AT\\V8\\N3\r\0"; +static char *MInit_6 = "ATL0M0&G0%E1\r\0"; +static char *MInit_7 = "AT%L1%M0%C3\r\0"; + +static char *MInit_speed28800 = "AT%G0%B28800\r\0"; + +static char *MInit_dialout = "ATs7=60 x1 d\r\0"; +static char *MInit_dialin = "ATs7=60 x1 a\r\0"; + + +static inline unsigned int serial_in(struct IsdnCardState *cs, int offset) +{ +#ifdef SERIAL_DEBUG_REG + u_int val = inb(cs->hw.elsa.base + 8 + offset); + debugl1(cs,"in %s %02x",ModemIn[offset], val); + return(val); +#else + return inb(cs->hw.elsa.base + 8 + offset); +#endif +} + +static inline unsigned int serial_inp(struct IsdnCardState *cs, int offset) +{ +#ifdef SERIAL_DEBUG_REG +#ifdef CONFIG_SERIAL_NOPAUSE_IO + u_int val = inb(cs->hw.elsa.base + 8 + offset); + debugl1(cs,"inp %s %02x",ModemIn[offset], val); +#else + u_int val = inb_p(cs->hw.elsa.base + 8 + offset); + debugl1(cs,"inP %s %02x",ModemIn[offset], val); +#endif + return(val); +#else +#ifdef CONFIG_SERIAL_NOPAUSE_IO + return inb(cs->hw.elsa.base + 8 + offset); +#else + return inb_p(cs->hw.elsa.base + 8 + offset); +#endif +#endif +} + +static inline void serial_out(struct IsdnCardState *cs, int offset, int value) +{ +#ifdef SERIAL_DEBUG_REG + debugl1(cs,"out %s %02x",ModemOut[offset], value); +#endif + outb(value, cs->hw.elsa.base + 8 + offset); +} + +static inline void serial_outp(struct IsdnCardState *cs, int offset, + int value) +{ +#ifdef SERIAL_DEBUG_REG +#ifdef CONFIG_SERIAL_NOPAUSE_IO + debugl1(cs,"outp %s %02x",ModemOut[offset], value); +#else + debugl1(cs,"outP %s %02x",ModemOut[offset], value); +#endif +#endif +#ifdef CONFIG_SERIAL_NOPAUSE_IO + outb(value, cs->hw.elsa.base + 8 + offset); +#else + outb_p(value, cs->hw.elsa.base + 8 + offset); +#endif +} + +/* + * This routine is called to set the UART divisor registers to match + * the specified baud rate for a serial port. + */ +static void change_speed(struct IsdnCardState *cs, int baud) +{ + int quot = 0, baud_base; + unsigned cval, fcr = 0; + int bits; + unsigned long flags; + + + /* byte size and parity */ + cval = 0x03; bits = 10; + /* Determine divisor based on baud rate */ + baud_base = BASE_BAUD; + quot = baud_base / baud; + /* If the quotient is ever zero, default to 9600 bps */ + if (!quot) + quot = baud_base / 9600; + + /* Set up FIFO's */ + if ((baud_base / quot) < 2400) + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + else + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_8; + serial_outp(cs, UART_FCR, fcr); + /* CTS flow control flag and modem status interrupts */ + cs->hw.elsa.IER &= ~UART_IER_MSI; + cs->hw.elsa.IER |= UART_IER_MSI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + + debugl1(cs,"modem quot=0x%x", quot); + save_flags(flags); + cli(); + serial_outp(cs, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ + serial_outp(cs, UART_DLL, quot & 0xff); /* LS of divisor */ + serial_outp(cs, UART_DLM, quot >> 8); /* MS of divisor */ + serial_outp(cs, UART_LCR, cval); /* reset DLAB */ + serial_inp(cs, UART_RX); + restore_flags(flags); +} + +static int mstartup(struct IsdnCardState *cs) +{ + unsigned long flags; + int retval=0; + + + save_flags(flags); cli(); + + /* + * Clear the FIFO buffers and disable them + * (they will be reenabled in change_speed()) + */ + serial_outp(cs, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); + + /* + * At this point there's no way the LSR could still be 0xFF; + * if it is, then bail out, because there's likely no UART + * here. + */ + if (serial_inp(cs, UART_LSR) == 0xff) { + retval = -ENODEV; + goto errout; + } + + /* + * Clear the interrupt registers. + */ + (void) serial_inp(cs, UART_RX); + (void) serial_inp(cs, UART_IIR); + (void) serial_inp(cs, UART_MSR); + + /* + * Now, initialize the UART + */ + serial_outp(cs, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */ + + cs->hw.elsa.MCR = 0; + cs->hw.elsa.MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2; + serial_outp(cs, UART_MCR, cs->hw.elsa.MCR); + + /* + * Finally, enable interrupts + */ + cs->hw.elsa.IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); /* enable interrupts */ + + /* + * And clear the interrupt registers again for luck. + */ + (void)serial_inp(cs, UART_LSR); + (void)serial_inp(cs, UART_RX); + (void)serial_inp(cs, UART_IIR); + (void)serial_inp(cs, UART_MSR); + + cs->hw.elsa.transcnt = cs->hw.elsa.transp = 0; + cs->hw.elsa.rcvcnt = cs->hw.elsa.rcvp =0; + + /* + * and set the speed of the serial port + */ + change_speed(cs, BASE_BAUD); + cs->hw.elsa.MFlag = 1; +errout: + restore_flags(flags); + return retval; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void mshutdown(struct IsdnCardState *cs) +{ + unsigned long flags; + + +#ifdef SERIAL_DEBUG_OPEN + printk(KERN_DEBUG"Shutting down serial ...."); +#endif + + save_flags(flags); cli(); /* Disable interrupts */ + + /* + * clear delta_msr_wait queue to avoid mem leaks: we may free the irq + * here so the queue might never be waken up + */ + + cs->hw.elsa.IER = 0; + serial_outp(cs, UART_IER, 0x00); /* disable all intrs */ + cs->hw.elsa.MCR &= ~UART_MCR_OUT2; + + /* disable break condition */ + serial_outp(cs, UART_LCR, serial_inp(cs, UART_LCR) & ~UART_LCR_SBC); + + cs->hw.elsa.MCR &= ~(UART_MCR_DTR|UART_MCR_RTS); + serial_outp(cs, UART_MCR, cs->hw.elsa.MCR); + + /* disable FIFO's */ + serial_outp(cs, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); + serial_inp(cs, UART_RX); /* read data port to reset things */ + + restore_flags(flags); +#ifdef SERIAL_DEBUG_OPEN + printk(" done\n"); +#endif +} + +inline int +write_modem(struct BCState *bcs) { + int ret=0; + struct IsdnCardState *cs = bcs->cs; + int count, len, fp, buflen; + long flags; + + if (!bcs->tx_skb) + return 0; + if (bcs->tx_skb->len <= 0) + return 0; + save_flags(flags); + cli(); + buflen = MAX_MODEM_BUF - cs->hw.elsa.transcnt; + len = MIN(buflen, bcs->tx_skb->len); + fp = cs->hw.elsa.transcnt + cs->hw.elsa.transp; + fp &= (MAX_MODEM_BUF -1); + count = MIN(len, MAX_MODEM_BUF - fp); + if (count < len) { + memcpy(cs->hw.elsa.transbuf + fp, bcs->tx_skb->data, count); + skb_pull(bcs->tx_skb, count); + cs->hw.elsa.transcnt += count; + ret = count; + count = len - count; + fp = 0; + } + memcpy((cs->hw.elsa.transbuf + fp), bcs->tx_skb->data, count); + skb_pull(bcs->tx_skb, count); + cs->hw.elsa.transcnt += count; + ret += count; + + if (cs->hw.elsa.transcnt && + !(cs->hw.elsa.IER & UART_IER_THRI)) { + cs->hw.elsa.IER |= UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } + restore_flags(flags); + return(ret); +} + +inline void +modem_fill(struct BCState *bcs) { + + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + write_modem(bcs); + return; + } else { + if (bcs->st->lli.l1writewakeup && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) + bcs->st->lli.l1writewakeup(bcs->st, + bcs->hw.hscx.count); + dev_kfree_skb(bcs->tx_skb); + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.hscx.count = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + write_modem(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + hscx_sched_event(bcs, B_XMTBUFREADY); + } +} + +static inline void receive_chars(struct IsdnCardState *cs, + int *status) +{ + unsigned char ch; + struct sk_buff *skb; + + do { + ch = serial_in(cs, UART_RX); + if (cs->hw.elsa.rcvcnt >= MAX_MODEM_BUF) + break; + cs->hw.elsa.rcvbuf[cs->hw.elsa.rcvcnt++] = ch; +#ifdef SERIAL_DEBUG_INTR + printk("DR%02x:%02x...", ch, *status); +#endif + if (*status & (UART_LSR_BI | UART_LSR_PE | + UART_LSR_FE | UART_LSR_OE)) { + +#ifdef SERIAL_DEBUG_INTR + printk("handling exept...."); +#endif + } + *status = serial_inp(cs, UART_LSR); + } while (*status & UART_LSR_DR); + if (cs->hw.elsa.MFlag == 2) { + if (!(skb = dev_alloc_skb(cs->hw.elsa.rcvcnt))) + printk(KERN_WARNING "ElsaSER: receive out of memory\n"); + else { + memcpy(skb_put(skb, cs->hw.elsa.rcvcnt), cs->hw.elsa.rcvbuf, + cs->hw.elsa.rcvcnt); + skb_queue_tail(& cs->hw.elsa.bcs->rqueue, skb); + } + hscx_sched_event(cs->hw.elsa.bcs, B_RCVBUFREADY); + } else { + char tmp[128]; + char *t = tmp; + + t += sprintf(t, "modem read cnt %d", cs->hw.elsa.rcvcnt); + QuickHex(t, cs->hw.elsa.rcvbuf, cs->hw.elsa.rcvcnt); + debugl1(cs, tmp); + } + cs->hw.elsa.rcvcnt = 0; +} + +static inline void transmit_chars(struct IsdnCardState *cs, int *intr_done) +{ + int count; + + debugl1(cs, "transmit_chars: p(%x) cnt(%x)", cs->hw.elsa.transp, + cs->hw.elsa.transcnt); + + if (cs->hw.elsa.transcnt <= 0) { + cs->hw.elsa.IER &= ~UART_IER_THRI; + serial_out(cs, UART_IER, cs->hw.elsa.IER); + return; + } + count = 16; + do { + serial_outp(cs, UART_TX, cs->hw.elsa.transbuf[cs->hw.elsa.transp++]); + if (cs->hw.elsa.transp >= MAX_MODEM_BUF) + cs->hw.elsa.transp=0; + if (--cs->hw.elsa.transcnt <= 0) + break; + } while (--count > 0); + if ((cs->hw.elsa.transcnt < WAKEUP_CHARS) && (cs->hw.elsa.MFlag==2)) + modem_fill(cs->hw.elsa.bcs); + +#ifdef SERIAL_DEBUG_INTR + printk("THRE..."); +#endif + if (intr_done) + *intr_done = 0; + if (cs->hw.elsa.transcnt <= 0) { + cs->hw.elsa.IER &= ~UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } +} + +#if 0 +static inline void check_modem_status(struct IsdnCardState *cs) +{ + int status; + struct async_struct *info = cs->hw.elsa.info; + struct async_icount *icount; + + status = serial_inp(info, UART_MSR); + + if (status & UART_MSR_ANY_DELTA) { + icount = &info->state->icount; + /* update input line counters */ + if (status & UART_MSR_TERI) + icount->rng++; + if (status & UART_MSR_DDSR) + icount->dsr++; + if (status & UART_MSR_DDCD) { + icount->dcd++; + } + if (status & UART_MSR_DCTS) + icount->cts++; +// wake_up_interruptible(&info->delta_msr_wait); + } + + if ((info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD)) { +#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR)) + printk("ttys%d CD now %s...", info->line, + (status & UART_MSR_DCD) ? "on" : "off"); +#endif + if (status & UART_MSR_DCD) +// wake_up_interruptible(&info->open_wait); +; + else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) && + (info->flags & ASYNC_CALLOUT_NOHUP))) { +#ifdef SERIAL_DEBUG_OPEN + printk("doing serial hangup..."); +#endif + if (info->tty) + tty_hangup(info->tty); + } + } +#if 0 + if (info->flags & ASYNC_CTS_FLOW) { + if (info->tty->hw_stopped) { + if (status & UART_MSR_CTS) { +#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) + printk("CTS tx start..."); +#endif + info->tty->hw_stopped = 0; + info->IER |= UART_IER_THRI; + serial_outp(info, UART_IER, info->IER); +// rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); + return; + } + } else { + if (!(status & UART_MSR_CTS)) { +#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) + printk("CTS tx stop..."); +#endif + info->tty->hw_stopped = 1; + info->IER &= ~UART_IER_THRI; + serial_outp(info, UART_IER, info->IER); + } + } + } +#endif 0 +} +#endif + +static void rs_interrupt_elsa(int irq, struct IsdnCardState *cs) +{ + int status, iir, msr; + int pass_counter = 0; + +#ifdef SERIAL_DEBUG_INTR + printk("rs_interrupt_single(%d)...", irq); +#endif + + do { + status = serial_inp(cs, UART_LSR); + debugl1(cs,"rs LSR %02x", status); +#ifdef SERIAL_DEBUG_INTR + printk("status = %x...", status); +#endif + if (status & UART_LSR_DR) + receive_chars(cs, &status); + if (status & UART_LSR_THRE) + transmit_chars(cs, 0); + if (pass_counter++ > RS_ISR_PASS_LIMIT) { + printk("rs_single loop break.\n"); + break; + } + iir = serial_inp(cs, UART_IIR); + debugl1(cs,"rs IIR %02x", iir); + if ((iir & 0xf) == 0) { + msr = serial_inp(cs, UART_MSR); + debugl1(cs,"rs MSR %02x", msr); + } + } while (!(iir & UART_IIR_NO_INT)); +#ifdef SERIAL_DEBUG_INTR + printk("end.\n"); +#endif +} + +extern int open_hscxstate(struct IsdnCardState *cs, struct BCState *bcs); +extern void modehscx(struct BCState *bcs, int mode, int bc); +extern void hscx_l2l1(struct PStack *st, int pr, void *arg); + +void +close_elsastate(struct BCState *bcs) +{ + struct sk_buff *skb; + + modehscx(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.hscx.rcvbuf) { + if (bcs->mode != L1_MODE_MODEM) + kfree(bcs->hw.hscx.rcvbuf); + bcs->hw.hscx.rcvbuf = NULL; + } + while ((skb = skb_dequeue(&bcs->rqueue))) { + dev_kfree_skb(skb); + } + while ((skb = skb_dequeue(&bcs->squeue))) { + dev_kfree_skb(skb); + } + if (bcs->tx_skb) { + dev_kfree_skb(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + } + } +} + +void +modem_write_cmd(struct IsdnCardState *cs, u_char *buf, int len) { + int count, fp; + u_char *msg = buf; + long flags; + + if (!len) + return; + save_flags(flags); + cli(); + if (len > (MAX_MODEM_BUF - cs->hw.elsa.transcnt)) { + restore_flags(flags); + return; + } + fp = cs->hw.elsa.transcnt + cs->hw.elsa.transp; + fp &= (MAX_MODEM_BUF -1); + count = MIN(len, MAX_MODEM_BUF - fp); + if (count < len) { + memcpy(cs->hw.elsa.transbuf + fp, msg, count); + cs->hw.elsa.transcnt += count; + msg += count; + count = len - count; + fp = 0; + } + memcpy(cs->hw.elsa.transbuf + fp, msg, count); + cs->hw.elsa.transcnt += count; + if (cs->hw.elsa.transcnt && + !(cs->hw.elsa.IER & UART_IER_THRI)) { + cs->hw.elsa.IER |= UART_IER_THRI; + serial_outp(cs, UART_IER, cs->hw.elsa.IER); + } + restore_flags(flags); +} + +void +modem_set_init(struct IsdnCardState *cs) { + long flags; + int timeout; + +#define RCV_DELAY 20000 + save_flags(flags); + sti(); + modem_write_cmd(cs, MInit_1, strlen(MInit_1)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_2, strlen(MInit_2)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_3, strlen(MInit_3)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_4, strlen(MInit_4)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY ); + modem_write_cmd(cs, MInit_5, strlen(MInit_5)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_6, strlen(MInit_6)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + modem_write_cmd(cs, MInit_7, strlen(MInit_7)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + restore_flags(flags); +} + +void +modem_set_dial(struct IsdnCardState *cs, int outgoing) { + long flags; + int timeout; +#define RCV_DELAY 20000 + + save_flags(flags); + sti(); + modem_write_cmd(cs, MInit_speed28800, strlen(MInit_speed28800)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + if (outgoing) + modem_write_cmd(cs, MInit_dialout, strlen(MInit_dialout)); + else + modem_write_cmd(cs, MInit_dialin, strlen(MInit_dialin)); + timeout = 1000; + while(timeout-- && cs->hw.elsa.transcnt) + udelay(1000); + debugl1(cs, "msi tout=%d", timeout); + udelay(RCV_DELAY); + restore_flags(flags); +} + +void +modem_l2l1(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + long flags; + + if (pr == (PH_DATA | REQUEST)) { + save_flags(flags); + cli(); + if (st->l1.bcs->tx_skb) { + skb_queue_tail(&st->l1.bcs->squeue, skb); + restore_flags(flags); + } else { + st->l1.bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &st->l1.bcs->Flag); + st->l1.bcs->hw.hscx.count = 0; + restore_flags(flags); + write_modem(st->l1.bcs); + } + } else if (pr == (PH_ACTIVATE | REQUEST)) { + test_and_set_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag); + st->l1.l1l2(st, PH_ACTIVATE | CONFIRM, NULL); + set_arcofi(st->l1.bcs->cs, st->l1.bc); + mstartup(st->l1.bcs->cs); + modem_set_dial(st->l1.bcs->cs, test_bit(FLG_ORIG, &st->l2.flag)); + st->l1.bcs->cs->hw.elsa.MFlag=2; + } else if (pr == (PH_DEACTIVATE | REQUEST)) { + test_and_clear_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag); + send_arcofi(st->l1.bcs->cs, ARCOFI_XOP_0, st->l1.bc, 0); + st->l1.bcs->cs->hw.elsa.MFlag=1; + } else { + printk(KERN_WARNING"ElsaSer: unknown pr %x\n", pr); + } +} + +int +setstack_elsa(struct PStack *st, struct BCState *bcs) +{ + + bcs->channel = st->l1.bc; + switch (st->l1.mode) { + case L1_MODE_HDLC: + case L1_MODE_TRANS: + if (open_hscxstate(st->l1.hardware, bcs)) + return (-1); + st->l2.l2l1 = hscx_l2l1; + break; + case L1_MODE_MODEM: + bcs->mode = L1_MODE_MODEM; + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + bcs->hw.hscx.rcvbuf = bcs->cs->hw.elsa.rcvbuf; + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + bcs->event = 0; + bcs->hw.hscx.rcvidx = 0; + bcs->tx_cnt = 0; + bcs->cs->hw.elsa.bcs = bcs; + st->l2.l2l1 = modem_l2l1; + break; + } + st->l1.bcs = bcs; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +void +init_modem(struct IsdnCardState *cs) { + + cs->bcs[0].BC_SetStack = setstack_elsa; + cs->bcs[1].BC_SetStack = setstack_elsa; + cs->bcs[0].BC_Close = close_elsastate; + cs->bcs[1].BC_Close = close_elsastate; + if (!(cs->hw.elsa.rcvbuf = kmalloc(MAX_MODEM_BUF, + GFP_ATOMIC))) { + printk(KERN_WARNING + "Elsa: No modem mem hw.elsa.rcvbuf\n"); + return; + } + if (!(cs->hw.elsa.transbuf = kmalloc(MAX_MODEM_BUF, + GFP_ATOMIC))) { + printk(KERN_WARNING + "Elsa: No modem mem hw.elsa.transbuf\n"); + kfree(cs->hw.elsa.rcvbuf); + cs->hw.elsa.rcvbuf = NULL; + return; + } + if (mstartup(cs)) { + printk(KERN_WARNING "Elsa: problem startup modem\n"); + } + modem_set_init(cs); +} + +void +release_modem(struct IsdnCardState *cs) { + + cs->hw.elsa.MFlag = 0; + if (cs->hw.elsa.transbuf) { + if (cs->hw.elsa.rcvbuf) { + mshutdown(cs); + kfree(cs->hw.elsa.rcvbuf); + cs->hw.elsa.rcvbuf = NULL; + } + kfree(cs->hw.elsa.transbuf); + cs->hw.elsa.transbuf = NULL; + } +} diff --git a/drivers/isdn/hisax/isar.c b/drivers/isdn/hisax/isar.c new file mode 100644 index 000000000..ba9247a9f --- /dev/null +++ b/drivers/isdn/hisax/isar.c @@ -0,0 +1,937 @@ +/* $Id: isar.c,v 1.2 1998/11/15 23:54:53 keil Exp $ + + * isar.c ISAR (Siemens PSB 7110) specific routines + * + * Author Karsten Keil (keil@isdn4linux.de) + * + * + * $Log: isar.c,v $ + * Revision 1.2 1998/11/15 23:54:53 keil + * changes from 2.0 + * + * Revision 1.1 1998/08/13 23:33:47 keil + * First version, only init + * + * + */ + +#define __NO_VERSION__ +#include "hisax.h" +#include "isar.h" +#include "isdnl1.h" +#include <linux/interrupt.h> + +#define DBG_LOADFIRM 0 +#define DUMP_MBOXFRAME 2 + +#define MIN(a,b) ((a<b)?a:b) + +void isar_setup(struct IsdnCardState *cs); + +static inline int +waitforHIA(struct IsdnCardState *cs, int timeout) +{ + + while ((cs->BC_Read_Reg(cs, 0, ISAR_HIA) & 1) && timeout) { + udelay(1); + timeout--; + } + if (!timeout) + printk(KERN_WARNING "HiSax: ISAR waitforHIA timeout\n"); + return(timeout); +} + + +int +sendmsg(struct IsdnCardState *cs, u_char his, u_char creg, u_char len, + u_char *msg) +{ + long flags; + int i; + + if (!waitforHIA(cs, 4000)) + return(0); +#if DUMP_MBOXFRAME + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "sendmsg(%02x,%02x,%d)", his, creg, len); +#endif + save_flags(flags); + cli(); + cs->BC_Write_Reg(cs, 0, ISAR_CTRL_H, creg); + cs->BC_Write_Reg(cs, 0, ISAR_CTRL_L, len); + cs->BC_Write_Reg(cs, 0, ISAR_WADR, 0); + if (msg && len) { + cs->BC_Write_Reg(cs, 1, ISAR_MBOX, msg[0]); + for (i=1; i<len; i++) + cs->BC_Write_Reg(cs, 2, ISAR_MBOX, msg[i]); +#if DUMP_MBOXFRAME>1 + if (cs->debug & L1_DEB_HSCX_FIFO) { + char tmp[256], *t; + + i = len; + while (i>0) { + t = tmp; + t += sprintf(t, "sendmbox cnt %d", len); + QuickHex(t, &msg[len-i], (i>64) ? 64:i); + debugl1(cs, tmp); + i -= 64; + } + } +#endif + } + cs->BC_Write_Reg(cs, 1, ISAR_HIS, his); + restore_flags(flags); + waitforHIA(cs, 10000); + return(1); +} + +/* Call only with IRQ disabled !!! */ +inline void +rcv_mbox(struct IsdnCardState *cs, struct isar_reg *ireg, u_char *msg) +{ + int i; + + cs->BC_Write_Reg(cs, 1, ISAR_RADR, 0); + if (msg && ireg->clsb) { + msg[0] = cs->BC_Read_Reg(cs, 1, ISAR_MBOX); + for (i=1; i < ireg->clsb; i++) + msg[i] = cs->BC_Read_Reg(cs, 2, ISAR_MBOX); +#if DUMP_MBOXFRAME>1 + if (cs->debug & L1_DEB_HSCX_FIFO) { + char tmp[256], *t; + + i = ireg->clsb; + while (i>0) { + t = tmp; + t += sprintf(t, "rcv_mbox cnt %d", ireg->clsb); + QuickHex(t, &msg[ireg->clsb-i], (i>64) ? 64:i); + debugl1(cs, tmp); + i -= 64; + } + } +#endif + } + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); +} + +/* Call only with IRQ disabled !!! */ +inline void +get_irq_infos(struct IsdnCardState *cs, struct isar_reg *ireg) +{ + ireg->iis = cs->BC_Read_Reg(cs, 1, ISAR_IIS); + ireg->cmsb = cs->BC_Read_Reg(cs, 1, ISAR_CTRL_H); + ireg->clsb = cs->BC_Read_Reg(cs, 1, ISAR_CTRL_L); +#if DUMP_MBOXFRAME + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "rcv_mbox(%02x,%02x,%d)", ireg->iis, ireg->cmsb, + ireg->clsb); +#endif +} + +int +waitrecmsg(struct IsdnCardState *cs, u_char *len, + u_char *msg, int maxdelay) +{ + int timeout = 0; + long flags; + struct isar_reg *ir = cs->bcs[0].hw.isar.reg; + + + while((!(cs->BC_Read_Reg(cs, 0, ISAR_IRQBIT) & ISAR_IRQSTA)) && + (timeout++ < maxdelay)) + udelay(1); + if (timeout >= maxdelay) { + printk(KERN_WARNING"isar recmsg IRQSTA timeout\n"); + return(0); + } + save_flags(flags); + cli(); + get_irq_infos(cs, ir); + rcv_mbox(cs, ir, msg); + *len = ir->clsb; + restore_flags(flags); + return(1); +} + +int +ISARVersion(struct IsdnCardState *cs, char *s) +{ + int ver; + u_char msg[] = ISAR_MSG_HWVER; + u_char tmp[64]; + u_char len; + int debug; + + cs->cardmsg(cs, CARD_RESET, NULL); + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + debug = cs->debug; + cs->debug &= ~(L1_DEB_HSCX | L1_DEB_HSCX_FIFO); + if (!sendmsg(cs, ISAR_HIS_VNR, 0, 3, msg)) + return(-1); + if (!waitrecmsg(cs, &len, tmp, 100000)) + return(-2); + cs->debug = debug; + if (cs->bcs[0].hw.isar.reg->iis == ISAR_IIS_VNR) { + if (len == 1) { + ver = tmp[0] & 0xf; + printk(KERN_INFO "%s ISAR version %d\n", s, ver); + return(ver); + } + return(-3); + } + return(-4); +} + +int +isar_load_firmware(struct IsdnCardState *cs, u_char *buf) +{ + int ret, size, cnt, debug; + u_char len, nom, noc; + u_short sadr, left, *sp; + u_char *p = buf; + u_char *msg, *tmpmsg, *mp, tmp[64]; + long flags; + struct isar_reg *ireg = cs->bcs[0].hw.isar.reg; + + struct {u_short sadr; + u_short len; + u_short d_key; + } blk_head; + +#define BLK_HEAD_SIZE 6 + if (1 != (ret = ISARVersion(cs, "Testing"))) { + printk(KERN_ERR"isar_load_firmware wrong isar version %d\n", ret); + return(1); + } + debug = cs->debug; +#if DBG_LOADFIRM<2 + cs->debug &= ~(L1_DEB_HSCX | L1_DEB_HSCX_FIFO); +#endif + printk(KERN_DEBUG"isar_load_firmware buf %#lx\n", (u_long)buf); + if ((ret = verify_area(VERIFY_READ, (void *) p, sizeof(int)))) { + printk(KERN_ERR"isar_load_firmware verify_area ret %d\n", ret); + return ret; + } + if ((ret = copy_from_user(&size, p, sizeof(int)))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + return ret; + } + p += sizeof(int); + printk(KERN_DEBUG"isar_load_firmware size: %d\n", size); + if ((ret = verify_area(VERIFY_READ, (void *) p, size))) { + printk(KERN_ERR"isar_load_firmware verify_area ret %d\n", ret); + return ret; + } + cnt = 0; + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + if (!(msg = kmalloc(256, GFP_KERNEL))) { + printk(KERN_ERR"isar_load_firmware no buffer\n"); + return (1); + } + if (!(tmpmsg = kmalloc(256, GFP_KERNEL))) { + printk(KERN_ERR"isar_load_firmware no tmp buffer\n"); + kfree(msg); + return (1); + } + while (cnt < size) { + if ((ret = copy_from_user(&blk_head, p, BLK_HEAD_SIZE))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + goto reterror; + } + cnt += BLK_HEAD_SIZE; + p += BLK_HEAD_SIZE; + printk(KERN_DEBUG"isar firmware block (%#x,%5d,%#x)\n", + blk_head.sadr, blk_head.len, blk_head.d_key & 0xff); + sadr = blk_head.sadr; + left = blk_head.len; + if (!sendmsg(cs, ISAR_HIS_DKEY, blk_head.d_key & 0xff, 0, NULL)) { + printk(KERN_ERR"isar sendmsg dkey failed\n"); + ret = 1;goto reterror; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg dkey failed\n"); + ret = 1;goto reterror; + } + if ((ireg->iis != ISAR_IIS_DKEY) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong dkey response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1;goto reterror; + } + while (left>0) { + noc = MIN(126, left); + nom = 2*noc; + mp = msg; + *mp++ = sadr / 256; + *mp++ = sadr % 256; + left -= noc; + *mp++ = noc; + if ((ret = copy_from_user(tmpmsg, p, nom))) { + printk(KERN_ERR"isar_load_firmware copy_from_user ret %d\n", ret); + goto reterror; + } + p += nom; + cnt += nom; + nom += 3; + sp = (u_short *)tmpmsg; +#if DBG_LOADFIRM + printk(KERN_DEBUG"isar: load %3d words at %04x\n", + noc, sadr); +#endif + sadr += noc; + while(noc) { + *mp++ = *sp / 256; + *mp++ = *sp % 256; + sp++; + noc--; + } + if (!sendmsg(cs, ISAR_HIS_FIRM, 0, nom, msg)) { + printk(KERN_ERR"isar sendmsg prog failed\n"); + ret = 1;goto reterror; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg prog failed\n"); + ret = 1;goto reterror; + } + if ((ireg->iis != ISAR_IIS_FIRM) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong prog response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1;goto reterror; + } + } + printk(KERN_DEBUG"isar firmware block %5d words loaded\n", + blk_head.len); + } + msg[0] = 0xff; + msg[1] = 0xfe; + ireg->bstat = 0; + if (!sendmsg(cs, ISAR_HIS_STDSP, 0, 2, msg)) { + printk(KERN_ERR"isar sendmsg start dsp failed\n"); + ret = 1;goto reterror; + } + if (!waitrecmsg(cs, &len, tmp, 100000)) { + printk(KERN_ERR"isar waitrecmsg start dsp failed\n"); + ret = 1;goto reterror; + } + if ((ireg->iis != ISAR_IIS_STDSP) || ireg->cmsb || len) { + printk(KERN_ERR"isar wrong start dsp response (%x,%x,%x)\n", + ireg->iis, ireg->cmsb, len); + ret = 1;goto reterror; + } else + printk(KERN_DEBUG"isar start dsp success\n"); + /* NORMAL mode entered */ + /* Enable IRQs of ISAR */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, ISAR_IRQSTA); + save_flags(flags); + sti(); + cnt = 1000; /* max 1s */ + while ((!ireg->bstat) && cnt) { + udelay(1000); + cnt--; + } + if (!cnt) { + printk(KERN_ERR"isar no general status event received\n"); + ret = 1;goto reterrflg; + } else { + printk(KERN_DEBUG"isar general status event %x\n", + ireg->bstat); + } + ireg->iis = 0; + if (!sendmsg(cs, ISAR_HIS_DIAG, ISAR_CTRL_STST, 0, NULL)) { + printk(KERN_ERR"isar sendmsg self tst failed\n"); + ret = 1;goto reterrflg; + } + cnt = 1000; /* max 10 ms */ + while ((ireg->iis != ISAR_IIS_DIAG) && cnt) { + udelay(10); + cnt--; + } + if (!cnt) { + printk(KERN_ERR"isar no self tst response\n"); + ret = 1;goto reterrflg; + } else if ((ireg->cmsb == ISAR_CTRL_STST) && (ireg->clsb == 1) + && (ireg->par[0] == 0)) { + printk(KERN_DEBUG"isar selftest OK\n"); + } else { + printk(KERN_DEBUG"isar selftest not OK %x/%x/%x\n", + ireg->cmsb, ireg->clsb, ireg->par[0]); + ret = 1;goto reterror; + } + ireg->iis = 0; + if (!sendmsg(cs, ISAR_HIS_DIAG, ISAR_CTRL_SWVER, 0, NULL)) { + printk(KERN_ERR"isar RQST SVN failed\n"); + ret = 1;goto reterror; + } + cnt = 10000; /* max 100 ms */ + while ((ireg->iis != ISAR_IIS_DIAG) && cnt) { + udelay(10); + cnt--; + } + if (!cnt) { + printk(KERN_ERR"isar no SVN response\n"); + ret = 1;goto reterrflg; + } else { + if ((ireg->cmsb == ISAR_CTRL_SWVER) && (ireg->clsb == 1)) + printk(KERN_DEBUG"isar software version %#x\n", + ireg->par[0]); + else { + printk(KERN_ERR"isar wrong swver response (%x,%x) cnt(%d)\n", + ireg->cmsb, ireg->clsb, cnt); + ret = 1;goto reterrflg; + } + } + cs->debug = debug; + isar_setup(cs); + ret = 0; +reterrflg: + restore_flags(flags); +reterror: + cs->debug = debug; + if (ret) + /* disable ISAR IRQ */ + cs->BC_Write_Reg(cs, 0, ISAR_IRQBIT, 0); + kfree(msg); + kfree(tmpmsg); + return(ret); +} + +void +isar_sched_event(struct BCState *bcs, int event) +{ + bcs->event |= 1 << event; + queue_task(&bcs->tqueue, &tq_immediate); + mark_bh(IMMEDIATE_BH); +} + +static inline void +isar_rcv_frame(struct IsdnCardState *cs, struct BCState *bcs) +{ + u_char *ptr; + struct sk_buff *skb; + struct isar_reg *ireg = bcs->hw.isar.reg; + + if (!ireg->clsb) { + debugl1(cs, "isar zero len frame"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + return; + } + switch (bcs->mode) { + case L1_MODE_NULL: + debugl1(cs, "isar mode 0 spurious IIS_RDATA %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + printk(KERN_WARNING"isar mode 0 spurious IIS_RDATA %x/%x/%x\n", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + case L1_MODE_TRANS: + if ((skb = dev_alloc_skb(ireg->clsb))) { + rcv_mbox(cs, ireg, (u_char *)skb_put(skb, ireg->clsb)); + skb_queue_tail(&bcs->rqueue, skb); + isar_sched_event(bcs, B_RCVBUFREADY); + } else { + printk(KERN_WARNING "HiSax: skb out of memory\n"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case L1_MODE_HDLC: + if ((bcs->hw.isar.rcvidx + ireg->clsb) > HSCX_BUFMAX) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar_rcv_frame: incoming packet too large"); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + bcs->hw.isar.rcvidx = 0; + } else if (ireg->cmsb & HDLC_ERROR) { + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "isar frame error %x len %d", + ireg->cmsb, ireg->clsb); + bcs->hw.isar.rcvidx = 0; + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } else { + if (ireg->cmsb & HDLC_FSD) + bcs->hw.isar.rcvidx = 0; + ptr = bcs->hw.isar.rcvbuf + bcs->hw.isar.rcvidx; + bcs->hw.isar.rcvidx += ireg->clsb; + rcv_mbox(cs, ireg, ptr); + if (ireg->cmsb & HDLC_FED) { + if (bcs->hw.isar.rcvidx < 3) { /* last 2 bytes are the FCS */ + printk(KERN_WARNING "ISAR: HDLC frame too short(%d)\n", + bcs->hw.isar.rcvidx); + } else if (!(skb = dev_alloc_skb(bcs->hw.isar.rcvidx-2))) + printk(KERN_WARNING "ISAR: receive out of memory\n"); + else { + memcpy(skb_put(skb, bcs->hw.isar.rcvidx-2), + bcs->hw.isar.rcvbuf, bcs->hw.isar.rcvidx-2); + skb_queue_tail(&bcs->rqueue, skb); + isar_sched_event(bcs, B_RCVBUFREADY); + } + } + } + break; + default: + printk(KERN_ERR"isar_rcv_frame mode (%x)error\n", bcs->mode); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + break; + } +} + +void +isar_fill_fifo(struct BCState *bcs) +{ + struct IsdnCardState *cs = bcs->cs; + int count; + u_char msb; + u_char *ptr; + long flags; + + if ((cs->debug & L1_DEB_HSCX) && !(cs->debug & L1_DEB_HSCX_FIFO)) + debugl1(cs, "isar_fill_fifo"); + if (!bcs->tx_skb) + return; + if (bcs->tx_skb->len <= 0) + return; + if (!(bcs->hw.isar.reg->bstat & + (bcs->hw.isar.dpath == 1 ? BSTAT_RDM1 : BSTAT_RDM2))) + return; + if (bcs->tx_skb->len > bcs->hw.isar.mml) { + msb = 0; + count = bcs->hw.isar.mml; + } else { + count = bcs->tx_skb->len; + msb = HDLC_FED; + } + if (!bcs->hw.isar.txcnt) + msb |= HDLC_FST; + save_flags(flags); + cli(); + ptr = bcs->tx_skb->data; + skb_pull(bcs->tx_skb, count); + bcs->tx_cnt -= count; + bcs->hw.isar.txcnt += count; + switch (bcs->mode) { + case L1_MODE_NULL: + printk(KERN_ERR"isar_fill_fifo wrong mode 0\n"); + break; + case L1_MODE_TRANS: + if (!sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + 0, count, ptr)) { + if (cs->debug) + debugl1(cs, "isar bin data send dp%d failed", + bcs->hw.isar.dpath); + } + break; + case L1_MODE_HDLC: + if (!sendmsg(cs, SET_DPS(bcs->hw.isar.dpath) | ISAR_HIS_SDATA, + msb, count, ptr)) { + if (cs->debug) + debugl1(cs, "isar hdlc data send dp%d failed", + bcs->hw.isar.dpath); + } + break; + default: + printk(KERN_ERR"isar_fill_fifo mode (%x)error\n", bcs->mode); + break; + } + restore_flags(flags); +} + +inline +struct BCState *sel_bcs_isar(struct IsdnCardState *cs, u_char dpath) +{ + if ((!dpath) || (dpath == 3)) + return(NULL); + if (cs->bcs[0].hw.isar.dpath == dpath) + return(&cs->bcs[0]); + if (cs->bcs[1].hw.isar.dpath == dpath) + return(&cs->bcs[1]); + return(NULL); +} + +inline void +send_frames(struct BCState *bcs) +{ + if (bcs->tx_skb) { + if (bcs->tx_skb->len) { + isar_fill_fifo(bcs); + return; + } else { + if (bcs->st->lli.l1writewakeup && + (PACKET_NOACK != bcs->tx_skb->pkt_type)) + bcs->st->lli.l1writewakeup(bcs->st, bcs->hw.isar.txcnt); + dev_kfree_skb(bcs->tx_skb); + bcs->hw.isar.txcnt = 0; + bcs->tx_skb = NULL; + } + } + if ((bcs->tx_skb = skb_dequeue(&bcs->squeue))) { + bcs->hw.isar.txcnt = 0; + test_and_set_bit(BC_FLG_BUSY, &bcs->Flag); + isar_fill_fifo(bcs); + } else { + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + isar_sched_event(bcs, B_XMTBUFREADY); + } +} + +inline void +check_send(struct IsdnCardState *cs, u_char rdm) +{ + struct BCState *bcs; + + if (rdm & BSTAT_RDM1) { + if ((bcs = sel_bcs_isar(cs, 1))) { + if (bcs->mode) { + send_frames(bcs); + } + } + } + if (rdm & BSTAT_RDM2) { + if ((bcs = sel_bcs_isar(cs, 2))) { + if (bcs->mode) { + send_frames(bcs); + } + } + } + +} + +static char debbuf[64]; + +void +isar_int_main(struct IsdnCardState *cs) +{ + long flags; + struct isar_reg *ireg = cs->bcs[0].hw.isar.reg; + struct BCState *bcs; + + save_flags(flags); + cli(); + get_irq_infos(cs, ireg); + switch (ireg->iis & ISAR_IIS_MSCMSD) { + case ISAR_IIS_RDATA: + if ((bcs = sel_bcs_isar(cs, ireg->iis >> 6))) { + isar_rcv_frame(cs, bcs); + } else { + debugl1(cs, "isar spurious IIS_RDATA %x/%x/%x", + ireg->iis, ireg->cmsb, ireg->clsb); + printk(KERN_WARNING"isar spurious IIS_RDATA %x/%x/%x\n", + ireg->iis, ireg->cmsb, ireg->clsb); + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + } + break; + case ISAR_IIS_GSTEV: + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + ireg->bstat |= ireg->cmsb; + check_send(cs, ireg->cmsb); + break; + case ISAR_IIS_BSTEV: + cs->BC_Write_Reg(cs, 1, ISAR_IIA, 0); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "Buffer STEV dpath%d msb(%x)", + ireg->iis>>6, ireg->cmsb); + break; + case ISAR_IIS_DIAG: + case ISAR_IIS_PSTRSP: + case ISAR_IIS_PSTEV: + case ISAR_IIS_BSTRSP: + case ISAR_IIS_IOM2RSP: + rcv_mbox(cs, ireg, (u_char *)ireg->par); + if ((cs->debug & (L1_DEB_HSCX | L1_DEB_HSCX_FIFO)) + == L1_DEB_HSCX) { + u_char *tp=debbuf; + + tp += sprintf(debbuf, "msg iis(%x) msb(%x)", + ireg->iis, ireg->cmsb); + QuickHex(tp, (u_char *)ireg->par, ireg->clsb); + debugl1(cs, debbuf); + } + break; + default: + rcv_mbox(cs, ireg, debbuf); + if (cs->debug & L1_DEB_WARN) + debugl1(cs, "unhandled msg iis(%x) ctrl(%x/%x)", + ireg->iis, ireg->cmsb, ireg->clsb); + break; + } + restore_flags(flags); +} + +void +setup_pump(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + + switch (bcs->mode) { + case L1_MODE_NULL: + case L1_MODE_TRANS: + case L1_MODE_HDLC: + if (!sendmsg(cs, dps | ISAR_HIS_PUMPCFG, PMOD_BYPASS, 0, NULL)) { + if (cs->debug) + debugl1(cs, "isar pump bypass cfg dp%d failed", + bcs->hw.isar.dpath); + } + break; + } + if (!sendmsg(cs, dps | ISAR_HIS_PSTREQ, 0, 0, NULL)) { + if (cs->debug) + debugl1(cs, "isar pump status req dp%d failed", + bcs->hw.isar.dpath); + } +} + +void +setup_sart(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + + switch (bcs->mode) { + case L1_MODE_NULL: + if (!sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_DISABLE, 0, NULL)) { + if (cs->debug) + debugl1(cs, "isar sart disable dp%d failed", + bcs->hw.isar.dpath); + } + break; + case L1_MODE_TRANS: + if (!sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_BINARY, 2, "\0\0")) { + if (cs->debug) + debugl1(cs, "isar sart binary dp%d failed", + bcs->hw.isar.dpath); + } + break; + case L1_MODE_HDLC: + if (!sendmsg(cs, dps | ISAR_HIS_SARTCFG, SMODE_HDLC, 1, "\0")) { + if (cs->debug) + debugl1(cs, "isar sart binary dp%d failed", + bcs->hw.isar.dpath); + } + break; + } + if (!sendmsg(cs, dps | ISAR_HIS_BSTREQ, 0, 0, NULL)) { + if (cs->debug) + debugl1(cs, "isar buf stat req dp%d failed", + bcs->hw.isar.dpath); + } +} + +void +setup_iom2(struct BCState *bcs) { + struct IsdnCardState *cs = bcs->cs; + u_char dps = SET_DPS(bcs->hw.isar.dpath); + u_char cmsb = 0, msg[5] = {0x10,0,0,0,0}; + + switch (bcs->mode) { + case L1_MODE_NULL: + /* dummy slot */ + msg[1] = msg[3] = bcs->hw.isar.dpath + 2; + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + cmsb = 0x80; + if (bcs->channel) + msg[1] = msg[3] = 1; + break; + } + if (!sendmsg(cs, dps | ISAR_HIS_IOM2CFG, cmsb, 5, msg)) { + if (cs->debug) + debugl1(cs, "isar iom2 dp%d failed", bcs->hw.isar.dpath); + } + if (!sendmsg(cs, dps | ISAR_HIS_IOM2REQ, 0, 0, NULL)) { + if (cs->debug) + debugl1(cs, "isar IOM2 cfg req dp%d failed", + bcs->hw.isar.dpath); + } +} + +int +modeisar(struct BCState *bcs, int mode, int bc) +{ + struct IsdnCardState *cs = bcs->cs; + + /* Here we are selecting the best datapath for requested mode */ + if(bcs->mode == L1_MODE_NULL) { /* New Setup */ + bcs->channel = bc; + switch (mode) { + case L1_MODE_NULL: /* init */ + break; + case L1_MODE_TRANS: + case L1_MODE_HDLC: + /* best is datapath 2 */ + if (!test_and_set_bit(ISAR_DP2_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 2; + else if (!test_and_set_bit(ISAR_DP1_USE, + &bcs->hw.isar.reg->Flags)) + bcs->hw.isar.dpath = 1; + else { + printk(KERN_ERR"isar modeisar both pathes in use\n"); + return(1); + } + break; + } + } + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "isar dp%d mode %d->%d ichan %d", + bcs->hw.isar.dpath, bcs->mode, mode, bc); + bcs->mode = mode; + setup_pump(bcs); + setup_sart(bcs); + setup_iom2(bcs); + if (bcs->mode == L1_MODE_NULL) { + /* Clear resources */ + if (bcs->hw.isar.dpath == 1) + test_and_clear_bit(ISAR_DP1_USE, &bcs->hw.isar.reg->Flags); + else if (bcs->hw.isar.dpath == 2) + test_and_clear_bit(ISAR_DP2_USE, &bcs->hw.isar.reg->Flags); + bcs->hw.isar.dpath = 0; + } + return(0); +} + +void +isar_setup(struct IsdnCardState *cs) +{ + u_char msg; + int i; + + /* Dpath 1, 2 */ + msg = 61; + for (i=0; i<2; i++) { + /* Buffer Config */ + if (!sendmsg(cs, (i ? ISAR_HIS_DPS2 : ISAR_HIS_DPS1) | + ISAR_HIS_P12CFG, 4, 1, &msg)) { + if (cs->debug) + debugl1(cs, "isar P%dCFG failed", i+1); + } + cs->bcs[i].hw.isar.mml = msg; + cs->bcs[i].mode = 0; + cs->bcs[i].hw.isar.dpath = i + 1; + modeisar(&cs->bcs[i], 0, 0); + } +} + +void +isar_l2l1(struct PStack *st, int pr, void *arg) +{ + struct sk_buff *skb = arg; + long flags; + + switch (pr) { + case (PH_DATA | REQUEST): + save_flags(flags); + cli(); + if (st->l1.bcs->tx_skb) { + skb_queue_tail(&st->l1.bcs->squeue, skb); + restore_flags(flags); + } else { + st->l1.bcs->tx_skb = skb; + test_and_set_bit(BC_FLG_BUSY, &st->l1.bcs->Flag); + if (st->l1.bcs->cs->debug & L1_DEB_HSCX) + debugl1(st->l1.bcs->cs, "DRQ set BC_FLG_BUSY"); + st->l1.bcs->hw.isar.txcnt = 0; + restore_flags(flags); + st->l1.bcs->cs->BC_Send_Data(st->l1.bcs); + } + break; + case (PH_PULL | INDICATION): + if (st->l1.bcs->tx_skb) { + printk(KERN_WARNING "isar_l2l1: this shouldn't happen\n"); + break; + } + test_and_set_bit(BC_FLG_BUSY, &st->l1.bcs->Flag); + if (st->l1.bcs->cs->debug & L1_DEB_HSCX) + debugl1(st->l1.bcs->cs, "PUI set BC_FLG_BUSY"); + st->l1.bcs->tx_skb = skb; + st->l1.bcs->hw.isar.txcnt = 0; + st->l1.bcs->cs->BC_Send_Data(st->l1.bcs); + break; + case (PH_PULL | REQUEST): + if (!st->l1.bcs->tx_skb) { + test_and_clear_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + st->l1.l1l2(st, PH_PULL | CONFIRM, NULL); + } else + test_and_set_bit(FLG_L1_PULL_REQ, &st->l1.Flags); + break; + case (PH_ACTIVATE | REQUEST): + test_and_set_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag); + modeisar(st->l1.bcs, st->l1.mode, st->l1.bc); + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | REQUEST): + l1_msg_b(st, pr, arg); + break; + case (PH_DEACTIVATE | CONFIRM): + test_and_clear_bit(BC_FLG_ACTIV, &st->l1.bcs->Flag); + test_and_clear_bit(BC_FLG_BUSY, &st->l1.bcs->Flag); + if (st->l1.bcs->cs->debug & L1_DEB_HSCX) + debugl1(st->l1.bcs->cs, "PDAC clear BC_FLG_BUSY"); + modeisar(st->l1.bcs, 0, st->l1.bc); + st->l1.l1l2(st, PH_DEACTIVATE | CONFIRM, NULL); + break; + } +} + +void +close_isarstate(struct BCState *bcs) +{ + modeisar(bcs, 0, bcs->channel); + if (test_and_clear_bit(BC_FLG_INIT, &bcs->Flag)) { + if (bcs->hw.isar.rcvbuf) { + kfree(bcs->hw.isar.rcvbuf); + bcs->hw.isar.rcvbuf = NULL; + } + discard_queue(&bcs->rqueue); + discard_queue(&bcs->squeue); + if (bcs->tx_skb) { + dev_kfree_skb(bcs->tx_skb); + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (bcs->cs->debug & L1_DEB_HSCX) + debugl1(bcs->cs, "closeisar clear BC_FLG_BUSY"); + } + } +} + +int +open_isarstate(struct IsdnCardState *cs, struct BCState *bcs) +{ + if (!test_and_set_bit(BC_FLG_INIT, &bcs->Flag)) { + if (!(bcs->hw.isar.rcvbuf = kmalloc(HSCX_BUFMAX, GFP_ATOMIC))) { + printk(KERN_WARNING + "HiSax: No memory for isar.rcvbuf\n"); + return (1); + } + skb_queue_head_init(&bcs->rqueue); + skb_queue_head_init(&bcs->squeue); + } + bcs->tx_skb = NULL; + test_and_clear_bit(BC_FLG_BUSY, &bcs->Flag); + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "openisar clear BC_FLG_BUSY"); + bcs->event = 0; + bcs->hw.isar.rcvidx = 0; + bcs->tx_cnt = 0; + return (0); +} + +int +setstack_isar(struct PStack *st, struct BCState *bcs) +{ + bcs->channel = st->l1.bc; + if (open_isarstate(st->l1.hardware, bcs)) + return (-1); + st->l1.bcs = bcs; + st->l2.l2l1 = isar_l2l1; + setstack_manager(st); + bcs->st = st; + setstack_l1_B(st); + return (0); +} + +HISAX_INITFUNC(void +initisar(struct IsdnCardState *cs)) +{ + cs->bcs[0].BC_SetStack = setstack_isar; + cs->bcs[1].BC_SetStack = setstack_isar; + cs->bcs[0].BC_Close = close_isarstate; + cs->bcs[1].BC_Close = close_isarstate; +} diff --git a/drivers/isdn/hisax/isar.h b/drivers/isdn/hisax/isar.h new file mode 100644 index 000000000..de892f813 --- /dev/null +++ b/drivers/isdn/hisax/isar.h @@ -0,0 +1,91 @@ +/* $Id: isar.h,v 1.2 1998/11/15 23:54:54 keil Exp $ + * isar.h ISAR (Siemens PSB 7110) specific defines + * + * Author Karsten Keil (keil@isdn4linux.de) + * + * + * $Log: isar.h,v $ + * Revision 1.2 1998/11/15 23:54:54 keil + * changes from 2.0 + * + * Revision 1.1 1998/08/13 23:33:48 keil + * First version, only init + * + * + */ + +#define ISAR_IRQMSK 0x04 +#define ISAR_IRQSTA 0x04 +#define ISAR_IRQBIT 0x75 +#define ISAR_CTRL_H 0x61 +#define ISAR_CTRL_L 0x60 +#define ISAR_IIS 0x58 +#define ISAR_IIA 0x58 +#define ISAR_HIS 0x50 +#define ISAR_HIA 0x50 +#define ISAR_MBOX 0x4c +#define ISAR_WADR 0x4a +#define ISAR_RADR 0x48 + +#define ISAR_HIS_VNR 0x14 +#define ISAR_HIS_DKEY 0x02 +#define ISAR_HIS_FIRM 0x1e +#define ISAR_HIS_STDSP 0x08 +#define ISAR_HIS_DIAG 0x05 +#define ISAR_HIS_P0CFG 0x3c +#define ISAR_HIS_P12CFG 0x24 +#define ISAR_HIS_SARTCFG 0x25 +#define ISAR_HIS_PUMPCFG 0x26 +#define ISAR_HIS_IOM2CFG 0x27 +#define ISAR_HIS_IOM2REQ 0x07 +#define ISAR_HIS_BSTREQ 0x0c +#define ISAR_HIS_PSTREQ 0x0e +#define ISAR_HIS_SDATA 0x20 +#define ISAR_HIS_DPS1 0x40 +#define ISAR_HIS_DPS2 0x80 +#define SET_DPS(x) ((x<<6) & 0xc0) + +#define ISAR_IIS_MSCMSD 0x3f +#define ISAR_IIS_VNR 0x15 +#define ISAR_IIS_DKEY 0x03 +#define ISAR_IIS_FIRM 0x1f +#define ISAR_IIS_STDSP 0x09 +#define ISAR_IIS_DIAG 0x25 +#define ISAR_IIS_GSTEV 0x0 +#define ISAR_IIS_BSTEV 0x28 +#define ISAR_IIS_BSTRSP 0x2c +#define ISAR_IIS_PSTRSP 0x2e +#define ISAR_IIS_PSTEV 0x2a +#define ISAR_IIS_IOM2RSP 0x27 + +#define ISAR_IIS_RDATA 0x20 +#define ISAR_CTRL_SWVER 0x10 +#define ISAR_CTRL_STST 0x40 + +#define ISAR_MSG_HWVER {0x20, 0, 1} + +#define ISAR_DP1_USE 1 +#define ISAR_DP2_USE 2 + +#define PMOD_BYPASS 7 + +#define SMODE_DISABLE 0 +#define SMODE_HDLC 3 +#define SMODE_BINARY 4 + +#define HDLC_FED 0x40 +#define HDLC_FSD 0x20 +#define HDLC_FST 0x20 +#define HDLC_ERROR 0x1c + +#define BSTAT_RDM0 0x1 +#define BSTAT_RDM1 0x2 +#define BSTAT_RDM2 0x4 +#define BSTAT_RDM3 0x8 + + +extern int ISARVersion(struct IsdnCardState *cs, char *s); +extern int isar_load_firmware(struct IsdnCardState *cs, u_char *buf); +extern void isar_int_main(struct IsdnCardState *cs); +extern void initisar(struct IsdnCardState *cs); +extern void isar_fill_fifo(struct BCState *bcs); diff --git a/drivers/isdn/hisax/md5sums.asc b/drivers/isdn/hisax/md5sums.asc new file mode 100644 index 000000000..49888c55d --- /dev/null +++ b/drivers/isdn/hisax/md5sums.asc @@ -0,0 +1,29 @@ +-----BEGIN PGP SIGNED MESSAGE----- + +# This are valid md5sums for certificated HiSax driver. +# The certification is valid only if the md5sums of all files match. +# The certification is valid only for ELSA QuickStep cards in the moment. +# Read ../../../Documentation/isdn/HiSax.cert for more informations. +# +a273c532aec063574273ee519975cd9a isac.c +27c5c5bfa2ceabf02e2e6d686b03abde isdnl1.c +8c89ac659d3188ab997fb575da22b566 isdnl2.c +d0fa912aa284b8fd19fed86b65999f6f isdnl3.c +1bce120740b615006286ad9b2d7fcdcb tei.c +8845f88dd17917d9b58badeff1605057 callc.c +f3ec2a634f06074d16167aaba02b6dc1 cert.c +71840ec8189f42b0db86fb38e5e5984c l3dss1.c +1882de8bea921b9ccd98fbe77267aa04 l3_1tr6.c +3bd7af3a11693d028300278744d0da09 elsa.c +# end of md5sums + +-----BEGIN PGP SIGNATURE----- +Version: 2.6.3i +Charset: noconv + +iQCVAwUBNrl5JDpxHvX/mS9tAQHm8wP+Nk64UJ2abdDG/igXZSrwcYhX/Kp7cxt9 +ccYp+aaur+pALA0lxwY3xcLt9u36fCYuTLHAVmQoiC9Vbemj37yzM2rUpz9nkw/7 +D6gLqZs2jxVpAwVVJgp0JwDONKXaRX6Lt2EPD9PTW6vxRWEu0HqGhM5hrtd/o4rV +mC1W7Wj13XM= +=LdhT +-----END PGP SIGNATURE----- diff --git a/drivers/isdn/hisax/s0box.c b/drivers/isdn/hisax/s0box.c new file mode 100644 index 000000000..479880bb3 --- /dev/null +++ b/drivers/isdn/hisax/s0box.c @@ -0,0 +1,277 @@ +/* $Id: s0box.c,v 2.1 1998/04/15 16:38:24 keil Exp $ + + * s0box.c low level stuff for Creatix S0BOX + * + * Author S0BOX specific stuff: Enrik Berkhan (enrik@starfleet.inka.de) + * + * + */ +#define __NO_VERSION__ +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" + +extern const char *CardType[]; +const char *s0box_revision = "$Revision: 2.1 $"; + +static inline void +writereg(unsigned int padr, signed int addr, u_char off, u_char val) { + unsigned long flags; + + save_flags(flags); + cli(); + outb_p(0x1c,padr+2); + outb_p(0x14,padr+2); + outb_p((addr+off)&0x7f,padr); + outb_p(0x16,padr+2); + outb_p(val,padr); + outb_p(0x17,padr+2); + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); + restore_flags(flags); +} + +static u_char nibtab[] = { 1, 9, 5, 0xd, 3, 0xb, 7, 0xf, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 8, 4, 0xc, 2, 0xa, 6, 0xe } ; + +static inline u_char +readreg(unsigned int padr, signed int addr, u_char off) { + register u_char n1, n2; + unsigned long flags; + + save_flags(flags); + cli(); + outb_p(0x1c,padr+2); + outb_p(0x14,padr+2); + outb_p((addr+off)|0x80,padr); + outb_p(0x16,padr+2); + outb_p(0x17,padr+2); + n1 = (inb_p(padr+1) >> 3) & 0x17; + outb_p(0x16,padr+2); + n2 = (inb_p(padr+1) >> 3) & 0x17; + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); + restore_flags(flags); + return nibtab[n1] | (nibtab[n2] << 4); +} + +static inline void +read_fifo(unsigned int padr, signed int adr, u_char * data, int size) +{ + int i; + register u_char n1, n2; + + outb_p(0x1c, padr+2); + outb_p(0x14, padr+2); + outb_p(adr|0x80, padr); + outb_p(0x16, padr+2); + for (i=0; i<size; i++) { + outb_p(0x17, padr+2); + n1 = (inb_p(padr+1) >> 3) & 0x17; + outb_p(0x16,padr+2); + n2 = (inb_p(padr+1) >> 3) & 0x17; + *(data++)=nibtab[n1] | (nibtab[n2] << 4); + } + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); + return; +} + +static inline void +write_fifo(unsigned int padr, signed int adr, u_char * data, int size) +{ + int i; + outb_p(0x1c, padr+2); + outb_p(0x14, padr+2); + outb_p(adr&0x7f, padr); + for (i=0; i<size; i++) { + outb_p(0x16, padr+2); + outb_p(*(data++), padr); + outb_p(0x17, padr+2); + } + outb_p(0x14,padr+2); + outb_p(0x1c,padr+2); + return; +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.isacfifo, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.isacfifo, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[hscx], offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[hscx], offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[nr], reg) +#define WRITEHSCX(cs, nr, reg, data) writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[nr], reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscxfifo[nr], ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscxfifo[nr], ptr, cnt) + +#include "hscx_irq.c" + +static void +s0box_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ +#define MAXCOUNT 20 + struct IsdnCardState *cs = dev_id; + u_char val, stat = 0; + int count = 0; + + if (!cs) { + printk(KERN_WARNING "Teles: Spurious interrupt!\n"); + return; + } + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_ISTA); + Start_HSCX: + if (val) { + hscx_int_main(cs, val); + stat |= 1; + } + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_ISTA); + Start_ISAC: + if (val) { + isac_interrupt(cs, val); + stat |= 2; + } + count++; + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_HSCX) + debugl1(cs, "HSCX IntStat after IntRoutine"); + goto Start_HSCX; + } + val = readreg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_ISTA); + if (val && count < MAXCOUNT) { + if (cs->debug & L1_DEB_ISAC) + debugl1(cs, "ISAC IntStat after IntRoutine"); + goto Start_ISAC; + } + if (count >= MAXCOUNT) + printk(KERN_WARNING "S0Box: more than %d loops in s0box_interrupt\n", count); + if (stat & 1) { + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[0], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[0], HSCX_MASK, 0x0); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.hscx[1], HSCX_MASK, 0x0); + } + if (stat & 2) { + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_MASK, 0xFF); + writereg(cs->hw.teles3.cfg_reg, cs->hw.teles3.isac, ISAC_MASK, 0x0); + } +} + +void +release_io_s0box(struct IsdnCardState *cs) +{ + release_region(cs->hw.teles3.cfg_reg, 8); +} + +static int +S0Box_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + switch (mt) { + case CARD_RESET: + break; + case CARD_RELEASE: + release_io_s0box(cs); + break; + case CARD_SETIRQ: + return(request_irq(cs->irq, &s0box_interrupt, + I4L_IRQ_FLAG, "HiSax", cs)); + case CARD_INIT: + inithscxisac(cs, 3); + break; + case CARD_TEST: + break; + } + return(0); +} + +__initfunc(int +setup_s0box(struct IsdnCard *card)) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, s0box_revision); + printk(KERN_INFO "HiSax: S0Box IO driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_S0BOX) + return (0); + + cs->hw.teles3.cfg_reg = card->para[1]; + cs->hw.teles3.hscx[0] = -0x20; + cs->hw.teles3.hscx[1] = 0x0; + cs->hw.teles3.isac = 0x20; + cs->hw.teles3.isacfifo = cs->hw.teles3.isac + 0x3e; + cs->hw.teles3.hscxfifo[0] = cs->hw.teles3.hscx[0] + 0x3e; + cs->hw.teles3.hscxfifo[1] = cs->hw.teles3.hscx[1] + 0x3e; + cs->irq = card->para[0]; + if (check_region(cs->hw.teles3.cfg_reg,8)) { + printk(KERN_WARNING + "HiSax: %s ports %x-%x already in use\n", + CardType[cs->typ], + cs->hw.teles3.cfg_reg, + cs->hw.teles3.cfg_reg + 7); + return 0; + } else + request_region(cs->hw.teles3.cfg_reg, 8, "S0Box parallel I/O"); + printk(KERN_INFO + "HiSax: %s config irq:%d isac:0x%x cfg:0x%x\n", + CardType[cs->typ], cs->irq, + cs->hw.teles3.isac, cs->hw.teles3.cfg_reg); + printk(KERN_INFO + "HiSax: hscx A:0x%x hscx B:0x%x\n", + cs->hw.teles3.hscx[0], cs->hw.teles3.hscx[1]); + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &S0Box_card_msg; + ISACVersion(cs, "S0Box:"); + if (HscxVersion(cs, "S0Box:")) { + printk(KERN_WARNING + "S0Box: wrong HSCX versions check IO address\n"); + release_io_s0box(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/hisax/telespci.c b/drivers/isdn/hisax/telespci.c new file mode 100644 index 000000000..88592aa8d --- /dev/null +++ b/drivers/isdn/hisax/telespci.c @@ -0,0 +1,372 @@ +/* $Id: telespci.c,v 2.5 1998/11/15 23:55:28 keil Exp $ + + * telespci.c low level stuff for Teles PCI isdn cards + * + * Author Ton van Rosmalen + * Karsten Keil (keil@temic-ech.spacenet.de) + * + * + * $Log: telespci.c,v $ + * Revision 2.5 1998/11/15 23:55:28 keil + * changes from 2.0 + * + * Revision 2.4 1998/10/05 09:38:08 keil + * Fix register addressing + * + * Revision 2.3 1998/05/25 12:58:26 keil + * HiSax golden code from certification, Don't use !!! + * No leased lines, no X75, but many changes. + * + * Revision 2.1 1998/04/15 16:38:23 keil + * Add S0Box and Teles PCI support + * + * + */ +#define __NO_VERSION__ +#include <linux/config.h> +#include <linux/string.h> +#include "hisax.h" +#include "isac.h" +#include "hscx.h" +#include "isdnl1.h" +#include <linux/pci.h> + +extern const char *CardType[]; + +const char *telespci_revision = "$Revision: 2.5 $"; + +#define ZORAN_PO_RQ_PEN 0x02000000 +#define ZORAN_PO_WR 0x00800000 +#define ZORAN_PO_GID0 0x00000000 +#define ZORAN_PO_GID1 0x00100000 +#define ZORAN_PO_GREG0 0x00000000 +#define ZORAN_PO_GREG1 0x00010000 +#define ZORAN_PO_DMASK 0xFF + +#define WRITE_ADDR_ISAC (ZORAN_PO_WR | ZORAN_PO_GID0 | ZORAN_PO_GREG0) +#define READ_DATA_ISAC (ZORAN_PO_GID0 | ZORAN_PO_GREG1) +#define WRITE_DATA_ISAC (ZORAN_PO_WR | ZORAN_PO_GID0 | ZORAN_PO_GREG1) +#define WRITE_ADDR_HSCX (ZORAN_PO_WR | ZORAN_PO_GID1 | ZORAN_PO_GREG0) +#define READ_DATA_HSCX (ZORAN_PO_GID1 | ZORAN_PO_GREG1) +#define WRITE_DATA_HSCX (ZORAN_PO_WR | ZORAN_PO_GID1 | ZORAN_PO_GREG1) + +#define ZORAN_WAIT_NOBUSY do { \ + portdata = readl(adr + 0x200); \ + } while (portdata & ZORAN_PO_RQ_PEN) + +static inline u_char +readisac(unsigned int adr, u_char off) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + + /* set address for ISAC */ + writel(WRITE_ADDR_ISAC | off, adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* read data from ISAC */ + writel(READ_DATA_ISAC, adr + 0x200); + ZORAN_WAIT_NOBUSY; + return((u_char)(portdata & ZORAN_PO_DMASK)); +} + +static inline void +writeisac(unsigned int adr, u_char off, u_char data) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + + /* set address for ISAC */ + writel(WRITE_ADDR_ISAC | off, adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* write data to ISAC */ + writel(WRITE_DATA_ISAC | data, adr + 0x200); + ZORAN_WAIT_NOBUSY; +} + +static inline u_char +readhscx(unsigned int adr, int hscx, u_char off) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + /* set address for HSCX */ + writel(WRITE_ADDR_HSCX | ((hscx ? 0x40:0) + off), adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* read data from HSCX */ + writel(READ_DATA_HSCX, adr + 0x200); + ZORAN_WAIT_NOBUSY; + return ((u_char)(portdata & ZORAN_PO_DMASK)); +} + +static inline void +writehscx(unsigned int adr, int hscx, u_char off, u_char data) +{ + register unsigned int portdata; + + ZORAN_WAIT_NOBUSY; + /* set address for HSCX */ + writel(WRITE_ADDR_HSCX | ((hscx ? 0x40:0) + off), adr + 0x200); + ZORAN_WAIT_NOBUSY; + + /* write data to HSCX */ + writel(WRITE_DATA_HSCX | data, adr + 0x200); + ZORAN_WAIT_NOBUSY; +} + +static inline void +read_fifo_isac(unsigned int adr, u_char * data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* read data from ISAC */ + for (i = 0; i < size; i++) { + /* set address for ISAC fifo */ + writel(WRITE_ADDR_ISAC | 0x1E, adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(READ_DATA_ISAC, adr + 0x200); + ZORAN_WAIT_NOBUSY; + data[i] = (u_char)(portdata & ZORAN_PO_DMASK); + } +} + +static void +write_fifo_isac(unsigned int adr, u_char * data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* write data to ISAC */ + for (i = 0; i < size; i++) { + /* set address for ISAC fifo */ + writel(WRITE_ADDR_ISAC | 0x1E, adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(WRITE_DATA_ISAC | data[i], adr + 0x200); + ZORAN_WAIT_NOBUSY; + } +} + +static inline void +read_fifo_hscx(unsigned int adr, int hscx, u_char * data, int size) +{ + register unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* read data from HSCX */ + for (i = 0; i < size; i++) { + /* set address for HSCX fifo */ + writel(WRITE_ADDR_HSCX |(hscx ? 0x5F:0x1F), adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(READ_DATA_HSCX, adr + 0x200); + ZORAN_WAIT_NOBUSY; + data[i] = (u_char) (portdata & ZORAN_PO_DMASK); + } +} + +static inline void +write_fifo_hscx(unsigned int adr, int hscx, u_char * data, int size) +{ + unsigned int portdata; + register int i; + + ZORAN_WAIT_NOBUSY; + /* write data to HSCX */ + for (i = 0; i < size; i++) { + /* set address for HSCX fifo */ + writel(WRITE_ADDR_HSCX |(hscx ? 0x5F:0x1F), adr + 0x200); + ZORAN_WAIT_NOBUSY; + writel(WRITE_DATA_HSCX | data[i], adr + 0x200); + ZORAN_WAIT_NOBUSY; + udelay(10); + } +} + +/* Interface functions */ + +static u_char +ReadISAC(struct IsdnCardState *cs, u_char offset) +{ + return (readisac(cs->hw.teles0.membase, offset)); +} + +static void +WriteISAC(struct IsdnCardState *cs, u_char offset, u_char value) +{ + writeisac(cs->hw.teles0.membase, offset, value); +} + +static void +ReadISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + read_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static void +WriteISACfifo(struct IsdnCardState *cs, u_char * data, int size) +{ + write_fifo_isac(cs->hw.teles0.membase, data, size); +} + +static u_char +ReadHSCX(struct IsdnCardState *cs, int hscx, u_char offset) +{ + return (readhscx(cs->hw.teles0.membase, hscx, offset)); +} + +static void +WriteHSCX(struct IsdnCardState *cs, int hscx, u_char offset, u_char value) +{ + writehscx(cs->hw.teles0.membase, hscx, offset, value); +} + +/* + * fast interrupt HSCX stuff goes here + */ + +#define READHSCX(cs, nr, reg) readhscx(cs->hw.teles0.membase, nr, reg) +#define WRITEHSCX(cs, nr, reg, data) writehscx(cs->hw.teles0.membase, nr, reg, data) +#define READHSCXFIFO(cs, nr, ptr, cnt) read_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) +#define WRITEHSCXFIFO(cs, nr, ptr, cnt) write_fifo_hscx(cs->hw.teles0.membase, nr, ptr, cnt) + +#include "hscx_irq.c" + +static void +telespci_interrupt(int intno, void *dev_id, struct pt_regs *regs) +{ +#define MAXCOUNT 20 + struct IsdnCardState *cs = dev_id; + u_char val, stat = 0; + + if (!cs) { + printk(KERN_WARNING "TelesPCI: Spurious interrupt!\n"); + return; + } + val = readhscx(cs->hw.teles0.membase, 1, HSCX_ISTA); + if (val) { + hscx_int_main(cs, val); + stat |= 1; + } + val = readisac(cs->hw.teles0.membase, ISAC_ISTA); + if (val) { + isac_interrupt(cs, val); + stat |= 2; + } + /* Clear interrupt register for Zoran PCI controller */ + writel(0x70000000, cs->hw.teles0.membase + 0x3C); + + if (stat & 1) { + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0xFF); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0xFF); + writehscx(cs->hw.teles0.membase, 0, HSCX_MASK, 0x0); + writehscx(cs->hw.teles0.membase, 1, HSCX_MASK, 0x0); + } + if (stat & 2) { + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0xFF); + writeisac(cs->hw.teles0.membase, ISAC_MASK, 0x0); + } +} + +void +release_io_telespci(struct IsdnCardState *cs) +{ + iounmap((void *)cs->hw.teles0.membase); +} + +static int +TelesPCI_card_msg(struct IsdnCardState *cs, int mt, void *arg) +{ + switch (mt) { + case CARD_RESET: + return(0); + case CARD_RELEASE: + release_io_telespci(cs); + return(0); + case CARD_SETIRQ: + return(request_irq(cs->irq, &telespci_interrupt, + I4L_IRQ_FLAG | SA_SHIRQ, "HiSax", cs)); + case CARD_INIT: + inithscxisac(cs, 3); + return(0); + case CARD_TEST: + return(0); + } + return(0); +} + +static struct pci_dev *dev_tel __initdata = NULL; + +__initfunc(int +setup_telespci(struct IsdnCard *card)) +{ + struct IsdnCardState *cs = card->cs; + char tmp[64]; + + strcpy(tmp, telespci_revision); + printk(KERN_INFO "HiSax: Teles/PCI driver Rev. %s\n", HiSax_getrev(tmp)); + if (cs->typ != ISDN_CTYPE_TELESPCI) + return (0); + +#if CONFIG_PCI + if (!pci_present()) { + printk(KERN_ERR "TelesPCI: no PCI bus present\n"); + return(0); + } + if ((dev_tel = pci_find_device (0x11DE, 0x6120, dev_tel))) { + cs->irq = dev_tel->irq; + if (!cs->irq) { + printk(KERN_WARNING "Teles: No IRQ for PCI card found\n"); + return(0); + } + cs->hw.teles0.membase = (u_int) ioremap(dev_tel->base_address[0], + PAGE_SIZE); + printk(KERN_INFO "Found: Zoran, base-address: 0x%lx, irq: 0x%x\n", + dev_tel->base_address[0], dev_tel->irq); + } else { + printk(KERN_WARNING "TelesPCI: No PCI card found\n"); + return(0); + } +#else + printk(KERN_WARNING "HiSax: Teles/PCI and NO_PCI_BIOS\n"); + printk(KERN_WARNING "HiSax: Teles/PCI unable to config\n"); + return (0); +#endif /* CONFIG_PCI */ + + /* Initialize Zoran PCI controller */ + writel(0x00000000, cs->hw.teles0.membase + 0x28); + writel(0x01000000, cs->hw.teles0.membase + 0x28); + writel(0x01000000, cs->hw.teles0.membase + 0x28); + writel(0x7BFFFFFF, cs->hw.teles0.membase + 0x2C); + writel(0x70000000, cs->hw.teles0.membase + 0x3C); + writel(0x61000000, cs->hw.teles0.membase + 0x40); + /* writel(0x00800000, cs->hw.teles0.membase + 0x200); */ + + printk(KERN_INFO + "HiSax: %s config irq:%d mem:%x\n", + CardType[cs->typ], cs->irq, + cs->hw.teles0.membase); + + cs->readisac = &ReadISAC; + cs->writeisac = &WriteISAC; + cs->readisacfifo = &ReadISACfifo; + cs->writeisacfifo = &WriteISACfifo; + cs->BC_Read_Reg = &ReadHSCX; + cs->BC_Write_Reg = &WriteHSCX; + cs->BC_Send_Data = &hscx_fill_fifo; + cs->cardmsg = &TelesPCI_card_msg; + ISACVersion(cs, "TelesPCI:"); + if (HscxVersion(cs, "TelesPCI:")) { + printk(KERN_WARNING + "TelesPCI: wrong HSCX versions check IO/MEM addresses\n"); + release_io_telespci(cs); + return (0); + } + return (1); +} diff --git a/drivers/isdn/isdn_bsdcomp.c b/drivers/isdn/isdn_bsdcomp.c new file mode 100644 index 000000000..92012c8e5 --- /dev/null +++ b/drivers/isdn/isdn_bsdcomp.c @@ -0,0 +1,934 @@ +/* + * BSD compression module + * + * Patched version for ISDN syncPPP written 1997/1998 by Michael Hipp + * The whole module is now SKB based. + * + * Compile with: + * gcc -O2 -I/usr/src/linux/include -D__KERNEL__ -DMODULE -c isdn_bsdcomp.c + */ + +/* + * Original copyright notice: + * + * Copyright (c) 1985, 1986 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * James A. Woods, derived from original work by Spencer Thomas + * and Joseph Orost. + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +#ifndef MODULE +#error This file must be compiled as a module. +#endif + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/malloc.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/sched.h> /* to get the struct task_struct */ +#include <linux/string.h> /* used in new tty drivers */ +#include <linux/signal.h> /* used in new tty drivers */ + +#include <asm/system.h> +#include <asm/bitops.h> +#include <asm/segment.h> +#include <asm/byteorder.h> +#include <asm/types.h> + +#include <linux/if.h> + +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/inet.h> +#include <linux/ioctl.h> + +#include <linux/ppp_defs.h> + +#include <linux/isdn.h> +#include <linux/isdn_ppp.h> +/* #include <linux/netprotocol.h> */ +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/if_arp.h> +#include <linux/ppp-comp.h> + +#include "isdn_ppp.h" + +#define BSD_VERSION(x) ((x) >> 5) +#define BSD_NBITS(x) ((x) & 0x1F) + +#define BSD_CURRENT_VERSION 1 + +#define DEBUG 1 + +/* + * A dictionary for doing BSD compress. + */ + +struct bsd_dict { + u32 fcode; + u16 codem1; /* output of hash table -1 */ + u16 cptr; /* map code to hash table entry */ +}; + +struct bsd_db { + int totlen; /* length of this structure */ + unsigned int hsize; /* size of the hash table */ + unsigned char hshift; /* used in hash function */ + unsigned char n_bits; /* current bits/code */ + unsigned char maxbits; /* maximum bits/code */ + unsigned char debug; /* non-zero if debug desired */ + unsigned char unit; /* ppp unit number */ + u16 seqno; /* sequence # of next packet */ + unsigned int mru; /* size of receive (decompress) bufr */ + unsigned int maxmaxcode; /* largest valid code */ + unsigned int max_ent; /* largest code in use */ + unsigned int in_count; /* uncompressed bytes, aged */ + unsigned int bytes_out; /* compressed bytes, aged */ + unsigned int ratio; /* recent compression ratio */ + unsigned int checkpoint; /* when to next check the ratio */ + unsigned int clear_count; /* times dictionary cleared */ + unsigned int incomp_count; /* incompressible packets */ + unsigned int incomp_bytes; /* incompressible bytes */ + unsigned int uncomp_count; /* uncompressed packets */ + unsigned int uncomp_bytes; /* uncompressed bytes */ + unsigned int comp_count; /* compressed packets */ + unsigned int comp_bytes; /* compressed bytes */ + unsigned short *lens; /* array of lengths of codes */ + struct bsd_dict *dict; /* dictionary */ + int xmit; +}; + +#define BSD_OVHD 2 /* BSD compress overhead/packet */ +#define MIN_BSD_BITS 9 +#define BSD_INIT_BITS MIN_BSD_BITS +#define MAX_BSD_BITS 15 + +/* + * the next two codes should not be changed lightly, as they must not + * lie within the contiguous general code space. + */ +#define CLEAR 256 /* table clear output code */ +#define FIRST 257 /* first free entry */ +#define LAST 255 + +#define MAXCODE(b) ((1 << (b)) - 1) +#define BADCODEM1 MAXCODE(MAX_BSD_BITS); + +#define BSD_HASH(prefix,suffix,hshift) ((((unsigned long)(suffix))<<(hshift)) \ + ^ (unsigned long)(prefix)) +#define BSD_KEY(prefix,suffix) ((((unsigned long)(suffix)) << 16) \ + + (unsigned long)(prefix)) + +#define CHECK_GAP 10000 /* Ratio check interval */ + +#define RATIO_SCALE_LOG 8 +#define RATIO_SCALE (1<<RATIO_SCALE_LOG) +#define RATIO_MAX (0x7fffffff>>RATIO_SCALE_LOG) + +/* + * clear the dictionary + */ + +static void bsd_clear(struct bsd_db *db) +{ + db->clear_count++; + db->max_ent = FIRST-1; + db->n_bits = BSD_INIT_BITS; + db->bytes_out = 0; + db->in_count = 0; + db->incomp_count = 0; + db->ratio = 0; + db->checkpoint = CHECK_GAP; +} + +/* + * If the dictionary is full, then see if it is time to reset it. + * + * Compute the compression ratio using fixed-point arithmetic + * with 8 fractional bits. + * + * Since we have an infinite stream instead of a single file, + * watch only the local compression ratio. + * + * Since both peers must reset the dictionary at the same time even in + * the absence of CLEAR codes (while packets are incompressible), they + * must compute the same ratio. + */ +static int bsd_check (struct bsd_db *db) /* 1=output CLEAR */ +{ + unsigned int new_ratio; + + if (db->in_count >= db->checkpoint) + { + /* age the ratio by limiting the size of the counts */ + if (db->in_count >= RATIO_MAX || db->bytes_out >= RATIO_MAX) + { + db->in_count -= (db->in_count >> 2); + db->bytes_out -= (db->bytes_out >> 2); + } + + db->checkpoint = db->in_count + CHECK_GAP; + + if (db->max_ent >= db->maxmaxcode) + { + /* Reset the dictionary only if the ratio is worse, + * or if it looks as if it has been poisoned + * by incompressible data. + * + * This does not overflow, because + * db->in_count <= RATIO_MAX. + */ + + new_ratio = db->in_count << RATIO_SCALE_LOG; + if (db->bytes_out != 0) + { + new_ratio /= db->bytes_out; + } + + if (new_ratio < db->ratio || new_ratio < 1 * RATIO_SCALE) + { + bsd_clear (db); + return 1; + } + db->ratio = new_ratio; + } + } + return 0; +} + +/* + * Return statistics. + */ + +static void bsd_stats (void *state, struct compstat *stats) +{ + struct bsd_db *db = (struct bsd_db *) state; + + stats->unc_bytes = db->uncomp_bytes; + stats->unc_packets = db->uncomp_count; + stats->comp_bytes = db->comp_bytes; + stats->comp_packets = db->comp_count; + stats->inc_bytes = db->incomp_bytes; + stats->inc_packets = db->incomp_count; + stats->in_count = db->in_count; + stats->bytes_out = db->bytes_out; +} + +/* + * Reset state, as on a CCP ResetReq. + */ +static void bsd_reset (void *state,unsigned char code, unsigned char id, + unsigned char *data, unsigned len, + struct isdn_ppp_resetparams *rsparm) +{ + struct bsd_db *db = (struct bsd_db *) state; + + bsd_clear(db); + db->seqno = 0; + db->clear_count = 0; +} + +/* + * Release the compression structure + */ +static void bsd_free (void *state) +{ + struct bsd_db *db = (struct bsd_db *) state; + + if (db) { + /* + * Release the dictionary + */ + if (db->dict) { + vfree (db->dict); + db->dict = NULL; + } + + /* + * Release the string buffer + */ + if (db->lens) { + vfree (db->lens); + db->lens = NULL; + } + + /* + * Finally release the structure itself. + */ + kfree (db); + MOD_DEC_USE_COUNT; + } +} + + +/* + * Allocate space for a (de) compressor. + */ +static void *bsd_alloc (struct isdn_ppp_comp_data *data) +{ + int bits; + unsigned int hsize, hshift, maxmaxcode; + struct bsd_db *db; + int decomp; + + static unsigned int htab[][2] = { + { 5003 , 4 } , { 5003 , 4 } , { 5003 , 4 } , { 5003 , 4 } , + { 9001 , 5 } , { 18013 , 6 } , { 35023 , 7 } , { 69001 , 8 } + }; + + if (data->optlen != 1 || data->num != CI_BSD_COMPRESS + || BSD_VERSION(data->options[0]) != BSD_CURRENT_VERSION) + return NULL; + + bits = BSD_NBITS(data->options[0]); + + if(bits < 9 || bits > 15) + return NULL; + + hsize = htab[bits-9][0]; + hshift = htab[bits-9][1]; + + /* + * Allocate the main control structure for this instance. + */ + maxmaxcode = MAXCODE(bits); + db = (struct bsd_db *) kmalloc (sizeof (struct bsd_db),GFP_KERNEL); + if (!db) + return NULL; + + memset (db, 0, sizeof(struct bsd_db)); + + db->xmit = data->flags & IPPP_COMP_FLAG_XMIT; + decomp = db->xmit ? 0 : 1; + + /* + * Allocate space for the dictionary. This may be more than one page in + * length. + */ + db->dict = (struct bsd_dict *) vmalloc (hsize * sizeof (struct bsd_dict)); + if (!db->dict) { + bsd_free (db); + return NULL; + } + + MOD_INC_USE_COUNT; + + /* + * If this is the compression buffer then there is no length data. + * For decompression, the length information is needed as well. + */ + if (!decomp) + db->lens = NULL; + else { + db->lens = (unsigned short *) vmalloc ((maxmaxcode + 1) * + sizeof (db->lens[0])); + if (!db->lens) { + bsd_free (db); /* calls MOD_DEC_USE_COUNT; */ + return (NULL); + } + } + + /* + * Initialize the data information for the compression code + */ + db->totlen = sizeof (struct bsd_db) + (sizeof (struct bsd_dict) * hsize); + db->hsize = hsize; + db->hshift = hshift; + db->maxmaxcode = maxmaxcode; + db->maxbits = bits; + + return (void *) db; +} + +/* + * Initialize the database. + */ +static int bsd_init (void *state, struct isdn_ppp_comp_data *data, int unit, int debug) +{ + struct bsd_db *db = state; + int indx; + int decomp; + + if(!state || !data) { + printk(KERN_ERR "isdn_bsd_init: [%d] ERR, state %lx data %lx\n",unit,(long)state,(long)data); + return 0; + } + + decomp = db->xmit ? 0 : 1; + + if (data->optlen != 1 || data->num != CI_BSD_COMPRESS + || (BSD_VERSION(data->options[0]) != BSD_CURRENT_VERSION) + || (BSD_NBITS(data->options[0]) != db->maxbits) + || (decomp && db->lens == NULL)) { + printk(KERN_ERR "isdn_bsd: %d %d %d %d %lx\n",data->optlen,data->num,data->options[0],decomp,(unsigned long)db->lens); + return 0; + } + + if (decomp) + for(indx=LAST;indx>=0;indx--) + db->lens[indx] = 1; + + indx = db->hsize; + while (indx-- != 0) { + db->dict[indx].codem1 = BADCODEM1; + db->dict[indx].cptr = 0; + } + + db->unit = unit; + db->mru = 0; + + db->debug = 1; + + bsd_reset(db,0,0,NULL,0,NULL); + + return 1; +} + +/* + * Obtain pointers to the various structures in the compression tables + */ + +#define dict_ptrx(p,idx) &(p->dict[idx]) +#define lens_ptrx(p,idx) &(p->lens[idx]) + +#ifdef DEBUG +static unsigned short *lens_ptr(struct bsd_db *db, int idx) +{ + if ((unsigned int) idx > (unsigned int) db->maxmaxcode) { + printk (KERN_DEBUG "<9>ppp: lens_ptr(%d) > max\n", idx); + idx = 0; + } + return lens_ptrx (db, idx); +} + +static struct bsd_dict *dict_ptr(struct bsd_db *db, int idx) +{ + if ((unsigned int) idx >= (unsigned int) db->hsize) { + printk (KERN_DEBUG "<9>ppp: dict_ptr(%d) > max\n", idx); + idx = 0; + } + return dict_ptrx (db, idx); +} + +#else +#define lens_ptr(db,idx) lens_ptrx(db,idx) +#define dict_ptr(db,idx) dict_ptrx(db,idx) +#endif + +/* + * compress a packet + */ +static int bsd_compress (void *state, struct sk_buff *skb_in, struct sk_buff *skb_out,int proto) +{ + struct bsd_db *db; + int hshift; + unsigned int max_ent; + unsigned int n_bits; + unsigned int bitno; + unsigned long accm; + int ent; + unsigned long fcode; + struct bsd_dict *dictp; + unsigned char c; + int hval,disp,ilen,mxcode; + unsigned char *rptr = skb_in->data; + int isize = skb_in->len; + +#define OUTPUT(ent) \ + { \ + bitno -= n_bits; \ + accm |= ((ent) << bitno); \ + do { \ + if(skb_out && skb_tailroom(skb_out) > 0) \ + *(skb_put(skb_out,1)) = (unsigned char) (accm>>24); \ + accm <<= 8; \ + bitno += 8; \ + } while (bitno <= 24); \ + } + + /* + * If the protocol is not in the range we're interested in, + * just return without compressing the packet. If it is, + * the protocol becomes the first byte to compress. + */ + printk(KERN_DEBUG "bsd_compress called with %x\n",proto); + + ent = proto; + if (proto < 0x21 || proto > 0xf9 || !(proto & 0x1) ) + return 0; + + db = (struct bsd_db *) state; + hshift = db->hshift; + max_ent = db->max_ent; + n_bits = db->n_bits; + bitno = 32; + accm = 0; + mxcode = MAXCODE (n_bits); + + /* This is the PPP header information */ + if(skb_out && skb_tailroom(skb_out) >= 2) { + char *v = skb_put(skb_out,2); + /* we only push our own data on the header, + AC,PC and protos is pushed by caller */ + v[0] = db->seqno >> 8; + v[1] = db->seqno; + } + + ilen = ++isize; /* This is off by one, but that is what is in draft! */ + + while (--ilen > 0) { + c = *rptr++; + fcode = BSD_KEY (ent, c); + hval = BSD_HASH (ent, c, hshift); + dictp = dict_ptr (db, hval); + + /* Validate and then check the entry. */ + if (dictp->codem1 >= max_ent) + goto nomatch; + + if (dictp->fcode == fcode) { + ent = dictp->codem1 + 1; + continue; /* found (prefix,suffix) */ + } + + /* continue probing until a match or invalid entry */ + disp = (hval == 0) ? 1 : hval; + + do { + hval += disp; + if (hval >= db->hsize) + hval -= db->hsize; + dictp = dict_ptr (db, hval); + if (dictp->codem1 >= max_ent) + goto nomatch; + } while (dictp->fcode != fcode); + + ent = dictp->codem1 + 1; /* finally found (prefix,suffix) */ + continue; + +nomatch: + OUTPUT(ent); /* output the prefix */ + + /* code -> hashtable */ + if (max_ent < db->maxmaxcode) { + struct bsd_dict *dictp2; + struct bsd_dict *dictp3; + int indx; + + /* expand code size if needed */ + if (max_ent >= mxcode) { + db->n_bits = ++n_bits; + mxcode = MAXCODE (n_bits); + } + + /* + * Invalidate old hash table entry using + * this code, and then take it over. + */ + dictp2 = dict_ptr (db, max_ent + 1); + indx = dictp2->cptr; + dictp3 = dict_ptr (db, indx); + + if (dictp3->codem1 == max_ent) + dictp3->codem1 = BADCODEM1; + + dictp2->cptr = hval; + dictp->codem1 = max_ent; + dictp->fcode = fcode; + db->max_ent = ++max_ent; + + if (db->lens) { + unsigned short *len1 = lens_ptr (db, max_ent); + unsigned short *len2 = lens_ptr (db, ent); + *len1 = *len2 + 1; + } + } + ent = c; + } + + OUTPUT(ent); /* output the last code */ + + if(skb_out) + db->bytes_out += skb_out->len; /* Do not count bytes from here */ + db->uncomp_bytes += isize; + db->in_count += isize; + ++db->uncomp_count; + ++db->seqno; + + if (bitno < 32) + ++db->bytes_out; /* must be set before calling bsd_check */ + + /* + * Generate the clear command if needed + */ + + if (bsd_check(db)) + OUTPUT (CLEAR); + + /* + * Pad dribble bits of last code with ones. + * Do not emit a completely useless byte of ones. + */ + if (bitno < 32 && skb_out && skb_tailroom(skb_out) > 0) + *(skb_put(skb_out,1)) = (unsigned char) ((accm | (0xff << (bitno-8))) >> 24); + + /* + * Increase code size if we would have without the packet + * boundary because the decompressor will do so. + */ + if (max_ent >= mxcode && max_ent < db->maxmaxcode) + db->n_bits++; + + /* If output length is too large then this is an incompressible frame. */ + if (!skb_out || (skb_out && skb_out->len >= skb_in->len) ) { + ++db->incomp_count; + db->incomp_bytes += isize; + return 0; + } + + /* Count the number of compressed frames */ + ++db->comp_count; + db->comp_bytes += skb_out->len; + return skb_out->len; + +#undef OUTPUT +} + +/* + * Update the "BSD Compress" dictionary on the receiver for + * incompressible data by pretending to compress the incoming data. + */ +static void bsd_incomp (void *state, struct sk_buff *skb_in,int proto) +{ + bsd_compress (state, skb_in, NULL, proto); +} + +/* + * Decompress "BSD Compress". + */ +static int bsd_decompress (void *state, struct sk_buff *skb_in, struct sk_buff *skb_out, + struct isdn_ppp_resetparams *rsparm) +{ + struct bsd_db *db; + unsigned int max_ent; + unsigned long accm; + unsigned int bitno; /* 1st valid bit in accm */ + unsigned int n_bits; + unsigned int tgtbitno; /* bitno when we have a code */ + struct bsd_dict *dictp; + int seq; + unsigned int incode; + unsigned int oldcode; + unsigned int finchar; + unsigned char *p,*ibuf; + int ilen; + int codelen; + int extra; + + db = (struct bsd_db *) state; + max_ent = db->max_ent; + accm = 0; + bitno = 32; /* 1st valid bit in accm */ + n_bits = db->n_bits; + tgtbitno = 32 - n_bits; /* bitno when we have a code */ + + printk(KERN_DEBUG "bsd_decompress called\n"); + + if(!skb_in || !skb_out) { + printk(KERN_ERR "bsd_decompress called with NULL parameter\n"); + return DECOMP_ERROR; + } + + /* + * Get the sequence number. + */ + if( (p = skb_pull(skb_in,2)) == NULL) { + return DECOMP_ERROR; + } + p-=2; + seq = (p[0] << 8) + p[1]; + ilen = skb_in->len; + ibuf = skb_in->data; + + /* + * Check the sequence number and give up if it differs from + * the value we're expecting. + */ + if (seq != db->seqno) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: bad sequence # %d, expected %d\n", + db->unit, seq, db->seqno - 1); + } + return DECOMP_ERROR; + } + + ++db->seqno; + db->bytes_out += ilen; + + if(skb_tailroom(skb_out) > 0) + *(skb_put(skb_out,1)) = 0; + else + return DECOMP_ERR_NOMEM; + + oldcode = CLEAR; + + /* + * Keep the checkpoint correctly so that incompressible packets + * clear the dictionary at the proper times. + */ + + for (;;) { + if (ilen-- <= 0) { + db->in_count += (skb_out->len - 1); /* don't count the header */ + break; + } + + /* + * Accumulate bytes until we have a complete code. + * Then get the next code, relying on the 32-bit, + * unsigned accm to mask the result. + */ + + bitno -= 8; + accm |= *ibuf++ << bitno; + if (tgtbitno < bitno) + continue; + + incode = accm >> tgtbitno; + accm <<= n_bits; + bitno += n_bits; + + /* + * The dictionary must only be cleared at the end of a packet. + */ + + if (incode == CLEAR) { + if (ilen > 0) { + if (db->debug) + printk(KERN_DEBUG "bsd_decomp%d: bad CLEAR\n", db->unit); + return DECOMP_FATALERROR; /* probably a bug */ + } + bsd_clear(db); + break; + } + + if ((incode > max_ent + 2) || (incode > db->maxmaxcode) + || (incode > max_ent && oldcode == CLEAR)) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: bad code 0x%x oldcode=0x%x ", + db->unit, incode, oldcode); + printk(KERN_DEBUG "max_ent=0x%x skb->Len=%d seqno=%d\n", + max_ent, skb_out->len, db->seqno); + } + return DECOMP_FATALERROR; /* probably a bug */ + } + + /* Special case for KwKwK string. */ + if (incode > max_ent) { + finchar = oldcode; + extra = 1; + } else { + finchar = incode; + extra = 0; + } + + codelen = *(lens_ptr (db, finchar)); + if( skb_tailroom(skb_out) < codelen + extra) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: ran out of mru\n", db->unit); +#ifdef DEBUG + printk(KERN_DEBUG " len=%d, finchar=0x%x, codelen=%d,skblen=%d\n", + ilen, finchar, codelen, skb_out->len); +#endif + } + return DECOMP_FATALERROR; + } + + /* + * Decode this code and install it in the decompressed buffer. + */ + + p = skb_put(skb_out,codelen); + p += codelen; + while (finchar > LAST) { + struct bsd_dict *dictp2 = dict_ptr (db, finchar); + + dictp = dict_ptr (db, dictp2->cptr); + +#ifdef DEBUG + if (--codelen <= 0 || dictp->codem1 != finchar-1) { + if (codelen <= 0) { + printk(KERN_ERR "bsd_decomp%d: fell off end of chain ", db->unit); + printk(KERN_ERR "0x%x at 0x%x by 0x%x, max_ent=0x%x\n", incode, finchar, dictp2->cptr, max_ent); + } else { + if (dictp->codem1 != finchar-1) { + printk(KERN_ERR "bsd_decomp%d: bad code chain 0x%x finchar=0x%x ",db->unit, incode, finchar); + printk(KERN_ERR "oldcode=0x%x cptr=0x%x codem1=0x%x\n", oldcode, dictp2->cptr, dictp->codem1); + } + } + return DECOMP_FATALERROR; + } +#endif + + { + u32 fcode = dictp->fcode; + *--p = (fcode >> 16) & 0xff; + finchar = fcode & 0xffff; + } + } + *--p = finchar; + +#ifdef DEBUG + if (--codelen != 0) + printk(KERN_ERR "bsd_decomp%d: short by %d after code 0x%x, max_ent=0x%x\n", db->unit, codelen, incode, max_ent); +#endif + + if (extra) /* the KwKwK case again */ + *(skb_put(skb_out,1)) = finchar; + + /* + * If not first code in a packet, and + * if not out of code space, then allocate a new code. + * + * Keep the hash table correct so it can be used + * with uncompressed packets. + */ + if (oldcode != CLEAR && max_ent < db->maxmaxcode) { + struct bsd_dict *dictp2, *dictp3; + u16 *lens1, *lens2; + unsigned long fcode; + int hval, disp, indx; + + fcode = BSD_KEY(oldcode,finchar); + hval = BSD_HASH(oldcode,finchar,db->hshift); + dictp = dict_ptr (db, hval); + + /* look for a free hash table entry */ + if (dictp->codem1 < max_ent) { + disp = (hval == 0) ? 1 : hval; + do { + hval += disp; + if (hval >= db->hsize) + hval -= db->hsize; + dictp = dict_ptr (db, hval); + } while (dictp->codem1 < max_ent); + } + + /* + * Invalidate previous hash table entry + * assigned this code, and then take it over + */ + + dictp2 = dict_ptr (db, max_ent + 1); + indx = dictp2->cptr; + dictp3 = dict_ptr (db, indx); + + if (dictp3->codem1 == max_ent) + dictp3->codem1 = BADCODEM1; + + dictp2->cptr = hval; + dictp->codem1 = max_ent; + dictp->fcode = fcode; + db->max_ent = ++max_ent; + + /* Update the length of this string. */ + lens1 = lens_ptr (db, max_ent); + lens2 = lens_ptr (db, oldcode); + *lens1 = *lens2 + 1; + + /* Expand code size if needed. */ + if (max_ent >= MAXCODE(n_bits) && max_ent < db->maxmaxcode) { + db->n_bits = ++n_bits; + tgtbitno = 32-n_bits; + } + } + oldcode = incode; + } + + ++db->comp_count; + ++db->uncomp_count; + db->comp_bytes += skb_in->len - BSD_OVHD; + db->uncomp_bytes += skb_out->len; + + if (bsd_check(db)) { + if (db->debug) + printk(KERN_DEBUG "bsd_decomp%d: peer should have cleared dictionary on %d\n", + db->unit, db->seqno - 1); + } + return skb_out->len; +} + +/************************************************************* + * Table of addresses for the BSD compression module + *************************************************************/ + +static struct isdn_ppp_compressor ippp_bsd_compress = { + NULL,NULL, /* prev,next: overwritten by isdn_ppp */ + CI_BSD_COMPRESS, /* compress_proto */ + bsd_alloc, /* alloc */ + bsd_free, /* free */ + bsd_init, /* init */ + bsd_reset, /* reset */ + bsd_compress, /* compress */ + bsd_decompress, /* decompress */ + bsd_incomp, /* incomp */ + bsd_stats /* comp_stat */ +}; + +/************************************************************* + * Module support routines + *************************************************************/ + +int init_module(void) +{ + int answer = isdn_ppp_register_compressor (&ippp_bsd_compress); + if (answer == 0) + printk (KERN_INFO "PPP BSD Compression module registered\n"); + return answer; +} + +void cleanup_module(void) +{ + isdn_ppp_unregister_compressor (&ippp_bsd_compress); +} diff --git a/drivers/isdn/isdn_budget.c b/drivers/isdn/isdn_budget.c new file mode 100644 index 000000000..985c97ba8 --- /dev/null +++ b/drivers/isdn/isdn_budget.c @@ -0,0 +1,206 @@ +/* $Id: isdn_budget.c,v 1.3 1998/10/23 10:18:39 paul Exp $ + * + * Linux ISDN subsystem, budget-accounting for network interfaces. + * + * Copyright 1997 by Christian Lademann <cal@zls.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Log: isdn_budget.c,v $ + * Revision 1.3 1998/10/23 10:18:39 paul + * Implementation of "dialmode" (successor of "status") + * You also need current isdnctrl for this! + * + * Revision 1.2 1998/03/07 23:17:30 fritz + * Added RCS keywords + * Bugfix: Did not compile without isdn_dumppkt beeing enabled. + * + */ + +/* +30.06.97:cal:angelegt +04.11.97:cal:budget.period: int --> time_t +*/ + +#include <linux/config.h> +#define __NO_VERSION__ +#include <linux/module.h> +#include <linux/isdn.h> +#include "isdn_common.h" +#include "isdn_net.h" + +#ifdef CONFIG_ISDN_BUDGET + +#define VERBOSE_PRINTK(v, l, p...) { \ + if(dev->net_verbose >= (v)) { \ + printk(l ## p); \ + } else { ; } \ +} + + +int +isdn_net_budget(int type, struct device *ndev) { + isdn_net_local *lp = (isdn_net_local *)ndev->priv; + int i, ret = 0; + + switch(type) { + case ISDN_BUDGET_INIT: + for(i = 0; i < ISDN_BUDGET_NUM_BUDGET; i++) { + lp->budget [i] .amount = -1; + lp->budget [i] .used = 0; + lp->budget [i] .period = (time_t)0; + lp->budget [i] .period_started = (time_t)0; + lp->budget [i] .last_check = CURRENT_TIME; + lp->budget [i] .notified = 0; + } + + return(0); + break; + + case ISDN_BUDGET_CHECK_DIAL: + case ISDN_BUDGET_CHECK_CHARGE: + case ISDN_BUDGET_CHECK_ONLINE: + ret = 0; + + for(i = 0; i < ISDN_BUDGET_NUM_BUDGET; i++) { + if(lp->budget [i] .amount < 0) + continue; + + if(lp->budget [i] .period_started + lp->budget [i] .period < CURRENT_TIME) { + lp->budget [i] .used = 0; + lp->budget [i] .period_started = CURRENT_TIME; + lp->budget [i] .notified = 0; + } + + if(lp->budget [i] .used >= lp->budget [i] .amount) + ret |= (1 << i); + } + + switch(type) { + case ISDN_BUDGET_CHECK_DIAL: + if(! ret) { + lp->budget [ISDN_BUDGET_DIAL] .used++; + lp->budget [ISDN_BUDGET_DIAL] .last_check = CURRENT_TIME; + } + break; + + case ISDN_BUDGET_CHECK_CHARGE: + lp->budget [ISDN_BUDGET_CHARGE] .used++; + lp->budget [ISDN_BUDGET_CHARGE] .last_check = CURRENT_TIME; + break; + + case ISDN_BUDGET_CHECK_ONLINE: + if(lp->budget [ISDN_BUDGET_ONLINE] .last_check) { + lp->budget [ISDN_BUDGET_ONLINE] .used += (CURRENT_TIME - lp->budget [ISDN_BUDGET_ONLINE] .last_check); + } + + lp->budget [ISDN_BUDGET_ONLINE] .last_check = CURRENT_TIME; + break; + } + +/* + if(ret) + lp->flags |= ISDN_NET_DM_OFF; +*/ + for(i = 0; i < ISDN_BUDGET_NUM_BUDGET; i++) { + if(ret & (1 << i) && ! lp->budget [i] .notified) { + switch(i) { + case ISDN_BUDGET_DIAL: + printk(KERN_WARNING "isdn_budget: dial budget used up.\n"); + break; + + case ISDN_BUDGET_CHARGE: + printk(KERN_WARNING "isdn_budget: charge budget used up.\n"); + break; + + case ISDN_BUDGET_ONLINE: + printk(KERN_WARNING "isdn_budget: online budget used up.\n"); + break; + + default: + printk(KERN_WARNING "isdn_budget: budget #%d used up.\n", i); + break; + } + + lp->budget [i] .notified = 1; + } + } + + return(ret); + break; + + case ISDN_BUDGET_START_ONLINE: + lp->budget [ISDN_BUDGET_ONLINE] .last_check = CURRENT_TIME; + return(0); + + break; + } + + return(-1); +} + + +int +isdn_budget_ioctl(isdn_ioctl_budget *iocmd) { + isdn_net_dev *p = isdn_net_findif(iocmd->name); + + if(p) { + switch(iocmd->command) { + case ISDN_BUDGET_SET_BUDGET: + if(! suser()) + return(-EPERM); + + if(iocmd->budget < 0 || iocmd->budget > ISDN_BUDGET_NUM_BUDGET) + return(-EINVAL); + + if(iocmd->amount < 0) + iocmd->amount = -1; + + p->local->budget [iocmd->budget] .amount = iocmd->amount; + p->local->budget [iocmd->budget] .period = iocmd->period; + + if(iocmd->used <= 0) + p->local->budget [iocmd->budget] .used = 0; + else + p->local->budget [iocmd->budget] .used = iocmd->used; + + if(iocmd->period_started == (time_t)0) + p->local->budget [iocmd->budget] .period_started = CURRENT_TIME; + else + p->local->budget [iocmd->budget] .period_started = iocmd->period_started; + + return(0); + break; + + case ISDN_BUDGET_GET_BUDGET: + if(iocmd->budget < 0 || iocmd->budget > ISDN_BUDGET_NUM_BUDGET) + return(-EINVAL); + + iocmd->amount = p->local->budget [iocmd->budget] .amount; + iocmd->used = p->local->budget [iocmd->budget] .used; + iocmd->period = p->local->budget [iocmd->budget] .period; + iocmd->period_started = p->local->budget [iocmd->budget] .period_started; + + return(0); + break; + + default: + return(-EINVAL); + break; + } + } + return(-ENODEV); +} +#endif diff --git a/drivers/net/cycx_drv.c b/drivers/net/cycx_drv.c new file mode 100644 index 000000000..e75552e48 --- /dev/null +++ b/drivers/net/cycx_drv.c @@ -0,0 +1,663 @@ +/* +* cycx_drv.c cycx Support Module. +* +* This module is a library of common hardware-specific +* functions used by all Cyclades sync and some async (8x & 16x) +* drivers. +* +* Copyright: (c) 1998, 1999 Arnaldo Carvalho de Melo <acme@conectiva.com.br> +* +* Author: Arnaldo Carvalho de Melo <acme@conectiva.com.br> +* +* Based on sdladrv.c by Gene Kozin <genek@compuserve.com> +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version +* 2 of the License, or (at your option) any later version. +* ============================================================================ +* 1999/05/28 acme cycx_intack & cycx_intde gone for good +* 1999/05/18 acme lots of unlogged work, submitting to Linus... +* 1999/01/03 acme more judicious use of data types +* 1999/01/03 acme judicious use of data types :> +* cycx_inten trying to reset pending interrupts +* from cyclom 2x - I think this isn't the way to +* go, but for now... +* 1999/01/02 acme cycx_intack ok, I think there's nothing to do +* to ack an int in cycx_drv.c, only handle it in +* cyx_isr (or in the other protocols: cyp_isr, +* cyf_isr, when they get implemented. +* Dec 31, 1998 Arnaldo cycx_data_boot & cycx_code_boot fixed, crossing +* fingers to see x25_configure in cycx_x25.c +* work... :) +* Dec 26, 1998 Arnaldo load implementation fixed, seems to work! :) +* cycx_2x_dpmbase_options with all the possible +* DPM addresses (20). +* cycx_intr implemented (test this!) +* general code cleanup +* Dec 8, 1998 Ivan Passos Cyclom-2X firmware load implementation. +* Aug 8, 1998 Arnaldo Initial version. +*/ + +#include <linux/config.h> +#ifdef MODULE +#ifdef MODVERSIONS +#include <linux/modversions.h> +#endif +#include <linux/module.h> +#else +#define EXPORT_SYMBOL(function) +#endif +#include <linux/kernel.h> /* printk(), and other useful stuff */ +#include <linux/stddef.h> /* offsetof(), etc. */ +#include <linux/errno.h> /* return codes */ +#include <linux/sched.h> /* for jiffies, HZ, etc. */ +#include <linux/cycx_drv.h> /* API definitions */ +#include <linux/cycx_cfm.h> /* CYCX firmware module definitions */ +#include <linux/delay.h> /* udelay */ +#include <asm/io.h> /* for inb(), outb(), etc. */ + +#define MOD_VERSION 0 +#define MOD_RELEASE 1 + +#ifdef MODULE +MODULE_AUTHOR("Arnaldo Carvalho de Melo"); +MODULE_DESCRIPTION("Cyclades Sync Cards Driver."); +#endif + +/* Function Prototypes */ +/* Module entry points. These are called by the OS and must be public. */ +int init_module (void); +void cleanup_module (void); + +/* Hardware-specific functions */ +static int cycx_detect (cycxhw_t *hw); +static int cycx_load (cycxhw_t *hw, cfm_t *cfm, u32 len); +static int cycx_init (cycxhw_t *hw); +static int cycx_reset (cycxhw_t *hw); +static void cycx_bootcfg (cycxhw_t *hw); + +static int init_cycx_2x (cycxhw_t *hw); +static int reset_cycx_2x (u32 addr); +static int detect_cycx_2x (u32 addr); + +/* Miscellaneous functions */ +static void delay_cycx (int sec); +static int get_option_index (u32 *optlist, u32 optval); +static u16 checksum (u8 *buf, u32 len); + +#define wait_cyc(addr) cycx_exec(addr + CMD_OFFSET) + +/* Global Data + * Note: All data must be explicitly initialized!!! */ + +/* private data */ +static char modname[] = "cycx_drv"; +static char fullname[] = "Cyclom X Support Module"; +static char copyright[] = "(c) 1998, 1999 Arnaldo Carvalho de Melo"; + +/* Hardware configuration options. + * These are arrays of configuration options used by verification routines. + * The first element of each array is its size (i.e. number of options). + */ +static u32 cycx_2x_dpmbase_options[] = +{ + 20, + 0xA0000, 0xA4000, 0xA8000, 0xAC000, 0xB0000, 0xB4000, 0xB8000, + 0xBC000, 0xC0000, 0xC4000, 0xC8000, 0xCC000, 0xD0000, 0xD4000, + 0xD8000, 0xDC000, 0xE0000, 0xE4000, 0xE8000, 0xEC000 +}; + +static u32 cycx_2x_irq_options[] = { 7, 3, 5, 9, 10, 11, 12, 15 }; + +/* Kernel Loadable Module Entry Points */ +/* Module 'insert' entry point. + * o print announcement + * o initialize static data + * + * Return: 0 Ok + * < 0 error. + * Context: process */ +#ifdef MODULE +int init_module (void) +{ + printk(KERN_INFO "%s v%u.%u %s\n", + fullname, MOD_VERSION, MOD_RELEASE, copyright); + printk(KERN_INFO "version=0x%X\n", LINUX_VERSION_CODE); + return 0; +} +/* Module 'remove' entry point. + * o release all remaining system resources */ +void cleanup_module (void) +{ +} +#endif +/* Kernel APIs */ +/* Set up adapter. + * o detect adapter type + * o verify hardware configuration options + * o check for hardware conflicts + * o set up adapter shared memory + * o test adapter memory + * o load firmware + * Return: 0 ok. + * < 0 error */ +EXPORT_SYMBOL(cycx_setup); +int cycx_setup (cycxhw_t *hw, void *cfm, u32 len) +{ + u32 *irq_opt = NULL; /* IRQ options */ + u32 *dpmbase_opt = NULL;/* DPM window base options */ + int err = 0; + + if (cycx_detect(hw)) { + printk(KERN_ERR "%s: adapter Cyclom %uX not found at " + "address 0x%lX!\n", + modname, hw->type, (unsigned long) hw->dpmbase); + return -EINVAL; + } + + printk(KERN_INFO "%s: found Cyclom %uX card at address 0x%lx.\n", + modname, hw->type, (unsigned long) hw->dpmbase); + + switch (hw->type) { + case CYCX_2X: + irq_opt = cycx_2x_irq_options; + dpmbase_opt = cycx_2x_dpmbase_options; + break; + default: + printk(KERN_ERR "%s: unknown card.\n", modname); + return -EINVAL; + } + + /* Verify IRQ configuration options */ + if (!get_option_index(irq_opt, hw->irq)) { + printk (KERN_ERR "%s: IRQ %d is illegal!\n", modname, hw->irq); + return -EINVAL; + } + + /* Setup adapter dual-port memory window and test memory */ + if (!hw->dpmbase) { + printk(KERN_ERR "%s: you must specify the dpm address!\n", + modname); + return -EINVAL; + } + else if (!get_option_index(dpmbase_opt, hw->dpmbase)) { + printk(KERN_ERR "%s: memory address 0x%lX is illegal!\n", + modname, (unsigned long) hw->dpmbase); + return -EINVAL; + } + + hw->dpmsize = CYCX_WINDOWSIZE; + /* FIXME! Is this the only amount ever available? */ + hw->memory = 0x40000; + + cycx_init(hw); + + printk(KERN_INFO "%s: dual-port memory window is set at 0x%lX.\n", + modname, (unsigned long) hw->dpmbase); + printk(KERN_INFO "%s: found %luK bytes of on-board memory.\n", + modname, (unsigned long) hw->memory / 1024); + + /* Load firmware. If loader fails then shut down adapter */ + err = cycx_load(hw, cfm, len); + if (err) cycx_down(hw); /* shutdown adapter */ + return err; +} + +/* Shut down CYCX: disable shared memory access and interrupts, stop CPU,etc.*/ +EXPORT_SYMBOL(cycx_down); +int cycx_down (cycxhw_t *hw) +{ + return 0; /* FIXME: anything needed here? */ +} + +/* Enable interrupt generation. */ +EXPORT_SYMBOL(cycx_inten); +int cycx_inten (cycxhw_t *hw) +{ + switch (hw->type) { + case CYCX_2X: writeb (0, hw->dpmbase); break; + default: return -EINVAL; + } + + return 0; +} + +/* Generate an interrupt to adapter's CPU. */ +EXPORT_SYMBOL(cycx_intr); +int cycx_intr (cycxhw_t *hw) +{ + switch (hw->type) { + case CYCX_2X: + writew(0, hw->dpmbase + GEN_CYCX_INTR); + return 0; + default: return -EINVAL; + } + + return 0; +} + +/* Execute Adapter Command. + * o Set exec flag. + * o Busy-wait until flag is reset. */ +EXPORT_SYMBOL(cycx_exec); +int cycx_exec (u32 addr) +{ + u16 i = 0; + /* wait till addr content is zeroed */ + + while (readw(addr) != 0) { + udelay(1000); + if (++i > 50) return -1; + } + + return 0; +} + +/* Read absolute adapter memory. + * Transfer data from adapter's memory to data buffer. */ +EXPORT_SYMBOL(cycx_peek); +int cycx_peek (cycxhw_t *hw, u32 addr, void *buf, u32 len) +{ + if (len == 1) *(u8*)buf = readb (hw->dpmbase + addr); + else memcpy_fromio(buf, hw->dpmbase + addr, len); + + return 0; +} + +/* Write Absolute Adapter Memory. + * Transfer data from data buffer to adapter's memory. */ +EXPORT_SYMBOL(cycx_poke); +int cycx_poke (cycxhw_t *hw, u32 addr, void *buf, u32 len) +{ + if (len == 1) writeb (*(u8*)buf, hw->dpmbase + addr); + else memcpy_toio(hw->dpmbase + addr, buf, len); + + return 0; +} + +/* Hardware-Specific Functions */ +/* Detect adapter type. + * o if adapter type is specified then call detection routine for that adapter + * type. Otherwise call detection routines for every adapter types until + * adapter is detected. + * + * Notes: + * 1) Detection tests are destructive! Adapter will be left in shutdown state + * after the test. */ +static int cycx_detect (cycxhw_t *hw) +{ + int err = 0; + + if (!hw->dpmbase) return -EFAULT; + + switch (hw->type) { + case CYCX_2X: + if (!detect_cycx_2x(hw->dpmbase)) err = -ENODEV; + break; + default: + if (detect_cycx_2x(hw->dpmbase)) hw->type = CYCX_2X; + else err = -ENODEV; + } + + return err; +} + +/* Load Aux Routines */ +/* Reset board hardware. + return 1 if memory exists at addr and 0 if not. */ +static int memory_exists(u32 addr) +{ + int timeout = 0; + + for (; timeout < 3 ; timeout++) { + writew (TEST_PATTERN, addr + 0x10); + + if (readw (addr + 0x10) == TEST_PATTERN) + if (readw (addr + 0x10) == TEST_PATTERN) return 1; + + delay_cycx(1); + } + + return 0; +} + +/* Reset board hardware. */ +static int cycx_reset(cycxhw_t *hw) +{ + /* Reset board */ + switch (hw->type) { + case CYCX_2X: return reset_cycx_2x(hw->dpmbase); + } + + return -EINVAL; +} + +/* Load reset code. */ +static void reset_load(u32 addr, u8 *buffer, u32 cnt) +{ + u32 pt_code = addr + RESET_OFFSET; + u16 i, j; + + for ( i = 0 ; i < cnt ; i++) { + for (j = 0 ; j < 50 ; j++); /* Delay - FIXME busy waiting... */ + writeb(*buffer++, pt_code++); + } +} + +/* Load buffer using boot interface. + * o copy data from buffer to Cyclom-X memory + * o wait for reset code to copy it to right portion of memory */ +static int buffer_load(u32 addr, u8 *buffer, u32 cnt) +{ + memcpy_toio(addr + DATA_OFFSET, buffer, cnt); + writew(GEN_BOOT_DAT, addr + CMD_OFFSET); + return wait_cyc(addr); +} + +/* Set up entry point and kick start Cyclom-X CPU. */ +static void cycx_start (u32 addr) +{ + /* put in 0x30 offset the jump instruction to the code entry point */ + writeb(0xea, addr + 0x30); + writeb(0x00, addr + 0x31); + writeb(0xc4, addr + 0x32); + writeb(0x00, addr + 0x33); + writeb(0x00, addr + 0x34); + + /* cmd to start executing code */ + writew(GEN_START, addr + CMD_OFFSET); +} + +/* Load and boot reset code. */ +static void cycx_reset_boot(u32 addr, u8 *code, u32 len) +{ + u32 pt_start = addr + START_OFFSET; + + writeb(0xea, pt_start++); /* jmp to f000:3f00 */ + writeb(0x00, pt_start++); + writeb(0xfc, pt_start++); + writeb(0x00, pt_start++); + writeb(0xf0, pt_start); + reset_load(addr, code, len); + + /* 80186 was in hold, go */ + writeb(0, addr + START_CPU); + delay_cycx(1); +} + +/* Load data.bin file through boot (reset) interface. */ +static int cycx_data_boot(u32 addr, u8 *code, u32 len) +{ + u32 pt_boot_cmd = addr + CMD_OFFSET; + u32 i; + + /* boot buffer lenght */ + writew(CFM_LOAD_BUFSZ, pt_boot_cmd + sizeof(u16)); + writew(GEN_DEFPAR, pt_boot_cmd); + + if (wait_cyc(addr) < 0) return 2; + + writew(0, pt_boot_cmd + sizeof(u16)); + writew(0x4000, pt_boot_cmd + 2 * sizeof(u16)); + writew(GEN_SET_SEG, pt_boot_cmd); + + if (wait_cyc(addr) < 0) return 2; + + for (i = 0 ; i < len ; i += CFM_LOAD_BUFSZ) + if (buffer_load(addr, code + i, + MIN(CFM_LOAD_BUFSZ, (len - i))) < 0) { + printk(KERN_ERR "%s: Error !!\n", modname); + return 4; + } + + return 0; +} + + +/* Load code.bin file through boot (reset) interface. */ +static int cycx_code_boot(u32 addr, u8 *code, u32 len) +{ + u32 pt_boot_cmd = addr + CMD_OFFSET; + u32 i; + + /* boot buffer lenght */ + writew(CFM_LOAD_BUFSZ, pt_boot_cmd + sizeof(u16)); + writew(GEN_DEFPAR, pt_boot_cmd); + + if (wait_cyc(addr) == -1) return 2; + + writew(0x0000, pt_boot_cmd + sizeof(u16)); + writew(0xc400, pt_boot_cmd + 2 * sizeof(u16)); + writew(GEN_SET_SEG, pt_boot_cmd); + + if (wait_cyc(addr) == -1) return 1; + + for (i = 0 ; i < len ; i += CFM_LOAD_BUFSZ) + if (buffer_load(addr, code + i,MIN(CFM_LOAD_BUFSZ,(len - i)))) { + printk(KERN_ERR "%s: Error !!\n", modname); + return 4; + } + + return 0; +} + +/* Initialize CYCX hardware: setup memory window, IRQ, etc. */ +static int cycx_init (cycxhw_t *hw) +{ + switch (hw->type) { + case CYCX_2X: return init_cycx_2x(hw); + } + + return -EINVAL; +} + +/* Load adapter from the memory image of the CYCX firmware module. + * o verify firmware integrity and compatibility + * o start adapter up */ +static int cycx_load (cycxhw_t *hw, cfm_t *cfm, u32 len) +{ + int i, j, status; + cycx_header_t *img_hdr; + u8 *reset_image, + *data_image, + *code_image; + u32 pt_cycld = hw->dpmbase + 0x400; + u16 cksum; + + /* Announce */ + printk(KERN_INFO "%s: firmware signature=\"%s\"\n", + modname, cfm->signature); + + /* Verify firmware signature */ + if (strcmp(cfm->signature, CFM_SIGNATURE)) { + printk(KERN_ERR "%s:cycx_load: not Cyclom-2X firmware!\n", + modname); + return -EINVAL; + } + + printk(KERN_INFO "%s: firmware version=%u\n", modname, cfm->version); + + /* Verify firmware module format version */ + if (cfm->version != CFM_VERSION) { + printk(KERN_ERR "%s:cycx_load: firmware format %u rejected! " + "Expecting %u.\n", + modname, cfm->version, CFM_VERSION); + return -EINVAL; + } + + /* Verify firmware module length and checksum */ + cksum = checksum((u8*)&cfm->info, sizeof(cfm_info_t) + + cfm->info.codesize); +/* + FIXME cfm->info.codesize is off by 2 + if (((len - sizeof(cfm_t) - 1) != cfm->info.codesize) || +*/ + if (cksum != cfm->checksum) { + printk(KERN_ERR "%s:cycx_load: firmware corrupted!\n", modname); + printk(KERN_ERR " cdsize = 0x%x (expected 0x%lx)\n", + len - sizeof(cfm_t) - 1, cfm->info.codesize); + printk(KERN_ERR " chksum = 0x%x (expected 0x%x)\n", + cksum, cfm->checksum); + return -EINVAL; + } + + /* If everything is ok, set reset, data and code pointers */ + + img_hdr = (cycx_header_t*)(((u8*) cfm) + sizeof(cfm_t) - 1); +#ifdef FIRMWARE_DEBUG + printk(KERN_INFO "%s:cycx_load: image sizes\n", modname); + printk(KERN_INFO " reset=%lu\n", img_hdr->reset_size); + printk(KERN_INFO " data=%lu\n", img_hdr->data_size); + printk(KERN_INFO " code=%lu\n", img_hdr->code_size); +#endif + reset_image = ((u8 *) img_hdr) + sizeof(cycx_header_t); + data_image = reset_image + img_hdr->reset_size; + code_image = data_image + img_hdr->data_size; + + /*---- Start load ----*/ + /* Announce */ + printk(KERN_INFO "%s: loading firmware %s (ID=%u)...\n", modname, + (cfm->descr[0] != '\0') ? cfm->descr : "unknown firmware", + cfm->info.codeid); + + for (i = 0 ; i < 5 ; i++) { + /* Reset Cyclom hardware */ + if ((status = cycx_reset(hw)) != 0) { + printk(KERN_ERR "%s: dpm problem or board not " + "found (%d).\n", modname, status); + return -EINVAL; + } + + /* Load reset.bin */ + cycx_reset_boot(hw->dpmbase, reset_image, img_hdr->reset_size); + /* reset is waiting for boot */ + writew(GEN_POWER_ON, pt_cycld); + delay_cycx(1); + + for (j = 0 ; j < 3 ; j++) + if (!readw(pt_cycld)) goto reset_loaded; + else delay_cycx(1); + } + + printk(KERN_ERR "%s: reset not started.\n", modname); + return -EINVAL; +reset_loaded: + /* Load data.bin */ + if((status = cycx_data_boot(hw->dpmbase, data_image, + img_hdr->data_size)) != 0) { + printk(KERN_ERR "%s: cannot load data file (%d).\n", + modname, status); + return -EINVAL; + } + + /* Load code.bin */ + if((status = cycx_code_boot(hw->dpmbase, code_image, + img_hdr->code_size)) != 0) { + printk(KERN_ERR "%s: cannot load code file (%d).\n", + modname, status); + return -EINVAL; + } + + /* Prepare boot-time configuration data */ + cycx_bootcfg(hw); + + /* kick-off CPU */ + cycx_start(hw->dpmbase); + + /* Arthur Ganzert's tip: wait a while after the firmware loading... + seg abr 26 17:17:12 EST 1999 - acme */ + delay_cycx(7); + printk(KERN_INFO "%s: firmware loaded!\n", modname); + + /* enable interrupts */ + if (cycx_inten(hw)) { + printk(KERN_ERR "%s: adapter hardware failure!\n", modname); + return -EIO; + } + + return 0; +} + +/* Prepare boot-time firmware configuration data. + * o initialize configuration data area + From async.doc - V_3.4.0 - 07/18/1994 + - As of now, only static buffers are available to the user. + So, the bit VD_RXDIRC must be set in 'valid'. That means that user + wants to use the static transmission and reception buffers. */ +static void cycx_bootcfg (cycxhw_t *hw) +{ + /* use fixed buffers */ + writeb(FIXED_BUFFERS, hw->dpmbase + CONF_OFFSET); +} + +/* Initialize CYCX_2X adapter. */ +static int init_cycx_2x (cycxhw_t *hw) +{ + if (!detect_cycx_2x(hw->dpmbase)) return -ENODEV; + return 0; +} + +/* Detect Cyclom 2x adapter. + * Following tests are used to detect Cyclom 2x adapter: + * to be completed based on the tests done below + * Return 1 if detected o.k. or 0 if failed. + * Note: This test is destructive! Adapter will be left in shutdown + * state after the test. */ +static int detect_cycx_2x (u32 addr) +{ + printk(KERN_INFO "%s: looking for a cyclom 2x at 0x%lX...\n", + modname, (unsigned long) addr); + + reset_cycx_2x(addr); + return memory_exists(addr); +} + +/* Miscellaneous */ +/* Get option's index into the options list. + * Return option's index (1 .. N) or zero if option is invalid. */ +static int get_option_index (u32 *optlist, u32 optval) +{ + int i = 1; + for (; i <= optlist[0]; ++i) if (optlist[i] == optval) return i; + return 0; +} + +/* Reset adapter's CPU. */ +static int reset_cycx_2x (u32 addr) +{ + writeb (0, addr + RST_ENABLE); delay_cycx (2); + writeb (0, addr + RST_DISABLE); delay_cycx (2); + return memory_exists(addr) ? 0 : 1; +} + +/* Delay */ +static void delay_cycx (int sec) +{ +/* acme + Thu dez 31 21:45:16 EDT 1998 + FIXME I'll keep this comment here just in case, as of now I don't + know it all the contexts where this routine is used are interruptible... */ + + current->state = TASK_INTERRUPTIBLE; + current->counter = 0; /* make us low-priority */ + schedule_timeout(sec*HZ); +} + +/* Calculate 16-bit CRC using CCITT polynomial. */ +static u16 checksum (u8 *buf, u32 len) +{ + u16 crc = 0; + u16 mask, flag; + + for (; len; --len, ++buf) + for (mask = 0x80; mask; mask >>= 1) { + flag = (crc & 0x8000); + crc <<= 1; + crc |= ((*buf & mask) ? 1 : 0); + if (flag) crc ^= 0x1021; + } + + return crc; +} +/* End */ diff --git a/drivers/net/cycx_main.c b/drivers/net/cycx_main.c new file mode 100644 index 000000000..d0371c3a1 --- /dev/null +++ b/drivers/net/cycx_main.c @@ -0,0 +1,377 @@ +/* +* cycx_main.c Cyclades Cyclom X Multiprotocol WAN Link Driver. Main module. +* +* Author: Arnaldo Carvalho de Melo <acme@conectiva.com.br> +* +* Copyright: (c) 1998, 1999 Arnaldo Carvalho de Melo +* +* Based on sdlamain.c by Gene Kozin <genek@compuserve.com> & +* Jaspreet Singh <jaspreet@sangoma.com> +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version +* 2 of the License, or (at your option) any later version. +* ============================================================================ +* 1999/05/19 acme works directly linked into the kernel +* init_waitqueue_head for 2.3.* kernel +* 1999/05/18 acme major cleanup (polling not needed), etc +* Aug 28, 1998 Arnaldo minor cleanup (ioctls for firmware deleted) +* queue_task activated +* Aug 08, 1998 Arnaldo Initial version. +*/ + +#include <linux/config.h> /* OS configuration options */ +#include <linux/stddef.h> /* offsetof(), etc. */ +#include <linux/errno.h> /* return codes */ +#include <linux/string.h> /* inline memset(), etc. */ +#include <linux/malloc.h> /* kmalloc(), kfree() */ +#include <linux/kernel.h> /* printk(), and other useful stuff */ +#include <linux/module.h> /* support for loadable modules */ +#include <linux/ioport.h> /* request_region(), release_region() */ +#include <linux/tqueue.h> /* for kernel task queues */ +#include <linux/wanrouter.h> /* WAN router definitions */ +#include <linux/cyclomx.h> /* cyclomx common user API definitions */ +#include <asm/uaccess.h> /* kernel <-> user copy */ +#include <linux/init.h> /* __initfunc (when not using as a module) */ + +#ifdef MODULE +MODULE_AUTHOR("Arnaldo Carvalho de Melo"); +MODULE_DESCRIPTION("Cyclades Sync Cards Driver."); +#endif + +/* Defines & Macros */ + +#define DRV_VERSION 0 /* version number */ +#define DRV_RELEASE 3 /* release (minor version) number */ +#define MAX_CARDS 1 /* max number of adapters */ + +#ifndef CONFIG_CYCLOMX_CARDS /* configurable option */ +#define CONFIG_CYCLOMX_CARDS 1 +#endif + +/* Function Prototypes */ + +/* Module entry points */ +int init_module (void); +void cleanup_module (void); + +/* WAN link driver entry points */ +static int setup (wan_device_t *wandev, wandev_conf_t *conf); +static int shutdown (wan_device_t *wandev); +static int ioctl (wan_device_t *wandev, unsigned cmd, unsigned long arg); + +/* Miscellaneous functions */ +static void cycx_isr (int irq, void *dev_id, struct pt_regs *regs); + +/* Global Data + * Note: All data must be explicitly initialized!!! + */ + +/* private data */ +static char drvname[] = "cyclomx"; +static char fullname[] = "CYCLOM X(tm) Multiprotocol Driver"; +static char copyright[] = "(c) 1998, 1999 Arnaldo Carvalho de Melo"; +static int ncards = CONFIG_CYCLOMX_CARDS; +static cycx_t *card_array = NULL; /* adapter data space */ + +/* Kernel Loadable Module Entry Points */ + +/* + * Module 'insert' entry point. + * o print announcement + * o allocate adapter data space + * o initialize static data + * o register all cards with WAN router + * o calibrate CYCX shared memory access delay. + * + * Return: 0 Ok + * < 0 error. + * Context: process + */ +#ifdef MODULE +int init_module (void) +#else +__initfunc(int cyclomx_init (void)) +#endif +{ + int cnt, err = 0; + + printk(KERN_INFO "%s v%u.%u %s\n", + fullname, DRV_VERSION, DRV_RELEASE, copyright); + + /* Verify number of cards and allocate adapter data space */ + ncards = min(ncards, MAX_CARDS); + ncards = max(ncards, 1); + card_array = kmalloc(sizeof(cycx_t) * ncards, GFP_KERNEL); + + if (card_array == NULL) return -ENOMEM; + + memset(card_array, 0, sizeof(cycx_t) * ncards); + + /* Register adapters with WAN router */ + for (cnt = 0; cnt < ncards; ++cnt) { + cycx_t *card = &card_array[cnt]; + wan_device_t *wandev = &card->wandev; + + sprintf(card->devname, "%s%d", drvname, cnt + 1); + wandev->magic = ROUTER_MAGIC; + wandev->name = card->devname; + wandev->private = card; + wandev->enable_tx_int = 0; + wandev->setup = &setup; + wandev->shutdown = &shutdown; + wandev->ioctl = &ioctl; + err = register_wan_device(wandev); + + if (err) { + printk(KERN_ERR + "%s: %s registration failed with error %d!\n", + drvname, card->devname, err); + break; + } + } + + if (cnt) ncards = cnt; /* adjust actual number of cards */ + else { + kfree(card_array); + err = -ENODEV; + } + + return err; +} + +/* + * Module 'remove' entry point. + * o unregister all adapters from the WAN router + * o release all remaining system resources + */ +#ifdef MODULE +void cleanup_module (void) +{ + int i = 0; + + for (; i < ncards; ++i) { + cycx_t *card = &card_array[i]; + unregister_wan_device(card->devname); + } + + kfree(card_array); +} +#endif +/* WAN Device Driver Entry Points */ +/* + * Setup/confugure WAN link driver. + * o check adapter state + * o make sure firmware is present in configuration + * o allocate interrupt vector + * o setup CYCLOM X hardware + * o call appropriate routine to perform protocol-specific initialization + * o mark I/O region as used + * + * This function is called when router handles ROUTER_SETUP IOCTL. The + * configuration structure is in kernel memory (including extended data, if + * any). + */ +static int setup (wan_device_t *wandev, wandev_conf_t *conf) +{ + cycx_t *card; + int err = 0; + int irq; + + /* Sanity checks */ + if (!wandev || !wandev->private || !conf) return -EFAULT; + + card = wandev->private; + + if (wandev->state != WAN_UNCONFIGURED) return -EBUSY; + + if (!conf->data_size || (conf->data == NULL)) { + printk(KERN_ERR "%s: firmware not found in configuration " + "data!\n", wandev->name); + return -EINVAL; + } + + if (conf->irq <= 0) { + printk(KERN_ERR "%s: can't configure without IRQ!\n", + wandev->name); + return -EINVAL; + } + + /* Allocate IRQ */ + irq = conf->irq == 2 ? 9 : conf->irq; /* IRQ2 -> IRQ9 */ + + if (request_irq(irq, cycx_isr, 0, wandev->name, card)) { + printk(KERN_ERR "%s: can't reserve IRQ %d!\n", + wandev->name, irq); + return -EINVAL; + } + + /* Configure hardware, load firmware, etc. */ + memset(&card->hw, 0, sizeof(cycxhw_t)); + card->hw.irq = (conf->irq == 9) ? 2 : conf->irq; + card->hw.dpmbase = conf->maddr; + card->hw.dpmsize = CYCX_WINDOWSIZE; + card->hw.type = conf->hw_opt[0]; + card->hw.fwid = CFID_X25_2X; + card->lock = SPIN_LOCK_UNLOCKED; +#if LINUX_VERSION_CODE >= 0x020300 + init_waitqueue_head(&card->wait_stats); +#else + card->wait_stats = NULL; +#endif + err = cycx_setup(&card->hw, conf->data, conf->data_size); + + if (err) { + free_irq(irq, card); + return err; + } + + /* Intialize WAN device data space */ + wandev->irq = irq; + wandev->dma = wandev->ioport = 0; + wandev->maddr = (unsigned long*)card->hw.dpmbase; + wandev->msize = card->hw.dpmsize; + wandev->hw_opt[0] = card->hw.type; + wandev->hw_opt[1] = card->hw.pclk; + wandev->hw_opt[2] = card->hw.memory; + wandev->hw_opt[3] = card->hw.fwid; + + /* Protocol-specific initialization */ + switch (card->hw.fwid) { +#ifdef CONFIG_CYCLOMX_X25 + case CFID_X25_2X: err = cyx_init(card, conf); break; +#endif + default: + printk(KERN_ERR "%s: this firmware is not supported!\n", + wandev->name); + err = -EINVAL; + } + + if (err) { + cycx_down(&card->hw); + free_irq(irq, card); + return err; + } + + wandev->critical = 0; + return 0; +} + +/* + * Shut down WAN link driver. + * o shut down adapter hardware + * o release system resources. + * + * This function is called by the router when device is being unregistered or + * when it handles ROUTER_DOWN IOCTL. + */ +static int shutdown (wan_device_t *wandev) +{ + cycx_t *card; + + /* sanity checks */ + if (!wandev || !wandev->private) return -EFAULT; + + if (wandev->state == WAN_UNCONFIGURED) return 0; + + card = wandev->private; + wandev->state = WAN_UNCONFIGURED; + cycx_down(&card->hw); + printk(KERN_INFO "%s: irq %d being freed!\n", wandev->name,wandev->irq); + free_irq(wandev->irq, card); + wandev->critical = 0; + return 0; +} + +/* + * Driver I/O control. + * o verify arguments + * o perform requested action + * + * This function is called when router handles one of the reserved user + * IOCTLs. Note that 'arg' stil points to user address space. + */ +static int ioctl (wan_device_t *wandev, unsigned cmd, unsigned long arg) +{ + return -EINVAL; +} + +/* Miscellaneous */ +/* + * CYCX Interrupt Service Routine. + * o acknowledge CYCX hardware interrupt. + * o call protocol-specific interrupt service routine, if any. + */ +static void cycx_isr (int irq, void *dev_id, struct pt_regs *regs) +{ +#define card ((cycx_t*)dev_id) + if (!card || card->wandev.state == WAN_UNCONFIGURED) return; + + if (card->in_isr) { + printk(KERN_WARNING "%s: interrupt re-entrancy on IRQ %d!\n", + card->devname, card->wandev.irq); + return; + } + + if (card->isr) card->isr(card); +#undef card +} + +/* + * This routine is called by the protocol-specific modules when network + * interface is being open. The only reason we need this, is because we + * have to call MOD_INC_USE_COUNT, but cannot include 'module.h' where it's + * defined more than once into the same kernel module. + */ +void cyclomx_open (cycx_t *card) +{ + ++card->open_cnt; + MOD_INC_USE_COUNT; +} + +/* + * This routine is called by the protocol-specific modules when network + * interface is being closed. The only reason we need this, is because we + * have to call MOD_DEC_USE_COUNT, but cannot include 'module.h' where it's + * defined more than once into the same kernel module. + */ +void cyclomx_close (cycx_t *card) +{ + --card->open_cnt; + MOD_DEC_USE_COUNT; +} + +/* Set WAN device state. */ +void cyclomx_set_state (cycx_t *card, int state) +{ + unsigned long flags; + + save_flags(flags); cli(); + + if (card->wandev.state != state) { + switch (state) { + case WAN_CONNECTED: + printk (KERN_INFO "%s: link connected!\n", + card->devname); + break; + + case WAN_CONNECTING: + printk (KERN_INFO "%s: link connecting...\n", + card->devname); + break; + + case WAN_DISCONNECTED: + printk (KERN_INFO "%s: link disconnected!\n", + card->devname); + break; + } + + card->wandev.state = state; + } + + card->state_tick = jiffies; + restore_flags(flags); +} + +/* End */ diff --git a/drivers/net/cycx_x25.c b/drivers/net/cycx_x25.c new file mode 100644 index 000000000..d6fbe07c3 --- /dev/null +++ b/drivers/net/cycx_x25.c @@ -0,0 +1,1538 @@ +/* +* cycx_x25.c CYCLOM X Multiprotocol WAN Link Driver. X.25 module. +* +* Author: Arnaldo Carvalho de Melo <acme@conectiva.com.br> +* Copyright: (c) 1998, 1999 Arnaldo Carvalho de Melo +* +* Based on sdla_x25.c by Gene Kozin <genek@compuserve.com> +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version +* 2 of the License, or (at your option) any later version. +* ============================================================================ +* 1999/05/28 acme fixed nibble_to_byte, ackvc now properly treated +* if_send simplified +* 1999/05/25 acme fixed t1, t2, t21 & t23 configuration +* use spinlocks instead of cli/sti in some points +* 1999/05/24 acme finished the x25_get_stat function +* 1999/05/23 acme dev->type = ARPHRD_X25 (tcpdump only works, +* AFAIT, with ARPHRD_ETHER). This seems to be +* needed to use socket(AF_X25)... +* Now the config file must specify a peer media +* address for svc channes over a crossover cable. +* Removed hold_timeout from x25_channel_t, +* not used. +* A little enhancement in the DEBUG processing +* 1999/05/22 acme go to DISCONNECTED in disconnect_confirm_intr, +* instead of chan_disc. +* 1999/05/16 marcelo fixed timer initialization in SVCs +* 1999/01/05 acme x25_configure now get (most of) all +* parameters... +* 1999/01/05 acme pktlen now (correctly) uses log2 (value +* configured) +* 1999/01/03 acme judicious use of data types (u8, u16, u32, etc) +* 1999/01/03 acme cyx_isr: reset dpmbase to acknowledge +* indication (interrupt from cyclom 2x) +* 1999/01/02 acme cyx_isr: first hackings... +* 1999/01/0203 acme when initializing an array don't give less +* elements than declared... +* example: char send_cmd[6] = "?\xFF\x10"; +* you'll gonna lose a couple hours, 'cause your +* brain won't admit that there's an error in the +* above declaration... the side effect is that +* memset is put into the unresolved symbols +* instead of using the inline memset functions... +* 1999/01/02 acme began chan_connect, chan_send, x25_send +* Dec 31, 1998 Arnaldo x25_configure +* this code can be compiled as non module +* Dec 27, 1998 Arnaldo code cleanup +* IPX code wiped out! let's decrease code +* complexity for now, remember: I'm learning! :) +* bps_to_speed_code OK +* Dec 26, 1998 Arnaldo Minimal debug code cleanup +* Aug 08, 1998 Arnaldo Initial version. +*/ +#define CYCLOMX_X25_DEBUG 1 + +#include <linux/version.h> +#include <linux/kernel.h> /* printk(), and other useful stuff */ +#include <linux/stddef.h> /* offsetof(), etc. */ +#include <linux/errno.h> /* return codes */ +#include <linux/string.h> /* inline memset(), etc. */ +#include <linux/malloc.h> /* kmalloc(), kfree() */ +#include <linux/wanrouter.h> /* WAN router definitions */ +#include <asm/byteorder.h> /* htons(), etc. */ +#include <linux/if_arp.h> /* ARPHRD_X25 */ +#include <linux/cyclomx.h> /* CYCLOM X common user API definitions */ +#include <linux/cycx_x25.h> /* X.25 firmware API definitions */ + +/* Defines & Macros */ +#define MAX_CMD_RETRY 5 +#define X25_CHAN_MTU 2048 /* unfragmented logical channel MTU */ +#define OUT_INTR 1 +#define IN_INTR 0 + +/* Data Structures */ +/* This is an extention of the 'struct device' we create for each network + interface to keep the rest of X.25 channel-specific data. */ +typedef struct x25_channel { + char name[WAN_IFNAME_SZ+1]; /* interface name, ASCIIZ */ + char addr[WAN_ADDRESS_SZ+1]; /* media address, ASCIIZ */ + char *local_addr; /* local media address, ASCIIZ - + svc thru crossover cable */ + s16 lcn; /* logical channel number/conn.req.key*/ + u8 link; + struct timer_list timer; /* timer used for svc channel disc. */ + spinlock_t lock; + u16 protocol; /* ethertype, 0 - multiplexed */ + u8 svc; /* 0 - permanent, 1 - switched */ + u8 state; /* channel state */ + u8 drop_sequence; /* mark sequence for dropping */ + u32 idle_tmout; /* sec, before disconnecting */ + struct sk_buff *rx_skb; /* receive socket buffer */ + cycx_t *card; /* -> owner */ + struct enet_statistics ifstats; /* interface statistics */ +} x25_channel_t; + +/* Function Prototypes */ +/* WAN link driver entry points. These are called by the WAN router module. */ +static int update (wan_device_t *wandev), + new_if (wan_device_t *wandev, struct device *dev,wanif_conf_t *conf), + del_if (wan_device_t *wandev, struct device *dev); + +/* Network device interface */ +static int if_init (struct device *dev), + if_open (struct device *dev), + if_close (struct device *dev), + if_header (struct sk_buff *skb, struct device *dev, + u16 type, void *daddr, void *saddr, unsigned len), + if_rebuild_hdr (struct sk_buff *skb), + if_send (struct sk_buff *skb, struct device *dev); + +static struct net_device_stats * if_stats (struct device *dev); + +/* Interrupt handlers */ +static void cyx_isr (cycx_t *card), + tx_intr (cycx_t *card, TX25Cmd *cmd), + rx_intr (cycx_t *card, TX25Cmd *cmd), + log_intr (cycx_t *card, TX25Cmd *cmd), + stat_intr (cycx_t *card, TX25Cmd *cmd), + connect_confirm_intr (cycx_t *card, TX25Cmd *cmd), + disconnect_confirm_intr (cycx_t *card, TX25Cmd *cmd), + connect_intr (cycx_t *card, TX25Cmd *cmd), + disconnect_intr (cycx_t *card, TX25Cmd *cmd), + spur_intr (cycx_t *card, TX25Cmd *cmd); + +/* X.25 firmware interface functions */ +static int x25_configure (cycx_t *card, TX25Config *conf), + x25_get_stats (cycx_t *card), + x25_send (cycx_t *card, u8 link, u8 lcn, u8 bitm, int len,void *buf), + x25_connect_response (cycx_t *card, x25_channel_t *chan), + x25_disconnect_response (cycx_t *card, u8 link, u8 lcn); + +/* Miscellaneous functions */ +static int chan_connect (struct device *dev), + chan_send (struct device *dev, struct sk_buff *skb); + +static void set_chan_state (struct device *dev, u8 state, u8 outside_intr), + nibble_to_byte (u8 *s, u8 *d, u8 len, u8 nibble), + reset_timer (struct device *dev), + chan_disc (struct device *dev), + chan_timer (unsigned long data); + +static u8 bps_to_speed_code (u32 bps); +static u8 log2 (u32 n); + +static unsigned dec_to_uint (u8 *str, int len); + +static struct device *get_dev_by_lcn (wan_device_t *wandev, s16 lcn); +static struct device *get_dev_by_dte_addr (wan_device_t *wandev, char *dte); + +#ifdef CYCLOMX_X25_DEBUG +static void hex_dump(char *msg, unsigned char *p, int len); +static void x25_dump_config(TX25Config *conf); +static void x25_dump_stats(TX25Stats *stats); +static void x25_dump_devs(wan_device_t *wandev); +#define dprintk(format, a...) printk(format, ##a) +#else +#define hex_dump(msg, p, len) +#define x25_dump_config(conf) +#define x25_dump_stats(stats) +#define x25_dump_devs(wandev) +#define dprintk(format, a...) +#endif +/* Public Functions */ + +/* X.25 Protocol Initialization routine. + * + * This routine is called by the main CYCLOM X module during setup. At this + * point adapter is completely initialized and X.25 firmware is running. + * o read firmware version (to make sure it's alive) + * o configure adapter + * o initialize protocol-specific fields of the adapter data space. + * + * Return: 0 o.k. + * < 0 failure. */ +int cyx_init (cycx_t *card, wandev_conf_t *conf) +{ + TX25Config cfg; + + /* Verify configuration ID */ + if (conf->config_id != WANCONFIG_X25) { + printk(KERN_INFO "%s: invalid configuration ID %u!\n", + card->devname, conf->config_id); + return -EINVAL; + } + + /* Initialize protocol-specific fields */ + card->mbox = card->hw.dpmbase + X25_MBOX_OFFS; + card->u.x.critical = 0; /* critical section flag */ + card->u.x.connection_keys = 0; + + /* Configure adapter. Here we set resonable defaults, then parse + * device configuration structure and set configuration options. + * Most configuration options are verified and corrected (if + * necessary) since we can't rely on the adapter to do so and don't + * want it to fail either. */ + memset(&cfg, 0, sizeof(cfg)); + cfg.link = 0; + cfg.clock = conf->clocking == WANOPT_EXTERNAL ? 8 : 55; + cfg.speed = bps_to_speed_code(conf->bps); + cfg.n3win = 7; + cfg.n2win = 2; + cfg.n2 = 5; + cfg.nvc = 1; + cfg.npvc = 1; + cfg.flags = 0x02; /* default = V35 */ + cfg.t1 = 10; /* line carrier timeout */ + cfg.t2 = 29; /* tx timeout */ + cfg.t21 = 180; /* CALL timeout */ + cfg.t23 = 180; /* CLEAR timeout */ + + /* adjust MTU */ + if (!conf->mtu || conf->mtu >= 512) + card->wandev.mtu = 512; + else if (conf->mtu >= 256) + card->wandev.mtu = 256; + else if (conf->mtu >= 128) + card->wandev.mtu = 128; + else + card->wandev.mtu = 64; + + cfg.pktlen = log2(card->wandev.mtu); + + if (conf->station == WANOPT_DTE) { + cfg.locaddr = 3; /* DTE */ + cfg.remaddr = 1; /* DCE */ + } else { + cfg.locaddr = 1; /* DCE */ + cfg.remaddr = 3; /* DTE */ + } + + if (conf->interface == WANOPT_RS232) + cfg.flags = 0; /* FIXME just reset the 2nd bit */ + + if (conf->u.x25.hi_pvc) { + card->u.x.hi_pvc = min(conf->u.x25.hi_pvc, 4095); + card->u.x.lo_pvc = min(conf->u.x25.lo_pvc, card->u.x.hi_pvc); + } + + if (conf->u.x25.hi_svc) { + card->u.x.hi_svc = min(conf->u.x25.hi_svc, 4095); + card->u.x.lo_svc = min(conf->u.x25.lo_svc, card->u.x.hi_svc); + } + + if (card->u.x.lo_pvc == 255) + cfg.npvc = 0; + else + cfg.npvc = card->u.x.hi_pvc - card->u.x.lo_pvc + 1; + + cfg.nvc = card->u.x.hi_svc - card->u.x.lo_svc + 1 + cfg.npvc; + + if (conf->u.x25.hdlc_window) + cfg.n2win = min(conf->u.x25.hdlc_window, 7); + + if (conf->u.x25.pkt_window) + cfg.n3win = min(conf->u.x25.pkt_window, 7); + + if (conf->u.x25.t1) + cfg.t1 = min(conf->u.x25.t1, 30); + + if (conf->u.x25.t2) + cfg.t2 = min(conf->u.x25.t2, 30); + + if (conf->u.x25.t11_t21) + cfg.t21 = min(conf->u.x25.t11_t21, 30); + + if (conf->u.x25.t13_t23) + cfg.t23 = min(conf->u.x25.t13_t23, 30); + + if (conf->u.x25.n2) + cfg.n2 = min(conf->u.x25.n2, 30); + + /* initialize adapter */ + if (x25_configure(card, &cfg)) + return -EIO; + + /* Initialize protocol-specific fields of adapter data space */ + card->wandev.bps = conf->bps; + card->wandev.interface = conf->interface; + card->wandev.clocking = conf->clocking; + card->wandev.station = conf->station; + card->isr = &cyx_isr; + card->exec = NULL; + card->wandev.update = &update; + card->wandev.new_if = &new_if; + card->wandev.del_if = &del_if; + card->wandev.state = WAN_DISCONNECTED; + card->wandev.enable_tx_int = card->irq_dis_if_send_count = 0; + return 0; +} + +/* WAN Device Driver Entry Points */ +/* Update device status & statistics. */ +static int update (wan_device_t *wandev) +{ + /* sanity checks */ + if (!wandev || !wandev->private) + return -EFAULT; + + if (wandev->state == WAN_UNCONFIGURED) + return -ENODEV; + + x25_get_stats(wandev->private); + return 0; +} + +/* Create new logical channel. + * This routine is called by the router when ROUTER_IFNEW IOCTL is being + * handled. + * o parse media- and hardware-specific configuration + * o make sure that a new channel can be created + * o allocate resources, if necessary + * o prepare network device structure for registaration. + * + * Return: 0 o.k. + * < 0 failure (channel will not be created) */ +static int new_if (wan_device_t *wandev, struct device *dev, wanif_conf_t *conf) +{ + cycx_t *card = wandev->private; + x25_channel_t *chan; + int err = 0; + + if (conf->name[0] == '\0' || strlen(conf->name) > WAN_IFNAME_SZ) { + printk(KERN_INFO "%s: invalid interface name!\n",card->devname); + return -EINVAL; + } + + /* allocate and initialize private data */ + if ((chan = kmalloc(sizeof(x25_channel_t), GFP_KERNEL)) == NULL) + return -ENOMEM; + + memset(chan, 0, sizeof(x25_channel_t)); + strcpy(chan->name, conf->name); + chan->card = card; + chan->link = conf->port; + chan->protocol = ETH_P_IP; + chan->rx_skb = NULL; + /* only used in svc connected thru crossover cable */ + chan->local_addr = NULL; + chan->lock = SPIN_LOCK_UNLOCKED; + + if (conf->addr[0] == '@') { /* SVC */ + int local_len = strlen(conf->local_addr); + + if (local_len) { + if (local_len > WAN_ADDRESS_SZ) { + printk(KERN_ERR "%s: %s local addr too long!\n", + wandev->name, chan->name); + kfree(chan); + return -EINVAL; + } else if ((chan->local_addr = kmalloc(local_len + 1, + GFP_KERNEL)) == NULL) { + kfree(chan); + return ENOMEM; + } + + strncpy(chan->local_addr, conf->local_addr, + WAN_ADDRESS_SZ); + } + + chan->svc = 1; + strncpy(chan->addr, &conf->addr[1], WAN_ADDRESS_SZ); + init_timer(&chan->timer); + chan->timer.function = chan_timer; + chan->timer.data = (unsigned long) dev; + + /* Set channel timeouts (default if not specified) */ + chan->idle_tmout = conf->idle_timeout ? conf->idle_timeout : 90; + } else if (is_digit(conf->addr[0])) { /* PVC */ + s16 lcn = dec_to_uint(conf->addr, 0); + + if (lcn >= card->u.x.lo_pvc && lcn <= card->u.x.hi_pvc) + chan->lcn = lcn; + else { + printk(KERN_ERR + "%s: PVC %u is out of range on interface %s!\n", + wandev->name, lcn, chan->name); + err = -EINVAL; + } + } else { + printk(KERN_ERR "%s: invalid media address on interface %s!\n", + wandev->name, chan->name); + err = -EINVAL; + } + + if (err) { + if (chan->local_addr) + kfree(chan->local_addr); + kfree(chan); + return err; + } + + /* prepare network device data space for registration */ + dev->name = chan->name; + dev->init = &if_init; + dev->priv = chan; + return 0; +} + +/* Delete logical channel. */ +static int del_if (wan_device_t *wandev, struct device *dev) +{ + if (!dev) { + printk(KERN_ERR "cycx_x25:del_if:dev == NULL!\n"); + return 0; + } + + if (dev->priv) { + x25_channel_t *chan = dev->priv; + if (chan->svc) { + if (chan->local_addr) + kfree(chan->local_addr); + + if (chan->state == WAN_CONNECTED) + del_timer(&chan->timer); + } + kfree(chan); + dev->priv = NULL; + } + + return 0; +} + +/* Network Device Interface */ +/* Initialize Linux network interface. + * + * This routine is called only once for each interface, during Linux network + * interface registration. Returning anything but zero will fail interface + * registration. */ +static int if_init (struct device *dev) +{ + x25_channel_t *chan = dev->priv; + cycx_t *card = chan->card; + wan_device_t *wandev = &card->wandev; + + /* Initialize device driver entry points */ + dev->open = &if_open; + dev->stop = &if_close; + dev->hard_header = &if_header; + dev->rebuild_header = &if_rebuild_hdr; + dev->hard_start_xmit = &if_send; + dev->get_stats = &if_stats; + + /* Initialize media-specific parameters */ + dev->mtu = X25_CHAN_MTU; + dev->type = ARPHRD_X25; /* ARP h/w type */ + dev->hard_header_len = 0; /* media header length */ + dev->addr_len = 0; /* hardware address length */ + + if (!chan->svc) + *(u16*)dev->dev_addr = htons(chan->lcn); + + /* Initialize hardware parameters (just for reference) */ + dev->irq = wandev->irq; + dev->dma = wandev->dma; + dev->base_addr = wandev->ioport; + dev->mem_start = (unsigned long)wandev->maddr; + dev->mem_end = (unsigned long)(wandev->maddr + wandev->msize - 1); + dev->flags |= IFF_NOARP; + + /* Set transmit buffer queue length */ + dev->tx_queue_len = 10; + + /* Initialize socket buffers */ + dev_init_buffers(dev); + set_chan_state(dev, WAN_DISCONNECTED, OUT_INTR); + return 0; +} + +/* Open network interface. + * o prevent module from unloading by incrementing use count + * o if link is disconnected then initiate connection + * + * Return 0 if O.k. or errno. */ +static int if_open (struct device *dev) +{ + x25_channel_t *chan = dev->priv; + cycx_t *card = chan->card; + + if (dev->start) + return -EBUSY; /* only one open is allowed */ + + if (test_and_set_bit(0, (void*)&card->wandev.critical)) + return -EAGAIN; + + dev->interrupt = 0; + dev->tbusy = 0; + dev->start = 1; + cyclomx_open(card); + + card->wandev.critical = 0; + return 0; +} + +/* Close network interface. + * o reset flags. + * o if there's no more open channels then disconnect physical link. */ +static int if_close (struct device *dev) +{ + x25_channel_t *chan = dev->priv; + cycx_t *card = chan->card; + + if (test_and_set_bit(0, (void*)&card->wandev.critical)) + return -EAGAIN; + + dev->start = 0; + + if (chan->state == WAN_CONNECTED || chan->state == WAN_CONNECTING) + chan_disc(dev); + + cyclomx_close(card); + + card->wandev.critical = 0; + return 0; +} + +/* Build media header. + * o encapsulate packet according to encapsulation type. + * + * The trick here is to put packet type (Ethertype) into 'protocol' field of + * the socket buffer, so that we don't forget it. If encapsulation fails, + * set skb->protocol to 0 and discard packet later. + * + * Return: media header length. */ +static int if_header (struct sk_buff *skb, struct device *dev, + u16 type, void *daddr, void *saddr, unsigned len) +{ + skb->protocol = type; + return dev->hard_header_len; +} + +/* * Re-build media header. + * Return: 1 physical address resolved. + * 0 physical address not resolved */ +static int if_rebuild_hdr (struct sk_buff *skb) +{ + return 1; +} + +/* Send a packet on a network interface. + * o set tbusy flag (marks start of the transmission). + * o check link state. If link is not up, then drop the packet. + * o check channel status. If it's down then initiate a call. + * o pass a packet to corresponding WAN device. + * o free socket buffer + * + * Return: 0 complete (socket buffer must be freed) + * non-0 packet may be re-transmitted (tbusy must be set) + * + * Notes: + * 1. This routine is called either by the protocol stack or by the "net + * bottom half" (with interrupts enabled). + * 2. Setting tbusy flag will inhibit further transmit requests from the + * protocol stack and can be used for flow control with protocol layer. */ +static int if_send (struct sk_buff *skb, struct device *dev) +{ + x25_channel_t *chan = dev->priv; + cycx_t *card = chan->card; + + if (dev->tbusy) { + ++chan->ifstats.rx_dropped; + return -EBUSY; + } + + dev->tbusy = 1; + + reset_timer(dev); + + if (!chan->svc) + chan->protocol = skb->protocol; + + if (card->wandev.state != WAN_CONNECTED) + ++chan->ifstats.tx_dropped; + else if (chan->svc && chan->protocol && + chan->protocol != skb->protocol) { + printk(KERN_INFO + "%s: unsupported Ethertype 0x%04X on interface %s!\n", + card->devname, skb->protocol, dev->name); + ++chan->ifstats.tx_errors; + } else switch (chan->state) { + case WAN_DISCONNECTED: + if (chan_connect(dev)) + return -EBUSY; + /* fall thru */ + case WAN_CONNECTED: + dev->trans_start = jiffies; + if (chan_send(dev, skb)) { + dev->tbusy = 1; + return -EBUSY; + } + break; + default: + ++chan->ifstats.tx_dropped; + ++card->wandev.stats.tx_dropped; + } + + dev_kfree_skb(skb); + return 0; +} + +/* Get Ethernet-style interface statistics. + * Return a pointer to struct net_device_stats */ +static struct net_device_stats *if_stats (struct device *dev) +{ + x25_channel_t *chan = dev->priv; + + return chan ? &chan->ifstats : NULL; +} + +/* Interrupt Handlers */ +/* X.25 Interrupt Service Routine. */ +static void cyx_isr (cycx_t *card) +{ + unsigned long host_cpu_flags; + TX25Cmd cmd; + u16 z = 0; + + card->in_isr = 1; + card->buff_int_mode_unbusy = 0; + + if (test_and_set_bit(0, (void*)&card->wandev.critical)) { + printk(KERN_INFO "cyx_isr: %s, wandev.critical set to 0x%02X\n", + card->devname, card->wandev.critical); + card->in_isr = 0; + return; + } + + /* For all interrupts set the critical flag to CRITICAL_RX_INTR. + * If the if_send routine is called with this flag set it will set + * the enable transmit flag to 1. (for a delayed interrupt) */ + card->wandev.critical = CRITICAL_IN_ISR; + cycx_peek(&card->hw, X25_RXMBOX_OFFS, &cmd, sizeof(cmd)); + switch (cmd.command) { + case X25_DATA_INDICATION: + rx_intr(card, &cmd); + break; + case X25_ACK_FROM_VC: + tx_intr(card, &cmd); + break; + case X25_LOG: + log_intr(card, &cmd); + break; + case X25_STATISTIC: + stat_intr(card, &cmd); + break; + case X25_CONNECT_CONFIRM: + connect_confirm_intr(card, &cmd); + break; + case X25_CONNECT_INDICATION: + connect_intr(card, &cmd); + break; + case X25_DISCONNECT_INDICATION: + disconnect_intr(card, &cmd); + break; + case X25_DISCONNECT_CONFIRM: + disconnect_confirm_intr(card, &cmd); + break; + case X25_LINE_ON: + cyclomx_set_state(card, WAN_CONNECTED); + break; + case X25_LINE_OFF: + cyclomx_set_state(card, WAN_DISCONNECTED); + break; + default: + spur_intr(card, &cmd); /* unwanted interrupt */ + } + + cycx_poke(&card->hw, 0, &z, sizeof(z)); + cycx_poke(&card->hw, X25_RXMBOX_OFFS, &z, sizeof(z)); + + card->wandev.critical = CRITICAL_INTR_HANDLED; + + if (card->wandev.enable_tx_int) + card->wandev.enable_tx_int = 0; + + spin_lock_irqsave(&card->lock, host_cpu_flags); + card->in_isr = 0; + card->wandev.critical = 0; + spin_unlock_irqrestore(&card->lock, host_cpu_flags); + + if (card->buff_int_mode_unbusy) + mark_bh(NET_BH); +} + +/* Transmit interrupt handler. + * o Release socket buffer + * o Clear 'tbusy' flag */ +static void tx_intr (cycx_t *card, TX25Cmd *cmd) +{ + struct device *dev; + wan_device_t *wandev = &card->wandev; + u8 lcn; + + cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn)); + + /* unbusy device and then dev_tint(); */ + if ((dev = get_dev_by_lcn (wandev, lcn)) != NULL) { + card->buff_int_mode_unbusy = 1; + dev->tbusy = 0; + } else + printk(KERN_ERR "%s:ackvc for inexistent lcn %d\n", + card->devname, lcn); +} + +/* Receive interrupt handler. + * This routine handles fragmented IP packets using M-bit according to the + * RFC1356. + * o map ligical channel number to network interface. + * o allocate socket buffer or append received packet to the existing one. + * o if M-bit is reset (i.e. it's the last packet in a sequence) then + * decapsulate packet and pass socket buffer to the protocol stack. + * + * Notes: + * 1. When allocating a socket buffer, if M-bit is set then more data is + * comming and we have to allocate buffer for the maximum IP packet size + * expected on this channel. + * 2. If something goes wrong and X.25 packet has to be dropped (e.g. no + * socket buffers available) the whole packet sequence must be discarded. */ +static void rx_intr (cycx_t *card, TX25Cmd *cmd) +{ + wan_device_t *wandev = &card->wandev; + struct device *dev; + x25_channel_t *chan; + struct sk_buff *skb; + u8 bitm, lcn; + int pktlen = cmd->len - 5; + + cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn)); + cycx_peek(&card->hw, cmd->buf + 4, &bitm, sizeof(bitm)); + bitm &= 0x10; + + if ((dev = get_dev_by_lcn(wandev, lcn)) == NULL) { + /* Invalid channel, discard packet */ + printk(KERN_INFO "%s: receiving on orphaned LCN %d!\n", + card->devname, lcn); + return; + } + + chan = dev->priv; + reset_timer(dev); + + if (chan->drop_sequence) + if (!bitm) + chan->drop_sequence = 0; + else + return; + + if ((skb = chan->rx_skb) == NULL) { + /* Allocate new socket buffer */ + int bufsize = bitm ? dev->mtu : pktlen; + + if ((skb = dev_alloc_skb(bufsize + + dev->hard_header_len)) == NULL) { + printk(KERN_INFO "%s: no socket buffers available!\n", + card->devname); + chan->drop_sequence = 1; + ++chan->ifstats.rx_dropped; + return; + } + + skb->dev = dev; + skb->protocol = htons(chan->protocol); + chan->rx_skb = skb; + } + + if (skb_tailroom(skb) < pktlen) { + /* No room for the packet. Call off the whole thing! */ + dev_kfree_skb(skb); + chan->rx_skb = NULL; + + if (bitm) + chan->drop_sequence = 1; + + printk(KERN_INFO "%s: unexpectedly long packet sequence " + "on interface %s!\n", card->devname, dev->name); + ++chan->ifstats.rx_length_errors; + return; + } + + /* Append packet to the socket buffer */ + cycx_peek(&card->hw, cmd->buf + 5, skb_put(skb, pktlen), pktlen); + + if (bitm) + return; /* more data is coming */ + + dev->last_rx = jiffies; /* timestamp */ + chan->rx_skb = NULL; /* dequeue packet */ + + skb->protocol = htons(ETH_P_IP); + skb->dev = dev; + skb->mac.raw = skb->data; + netif_rx(skb); + ++chan->ifstats.rx_packets; + chan->ifstats.rx_bytes += skb->len; +} + +/* Connect interrupt handler. */ +static void connect_intr (cycx_t *card, TX25Cmd *cmd) +{ + wan_device_t *wandev = &card->wandev; + struct device *dev = NULL; + x25_channel_t *chan; + u8 data[32], + local[24], + rem[24]; + u8 lcn, sizelocal, sizerem; + + cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn)); + cycx_peek(&card->hw, cmd->buf + 5, &sizelocal, sizeof(sizelocal)); + cycx_peek(&card->hw, cmd->buf + 6, data, cmd->len - 6); + + sizerem = sizelocal >> 4; + sizelocal &= 0x0F; + + local[0] = rem[0] = '\0'; + + if (sizelocal) + nibble_to_byte(data, local, sizelocal, 0); + + if (sizerem) + nibble_to_byte(data + (sizelocal >> 1), rem, sizerem, sizelocal & 1); + dprintk(KERN_INFO "connect_intr:lcn=%d, local=%s, remote=%s\n", + lcn, local, rem); + if ((dev = get_dev_by_dte_addr(wandev, rem)) == NULL) { + /* Invalid channel, discard packet */ + printk(KERN_INFO "%s: connect not expected: remote %s!\n", + card->devname, rem); + return; + } + + chan = dev->priv; + chan->lcn = lcn; + x25_connect_response(card, chan); + set_chan_state(dev, WAN_CONNECTED, IN_INTR); +} + +/* Connect confirm interrupt handler. */ +static void connect_confirm_intr (cycx_t *card, TX25Cmd *cmd) +{ + wan_device_t *wandev = &card->wandev; + struct device *dev; + x25_channel_t *chan; + u8 lcn, key; + + cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn)); + cycx_peek(&card->hw, cmd->buf + 1, &key, sizeof(key)); + dprintk(KERN_INFO "%s: connect_confirm_intr:lcn=%d, key=%d\n", + card->devname, lcn, key); + if ((dev = get_dev_by_lcn(wandev, -key)) == NULL) { + /* Invalid channel, discard packet */ + clear_bit(--key, (void*)&card->u.x.connection_keys); + printk(KERN_INFO "%s: connect confirm not expected: lcn %d, " + "key=%d!\n", card->devname, lcn, key); + return; + } + + clear_bit(--key, (void*)&card->u.x.connection_keys); + chan = dev->priv; + chan->lcn = lcn; + set_chan_state(dev, WAN_CONNECTED, IN_INTR); +} + +/* Disonnect confirm interrupt handler. */ +static void disconnect_confirm_intr (cycx_t *card, TX25Cmd *cmd) +{ + wan_device_t *wandev = &card->wandev; + struct device *dev; + u8 lcn; + + cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn)); + dprintk(KERN_INFO "%s: disconnect_confirm_intr:lcn=%d\n", + card->devname, lcn); + if ((dev = get_dev_by_lcn(wandev, lcn)) == NULL) { + /* Invalid channel, discard packet */ + printk(KERN_INFO "%s:disconnect confirm not expected!:lcn %d\n", + card->devname, lcn); + return; + } + + set_chan_state(dev, WAN_DISCONNECTED, IN_INTR); +} + +/* disconnect interrupt handler. */ +static void disconnect_intr (cycx_t *card, TX25Cmd *cmd) +{ + wan_device_t *wandev = &card->wandev; + struct device *dev; + u8 lcn; + + cycx_peek(&card->hw, cmd->buf, &lcn, sizeof(lcn)); + dprintk(KERN_INFO "disconnect_intr:lcn=%d\n", lcn); + x25_disconnect_response(card, 0, lcn); + + if ((dev = get_dev_by_lcn(wandev, lcn)) != NULL) + set_chan_state(dev, WAN_DISCONNECTED, IN_INTR); +} + +/* LOG interrupt handler. */ +static void log_intr (cycx_t *card, TX25Cmd *cmd) +{ +#if CYCLOMX_X25_DEBUG + char bf[20]; + u16 size, toread, link, msg_code; + u8 code, routine; + + cycx_peek(&card->hw, cmd->buf, &msg_code, sizeof(msg_code)); + cycx_peek(&card->hw, cmd->buf + 2, &link, sizeof(link)); + cycx_peek(&card->hw, cmd->buf + 4, &size, sizeof(size)); + /* at most 20 bytes are available... thanx to Daniela :) */ + toread = size < 20 ? size : 20; + cycx_peek(&card->hw, cmd->buf + 10, &bf, toread); + cycx_peek(&card->hw, cmd->buf + 10 + toread, &code, 1); + cycx_peek(&card->hw, cmd->buf + 10 + toread + 1, &routine, 1); + + printk(KERN_INFO "cyx_isr: X25_LOG (0x4500) indic.:\n"); + printk(KERN_INFO "cmd->buf=0x%X\n", cmd->buf); + printk(KERN_INFO "Log message code=0x%X\n", msg_code); + printk(KERN_INFO "Link=%d\n", link); + printk(KERN_INFO "log code=0x%X\n", code); + printk(KERN_INFO "log routine=0x%X\n", routine); + printk(KERN_INFO "Message size=%d\n", size); + hex_dump("Message", bf, toread); +#endif +} + +/* STATISTIC interrupt handler. */ +static void stat_intr (cycx_t *card, TX25Cmd *cmd) +{ + cycx_peek(&card->hw, cmd->buf, &card->u.x.stats, + sizeof(card->u.x.stats)); + hex_dump("stat_intr", (unsigned char*)&card->u.x.stats, + sizeof(card->u.x.stats)); + x25_dump_stats(&card->u.x.stats); + wake_up_interruptible(&card->wait_stats); +} + +/* Spurious interrupt handler. + * o print a warning + * If number of spurious interrupts exceeded some limit, then ??? */ +static void spur_intr (cycx_t *card, TX25Cmd *cmd) +{ + printk(KERN_INFO "%s: spurious interrupt (0x%X)!\n", + card->devname, cmd->command); +} +#ifdef CYCLOMX_X25_DEBUG +static void hex_dump(char *msg, unsigned char *p, int len) +{ + unsigned char hex[1024], + * phex = hex; + + if (len >= (sizeof(hex) / 2)) + len = (sizeof(hex) / 2) - 1; + + while (len--) { + sprintf(phex, "%02x", *p++); + phex += 2; + } + + printk(KERN_INFO "%s: %s\n", msg, hex); +} +#endif +/* CYCLOM X Firmware-Specific Functions + * + * Almost all X.25 commands can unexpetedly fail due to so called 'X.25 + * asynchronous events' such as restart, interrupt, incoming call request, + * call clear request, etc. They can't be ignored and have to be dealt with + * immediately. To tackle with this problem we execute each interface command + * in a loop until good return code is received or maximum number of retries + * is reached. Each interface command returns non-zero return code, an + * asynchronous event/error handler x25_error() is called. + */ +/* Exec x25 command. */ +static int x25_exec (cycx_t *card, int command, int link, + void *data1, int len1, void *data2, int len2) +{ + TX25Cmd c; + u32 addr = 0x1200 + 0x2E0 * link + 0x1E2; + int err = 0; + + c.command = command; + c.link = link; + c.len = len1 + len2; + + if (test_and_set_bit(0, (void*)&card->u.x.critical)) + return -EAGAIN; + + /* write command */ + cycx_poke(&card->hw, X25_MBOX_OFFS, &c, sizeof(c) - sizeof(c.buf)); + + /* write x25 data */ + if (data1) { + cycx_poke(&card->hw, addr, data1, len1); + + if (data2) + if (len2 > 254) { + u32 addr1 = 0xA00 + 0x400 * link; + + cycx_poke(&card->hw, addr + len1, data2, 249); + cycx_poke(&card->hw, addr1, ((u8*) data2) + 249, + len2 - 249); + } else + cycx_poke(&card->hw, addr + len1, data2, len2); + } + + /* generate interruption, executing command */ + cycx_intr(&card->hw); + + /* wait till card->mbox == 0 */ + err = cycx_exec(card->mbox); + card->u.x.critical = 0; + + return err; +} + +/* Configure adapter. */ +static int x25_configure (cycx_t *card, TX25Config *conf) +{ + struct { + u16 nlinks; + TX25Config conf[2]; + } x25_cmd_conf; + + memset (&x25_cmd_conf, 0, sizeof(x25_cmd_conf)); + x25_cmd_conf.nlinks = 2; + x25_cmd_conf.conf[0] = *conf; + /* FIXME: we need to find a way in the wanrouter framework + to configure the second link, for now lets use it + with the same config from the first link, fixing + the interface type to RS232, the speed in 38400 and + the clock to external */ + x25_cmd_conf.conf[1] = *conf; + x25_cmd_conf.conf[1].link = 1; + x25_cmd_conf.conf[1].speed = 5; /* 38400 */ + x25_cmd_conf.conf[1].clock = 8; + x25_cmd_conf.conf[1].flags = 0; /* default = RS232 */ + + x25_dump_config(&x25_cmd_conf.conf[0]); + x25_dump_config(&x25_cmd_conf.conf[1]); + + return x25_exec(card, X25_CONFIG, 0, + &x25_cmd_conf, sizeof(x25_cmd_conf), NULL, 0); +} + +/* Get protocol statistics. */ +static int x25_get_stats (cycx_t *card) +{ + /* the firmware expects 20 in the size field!!! + thanx to Daniela */ + int err = x25_exec(card, X25_STATISTIC, 0, NULL, 20, NULL, 0); + + if (err) + return err; + + interruptible_sleep_on(&card->wait_stats); + + if (signal_pending(current)) + return -EINTR; + + card->wandev.stats.rx_packets = card->u.x.stats.n2_rx_frames; + card->wandev.stats.rx_over_errors = card->u.x.stats.rx_over_errors; + card->wandev.stats.rx_crc_errors = card->u.x.stats.rx_crc_errors; + card->wandev.stats.rx_length_errors = 0; /* not available from fw */ + card->wandev.stats.rx_frame_errors = 0; /* not available from fw */ + card->wandev.stats.rx_missed_errors = card->u.x.stats.rx_aborts; + card->wandev.stats.rx_dropped = 0; /* not available from fw */ + card->wandev.stats.rx_errors = 0; /* not available from fw */ + card->wandev.stats.tx_packets = card->u.x.stats.n2_tx_frames; + card->wandev.stats.tx_aborted_errors = card->u.x.stats.tx_aborts; + card->wandev.stats.tx_dropped = 0; /* not available from fw */ + card->wandev.stats.collisions = 0; /* not available from fw */ + card->wandev.stats.tx_errors = 0; /* not available from fw */ + + x25_dump_devs(&card->wandev); + return 0; +} + +/* return the number of nibbles */ +static int byte_to_nibble(u8 *s, u8 *d, char *nibble) +{ + int i = 0; + + if (*nibble && *s) { + d[i] |= *s++ - '0'; + *nibble = 0; + ++i; + } + + while (*s) { + d[i] = (*s - '0') << 4; + if (*(s + 1)) + d[i] |= *(s + 1) - '0'; + else { + *nibble = 1; + break; + } + ++i; + s += 2; + } + + return i; +} + +static void nibble_to_byte(u8 *s, u8 *d, u8 len, u8 nibble) +{ + if (nibble) { + *d++ = '0' + (*s++ & 0x0F); + --len; + } + + while (len) { + *d++ = '0' + (*s >> 4); + if (--len) { + *d++ = '0' + (*s & 0x0F); + --len; + } else break; + + ++s; + } + + *d = '\0'; +} + +/* Place X.25 call. */ +static int x25_place_call (cycx_t *card, x25_channel_t *chan) +{ + int err = 0, + retry = MAX_CMD_RETRY, + len; + char data[64], + nibble = 0, + mylen = chan->local_addr ? strlen(chan->local_addr) : 0, + remotelen = strlen(chan->addr); + u8 key; + + if (card->u.x.connection_keys == ~0UL) { + printk(KERN_INFO "%s: too many simultaneous connection " + "requests!\n", card->devname); + return -EAGAIN; + } + + key = ffz(card->u.x.connection_keys); + set_bit(key, (void*)&card->u.x.connection_keys); + ++key; + dprintk(KERN_INFO "%s:x25_place_call:key=%d\n", card->devname, key); + memset(data, 0, sizeof(data)); + data[1] = key; /* user key */ + data[2] = 0x10; + data[4] = 0x0B; + + len = byte_to_nibble(chan->addr, data + 6, &nibble); + len += chan->local_addr ? byte_to_nibble(chan->local_addr, + data + 6 + len, &nibble) : 0; + if (nibble) + ++len; + data[5] = mylen << 4 | remotelen; + data[6 + len + 1] = 0xCC; /* TCP/IP over X.25, thanx to Daniela :) */ + + do err = x25_exec(card, X25_CONNECT_REQUEST, chan->link, + &data, 7 + len + 1, NULL, 0); + while (err && retry--); + + if (err) + clear_bit(--key, (void*)&card->u.x.connection_keys); + else { + chan->lcn = -key; + chan->protocol = ETH_P_IP; + } + + return err; +} + +/* Place X.25 CONNECT RESPONSE. */ +static int x25_connect_response (cycx_t *card, x25_channel_t *chan) +{ + int err = 0, + retry = MAX_CMD_RETRY; + char data[32]; + + memset(data, 0, sizeof(data)); + data[0] = data[3] = chan->lcn; + data[2] = 0x10; + data[4] = 0x0F; + data[7] = 0xCC; /* TCP/IP over X.25, thanx Daniela */ + + do err = x25_exec(card, X25_CONNECT_RESPONSE, chan->link, + &data, 8, NULL, 0); + while (err && retry--); + + return err; +} + +/* Place X.25 DISCONNECT RESPONSE. */ +static int x25_disconnect_response (cycx_t *card, u8 link, u8 lcn) +{ + int err = 0, + retry = MAX_CMD_RETRY; + char data[5]; + + memset(data, 0, sizeof(data)); + data[0] = data[3] = lcn; + data[2] = 0x10; + data[4] = 0x17; + do err = x25_exec(card, X25_DISCONNECT_RESPONSE, link, + &data, 5, NULL, 0); + while (err && retry--); + + return err; +} + +/* Clear X.25 call. */ +static int x25_clear_call (cycx_t *card, u8 link, u8 lcn, u8 cause, u8 diagn) +{ + int retry = MAX_CMD_RETRY, + err; + u8 data[7]; + + memset(data, 0, sizeof(data)); + data[0] = data[3] = lcn; + data[2] = 0x10; + data[4] = 0x13; + data[5] = cause; + data[6] = diagn; + + do err = x25_exec(card, X25_DISCONNECT_REQUEST, link, data, 7, NULL, 0); + while (err && retry--); + + return err; +} + +/* Send X.25 data packet. */ +static int x25_send (cycx_t *card, u8 link, u8 lcn, u8 bitm, int len, void *buf) +{ + int err = 0, + retry = MAX_CMD_RETRY; + u8 data[] = "?\xFF\x10??"; + + data[0] = data[3] = lcn; + data[4] = bitm; + + do err = x25_exec(card, X25_DATA_REQUEST, link, &data, 5, buf, len); + while (err && retry--); + + return err; +} + +/* Miscellaneous */ +/* Find network device by its channel number. */ +static struct device *get_dev_by_lcn (wan_device_t *wandev, s16 lcn) +{ + struct device *dev = wandev->dev; + + for (; dev; dev = dev->slave) + if (((x25_channel_t*)dev->priv)->lcn == lcn) + break; + return dev; +} + +/* Find network device by its remote dte address. */ +static struct device *get_dev_by_dte_addr (wan_device_t *wandev, char *dte) +{ + struct device *dev = wandev->dev; + + for (; dev; dev = dev->slave) + if (!strcmp (((x25_channel_t*)dev->priv)->addr, dte)) + break; + return dev; +} + +/* Initiate connection on the logical channel. + * o for PVC we just get channel configuration + * o for SVCs place an X.25 call + * + * Return: 0 connected + * >0 connection in progress + * <0 failure */ +static int chan_connect (struct device *dev) +{ + x25_channel_t *chan = dev->priv; + cycx_t *card = chan->card; + + if (chan->svc) { + if (!chan->addr[0]) + return -EINVAL; /* no destination address */ + dprintk(KERN_INFO "%s: placing X.25 call to %s...\n", + card->devname, chan->addr); + if (x25_place_call(card, chan)) + return -EIO; + set_chan_state(dev, WAN_CONNECTING, OUT_INTR); + return 1; + } else + set_chan_state(dev, WAN_CONNECTED, OUT_INTR); + + return 0; +} + +/* Disconnect logical channel. + * o if SVC then clear X.25 call */ +static void chan_disc (struct device *dev) +{ + x25_channel_t *chan = dev->priv; + + if (chan->svc) { + x25_clear_call(chan->card, chan->link, chan->lcn, 0, 0); + set_chan_state(dev, WAN_DISCONNECTING, OUT_INTR); + } else + set_chan_state(dev, WAN_DISCONNECTED, OUT_INTR); +} + +/* Called by kernel timer */ +static void chan_timer (unsigned long data) +{ + struct device *dev = (struct device*) data; + x25_channel_t *chan = dev->priv; + + switch (chan->state) { + case WAN_CONNECTED: + chan_disc(dev); + break; + default: + printk (KERN_ERR "%s: chan_timer for svc (%s) not " + "connected!\n", + chan->card->devname, dev->name); + } +} + +/* Set logical channel state. */ +static void set_chan_state (struct device *dev, u8 state, u8 outside_intr) +{ + x25_channel_t *chan = dev->priv; + cycx_t *card = chan->card; + u32 flags = 0; + + if (outside_intr) + spin_lock(&card->lock); + else + spin_lock_irqsave(&card->lock, flags); + + if (chan->state != state) { + if (chan->svc && chan->state == WAN_CONNECTED) + del_timer(&chan->timer); + + switch (state) { + case WAN_CONNECTED: + printk (KERN_INFO "%s: interface %s " + "connected!\n", + card->devname, dev->name); + *(u16*)dev->dev_addr = htons(chan->lcn); + dev->tbusy = 0; + reset_timer(dev); + break; + + case WAN_CONNECTING: + printk (KERN_INFO "%s: interface %s " + "connecting...\n", + card->devname, dev->name); + break; + + case WAN_DISCONNECTING: + printk (KERN_INFO "%s: interface %s " + "disconnecting...\n", + card->devname, dev->name); + break; + + case WAN_DISCONNECTED: + printk (KERN_INFO "%s: interface %s " + "disconnected!\n", + card->devname, dev->name); + if (chan->svc) { + *(unsigned short*)dev->dev_addr = 0; + chan->lcn = 0; + } + break; + } + + chan->state = state; + } + + if (outside_intr) + spin_unlock(&card->lock); + else + spin_unlock_irqrestore(&card->lock, flags); +} + +/* Send packet on a logical channel. + * When this function is called, tx_skb field of the channel data space + * points to the transmit socket buffer. When transmission is complete, + * release socket buffer and reset 'tbusy' flag. + * + * Return: 0 - transmission complete + * 1 - busy + * + * Notes: + * 1. If packet length is greater than MTU for this channel, we'll fragment + * the packet into 'complete sequence' using M-bit. + * 2. When transmission is complete, an event notification should be issued + * to the router. */ +static int chan_send (struct device *dev, struct sk_buff *skb) +{ + x25_channel_t *chan = dev->priv; + cycx_t *card = chan->card; + int bitm = 0; /* final packet */ + unsigned len = skb->len; + + if (skb->len > card->wandev.mtu) { + len = card->wandev.mtu; + bitm = 0x10; /* set M-bit (more data) */ + } + + if (x25_send(card, chan->link, chan->lcn, bitm, len, skb->data)) + return 1; + + if (bitm) { + skb_pull(skb, len); + return 1; + } + + ++chan->ifstats.tx_packets; + chan->ifstats.tx_bytes += len; + return 0; +} + +/* Convert line speed in bps to a number used by cyclom 2x code. */ +static u8 bps_to_speed_code (u32 bps) +{ + u8 number = 0; /* defaults to the lowest (1200) speed ;> */ + + if (bps >= 512000) number = 8; + else if (bps >= 256000) number = 7; + else if (bps >= 64000) number = 6; + else if (bps >= 38400) number = 5; + else if (bps >= 19200) number = 4; + else if (bps >= 9600) number = 3; + else if (bps >= 4800) number = 2; + else if (bps >= 2400) number = 1; + + return number; +} + +/* log base 2 */ +static u8 log2 (u32 n) +{ + u8 log = 0; + + if (!n) + return 0; + + while (n > 1) { + n >>= 1; + ++log; + } + + return log; +} + +/* Convert decimal string to unsigned integer. + * If len != 0 then only 'len' characters of the string are converted. */ +static unsigned dec_to_uint (u8 *str, int len) +{ + unsigned val = 0; + + if (!len) + len = strlen(str); + + for (; len && is_digit(*str); ++str, --len) + val = (val * 10) + (*str - (unsigned)'0'); + + return val; +} + +static void reset_timer(struct device *dev) +{ + x25_channel_t *chan = dev->priv; + + if (!chan->svc) + return; + + del_timer(&chan->timer); + chan->timer.expires = jiffies + chan->idle_tmout * HZ; + add_timer(&chan->timer); +} +#ifdef CYCLOMX_X25_DEBUG +static void x25_dump_config(TX25Config *conf) +{ + printk (KERN_INFO "x25 configuration\n"); + printk (KERN_INFO "-----------------\n"); + printk (KERN_INFO "link number=%d\n", conf->link); + printk (KERN_INFO "line speed=%d\n", conf->speed); + printk (KERN_INFO "clock=%sternal\n", conf->clock == 8 ? "Ex" : "In"); + printk (KERN_INFO "# level 2 retransm.=%d\n", conf->n2); + printk (KERN_INFO "level 2 window=%d\n", conf->n2win); + printk (KERN_INFO "level 3 window=%d\n", conf->n3win); + printk (KERN_INFO "# logical channels=%d\n", conf->nvc); + printk (KERN_INFO "level 3 pkt len=%d\n", conf->pktlen); + printk (KERN_INFO "my address=%d\n", conf->locaddr); + printk (KERN_INFO "remote address=%d\n", conf->remaddr); + printk (KERN_INFO "t1=%d seconds\n", conf->t1); + printk (KERN_INFO "t2=%d seconds\n", conf->t2); + printk (KERN_INFO "t21=%d seconds\n", conf->t21); + printk (KERN_INFO "# PVCs=%d\n", conf->npvc); + printk (KERN_INFO "t23=%d seconds\n", conf->t23); + printk (KERN_INFO "flags=0x%x\n", conf->flags); +} + +static void x25_dump_stats(TX25Stats *stats) +{ + printk (KERN_INFO "x25 statistics\n"); + printk (KERN_INFO "--------------\n"); + printk (KERN_INFO "rx_crc_errors=%d\n", stats->rx_crc_errors); + printk (KERN_INFO "rx_over_errors=%d\n", stats->rx_over_errors); + printk (KERN_INFO "n2_tx_frames=%d\n", stats->n2_tx_frames); + printk (KERN_INFO "n2_rx_frames=%d\n", stats->n2_rx_frames); + printk (KERN_INFO "tx_timeouts=%d\n", stats->tx_timeouts); + printk (KERN_INFO "rx_timeouts=%d\n", stats->rx_timeouts); + printk (KERN_INFO "n3_tx_packets=%d\n", stats->n3_tx_packets); + printk (KERN_INFO "n3_rx_packets=%d\n", stats->n3_rx_packets); + printk (KERN_INFO "tx_aborts=%d\n", stats->tx_aborts); + printk (KERN_INFO "rx_aborts=%d\n", stats->rx_aborts); +} + +static void x25_dump_devs(wan_device_t *wandev) +{ + struct device *dev = wandev->dev; + + printk (KERN_INFO "x25 dev states\n"); + printk (KERN_INFO "name: addr: tbusy:\n"); + printk (KERN_INFO "----------------------------\n"); + + for (; dev; dev = dev->slave) { + x25_channel_t *chan = dev->priv; + + printk (KERN_INFO "%-5.5s %-15.15s %ld\n", + chan->name, chan->addr, dev->tbusy); + } +} + +#endif /* CYCLOMX_X25_DEBUG */ +/* End */ diff --git a/drivers/net/irda/litelink.c b/drivers/net/irda/litelink.c new file mode 100644 index 000000000..84df367da --- /dev/null +++ b/drivers/net/irda/litelink.c @@ -0,0 +1,206 @@ +/********************************************************************* + * + * Filename: litelink.c + * Version: 1.0 + * Description: Driver for the Parallax LiteLink dongle + * Status: Stable + * Author: Dag Brattli <dagb@cs.uit.no> + * Created at: Fri May 7 12:50:33 1999 + * Modified at: Wed May 19 07:25:15 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1999 Dag Brattli, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + ********************************************************************/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/tty.h> +#include <linux/sched.h> +#include <linux/init.h> + +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irda_device.h> +#include <net/irda/dongle.h> + +#define MIN_DELAY 25 /* 15 us, but wait a little more to be sure */ +#define MAX_DELAY 10000 /* 1 ms */ + +static void litelink_open(struct irda_device *idev, int type); +static void litelink_close(struct irda_device *dev); +static void litelink_change_speed(struct irda_device *dev, int baudrate); +static void litelink_reset(struct irda_device *dev); +static void litelink_init_qos(struct irda_device *idev, struct qos_info *qos); + +/* These are the baudrates supported */ +static int baud_rates[] = { 115200, 57600, 38400, 19200, 9600 }; + +static struct dongle dongle = { + LITELINK_DONGLE, + litelink_open, + litelink_close, + litelink_reset, + litelink_change_speed, + litelink_init_qos, +}; + +__initfunc(int litelink_init(void)) +{ + return irda_device_register_dongle(&dongle); +} + +void litelink_cleanup(void) +{ + irda_device_unregister_dongle(&dongle); +} + +static void litelink_open(struct irda_device *idev, int type) +{ + strcat(idev->description, " <-> litelink"); + + idev->io.dongle_id = type; + idev->flags |= IFF_DONGLE; + + MOD_INC_USE_COUNT; +} + +static void litelink_close(struct irda_device *idev) +{ + /* Power off dongle */ + irda_device_set_dtr_rts(idev, FALSE, FALSE); + + MOD_DEC_USE_COUNT; +} + +/* + * Function litelink_change_speed (tty, baud) + * + * Change speed of the Litelink dongle. To cycle through the available + * baud rates, pulse RTS low for a few ms. + */ +static void litelink_change_speed(struct irda_device *idev, int baudrate) +{ + int i; + + ASSERT(idev != NULL, return;); + ASSERT(idev->magic == IRDA_DEVICE_MAGIC, return;); + + /* Clear RTS to reset dongle */ + irda_device_set_dtr_rts(idev, TRUE, FALSE); + + /* Sleep a minimum of 15 us */ + udelay(MIN_DELAY); + + /* Go back to normal mode */ + irda_device_set_dtr_rts(idev, TRUE, TRUE); + + /* Sleep a minimum of 15 us */ + udelay(MIN_DELAY); + + /* Cycle through avaiable baudrates until we reach the correct one */ + for (i=0; i<5 && baud_rates[i] != baudrate; i++) { + + /* Set DTR, clear RTS */ + irda_device_set_dtr_rts(idev, FALSE, TRUE); + + /* Sleep a minimum of 15 us */ + udelay(MIN_DELAY); + + /* Set DTR, Set RTS */ + irda_device_set_dtr_rts(idev, TRUE, TRUE); + + /* Sleep a minimum of 15 us */ + udelay(MIN_DELAY); + } +} + +/* + * Function litelink_reset (dev) + * + * Reset the Litelink type dongle. Warning, this function must only be + * called with a process context! + * + */ +static void litelink_reset(struct irda_device *idev) +{ + ASSERT(idev != NULL, return;); + ASSERT(idev->magic == IRDA_DEVICE_MAGIC, return;); + + /* Power on dongle */ + irda_device_set_dtr_rts(idev, TRUE, TRUE); + + /* Sleep a minimum of 15 us */ + udelay(MIN_DELAY); + + /* Clear RTS to reset dongle */ + irda_device_set_dtr_rts(idev, TRUE, FALSE); + + /* Sleep a minimum of 15 us */ + udelay(MIN_DELAY); + + /* Go back to normal mode */ + irda_device_set_dtr_rts(idev, TRUE, TRUE); + + /* Sleep a minimum of 15 us */ + udelay(MIN_DELAY); + + /* This dongles speed defaults to 115200 bps */ + idev->qos.baud_rate.value = 115200; +} + +/* + * Function litelink_init_qos (qos) + * + * Initialize QoS capabilities + * + */ +static void litelink_init_qos(struct irda_device *idev, struct qos_info *qos) +{ + qos->baud_rate.bits &= IR_9600|IR_19200|IR_38400|IR_57600|IR_115200; + qos->min_turn_time.bits &= 0x40; /* Needs 0.01 ms */ +} + +#ifdef MODULE + +MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>"); +MODULE_DESCRIPTION("Parallax Litelink dongle driver"); + +/* + * Function init_module (void) + * + * Initialize Litelink module + * + */ +int init_module(void) +{ + return litelink_init(); +} + +/* + * Function cleanup_module (void) + * + * Cleanup Litelink module + * + */ +void cleanup_module(void) +{ + litelink_cleanup(); +} + +#endif diff --git a/drivers/net/irda/smc-ircc.c b/drivers/net/irda/smc-ircc.c new file mode 100644 index 000000000..4daa5f7e2 --- /dev/null +++ b/drivers/net/irda/smc-ircc.c @@ -0,0 +1,969 @@ +/********************************************************************* + * + * Filename: smc-ircc.c + * Version: 0.1 + * Description: Driver for the SMC Infrared Communications Controller (SMC) + * Status: Experimental. + * Author: Thomas Davis (tadavis@jps.net) + * Created at: + * Modified at: Wed May 19 15:30:08 1999 + * Modified by: Dag Brattli <dagb@cs.uit.no> + * + * Copyright (c) 1998-1999 Thomas Davis, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * I, Thomas Davis, admit no liability nor provide warranty for any + * of this software. This material is provided "AS-IS" and at no charge. + * + * Applicable Models : Fujitsu Lifebook 635t + * Sony PCG-505TX (gets DMA wrong.) + * + ********************************************************************/ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/malloc.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#include <net/irda/wrapper.h> +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irlap_frame.h> +#include <net/irda/irda_device.h> + +#include <net/irda/smc-ircc.h> +#include <net/irda/irport.h> + +static char *driver_name = "smc-ircc"; + +#define CHIP_IO_EXTENT 8 + +static unsigned int io[] = { 0x2e8, 0x140, ~0, ~0 }; +static unsigned int io2[] = { 0x2f8, 0x3e8, 0, 0}; + +static struct ircc_cb *dev_self[] = { NULL, NULL, NULL, NULL}; + +/* Some prototypes */ +static int ircc_open( int i, unsigned int iobase, unsigned int board_addr); +static int ircc_close( struct irda_device *idev); +static int ircc_probe( int iobase, int board_addr); +static int ircc_dma_receive( struct irda_device *idev); +static int ircc_dma_receive_complete(struct irda_device *idev, int iobase); +static int ircc_hard_xmit( struct sk_buff *skb, struct device *dev); +static void ircc_dma_write( struct irda_device *idev, int iobase); +static void ircc_change_speed( struct irda_device *idev, int baud); +static void ircc_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void ircc_wait_until_sent( struct irda_device *idev); +static int ircc_is_receiving( struct irda_device *idev); + +static int ircc_net_init( struct device *dev); +static int ircc_net_open( struct device *dev); +static int ircc_net_close( struct device *dev); + +static int ircc_debug=3; +static int ircc_irq=255; +static int ircc_dma=255; + +static inline void register_bank(int port, int bank) +{ + outb(((inb(port+UART_MASTER) & 0xF0) | (bank & 0x07)), + port+UART_MASTER); +} + +static inline unsigned int serial_in(int port, int offset) +{ + return inb(port+offset); +} + +static inline void serial_out(int port, int offset, int value) +{ + outb(value, port+offset); +} + +/* + * Function ircc_init () + * + * Initialize chip. Just try to find out how many chips we are dealing with + * and where they are + */ +__initfunc(int ircc_init(void)) +{ + int i; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + for ( i=0; (io[i] < 2000) && (i < 4); i++) { + int ioaddr = io[i]; + if (check_region(ioaddr, CHIP_IO_EXTENT)) + continue; + if (ircc_open( i, io[i], io2[i]) == 0) + return 0; + } + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + + return -ENODEV; +} + +/* + * Function ircc_cleanup () + * + * Close all configured chips + * + */ +#ifdef MODULE +static void ircc_cleanup(void) +{ + int i; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + for ( i=0; i < 4; i++) { + if ( dev_self[i]) + ircc_close( &(dev_self[i]->idev)); + } + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); +} +#endif /* MODULE */ + +/* + * Function ircc_open (iobase, irq) + * + * Open driver instance + * + */ +static int ircc_open( int i, unsigned int iobase, unsigned int iobase2) +{ + struct ircc_cb *self; + struct irda_device *idev; + int ret; + int config; + + DEBUG( ircc_debug, __FUNCTION__ " -->\n"); + + if ((config = ircc_probe( iobase, iobase2)) == -1) { + DEBUG(ircc_debug, + __FUNCTION__ ": addr 0x%04x - no device found!\n", iobase); + return -1; + } + + /* + * Allocate new instance of the driver + */ + self = kmalloc( sizeof(struct ircc_cb), GFP_KERNEL); + if ( self == NULL) { + printk( KERN_ERR "IrDA: Can't allocate memory for " + "IrDA control block!\n"); + return -ENOMEM; + } + memset(self, 0, sizeof(struct ircc_cb)); + + /* Need to store self somewhere */ + dev_self[i] = self; + + idev = &self->idev; + + /* Initialize IO */ + idev->io.iobase = iobase; + idev->io.iobase2 = iobase2; /* Used by irport */ + idev->io.irq = config >> 4 & 0x0f; + if (ircc_irq < 255) { + printk(KERN_INFO "smc: Overriding IRQ - chip says %d, using %d\n", + idev->io.irq, ircc_irq); + idev->io.irq = ircc_irq; + } + idev->io.io_ext = CHIP_IO_EXTENT; + idev->io.io_ext2 = 8; /* Used by irport */ + idev->io.dma = config & 0x0f; + if (ircc_dma < 255) { + printk(KERN_INFO "smc: Overriding DMA - chip says %d, using %d\n", + idev->io.dma, ircc_dma); + idev->io.dma = ircc_dma; + } + idev->io.fifo_size = 16; + + /* Lock the port that we need */ + ret = check_region( idev->io.iobase, idev->io.io_ext); + if ( ret < 0) { + DEBUG( 0, __FUNCTION__ ": can't get iobase of 0x%03x\n", + idev->io.iobase); + /* ircc_cleanup( self->idev); */ + return -ENODEV; + } + ret = check_region( idev->io.iobase2, idev->io.io_ext2); + if ( ret < 0) { + DEBUG( 0, __FUNCTION__ ": can't get iobase of 0x%03x\n", + idev->io.iobase2); + /* ircc_cleanup( self->idev); */ + return -ENODEV; + } + request_region( idev->io.iobase, idev->io.io_ext, idev->name); + request_region( idev->io.iobase2, idev->io.io_ext2, idev->name); + + /* Initialize QoS for this device */ + irda_init_max_qos_capabilies( &idev->qos); + +#if 1 + /* The only value we must override it the baudrate */ + idev->qos.baud_rate.bits = IR_9600|IR_19200|IR_38400|IR_57600| + IR_115200|IR_576000|IR_1152000|(IR_4000000 << 8); +#else + /* The only value we must override it the baudrate */ + idev->qos.baud_rate.bits = IR_9600|IR_19200|IR_38400|IR_57600| + IR_115200; +#endif + + idev->qos.min_turn_time.bits = 0x07; + irda_qos_bits_to_value( &idev->qos); + + idev->flags = IFF_FIR|IFF_SIR|IFF_DMA|IFF_PIO; + + /* Specify which buffer allocation policy we need */ + idev->rx_buff.flags = GFP_KERNEL | GFP_DMA; + idev->tx_buff.flags = GFP_KERNEL | GFP_DMA; + + /* Max DMA buffer size needed = (data_size + 6) * (window_size) + 6; */ + idev->rx_buff.truesize = 4000; + idev->tx_buff.truesize = 4000; + + /* Initialize callbacks */ + idev->change_speed = ircc_change_speed; + idev->wait_until_sent = ircc_wait_until_sent; + idev->is_receiving = ircc_is_receiving; + + /* Override the network functions we need to use */ + idev->netdev.init = ircc_net_init; + idev->netdev.hard_start_xmit = ircc_hard_xmit; + idev->netdev.open = ircc_net_open; + idev->netdev.stop = ircc_net_close; + + irport_start(idev, iobase2); + + /* Open the IrDA device */ + irda_device_open( idev, driver_name, self); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return 0; +} + +/* + * Function ircc_close (idev) + * + * Close driver instance + * + */ +static int ircc_close( struct irda_device *idev) +{ + int iobase; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + iobase = idev->io.iobase; + + irport_stop(idev, idev->io.iobase2); + + register_bank(iobase, 0); + serial_out(iobase, UART_IER, 0); + serial_out(iobase, UART_MASTER, UART_MASTER_RESET); + + register_bank(iobase, 1); + + serial_out(iobase, UART_SCE_CFGA, + UART_CFGA_IRDA_SIR_A | UART_CFGA_TX_POLARITY); + serial_out(iobase, UART_SCE_CFGB, UART_CFGB_IR); + + /* Release the PORT that this driver is using */ + DEBUG( ircc_debug, + __FUNCTION__ ": releasing 0x%03x\n", idev->io.iobase); + + release_region( idev->io.iobase, idev->io.io_ext); + + if ( idev->io.iobase2) { + DEBUG( ircc_debug, __FUNCTION__ ": releasing 0x%03x\n", + idev->io.iobase2); + release_region( idev->io.iobase2, idev->io.io_ext2); + } + + irda_device_close( idev); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return 0; +} + +/* + * Function ircc_probe (iobase, board_addr, irq, dma) + * + * Returns non-negative on success. + * + */ +static int ircc_probe( int iobase, int iobase2) +{ + int version = 1; + int low, high, chip, config, dma, irq; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + register_bank(iobase, 3); + high = serial_in(iobase, UART_ID_HIGH); + low = serial_in(iobase, UART_ID_LOW); + chip = serial_in(iobase, UART_CHIP_ID); + version = serial_in(iobase, UART_VERSION); + config = serial_in(iobase, UART_INTERFACE); + irq = config >> 4 & 0x0f; + dma = config & 0x0f; + + if (high == 0x10 && low == 0xb8 && chip == 0xf1) { + DEBUG(0, "SMC IrDA Controller found; version = %d, " + "port 0x%04x, dma %d, interrupt %d\n", + version, iobase, dma, irq); + } else { + return -1; + } + + serial_out(iobase, UART_MASTER, 0); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + + return config; +} + +/* + * Function ircc_change_speed (idev, baud) + * + * Change the speed of the device + * + */ +static void ircc_change_speed( struct irda_device *idev, int speed) +{ + struct ircc_cb *self; + int iobase, ir_mode, select, fast; + + DEBUG(ircc_debug+1, __FUNCTION__ " -->\n"); + + ASSERT(idev != NULL, return;); + ASSERT(idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = idev->priv; + iobase = idev->io.iobase; + + /* Update accounting for new speed */ + idev->io.baudrate = speed; + + switch ( speed) { + case 9600: + case 19200: + case 37600: + case 57600: + case 115200: + DEBUG(ircc_debug+1, + __FUNCTION__ ": using irport to change speed to %d\n", + speed); + register_bank(iobase, 0); + serial_out(iobase, UART_IER, 0); + serial_out(iobase, UART_MASTER, UART_MASTER_RESET); + serial_out(iobase, UART_MASTER, UART_MASTER_INT_EN); + irport_start(idev, idev->io.iobase2); + irport_change_speed( idev, speed); + return; + break; + + case 576000: + ir_mode = UART_CFGA_IRDA_HDLC; + select = 0; + fast = 0; + DEBUG( ircc_debug, __FUNCTION__ ": handling baud of 576000\n"); + break; + case 1152000: + ir_mode = UART_CFGA_IRDA_HDLC; + select = UART_1152; + fast = 0; + DEBUG(ircc_debug, __FUNCTION__ ": handling baud of 1152000\n"); + break; + case 4000000: + ir_mode = UART_CFGA_IRDA_4PPM; + select = 0; + fast = UART_LCR_A_FAST; + DEBUG(ircc_debug, __FUNCTION__ ": handling baud of 4000000\n"); + break; + default: + DEBUG( 0, __FUNCTION__ ": unknown baud rate of %d\n", speed); + return; + } + +#if 0 + serial_out(idev->io.iobase2, 4, 0x08); +#endif + + serial_out(iobase, UART_MASTER, UART_MASTER_RESET); + + register_bank(iobase, 0); + serial_out(iobase, UART_IER, 0); + + irport_stop(idev, idev->io.iobase2); + + idev->netdev.tbusy = 0; + + register_bank(iobase, 1); + + serial_out(iobase, UART_SCE_CFGA, + ((serial_in(iobase, UART_SCE_CFGA) & 0x87) | ir_mode)); + + serial_out(iobase, UART_SCE_CFGB, + ((serial_in(iobase, UART_SCE_CFGB) & 0x3f) | UART_CFGB_IR)); + + (void) serial_in(iobase, UART_FIFO_THRESHOLD); + serial_out(iobase, UART_FIFO_THRESHOLD, 64); + + register_bank(iobase, 4); + + serial_out(iobase, UART_CONTROL, + (serial_in(iobase, UART_CONTROL) & 0x30) + | select | UART_CRC ); + + register_bank(iobase, 0); + + serial_out(iobase, UART_LCR_A, fast); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); +} + +/* + * Function ircc_hard_xmit (skb, dev) + * + * Transmit the frame! + * + */ +static int ircc_hard_xmit( struct sk_buff *skb, struct device *dev) +{ + struct irda_device *idev; + int iobase; + int mtt; + + DEBUG(ircc_debug+1, __FUNCTION__ " -->\n"); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + DEBUG(ircc_debug+1, __FUNCTION__ "(%ld), skb->len=%d\n", jiffies, (int) skb->len); + + /* Use irport for SIR speeds */ + if (idev->io.baudrate <= 115200) { + DEBUG(ircc_debug+1, __FUNCTION__ ": calling irport_hard_xmit\n"); + return irport_hard_xmit(skb, dev); + } + + DEBUG(ircc_debug, __FUNCTION__ ": using dma; len=%d\n", skb->len); + + /* Lock transmit buffer */ + if (irda_lock((void *) &dev->tbusy) == FALSE) + return -EBUSY; + + memcpy( idev->tx_buff.head, skb->data, skb->len); + + /* Make sure that the length is a multiple of 16 bits */ + if ( skb->len & 0x01) + skb->len++; + + idev->tx_buff.len = skb->len; + idev->tx_buff.data = idev->tx_buff.head; +#if 0 + idev->tx_buff.offset = 0; +#endif + + mtt = irda_get_mtt( skb); + + /* Use udelay for delays less than 50 us. */ + if (mtt) + udelay( mtt); + + ircc_dma_write( idev, iobase); + + dev_kfree_skb( skb); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return 0; +} + +/* + * Function ircc_dma_xmit (idev, iobase) + * + * Transmit data using DMA + * + */ +static void ircc_dma_write( struct irda_device *idev, int iobase) +{ + struct ircc_cb *self; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = idev->priv; + iobase = idev->io.iobase; + + setup_dma( idev->io.dma, idev->tx_buff.data, idev->tx_buff.len, + DMA_MODE_WRITE); + + idev->io.direction = IO_XMIT; + + serial_out(idev->io.iobase2, 4, 0x08); + + register_bank(iobase, 4); + serial_out(iobase, UART_CONTROL, + (serial_in(iobase, UART_CONTROL) & 0xF0)); + + serial_out(iobase, UART_BOF_COUNT_LO, 2); + serial_out(iobase, UART_BRICKWALL_CNT_LO, 0); +#if 1 + serial_out(iobase, UART_BRICKWALL_TX_CNT_HI, idev->tx_buff.len >> 8); + serial_out(iobase, UART_TX_SIZE_LO, idev->tx_buff.len & 0xff); +#else + serial_out(iobase, UART_BRICKWALL_TX_CNT_HI, 0); + serial_out(iobase, UART_TX_SIZE_LO, 0); +#endif + + register_bank(iobase, 1); + serial_out(iobase, UART_SCE_CFGB, + serial_in(iobase, UART_SCE_CFGB) | UART_CFGB_DMA_ENABLE); + + register_bank(iobase, 0); + + serial_out(iobase, UART_IER, UART_IER_ACTIVE_FRAME | UART_IER_EOM); + serial_out(iobase, UART_LCR_B, + UART_LCR_B_SCE_TRANSMIT|UART_LCR_B_SIP_ENABLE); + + serial_out(iobase, UART_MASTER, UART_MASTER_INT_EN); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); +} + +/* + * Function ircc_dma_xmit_complete (idev) + * + * The transfer of a frame in finished. This function will only be called + * by the interrupt handler + * + */ +static void ircc_dma_xmit_complete( struct irda_device *idev, int underrun) +{ + struct ircc_cb *self; + int iobase, d; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + ASSERT( idev != NULL, return;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return;); + + register_bank(idev->io.iobase, 1); + + serial_out(idev->io.iobase, UART_SCE_CFGB, + serial_in(idev->io.iobase, UART_SCE_CFGB) & + ~UART_CFGB_DMA_ENABLE); + + d = get_dma_residue(idev->io.dma); + + DEBUG(ircc_debug, __FUNCTION__ ": dma residue = %d, len=%d, sent=%d\n", + d, idev->tx_buff.len, idev->tx_buff.len - d); + + self = idev->priv; + + iobase = idev->io.iobase; + + /* Check for underrrun! */ + if ( underrun) { + idev->stats.tx_errors++; + idev->stats.tx_fifo_errors++; + } else { + idev->stats.tx_packets++; + idev->stats.tx_bytes += idev->tx_buff.len; + } + + /* Unlock tx_buff and request another frame */ + idev->netdev.tbusy = 0; /* Unlock */ + idev->media_busy = FALSE; + + /* Tell the network layer, that we can accept more frames */ + mark_bh( NET_BH); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); +} + +/* + * Function ircc_dma_receive (idev) + * + * Get ready for receiving a frame. The device will initiate a DMA + * if it starts to receive a frame. + * + */ +static int ircc_dma_receive( struct irda_device *idev) +{ + struct ircc_cb *self; + int iobase; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + ASSERT( idev != NULL, return -1;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + self = idev->priv; + iobase= idev->io.iobase; + + setup_dma( idev->io.dma, idev->rx_buff.data, idev->rx_buff.truesize, + DMA_MODE_READ); + + /* driver->media_busy = FALSE; */ + idev->io.direction = IO_RECV; + idev->rx_buff.data = idev->rx_buff.head; +#if 0 + idev->rx_buff.offset = 0; +#endif + + register_bank(iobase, 4); + serial_out(iobase, UART_CONTROL, + (serial_in(iobase, UART_CONTROL) &0xF0)); + serial_out(iobase, UART_BOF_COUNT_LO, 2); + serial_out(iobase, UART_BRICKWALL_CNT_LO, 0); + serial_out(iobase, UART_BRICKWALL_TX_CNT_HI, 0); + serial_out(iobase, UART_TX_SIZE_LO, 0); + serial_out(iobase, UART_RX_SIZE_HI, 0); + serial_out(iobase, UART_RX_SIZE_LO, 0); + + register_bank(iobase, 0); + serial_out(iobase, + UART_LCR_B, UART_LCR_B_SCE_RECEIVE | UART_LCR_B_SIP_ENABLE); + + register_bank(iobase, 1); + serial_out(iobase, UART_SCE_CFGB, + serial_in(iobase, UART_SCE_CFGB) | + UART_CFGB_DMA_ENABLE | UART_CFGB_DMA_BURST); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return 0; +} + +/* + * Function ircc_dma_receive_complete (idev) + * + * Finished with receiving frames + * + * + */ +static int ircc_dma_receive_complete( struct irda_device *idev, int iobase) +{ + struct sk_buff *skb; + struct ircc_cb *self; + int len, msgcnt; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + self = idev->priv; + + msgcnt = serial_in(idev->io.iobase, UART_LCR_B) & 0x08; + + DEBUG(ircc_debug, __FUNCTION__ ": dma count = %d\n", + get_dma_residue(idev->io.dma)); + + len = idev->rx_buff.truesize - get_dma_residue(idev->io.dma) - 4; + + DEBUG(ircc_debug, __FUNCTION__ ": msgcnt = %d, len=%d\n", msgcnt, len); + + skb = dev_alloc_skb( len+1); + + if (skb == NULL) { + printk( KERN_INFO __FUNCTION__ + ": memory squeeze, dropping frame.\n"); + return FALSE; + } + + /* Make sure IP header gets aligned */ + skb_reserve( skb, 1); + skb_put( skb, len); + + memcpy(skb->data, idev->rx_buff.data, len); + idev->stats.rx_packets++; + + skb->dev = &idev->netdev; + skb->mac.raw = skb->data; + skb->protocol = htons(ETH_P_IRDA); + netif_rx( skb); + + register_bank(idev->io.iobase, 1); + serial_out(idev->io.iobase, UART_SCE_CFGB, + serial_in(idev->io.iobase, UART_SCE_CFGB) & + ~UART_CFGB_DMA_ENABLE); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return TRUE; +} + +/* + * Function ircc_interrupt (irq, dev_id, regs) + * + * An interrupt from the chip has arrived. Time to do some work + * + */ +static void ircc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int iobase, iir; + + struct irda_device *idev = (struct irda_device *) dev_id; + + DEBUG(ircc_debug+1, __FUNCTION__ " -->\n"); + + if (idev == NULL) { + printk( KERN_WARNING "%s: irq %d for unknown device.\n", + driver_name, irq); + return; + } + + if (idev->io.baudrate <= 115200) { + DEBUG(ircc_debug+1, __FUNCTION__ + ": routing interrupt to irport_interrupt\n"); + return irport_interrupt( irq, dev_id, regs); + } + + iobase = idev->io.iobase; + + idev->netdev.interrupt = 1; + + serial_out(iobase, UART_MASTER, 0); + + register_bank(iobase, 0); + + iir = serial_in(iobase, UART_IIR); + + serial_out(iobase, UART_IER, 0); + + DEBUG(ircc_debug, __FUNCTION__ ": iir = 0x%02x\n", iir); + + if (iir & UART_IIR_EOM) { + DEBUG(ircc_debug, __FUNCTION__ ": UART_IIR_EOM\n"); + if (idev->io.direction == IO_RECV) { + ircc_dma_receive_complete(idev, iobase); + } else { + ircc_dma_xmit_complete(idev, iobase); + } + ircc_dma_receive(idev); + } + + if (iir & UART_IIR_ACTIVE_FRAME) { + DEBUG(ircc_debug, __FUNCTION__ ": UART_IIR_ACTIVE_FRAME\n"); + idev->rx_buff.state = INSIDE_FRAME; +#if 0 + ircc_dma_receive(idev); +#endif + } + + if (iir & UART_IIR_RAW_MODE) { + DEBUG(ircc_debug, __FUNCTION__ ": IIR RAW mode interrupt.\n"); + } + + idev->netdev.interrupt = 0; + + register_bank(iobase, 0); + serial_out(iobase, UART_IER, UART_IER_ACTIVE_FRAME|UART_IER_EOM); + serial_out(iobase, UART_MASTER, UART_MASTER_INT_EN); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); +} + +/* + * Function ircc_wait_until_sent (idev) + * + * This function should put the current thread to sleep until all data + * have been sent, so it is safe to change the speed. + */ +static void ircc_wait_until_sent( struct irda_device *idev) +{ + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + /* Just delay 60 ms */ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(6); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); +} + +/* + * Function ircc_is_receiving (idev) + * + * Return TRUE is we are currently receiving a frame + * + */ +static int ircc_is_receiving( struct irda_device *idev) +{ + int status = FALSE; + /* int iobase; */ + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + ASSERT( idev != NULL, return FALSE;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return FALSE;); + + DEBUG(ircc_debug, __FUNCTION__ ": dma count = %d\n", + get_dma_residue(idev->io.dma)); + + status = ( idev->rx_buff.state != OUTSIDE_FRAME); + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + + return status; +} + +/* + * Function ircc_net_init (dev) + * + * Initialize network device + * + */ +static int ircc_net_init( struct device *dev) +{ + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + /* Setup to be a normal IrDA network device driver */ + irda_device_setup( dev); + + /* Insert overrides below this line! */ + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return 0; +} + + +/* + * Function ircc_net_open (dev) + * + * Start the device + * + */ +static int ircc_net_open( struct device *dev) +{ + struct irda_device *idev; + int iobase; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + if (request_irq( idev->io.irq, ircc_interrupt, 0, idev->name, + (void *) idev)) { + return -EAGAIN; + } + /* + * Always allocate the DMA channel after the IRQ, + * and clean up on failure. + */ + if (request_dma(idev->io.dma, idev->name)) { + free_irq( idev->io.irq, idev); + return -EAGAIN; + } + + /* Ready to play! */ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + /* turn on interrupts */ + + MOD_INC_USE_COUNT; + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return 0; +} + +/* + * Function ircc_net_close (dev) + * + * Stop the device + * + */ +static int ircc_net_close(struct device *dev) +{ + struct irda_device *idev; + int iobase; + + DEBUG(ircc_debug, __FUNCTION__ " -->\n"); + + /* Stop device */ + dev->tbusy = 1; + dev->start = 0; + + ASSERT( dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT( idev != NULL, return 0;); + ASSERT( idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + iobase = idev->io.iobase; + + disable_dma( idev->io.dma); + + /* Disable interrupts */ + + free_irq( idev->io.irq, idev); + free_dma( idev->io.dma); + + MOD_DEC_USE_COUNT; + + DEBUG( ircc_debug, "--> " __FUNCTION__ "\n"); + return 0; +} + +#ifdef MODULE + +MODULE_AUTHOR("Thomas Davis <tadavis@jps.net>"); +MODULE_DESCRIPTION("SMC IrCC controller driver"); +MODULE_PARM(ircc_debug,"1i"); +MODULE_PARM(ircc_dma, "1i"); +MODULE_PARM(ircc_irq, "1i"); + +/* + * Function init_module (void) + * + * + * + */ +int init_module(void) +{ + return ircc_init(); +} + +/* + * Function cleanup_module (void) + * + * + * + */ +void cleanup_module(void) +{ + ircc_cleanup(); +} + +#endif diff --git a/drivers/net/irda/toshoboe.c b/drivers/net/irda/toshoboe.c new file mode 100644 index 000000000..c7ef984ea --- /dev/null +++ b/drivers/net/irda/toshoboe.c @@ -0,0 +1,901 @@ +/********************************************************************* + * + * Filename: toshoboe.c + * Version: 0.1 + * Description: Driver for the Toshiba OBOE (or type-O or 700 or 701) + * FIR Chipset. + * Status: Experimental. + * Author: James McKenzie <james@fishsoup.dhs.org> + * Created at: Sat May 8 12:35:27 1999 + * + * Copyright (c) 1999 James McKenzie, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * Neither James McKenzie nor Cambridge University admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + * Applicable Models : Libretto 100CT. and many more + * Toshiba refers to this chip as the type-O IR port. + * + ********************************************************************/ + +/* This driver is experimental, I have only three ir devices */ +/* an olivetti notebook which doesn't have FIR, a toshiba libretto, and */ +/* an hp printer, this works fine at 4MBPS with my HP printer */ + +static char *rcsid = "$Id: toshoboe.c,v 1.5 1999/05/12 12:24:39 root Exp root $"; + +/* + * $Log: toshoboe.c,v $ + * Revision 1.5 1999/05/12 12:24:39 root + * *** empty log message *** + * + * Revision 1.4 1999/05/12 11:55:08 root + * *** empty log message *** + * + * Revision 1.3 1999/05/09 01:33:12 root + * *** empty log message *** + * + * Revision 1.2 1999/05/09 01:30:38 root + * *** empty log message *** + * + * Revision 1.1 1999/05/09 01:25:04 root + * Initial revision + * + */ + +/* Define this to have only one frame in the XMIT or RECV queue */ +/* Toshiba's drivers do this, but it disables back to back tansfers */ +/* I think that the chip may have some problems certainly, I have */ +/* seen it jump over tasks in the taskfile->xmit with this turned on */ +#define ONETASK + +/* To adjust the number of tasks in use edit toshoboe.h */ + +/* Define this to enable FIR and MIR support */ +#define ENABLE_FAST + +/* Number of ports this driver can support, you also need to edit dev_self below */ +#define NSELFS 4 + +/* Size of IO window */ +#define CHIP_IO_EXTENT 0x1f + +/* Transmit and receive buffer sizes, adjust at your peril */ +#define RX_BUF_SZ 4196 +#define TX_BUF_SZ 4196 + +/* No user servicable parts below here */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/malloc.h> +#include <linux/init.h> +#include <linux/pci.h> + +#include <asm/system.h> +#include <asm/io.h> + +#include <net/irda/wrapper.h> +#include <net/irda/irda.h> +#include <net/irda/irmod.h> +#include <net/irda/irlap_frame.h> +#include <net/irda/irda_device.h> + +#include <net/irda/toshoboe.h> + +static char *driver_name = "toshoboe"; + +static struct toshoboe_cb *dev_self[NSELFS + 1] = +{NULL, NULL, NULL, NULL, NULL}; + +/* Shutdown the chip and point the taskfile reg somewhere else */ +static void +toshoboe_stopchip (struct toshoboe_cb *self) +{ + DEBUG (4, __FUNCTION__ "()\n"); + + outb_p (0x0e, OBOE_REG_11); + + outb_p (0x00, OBOE_RST); + outb_p (0x3f, OBOE_TFP2); /*Write the taskfile address */ + outb_p (0xff, OBOE_TFP1); + outb_p (0xff, OBOE_TFP0); + outb_p (0x0f, OBOE_REG_1B); + outb_p (0xff, OBOE_REG_1A); + outb_p (0x00, OBOE_ISR); /*FIXME: should i do this to disbale ints */ + outb_p (0x80, OBOE_RST); + outb_p (0xe, OBOE_LOCK); +} + +/*Set the baud rate */ +static void +toshoboe_setbaud (struct toshoboe_cb *self, int baud) +{ + DEBUG (4, __FUNCTION__ "()\n"); + + printk (KERN_WARNING "ToshOboe: seting baud to %d\n", baud); + + cli (); + switch (baud) + { + case 2400: + outb_p (OBOE_PMDL_SIR, OBOE_PMDL); + outb_p (OBOE_SMDL_SIR, OBOE_SMDL); + outb_p (0xbf, OBOE_UDIV); + break; + case 4800: + outb_p (OBOE_PMDL_SIR, OBOE_PMDL); + outb_p (OBOE_SMDL_SIR, OBOE_SMDL); + outb_p (0x5f, OBOE_UDIV); + break; + case 9600: + outb_p (OBOE_PMDL_SIR, OBOE_PMDL); + outb_p (OBOE_SMDL_SIR, OBOE_SMDL); + outb_p (0x2f, OBOE_UDIV); + break; + case 19200: + outb_p (OBOE_PMDL_SIR, OBOE_PMDL); + outb_p (OBOE_SMDL_SIR, OBOE_SMDL); + outb_p (0x17, OBOE_UDIV); + break; + case 38400: + outb_p (OBOE_PMDL_SIR, OBOE_PMDL); + outb_p (OBOE_SMDL_SIR, OBOE_SMDL); + outb_p (0xb, OBOE_UDIV); + break; + case 57600: + outb_p (OBOE_PMDL_SIR, OBOE_PMDL); + outb_p (OBOE_SMDL_SIR, OBOE_SMDL); + outb_p (0x7, OBOE_UDIV); + break; + case 115200: + outb_p (OBOE_PMDL_SIR, OBOE_PMDL); + outb_p (OBOE_SMDL_SIR, OBOE_SMDL); + outb_p (0x3, OBOE_UDIV); + break; + case 1152000: + outb_p (OBOE_PMDL_MIR, OBOE_PMDL); + outb_p (OBOE_SMDL_MIR, OBOE_SMDL); + outb_p (0x1, OBOE_UDIV); + break; + case 4000000: + outb_p (OBOE_PMDL_FIR, OBOE_PMDL); + outb_p (OBOE_SMDL_FIR, OBOE_SMDL); + outb_p (0x0, OBOE_UDIV); + break; + } + + sti (); + + outb_p (0x00, OBOE_RST); + outb_p (0x80, OBOE_RST); + outb_p (0x01, OBOE_REG_9); + +} + +/* Wake the chip up and get it looking at the taskfile */ +static void +toshoboe_startchip (struct toshoboe_cb *self) +{ + __u32 physaddr; + + DEBUG (4, __FUNCTION__ "()\n"); + + + outb_p (0, OBOE_LOCK); + outb_p (0, OBOE_RST); + outb_p (OBOE_NTR_VAL, OBOE_NTR); + outb_p (0xf0, OBOE_REG_D); + outb_p (0xff, OBOE_ISR); + outb_p (0x0f, OBOE_REG_1A); + outb_p (0xff, OBOE_REG_1B); + + + physaddr = virt_to_bus (self->taskfile); + + outb_p ((physaddr >> 0x0a) & 0xff, OBOE_TFP0); + outb_p ((physaddr >> 0x12) & 0xff, OBOE_TFP1); + outb_p ((physaddr >> 0x1a) & 0x3f, OBOE_TFP2); + + outb_p (0x0e, OBOE_REG_11); + outb_p (0x80, OBOE_RST); + + toshoboe_setbaud (self, 9600); + +} + +/*Let the chip look at memory */ +static void +toshoboe_enablebm (struct toshoboe_cb *self) +{ + DEBUG (4, __FUNCTION__ "()\n"); + pci_set_master (self->pdev); +} + +/*Don't let the chip look at memory */ +static void +toshoboe_disablebm (struct toshoboe_cb *self) +{ + __u8 command; + DEBUG (4, __FUNCTION__ "()\n"); + + pci_read_config_byte (self->pdev, PCI_COMMAND, &command); + command &= ~PCI_COMMAND_MASTER; + pci_write_config_byte (self->pdev, PCI_COMMAND, command); + +} + +/*setup the taskfile */ +static void +toshoboe_initbuffs (struct toshoboe_cb *self) +{ + int i; + + DEBUG (4, __FUNCTION__ "()\n"); + + cli (); + + for (i = 0; i < TX_SLOTS; ++i) + { + self->taskfile->xmit[i].len = 0; + self->taskfile->xmit[i].control = 0x00; + self->taskfile->xmit[i].buffer = virt_to_bus (self->xmit_bufs[i]); + } + + for (i = 0; i < RX_SLOTS; ++i) + { + self->taskfile->recv[i].len = 0; + self->taskfile->recv[i].control = 0x83; + self->taskfile->recv[i].buffer = virt_to_bus (self->recv_bufs[i]); + } + + sti (); +} + + +/*Transmit something */ +static int +toshoboe_hard_xmit (struct sk_buff *skb, struct device *dev) +{ + struct irda_device *idev; + struct toshoboe_cb *self; + int mtt, len; + + idev = (struct irda_device *) dev->priv; + ASSERT (idev != NULL, return 0;); + ASSERT (idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + self = idev->priv; + ASSERT (self != NULL, return 0;); + + +#ifdef ONETASK + if (self->txpending) + return -EBUSY; + + self->txs = inb_p (OBOE_XMTT) - OBOE_XMTT_OFFSET; + + self->txs &= 0x3f; + +#endif + + if (self->taskfile->xmit[self->txs].control) + return -EBUSY; + + + if (inb_p (OBOE_RST) & OBOE_RST_WRAP) + { + len = async_wrap_skb (skb, self->xmit_bufs[self->txs], TX_BUF_SZ); + } + else + { + len = skb->len; + memcpy (self->xmit_bufs[self->txs], skb->data, len); + } + self->taskfile->xmit[self->txs].len = len & 0x0fff; + + + + outb_p (0, OBOE_RST); + outb_p (0x1e, OBOE_REG_11); + + self->taskfile->xmit[self->txs].control = 0x84; + + mtt = irda_get_mtt (skb); + if (mtt) + udelay (mtt); + + self->txpending++; + + /*FIXME: ask about tbusy,media_busy stuff, for the moment */ + /*tbusy means can't queue any more */ +#ifndef ONETASK + if (self->txpending == TX_SLOTS) + { +#else + { +#endif + if (irda_lock ((void *) &dev->tbusy) == FALSE) + return -EBUSY; + } + + outb_p (0x80, OBOE_RST); + outb_p (1, OBOE_REG_9); + + self->txs++; + self->txs %= TX_SLOTS; + + dev_kfree_skb (skb); + + return 0; +} + +/*interrupt handler */ +static void +toshoboe_interrupt (int irq, void *dev_id, struct pt_regs *regs) +{ + struct irda_device *idev = (struct irda_device *) dev_id; + struct toshoboe_cb *self; + __u8 irqstat; + struct sk_buff *skb; + + if (idev == NULL) + { + printk (KERN_WARNING "%s: irq %d for unknown device.\n", + driver_name, irq); + return; + } + + self = idev->priv; + + if (!self) + return; + + DEBUG (4, __FUNCTION__ "()\n"); + + irqstat = inb_p (OBOE_ISR); + +/* woz it us */ + if (!(irqstat & 0xf8)) + return; + + outb_p (irqstat, OBOE_ISR); /*Acknologede it */ + + +/* Txdone */ + if (irqstat & OBOE_ISR_TXDONE) + { + self->txpending--; + + idev->stats.tx_packets++; + + idev->media_busy = FALSE; + idev->netdev.tbusy = 0; + + mark_bh (NET_BH); + } + + if (irqstat & OBOE_ISR_RXDONE) + { + +#ifdef ONETASK + self->rxs = inb_p (OBOE_RCVT); + self->rxs += (RX_SLOTS - 1); + self->rxs %= RX_SLOTS; +#else + while (self->taskfile->recv[self->rxs].control == 0) +#endif + { + int len = self->taskfile->recv[self->rxs].len; + + if (len>2) len-=2; + + skb = dev_alloc_skb (len + 1); + if (skb) + { + skb_reserve (skb, 1); + + skb_put (skb, len); + memcpy (skb->data, self->recv_bufs[self->rxs], len); + + idev->stats.rx_packets++; + skb->dev = &idev->netdev; + skb->mac.raw = skb->data; + skb->protocol = htons (ETH_P_IRDA); + } + else + { + printk (KERN_INFO __FUNCTION__ + "(), memory squeeze, dropping frame.\n"); + } + + + + self->taskfile->recv[self->rxs].control = 0x83; + self->taskfile->recv[self->rxs].len = 0x0; + + self->rxs++; + self->rxs %= RX_SLOTS; + + if (skb) + netif_rx (skb); + + } + + } + + if (irqstat & OBOE_ISR_20) + { + printk (KERN_WARNING "Oboe_irq: 20\n"); + } + if (irqstat & OBOE_ISR_10) + { + printk (KERN_WARNING "Oboe_irq: 10\n"); + } + if (irqstat & 0x8) + { + /*FIXME: I think this is a TX or RX error of some sort */ + + idev->stats.tx_errors++; + idev->stats.rx_errors++; + + } + + +} + + + +/* Change the baud rate */ +static void +toshoboe_change_speed (struct irda_device *idev, int speed) +{ + struct toshoboe_cb *self; + DEBUG (4, __FUNCTION__ "()\n"); + + ASSERT (idev != NULL, return;); + ASSERT (idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = idev->priv; + ASSERT (self != NULL, return;); + + idev->io.baudrate = speed; + + toshoboe_setbaud (self, speed); + +} + + +/* Check all xmit_tasks finished */ +static void +toshoboe_wait_until_sent (struct irda_device *idev) +{ + struct toshoboe_cb *self; + int i; + + DEBUG (4, __FUNCTION__ "()\n"); + + ASSERT (idev != NULL, return;); + ASSERT (idev->magic == IRDA_DEVICE_MAGIC, return;); + + self = idev->priv; + ASSERT (self != NULL, return;); + + for (i = 0; i < TX_SLOTS; ++i) + { + while (self->taskfile->xmit[i].control) + { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout (6); + } + } + +} + +static int +toshoboe_is_receiving (struct irda_device *idev) +{ + DEBUG (4, __FUNCTION__ "()\n"); + +/*FIXME Can't tell! */ + return (FALSE); +} + + +static int +toshoboe_net_init (struct device *dev) +{ + DEBUG (4, __FUNCTION__ "()\n"); + + /* Setup to be a normal IrDA network device driver */ + irda_device_setup (dev); + + /* Insert overrides below this line! */ + return 0; +} + + + + +static int +toshoboe_net_open (struct device *dev) +{ + struct irda_device *idev; + struct toshoboe_cb *self; + + DEBUG (4, __FUNCTION__ "()\n"); + + ASSERT (dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT (idev != NULL, return 0;); + ASSERT (idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + self = idev->priv; + ASSERT (self != NULL, return 0;); + + if (request_irq (idev->io.irq, toshoboe_interrupt, + SA_SHIRQ | SA_INTERRUPT, idev->name, (void *) idev)) + { + + return -EAGAIN; + } + + toshoboe_initbuffs (self); + toshoboe_enablebm (self); + toshoboe_startchip (self); + + + cli (); + + /*FIXME: need to test this carefully to check which one */ + /*of the two possible startup logics the chip uses */ + /*although it won't make any difference if no-one xmits durining init */ + /*and none what soever if using ONETASK */ + + self->rxs = inb_p (OBOE_RCVT); + self->txs = inb_p (OBOE_XMTT) - OBOE_XMTT_OFFSET; + +#ifdef 0 + self->rxs = 0; + self->txs = 0; +#endif +#ifdef 0 + self->rxs = RX_SLOTS - 1; + self->txs = 0; +#endif + + + self->txpending = 0; + + sti (); + + + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + MOD_INC_USE_COUNT; + + return 0; + +} + +static int +toshoboe_net_close (struct device *dev) +{ + struct irda_device *idev; + struct toshoboe_cb *self; + + DEBUG (4, __FUNCTION__ "()\n"); + + ASSERT (dev != NULL, return -1;); + idev = (struct irda_device *) dev->priv; + + ASSERT (idev != NULL, return 0;); + ASSERT (idev->magic == IRDA_DEVICE_MAGIC, return 0;); + + dev->tbusy = 1; + dev->start = 0; + + + self = idev->priv; + + ASSERT (self != NULL, return 0;); + + free_irq (idev->io.irq, (void *) idev); + + toshoboe_stopchip (self); + toshoboe_disablebm (self); + + MOD_DEC_USE_COUNT; + + return 0; + +} + + + +#ifdef MODULE + +static int +toshoboe_close (struct irda_device *idev) +{ + struct toshoboe_cb *self; + int i; + + DEBUG (4, __FUNCTION__ "()\n"); + + ASSERT (idev != NULL, return -1;); + ASSERT (idev->magic == IRDA_DEVICE_MAGIC, return -1;); + + self = idev->priv; + + ASSERT (self != NULL, return -1;); + + toshoboe_stopchip (self); + + release_region (idev->io.iobase, idev->io.io_ext); + + + for (i = 0; i < TX_SLOTS; ++i) + { + kfree (self->xmit_bufs[i]); + self->xmit_bufs[i] = NULL; + } + + for (i = 0; i < RX_SLOTS; ++i) + { + kfree (self->recv_bufs[i]); + self->recv_bufs[i] = NULL; + } + + + kfree (self->taskfilebuf); + self->taskfilebuf = NULL; + self->taskfile = NULL; + + + irda_device_close (idev); + + return (0); + +} + +#endif + + + +static int +toshoboe_open (struct pci_dev *pci_dev) +{ + struct toshoboe_cb *self; + struct irda_device *idev; + int i = 0; + int ok=0; + + + DEBUG (4, __FUNCTION__ "()\n"); + + while (dev_self[i]) + i++; + + if (i == NSELFS) + { + printk (KERN_ERR "Oboe: No more instances available"); + return -ENOMEM; + } + + self = kmalloc (sizeof (struct toshoboe_cb), GFP_KERNEL); + + if (self == NULL) + { + printk (KERN_ERR "IrDA: Can't allocate memory for " + "IrDA control block!\n"); + return -ENOMEM; + } + + memset (self, 0, sizeof (struct toshoboe_cb)); + + + dev_self[i] = self; + + self->pdev = pci_dev; + self->base = pci_dev->base_address[0] & PCI_BASE_ADDRESS_IO_MASK; + + idev = &self->idev; + + /*Setup idev */ + + idev->io.iobase = self->base; + idev->io.irq = pci_dev->irq; + idev->io.io_ext = CHIP_IO_EXTENT; + + /* Lock the port that we need */ + i = check_region (idev->io.iobase, idev->io.io_ext); + if (i < 0) + { + DEBUG (0, __FUNCTION__ "(), can't get iobase of 0x%03x\n", + idev->io.iobase); + + dev_self[i] = NULL; + kfree (self); + + return -ENODEV; + } + + request_region (idev->io.iobase, idev->io.io_ext, driver_name); + + irda_init_max_qos_capabilies (&idev->qos); + + idev->qos.baud_rate.bits = IR_2400 | /*IR_4800 | */ IR_9600 | IR_19200 | + IR_115200; +#ifdef ENABLE_FAST + idev->qos.baud_rate.bits|= IR_576000 | IR_1152000 | (IR_4000000 << 8); +#endif + + idev->qos.min_turn_time.bits = 0xff; /*FIXME: what does this do? */ + + irda_qos_bits_to_value (&idev->qos); + + idev->flags = IFF_SIR | IFF_DMA | IFF_PIO; + +#ifdef ENABLE_FAST + idev->flags |= IFF_FIR; +#endif + + /* These aren't much use as we need to have a whole panoply of + * buffers running */ + + idev->rx_buff.flags = 0; + idev->tx_buff.flags = 0; + idev->rx_buff.truesize = 0; + idev->rx_buff.truesize = 0; + + idev->change_speed = toshoboe_change_speed; + idev->wait_until_sent = toshoboe_wait_until_sent; + idev->is_receiving = toshoboe_is_receiving; + + idev->netdev.init = toshoboe_net_init; + idev->netdev.hard_start_xmit = toshoboe_hard_xmit; + idev->netdev.open = toshoboe_net_open; + idev->netdev.stop = toshoboe_net_close; + + + /* Now setup the endless buffers we need */ + + self->txs = 0; + self->rxs = 0; + + self->taskfilebuf = kmalloc (OBOE_TASK_BUF_LEN, GFP_KERNEL | GFP_DMA); + if (!self->taskfilebuf) { + printk(KERN_ERR "toshoboe: kmalloc for DMA failed()\n"); + kfree(self); + return -ENOMEM; + } + + + memset (self->taskfilebuf, 0, OBOE_TASK_BUF_LEN); + + /*We need to align the taskfile on a taskfile size boundary */ + { + __u32 addr; + + addr = (__u32) self->taskfilebuf; + addr &= ~(sizeof (struct OboeTaskFile) - 1); + addr += sizeof (struct OboeTaskFile); + + self->taskfile = (struct OboeTaskFile *) addr; + } + + for (i = 0; i < TX_SLOTS; ++i) + { + self->xmit_bufs[i] = kmalloc (TX_BUF_SZ, GFP_KERNEL | GFP_DMA); + if (self->xmit_bufs[i]) ok++; + } + + for (i = 0; i < RX_SLOTS; ++i) + { + self->recv_bufs[i] = kmalloc (TX_BUF_SZ, GFP_KERNEL | GFP_DMA); + if (self->recv_bufs[i]) ok++; + } + + if (ok!=RX_SLOTS+TX_SLOTS) { + printk(KERN_ERR "toshoboe: kmalloc for buffers failed()\n"); + + + for (i = 0; i < TX_SLOTS; ++i) if (self->xmit_bufs[i]) kfree(self->xmit_bufs[i]); + for (i = 0; i < RX_SLOTS; ++i) if (self->recv_bufs[i]) kfree(self->recv_bufs[i]); + + kfree(self); + return -ENOMEM; + + } + + + irda_device_open (idev, driver_name, self); + + printk (KERN_WARNING "ToshOboe: Using "); +#ifdef ONETASK + printk ("single"); +#else + printk ("multiple"); +#endif + printk (" tasks, version %s\n", rcsid); + + return (0); +} + +__initfunc (int toshoboe_init (void)) +{ + struct pci_dev *pci_dev = NULL; + int found = 0; + + do + { + pci_dev = pci_find_device (PCI_VENDOR_ID_TOSHIBA, + PCI_DEVICE_ID_FIR701, pci_dev); + if (pci_dev) + { + printk (KERN_WARNING "ToshOboe: Found 701 chip at 0x%0lx irq %d\n", + pci_dev->base_address[0] & PCI_BASE_ADDRESS_IO_MASK, + pci_dev->irq); + + if (!toshoboe_open (pci_dev)) + found++; + } + + } + while (pci_dev); + + if (found) + return 0; + + return -ENODEV; +} + +#ifdef MODULE + +static void +toshoboe_cleanup (void) +{ + int i; + + DEBUG (4, __FUNCTION__ "()\n"); + + for (i = 0; i < 4; i++) + { + if (dev_self[i]) + toshoboe_close (&(dev_self[i]->idev)); + } +} + + + +int +init_module (void) +{ + return toshoboe_init (); +} + + +void +cleanup_module (void) +{ + toshoboe_cleanup (); +} + + +#endif diff --git a/drivers/net/sk_mca.c b/drivers/net/sk_mca.c new file mode 100644 index 000000000..720b50beb --- /dev/null +++ b/drivers/net/sk_mca.c @@ -0,0 +1,1143 @@ +/* +net-3-driver for the SKNET MCA-based cards + +This is an extension to the Linux operating system, and is covered by the +same Gnu Public License that covers that work. + +Copyright 1999 by Alfred Arnold (alfred@ccac.rwth-aachen.de, aarnold@elsa.de) + +This driver is based both on the 3C523 driver and the SK_G16 driver. + +paper sources: + 'PC Hardware: Aufbau, Funktionsweise, Programmierung' by + Hans-Peter Messmer for the basic Microchannel stuff + + 'Linux Geraetetreiber' by Allesandro Rubini, Kalle Dalheimer + for help on Ethernet driver programming + + 'Ethernet/IEEE 802.3 Family 1992 World Network Data Book/Handbook' by AMD + for documentation on the AM7990 LANCE + + 'SKNET Personal Technisches Manual', Version 1.2 by Schneider&Koch + for documentation on the Junior board + + 'SK-NET MC2+ Technical Manual", Version 1.1 by Schneider&Koch for + documentation on the MC2 bord + + A big thank you to the S&K support for providing me so quickly with + documentation! + + Also see http://www.syskonnect.com/ + + Missing things: + + -> set debug level via ioctl instead of compile-time switches + -> I didn't follow the development of the 2.1.x kernels, so my + assumptions about which things changed with which kernel version + are probably nonsense + +History: + May 16th, 1999 + startup + May 22st, 1999 + added private structure, methods + begun building data structures in RAM + May 23nd, 1999 + can receive frames, send frames + May 24th, 1999 + modularized intialization of LANCE + loadable as module + still Tx problem :-( + May 26th, 1999 + MC2 works + support for multiple devices + display media type for MC2+ + May 28th, 1999 + fixed problem in GetLANCE leaving interrupts turned off + increase TX queue to 4 packets to improve send performance + May 29th, 1999 + a few corrections in statistics, caught rcvr overruns + reinitialization of LANCE/board in critical situations + MCA info implemented + implemented LANCE multicast filter + Jun 6th, 1999 + additions for Linux 2.2 + + *************************************************************************/ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/mca.h> +#include <asm/processor.h> +#include <asm/bitops.h> +#include <asm/io.h> + +#ifdef MODULE +#include <linux/module.h> +#include <linux/version.h> +#endif + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#define _SK_MCA_DRIVER_ +#include "sk_mca.h" + +/* ------------------------------------------------------------------------ + * global static data - not more since we can handle multiple boards and + * have to pack all state info into the device struct! + * ------------------------------------------------------------------------ */ + +static char *MediaNames[Media_Count]= + {"10Base2", "10BaseT", "10Base5", "Unknown"}; + +static unsigned char poly[] = + {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0}; + +/* ------------------------------------------------------------------------ + * private subfunctions + * ------------------------------------------------------------------------ */ + +/* dump parts of shared memory - only needed during debugging */ + +#ifdef DEBUG +static void dumpmem(struct device *dev, u32 start, u32 len) +{ + int z; + + for (z = 0; z < len; z++) + { + if ((z & 15) == 0) + printk("%04x:", z); + printk(" %02x", readb(dev->mem_start + start + z)); + if ((z & 15) == 15) + printk("\n"); + } +} + +/* print exact time - ditto */ + +static void PrTime(void) +{ + struct timeval tv; + + do_gettimeofday(&tv); + printk("%9d:%06d: ", tv.tv_sec, tv.tv_usec); +} +#endif + +/* deduce resources out of POS registers */ + +static void getaddrs(int slot, int junior, int *base, int *irq, + skmca_medium *medium) +{ + u_char pos0, pos1, pos2; + + if (junior) + { + pos0 = mca_read_stored_pos(slot, 2); + *base = ((pos0 & 0x0e) << 13) + 0xc0000; + *irq = ((pos0 & 0x10) >> 4) + 10; + *medium = Media_Unknown; + } + else + { + /* reset POS 104 Bits 0+1 so the shared memory region goes to the + configured area between 640K and 1M. Afterwards, enable the MC2. + I really don't know what rode SK to do this... */ + + mca_write_pos(slot, 4, mca_read_stored_pos(slot, 4) & 0xfc); + mca_write_pos(slot, 2, mca_read_stored_pos(slot, 2) | 0x01); + + pos1 = mca_read_stored_pos(slot, 3); + pos2 = mca_read_stored_pos(slot, 4); + *base = ((pos1 & 0x07) << 14) + 0xc0000; + switch (pos2 & 0x0c) + { + case 0: *irq = 3; break; + case 4: *irq = 5; break; + case 8: *irq = 10; break; + case 12: *irq = 11; break; + } + *medium = (pos2 >> 6) & 3; + } +} + +/* check for both cards: + When the MC2 is turned off, it was configured for more than 15MB RAM, + is disabled and won't get detected using the standard probe. We + therefore have to scan the slots manually :-( */ + +static int dofind(int *junior, int firstslot) +{ + int slot; + unsigned int id; + + for (slot = firstslot; slot < MCA_MAX_SLOT_NR; slot++) + { + id = mca_read_stored_pos(slot, 0) + + (((unsigned int) mca_read_stored_pos(slot, 1)) << 8); + + *junior = 0; + if (id == SKNET_MCA_ID) + return slot; + *junior = 1; + if (id == SKNET_JUNIOR_MCA_ID) + return slot; + } + return MCA_NOTFOUND; +} + +/* reset the whole board */ + +static void ResetBoard(struct device *dev) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + + writeb(CTRL_RESET_ON, priv->ctrladdr); + udelay(10); + writeb(CTRL_RESET_OFF, priv->ctrladdr); +} + +/* set LANCE register - must be atomic */ + +static void SetLANCE(struct device *dev, u16 addr, u16 value) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + unsigned long flags; + + /* disable interrupts */ + + save_flags(flags); + cli(); + + /* wait until no transfer is pending */ + + while ((readb(priv->ctrladdr) & STAT_IO_BUSY) == STAT_IO_BUSY); + + /* transfer register address to RAP */ + + writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_RAP, priv->ctrladdr); + writew(addr, priv->ioregaddr); + writeb(IOCMD_GO, priv->cmdaddr); + udelay(1); + while ((readb(priv->ctrladdr) & STAT_IO_BUSY) == STAT_IO_BUSY); + + /* transfer data to register */ + + writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_DATA, priv->ctrladdr); + writew(value, priv->ioregaddr); + writeb(IOCMD_GO, priv->cmdaddr); + udelay(1); + while ((readb(priv->ctrladdr) & STAT_IO_BUSY) == STAT_IO_BUSY); + + /* reenable interrupts */ + + restore_flags(flags); +} + +/* get LANCE register */ + +static u16 GetLANCE(struct device *dev, u16 addr) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + unsigned long flags; + unsigned int res; + + /* disable interrupts */ + + save_flags(flags); + cli(); + + /* wait until no transfer is pending */ + + while ((readb(priv->ctrladdr) & STAT_IO_BUSY) == STAT_IO_BUSY); + + /* transfer register address to RAP */ + + writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_RAP, priv->ctrladdr); + writew(addr, priv->ioregaddr); + writeb(IOCMD_GO, priv->cmdaddr); + udelay(1); + while ((readb(priv->ctrladdr) & STAT_IO_BUSY) == STAT_IO_BUSY); + + /* transfer data from register */ + + writeb(CTRL_RESET_OFF | CTRL_RW_READ | CTRL_ADR_DATA, priv->ctrladdr); + writeb(IOCMD_GO, priv->cmdaddr); + udelay(1); + while ((readb(priv->ctrladdr) & STAT_IO_BUSY) == STAT_IO_BUSY); + res = readw(priv->ioregaddr); + + /* reenable interrupts */ + + restore_flags(flags); + + return res; +} + +/* build up descriptors in shared RAM */ + +static void InitDscrs(struct device *dev) +{ + u32 bufaddr; + + /* Set up Tx descriptors. The board has only 16K RAM so bits 16..23 + are always 0. */ + + bufaddr = RAM_DATABASE; + { + LANCE_TxDescr descr; + int z; + + for (z = 0; z < TXCOUNT; z++) + { + descr.LowAddr = bufaddr; + descr.Flags = 0; + descr.Len = 0xf000; + descr.Status = 0; + memcpy_toio(dev->mem_start + RAM_TXBASE + (z * sizeof(LANCE_TxDescr)), + &descr, sizeof(LANCE_TxDescr)); + memset_io(dev->mem_start + bufaddr, 0, RAM_BUFSIZE); + bufaddr += RAM_BUFSIZE; + } + } + + /* do the same for the Rx descriptors */ + + { + LANCE_RxDescr descr; + int z; + + for (z = 0; z < RXCOUNT; z++) + { + descr.LowAddr = bufaddr; + descr.Flags = RXDSCR_FLAGS_OWN; + descr.MaxLen = -RAM_BUFSIZE; + descr.Len = 0; + memcpy_toio(dev->mem_start + RAM_RXBASE + (z * sizeof(LANCE_RxDescr)), + &descr, sizeof(LANCE_RxDescr)); + memset_io(dev->mem_start + bufaddr, 0, RAM_BUFSIZE); + bufaddr += RAM_BUFSIZE; + } + } +} + +/* calculate the hash bit position for a given multicast address + taken more or less directly from the AMD datasheet... */ + +static void UpdateCRC(unsigned char *CRC, int bit) +{ + int j; + + /* shift CRC one bit */ + + memmove(CRC + 1, CRC, 32 * sizeof(unsigned char)); + CRC[0] = 0; + + /* if bit XOR controlbit = 1, set CRC = CRC XOR polynomial */ + + if (bit ^ CRC[32]) + for (j = 0; j < 32; j++) + CRC[j] ^= poly[j]; +} + +static unsigned int GetHash(char *address) +{ + unsigned char CRC[33]; + int i, byte, hashcode; + + /* a multicast address has bit 0 in the first byte set */ + + if ((address[0] & 1) == 0) + return -1; + + /* initialize CRC */ + + memset(CRC, 1, sizeof(CRC)); + + /* loop through address bits */ + + for (byte = 0; byte < 6; byte++) + for (i = 0; i < 8; i++) + UpdateCRC(CRC, (address[byte] >> i) & 1); + + /* hashcode is the 6 least significant bits of the CRC */ + + hashcode = 0; + for (i = 0; i < 6; i++) + hashcode = (hashcode << 1) + CRC[i]; + return hashcode; +} + +/* feed ready-built initialization block into LANCE */ + +static void InitLANCE(struct device *dev) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + + /* build up descriptors. */ + + InitDscrs(dev); + + /* next RX descriptor to be read is the first one. Since the LANCE + will start from the beginning after initialization, we have to + reset out pointers too. */ + + priv->nextrx = 0; + + /* no TX descriptors active */ + + priv->nexttxput = priv->nexttxdone = priv->txbusy = 0; + + /* set up the LANCE bus control register - constant for SKnet boards */ + + SetLANCE(dev, LANCE_CSR3, CSR3_BSWAP_OFF | CSR3_ALE_LOW | CSR3_BCON_HOLD); + + /* write address of initialization block into LANCE */ + + SetLANCE(dev, LANCE_CSR1, RAM_INITBASE & 0xffff); + SetLANCE(dev, LANCE_CSR2, (RAM_INITBASE >> 16) & 0xff); + + /* we don't get ready until the LANCE has read the init block */ + + dev->tbusy = 1; + + /* let LANCE read the initialization block. LANCE is ready + when we receive the corresponding interrupt. */ + + SetLANCE(dev, LANCE_CSR0, CSR0_INEA | CSR0_INIT); +} + +/* stop the LANCE so we can reinitialize it */ + +static void StopLANCE(struct device *dev) +{ + /* can't take frames any more */ + + dev->tbusy = 1; + + /* disable interrupts, stop it */ + + SetLANCE(dev, LANCE_CSR0, CSR0_STOP); +} + +/* initialize card and LANCE for proper operation */ + +static void InitBoard(struct device *dev) +{ + LANCE_InitBlock block; + + /* Lay out the shared RAM - first we create the init block for the LANCE. + We do not overwrite it later because we need it again when we switch + promiscous mode on/off. */ + + block.Mode = 0; + if (dev->flags & IFF_PROMISC) + block.Mode |= LANCE_INIT_PROM; + memcpy(block.PAdr, dev->dev_addr, 6); + memset(block.LAdrF, 0, sizeof(block.LAdrF)); + block.RdrP = (RAM_RXBASE & 0xffffff) | (LRXCOUNT << 29); + block.TdrP = (RAM_TXBASE & 0xffffff) | (LTXCOUNT << 29); + + memcpy_toio(dev->mem_start + RAM_INITBASE, &block, sizeof(block)); + + /* initialize LANCE. Implicitly sets up other structures in RAM. */ + + InitLANCE(dev); +} + +/* deinitialize card and LANCE */ + +static void DeinitBoard(struct device *dev) +{ + /* stop LANCE */ + + StopLANCE(dev); + + /* reset board */ + + ResetBoard(dev); +} + +/* ------------------------------------------------------------------------ + * interrupt handler(s) + * ------------------------------------------------------------------------ */ + +/* LANCE has read initializazion block -> start it */ + +static u16 irqstart_handler(struct device *dev, u16 oldcsr0) +{ + /* now we're ready to transmit */ + + dev->tbusy = 0; + + /* reset IDON bit, start LANCE */ + + SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_IDON | CSR0_STRT); + return GetLANCE(dev, LANCE_CSR0); +} + +/* receive interrupt */ + +static u16 irqrx_handler(struct device *dev, u16 oldcsr0) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + LANCE_RxDescr descr; + unsigned int descraddr; + + /* did we loose blocks due to a FIFO overrun ? */ + + if (oldcsr0 & CSR0_MISS) + priv->stat.rx_fifo_errors++; + + /* run through queue until we reach a descriptor we do not own */ + + descraddr = RAM_RXBASE + (priv->nextrx * sizeof(LANCE_RxDescr)); + while (1) + { + /* read descriptor */ + memcpy_fromio(&descr, dev->mem_start + descraddr, sizeof(LANCE_RxDescr)); + + /* if we reach a descriptor we do not own, we're done */ + if ((descr.Flags & RXDSCR_FLAGS_OWN) != 0) + break; + +#ifdef DEBUG + PrTime(); printk("Receive packet on descr %d len %d\n", priv->nextrx, descr.Len); +#endif + + /* erroneous packet ? */ + if ((descr.Flags & RXDSCR_FLAGS_ERR) != 0) + { + priv->stat.rx_errors++; + if ((descr.Flags & RXDSCR_FLAGS_CRC) != 0) + priv->stat.rx_crc_errors++; + else if ((descr.Flags & RXDSCR_FLAGS_CRC) != 0) + priv->stat.rx_frame_errors++; + else if ((descr.Flags & RXDSCR_FLAGS_OFLO) != 0) + priv->stat.rx_fifo_errors++; + } + + /* good packet ? */ + else + { + struct sk_buff *skb; + + skb = dev_alloc_skb(descr.Len + 2); + if (skb == NULL) + priv->stat.rx_dropped++; + else + { + memcpy_fromio(skb_put(skb, descr.Len), + dev->mem_start + descr.LowAddr, descr.Len); + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_NONE; + priv->stat.rx_packets++; +#if LINUX_VERSION_CODE >= 0x020119 /* byte counters for >= 2.1.25 */ + priv->stat.rx_bytes += descr.Len; +#endif + netif_rx(skb); + } + } + + /* give descriptor back to LANCE */ + descr.Len = 0; + descr.Flags |= RXDSCR_FLAGS_OWN; + + /* update descriptor in shared RAM */ + memcpy_toio(dev->mem_start + descraddr, &descr, sizeof(LANCE_RxDescr)); + + /* go to next descriptor */ + priv->nextrx++; descraddr += sizeof(LANCE_RxDescr); + if (priv->nextrx >= RXCOUNT) + { + priv->nextrx = 0; + descraddr = RAM_RXBASE; + } + } + + /* reset RINT bit */ + + SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_RINT); + return GetLANCE(dev, LANCE_CSR0); +} + +/* transmit interrupt */ + +static u16 irqtx_handler(struct device *dev, u16 oldcsr0) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + LANCE_TxDescr descr; + unsigned int descraddr; + + /* check descriptors at most until no busy one is left */ + + descraddr = RAM_TXBASE + (priv->nexttxdone * sizeof(LANCE_TxDescr)); + while (priv->txbusy > 0) + { + /* read descriptor */ + memcpy_fromio(&descr, dev->mem_start + descraddr, sizeof(LANCE_TxDescr)); + + /* if the LANCE still owns this one, we've worked out all sent packets */ + if ((descr.Flags & TXDSCR_FLAGS_OWN) != 0) + break; + +#ifdef DEBUG + PrTime(); printk("Send packet done on descr %d\n", priv->nexttxdone); +#endif + + /* update statistics */ + if ((descr.Flags & TXDSCR_FLAGS_ERR) == 0) + { + priv->stat.tx_packets++; +#if LINUX_VERSION_CODE >= 0x020119 /* byte counters for >= 2.1.25 */ + priv->stat.tx_bytes++; +#endif + } + else + { + priv->stat.tx_errors++; + if ((descr.Status & TXDSCR_STATUS_UFLO) != 0) + { + priv->stat.tx_fifo_errors++; + InitLANCE(dev); + } + else if ((descr.Status & TXDSCR_STATUS_LCOL) != 0) + priv->stat.tx_window_errors++; + else if ((descr.Status & TXDSCR_STATUS_LCAR) != 0) + priv->stat.tx_carrier_errors++; + else if ((descr.Status & TXDSCR_STATUS_RTRY) != 0) + priv->stat.tx_aborted_errors++; + } + + /* go to next descriptor */ + priv->nexttxdone++; + descraddr += sizeof(LANCE_TxDescr); + if (priv->nexttxdone >= TXCOUNT) + { + priv->nexttxdone = 0; + descraddr = RAM_TXBASE; + } + priv->txbusy--; + } + + /* reset TX interrupt bit */ + + SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_TINT); + oldcsr0 = GetLANCE(dev, LANCE_CSR0); + + /* at least one descriptor is freed. Therefore we can accept + a new one */ + + dev->tbusy = 0; + + /* inform upper layers we're in business again */ + + mark_bh(NET_BH); + + return oldcsr0; +} + +/* general interrupt entry */ + +static void irq_handler(int irq, void *device, struct pt_regs *regs) +{ + struct device *dev = (struct device*) device; + u16 csr0val; + + /* read CSR0 to get interrupt cause */ + + csr0val = GetLANCE(dev, LANCE_CSR0); + + /* in case we're not meant... */ + + if ((csr0val & CSR0_INTR) == 0) + return; + + dev->interrupt = 1; + + /* loop through the interrupt bits until everything is clear */ + + do + { + if ((csr0val & CSR0_IDON) != 0) + csr0val = irqstart_handler(dev, csr0val); + if ((csr0val & CSR0_RINT) != 0) + csr0val = irqrx_handler(dev, csr0val); + if ((csr0val & CSR0_TINT) != 0) + csr0val = irqtx_handler(dev, csr0val); + } + while ((csr0val & CSR0_INTR) != 0); + + dev->interrupt = 0; +} + +/* ------------------------------------------------------------------------ + * driver methods + * ------------------------------------------------------------------------ */ + +/* MCA info */ + +static int skmca_getinfo(char *buf, int slot, void *d) +{ + int len = 0, i; + struct device *dev = (struct device*) d; + skmca_priv *priv; + + /* can't say anything about an uninitialized device... */ + + if (dev == NULL) + return len; + if (dev->priv == NULL) + return len; + priv = (skmca_priv*) dev->priv; + + /* print info */ + + len += sprintf(buf + len, "IRQ: %d\n", priv->realirq); + len += sprintf(buf + len, "Memory: %#lx-%#lx\n", dev->mem_start, + dev->mem_end - 1); + len += sprintf(buf + len, "Transceiver: %s\n", MediaNames[priv->medium]); + len += sprintf(buf + len, "Device: %s\n", dev->name); + len += sprintf(buf + len, "MAC address:"); + for (i = 0; i < 6; i ++ ) + len += sprintf( buf+len, " %02x", dev->dev_addr[i] ); + buf[len++] = '\n'; + buf[len] = 0; + + return len; +} + +/* open driver. Means also initialization and start of LANCE */ + +static int skmca_open(struct device *dev) +{ + int result; + skmca_priv *priv = (skmca_priv*) dev->priv; + + /* register resources - only necessary for IRQ */ + result = request_irq(priv->realirq, irq_handler, SA_SHIRQ | SA_SAMPLE_RANDOM, + "sk_mca", dev); + if (result != 0) + { + printk("%s: failed to register irq %d\n", dev->name, dev->irq); + return result; + } + dev->irq = priv->realirq; + + /* set up the card and LANCE */ + InitBoard(dev); + +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif + + return 0; +} + +/* close driver. Shut down board and free allocated resources */ + +static int skmca_close(struct device *dev) +{ + /* turn off board */ + DeinitBoard(dev); + + /* release resources */ + if (dev->irq != 0) + free_irq(dev->irq, dev); + dev->irq = 0; + +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif + + return 0; +} + +/* transmit a block. */ + +static int skmca_tx(struct sk_buff *skb, struct device *dev) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + LANCE_TxDescr descr; + unsigned int address; + int tmplen, retval = 0; + unsigned long flags; + + /* if we get called with a NULL descriptor, the Ethernet layer thinks + our card is stuck an we should reset it. We'll do this completely: */ + + if (skb == NULL) + { + DeinitBoard(dev); + InitBoard(dev); + return 0; /* don't try to free the block here ;-) */ + } + + /* is there space in the Tx queue ? If no, the upper layer gave us a + packet in spite of us not being ready and is really in trouble. + We'll do the dropping for him: */ + if (priv->txbusy >= TXCOUNT) + { + priv->stat.tx_dropped++; + retval = -EIO; + goto tx_done; + } + + /* get TX descriptor */ + address = RAM_TXBASE + (priv->nexttxput * sizeof(LANCE_TxDescr)); + memcpy_fromio(&descr, dev->mem_start + address, sizeof(LANCE_TxDescr)); + + /* enter packet length as 2s complement - assure minimum length */ + tmplen = skb->len; + if (tmplen < 60) + tmplen = 60; + descr.Len = 65536 - tmplen; + + /* copy filler into RAM - in case we're filling up... + we're filling a bit more than necessary, but that doesn't harm + since the buffer is far larger... */ + if (tmplen > skb->len) + { + char *fill = "NetBSD is a nice OS too! "; + unsigned int destoffs = 0, l = strlen(fill); + + while (destoffs < tmplen) + { + memcpy_toio(dev->mem_start + descr.LowAddr + destoffs, fill, l); + destoffs += l; + } + } + + /* do the real data copying */ + memcpy_toio(dev->mem_start + descr.LowAddr, skb->data, skb->len); + + /* hand descriptor over to LANCE - this is the first and last chunk */ + descr.Flags = TXDSCR_FLAGS_OWN | TXDSCR_FLAGS_STP | TXDSCR_FLAGS_ENP; + +#ifdef DEBUG + PrTime(); printk("Send packet on descr %d len %d\n", priv->nexttxput, skb->len); +#endif + + /* one more descriptor busy */ + save_flags(flags); + cli(); + priv->nexttxput++; + if (priv->nexttxput >= TXCOUNT) + priv->nexttxput = 0; + priv->txbusy++; + dev->tbusy = (priv->txbusy >= TXCOUNT); + + /* write descriptor back to RAM */ + memcpy_toio(dev->mem_start + address, &descr, sizeof(LANCE_TxDescr)); + + /* if no descriptors were active, give the LANCE a hint to read it + immediately */ + + if (priv->txbusy == 0) + SetLANCE(dev, LANCE_CSR0, CSR0_INEA | CSR0_TDMD); + + restore_flags(flags); + +tx_done: + + /* When did that change exactly ? */ + +#if LINUX_VERSION_CODE >= 0x020200 + dev_kfree_skb(skb); +#else + dev_kfree_skb(skb, FREE_WRITE); +#endif + return retval; +} + +/* return pointer to Ethernet statistics */ + +static struct enet_statistics *skmca_stats(struct device *dev) +{ + skmca_priv *priv = (skmca_priv*) dev->priv; + + return &(priv->stat); +} + +/* we don't support runtime reconfiguration, since am MCA card can + be unambigously identified by its POS registers. */ + +static int skmca_config(struct device *dev, struct ifmap *map) +{ + return 0; +} + +/* switch receiver mode. We use the LANCE's multicast filter to prefilter + multicast addresses. */ + +static void skmca_set_multicast_list(struct device *dev) +{ + LANCE_InitBlock block; + + /* first stop the LANCE... */ + StopLANCE(dev); + + /* ...then modify the initialization block... */ + memcpy_fromio(&block, dev->mem_start + RAM_INITBASE, sizeof(block)); + if (dev->flags & IFF_PROMISC) + block.Mode |= LANCE_INIT_PROM; + else + block.Mode &= ~LANCE_INIT_PROM; + + if (dev->flags & IFF_ALLMULTI) /* get all multicasts */ + { + memset(block.LAdrF, 8, 0xff); + } + else /* get selected/no multicasts */ + { + struct dev_mc_list *mptr; + int code; + + memset(block.LAdrF, 8, 0x00); + for (mptr = dev->mc_list; mptr != NULL; mptr = mptr->next) + { + code = GetHash(mptr->dmi_addr); + block.LAdrF[(code >> 3) & 7] |= 1 << (code & 7); + } + } + + memcpy_toio(dev->mem_start + RAM_INITBASE, &block, sizeof(block)); + + /* ...then reinit LANCE with the correct flags */ + InitLANCE(dev); +} + +/* ------------------------------------------------------------------------ + * hardware check + * ------------------------------------------------------------------------ */ + +#ifdef MODULE +static int startslot; /* counts through slots when probing multiple devices */ +#else +#define startslot 0 /* otherwise a dummy, since there is only eth0 in-kern*/ +#endif + +int skmca_probe(struct device *dev) +{ + int force_detect = 0; + int junior, slot, i; + int base = 0, irq = 0; + skmca_priv *priv; + skmca_medium medium; + + /* can't work without an MCA bus ;-) */ + + if (MCA_bus == 0) + return ENODEV; + + /* start address of 1 --> forced detection */ + + if (dev->mem_start == 1) + force_detect = 1; + + /* search through slots */ + + if (dev != NULL) + { + base = dev->mem_start; + irq = dev->irq; + } + slot = dofind(&junior, startslot); + + while (slot != -1) + { + /* deduce card addresses */ + + getaddrs(slot, junior, &base, &irq, &medium); + +#if 0 + /* this should work, but it doesn't with 2.2.9 :-( + somehow 'mca_is_adapter_used()' is missing in kernel syms... */ +#if LINUX_VERSION_CODE >= 0x020200 + /* slot already in use ? */ + + if (mca_is_adapter_used(slot)) + { + slot = dofind(&junior, slot + 1); + continue; + } +#endif +#endif + + /* were we looking for something different ? */ + + if ((dev->irq != 0) || (dev->mem_start != 0)) + { + if ((dev->irq != 0) && (dev->irq != irq)) + { + slot = dofind(&junior, slot + 1); + continue; + } + if ((dev->mem_start != 0) && (dev->mem_start != base)) + { + slot = dofind(&junior, slot + 1); + continue; + } + } + + /* found something that matches */ + + break; + } + + /* nothing found ? */ + + if (slot == -1) + return ((base != 0) || (irq != 0)) ? ENXIO : ENODEV; + + /* make procfs entries */ + + if (junior) + mca_set_adapter_name(slot, "SKNET junior MC2 Ethernet Adapter"); + else + mca_set_adapter_name(slot, "SKNET MC2+ Ethernet Adapter"); + mca_set_adapter_procfn(slot, (MCA_ProcFn) skmca_getinfo, dev); + +#if LINUX_VERSION_CODE >= 0x020200 + mca_mark_as_used(slot); +#endif + + /* announce success */ + printk("%s: SKNet %s adapter found in slot %d\n", dev->name, + junior ? "Junior MC2" : "MC2+", slot + 1); + + /* allocate structure */ + priv = dev->priv = (skmca_priv*) kmalloc(sizeof(skmca_priv), GFP_KERNEL); + priv->slot = slot; + priv->macbase = base + 0x3fc0; + priv->ioregaddr = base + 0x3ff0; + priv->ctrladdr = base + 0x3ff2; + priv->cmdaddr = base + 0x3ff3; + priv->realirq = irq; + priv->medium = medium; + memset(&(priv->stat), 0, sizeof(struct enet_statistics)); + + /* set base + irq for this device (irq not allocated so far) */ + dev->irq = 0; + dev->mem_start = base; + dev->mem_end = base + 0x4000; + + /* set methods */ + dev->open = skmca_open; + dev->stop = skmca_close; + dev->set_config = skmca_config; + dev->hard_start_xmit = skmca_tx; + dev->do_ioctl = NULL; + dev->get_stats = skmca_stats; + dev->set_multicast_list = skmca_set_multicast_list; + dev->flags |= IFF_MULTICAST; + + /* generic setup */ + ether_setup(dev); + dev->interrupt = 0; + dev->tbusy = 0; + dev->start = 0; + + /* copy out MAC address */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = readb(priv->macbase + (i << 1)); + + /* print config */ + printk("%s: IRQ %d, memory %#lx-%#lx, " + "MAC address %02x:%02x:%02x:%02x:%02x:%02x.\n", + dev->name, priv->realirq, dev->mem_start, dev->mem_end - 1, + dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + printk("%s: %s medium\n", dev->name, MediaNames[priv->medium]); + + /* reset board */ + + ResetBoard(dev); + +#ifdef MODULE + startslot = slot + 1; +#endif + + return 0; +} + +/* ------------------------------------------------------------------------ + * modularization support + * ------------------------------------------------------------------------ */ + +#ifdef MODULE + +#define DEVMAX 5 + +static char NameSpace[8 * DEVMAX]; +static struct device moddevs[DEVMAX] = + {{NameSpace + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, skmca_probe}, + {NameSpace + 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, skmca_probe}, + {NameSpace + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, skmca_probe}, + {NameSpace + 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, skmca_probe}, + {NameSpace + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, skmca_probe}}; + +int irq=0; +int io=0; + +int init_module(void) +{ + int z, res; + + startslot = 0; + for (z = 0; z < DEVMAX; z++) + { + strcpy(moddevs[z].name, " "); + res = register_netdev(moddevs + z); + if (res != 0) + return (z > 0) ? 0 : -EIO; + } + + return 0; +} + +void cleanup_module(void) +{ + struct device *dev; + skmca_priv *priv; + int z; + + if (MOD_IN_USE) + { + printk("cannot unload, module in use\n"); + return; + } + + for (z = 0; z < DEVMAX; z++) + { + dev = moddevs + z; + if (dev->priv != NULL) + { + priv = (skmca_priv*) dev->priv; + DeinitBoard(dev); + if (dev->irq != 0) + free_irq(dev->irq, dev); + dev->irq = 0; + unregister_netdev(dev); +#if LINUX_VERSION_CODE >= 0x020200 + mca_mark_as_unused(priv->slot); +#endif + mca_set_adapter_procfn(priv->slot, NULL, NULL); + kfree_s(dev->priv, sizeof(skmca_priv)); + dev->priv = NULL; + } + } +} +#endif /* MODULE */ diff --git a/drivers/net/sk_mca.h b/drivers/net/sk_mca.h new file mode 100644 index 000000000..f45aadf61 --- /dev/null +++ b/drivers/net/sk_mca.h @@ -0,0 +1,174 @@ +#ifndef _SK_MCA_INCLUDE_ +#define _SK_MCA_INCLUDE_ + +#ifdef _SK_MCA_DRIVER_ + +/* Adapter ID's */ +#define SKNET_MCA_ID 0x6afd +#define SKNET_JUNIOR_MCA_ID 0x6be9 + +/* media enumeration - defined in a way that it fits onto the MC2+'s + POS registers... */ + +typedef enum {Media_10Base2, Media_10BaseT, + Media_10Base5, Media_Unknown, Media_Count} skmca_medium; + +/* private structure */ +typedef struct + { + unsigned int slot; /* MCA-Slot-# */ + unsigned int macbase; /* base address of MAC address PROM */ + unsigned int ioregaddr; /* address of I/O-register (Lo) */ + unsigned int ctrladdr; /* address of control/stat register */ + unsigned int cmdaddr; /* address of I/O-command register */ + int nextrx; /* index of next RX descriptor to + be read */ + int nexttxput; /* index of next free TX descriptor */ + int nexttxdone; /* index of next TX descriptor to + be finished */ + int txbusy; /* # of busy TX descriptors */ + struct enet_statistics stat; /* packet statistics */ + int realirq; /* memorizes actual IRQ, even when + currently not allocated */ + skmca_medium medium; /* physical cannector */ + } skmca_priv; + +/* card registers: control/status register bits */ + +#define CTRL_ADR_DATA 0 /* Bit 0 = 0 ->access data register */ +#define CTRL_ADR_RAP 1 /* Bit 0 = 1 ->access RAP register */ +#define CTRL_RW_WRITE 0 /* Bit 1 = 0 ->write register */ +#define CTRL_RW_READ 2 /* Bit 1 = 1 ->read register */ +#define CTRL_RESET_ON 0 /* Bit 3 = 0 ->reset board */ +#define CTRL_RESET_OFF 8 /* Bit 3 = 1 ->no reset of board */ + +#define STAT_ADR_DATA 0 /* Bit 0 of ctrl register read back */ +#define STAT_ADR_RAP 1 +#define STAT_RW_WRITE 0 /* Bit 1 of ctrl register read back */ +#define STAT_RW_READ 2 +#define STAT_RESET_ON 0 /* Bit 3 of ctrl register read back */ +#define STAT_RESET_OFF 8 +#define STAT_IRQ_ACT 0 /* interrupt pending */ +#define STAT_IRQ_NOACT 16 /* no interrupt pending */ +#define STAT_IO_NOBUSY 0 /* no transfer busy */ +#define STAT_IO_BUSY 32 /* transfer busy */ + +/* I/O command register bits */ + +#define IOCMD_GO 128 /* Bit 7 = 1 -> start register xfer */ + +/* LANCE registers */ + +#define LANCE_CSR0 0 /* Status/Control */ + +#define CSR0_ERR 0x8000 /* general error flag */ +#define CSR0_BABL 0x4000 /* transmitter timeout */ +#define CSR0_CERR 0x2000 /* collision error */ +#define CSR0_MISS 0x1000 /* lost Rx block */ +#define CSR0_MERR 0x0800 /* memory access error */ +#define CSR0_RINT 0x0400 /* receiver interrupt */ +#define CSR0_TINT 0x0200 /* transmitter interrupt */ +#define CSR0_IDON 0x0100 /* initialization done */ +#define CSR0_INTR 0x0080 /* general interrupt flag */ +#define CSR0_INEA 0x0040 /* interrupt enable */ +#define CSR0_RXON 0x0020 /* receiver enabled */ +#define CSR0_TXON 0x0010 /* transmitter enabled */ +#define CSR0_TDMD 0x0008 /* force transmission now */ +#define CSR0_STOP 0x0004 /* stop LANCE */ +#define CSR0_STRT 0x0002 /* start LANCE */ +#define CSR0_INIT 0x0001 /* read initialization block */ + +#define LANCE_CSR1 1 /* addr bit 0..15 of initialization */ +#define LANCE_CSR2 2 /* 16..23 block */ + +#define LANCE_CSR3 3 /* Bus control */ +#define CSR3_BCON_HOLD 0 /* Bit 0 = 0 -> BM1,BM0,HOLD */ +#define CSR3_BCON_BUSRQ 1 /* Bit 0 = 1 -> BUSAK0,BYTE,BUSRQ */ +#define CSR3_ALE_HIGH 0 /* Bit 1 = 0 -> ALE asserted high */ +#define CSR3_ALE_LOW 2 /* Bit 1 = 1 -> ALE asserted low */ +#define CSR3_BSWAP_OFF 0 /* Bit 2 = 0 -> no byte swap */ +#define CSR3_BSWAP_ON 0 /* Bit 2 = 1 -> byte swap */ + +/* LANCE structures */ + +typedef struct /* LANCE initialization block */ + { + u16 Mode; /* mode flags */ + u8 PAdr[6]; /* MAC address */ + u8 LAdrF[8]; /* Multicast filter */ + u32 RdrP; /* Receive descriptor */ + u32 TdrP; /* Transmit descriptor */ + } LANCE_InitBlock; + +/* Mode flags init block */ + +#define LANCE_INIT_PROM 0x8000 /* enable promiscous mode */ +#define LANCE_INIT_INTL 0x0040 /* internal loopback */ +#define LANCE_INIT_DRTY 0x0020 /* disable retry */ +#define LANCE_INIT_COLL 0x0010 /* force collision */ +#define LANCE_INIT_DTCR 0x0008 /* disable transmit CRC */ +#define LANCE_INIT_LOOP 0x0004 /* loopback */ +#define LANCE_INIT_DTX 0x0002 /* disable transmitter */ +#define LANCE_INIT_DRX 0x0001 /* disable receiver */ + +typedef struct /* LANCE Tx descriptor */ + { + u16 LowAddr; /* bit 0..15 of address */ + u16 Flags; /* bit 16..23 of address + Flags */ + u16 Len; /* 2s complement of packet length */ + u16 Status; /* Result of transmission */ + } LANCE_TxDescr; + +#define TXDSCR_FLAGS_OWN 0x8000 /* LANCE owns descriptor */ +#define TXDSCR_FLAGS_ERR 0x4000 /* summary error flag */ +#define TXDSCR_FLAGS_MORE 0x1000 /* more than one retry needed? */ +#define TXDSCR_FLAGS_ONE 0x0800 /* one retry? */ +#define TXDSCR_FLAGS_DEF 0x0400 /* transmission deferred? */ +#define TXDSCR_FLAGS_STP 0x0200 /* first packet in chain? */ +#define TXDSCR_FLAGS_ENP 0x0100 /* last packet in chain? */ + +#define TXDSCR_STATUS_BUFF 0x8000 /* buffer error? */ +#define TXDSCR_STATUS_UFLO 0x4000 /* silo underflow during transmit? */ +#define TXDSCR_STATUS_LCOL 0x1000 /* late collision? */ +#define TXDSCR_STATUS_LCAR 0x0800 /* loss of carrier? */ +#define TXDSCR_STATUS_RTRY 0x0400 /* retry error? */ + +typedef struct /* LANCE Rx descriptor */ + { + u16 LowAddr; /* bit 0..15 of address */ + u16 Flags; /* bit 16..23 of address + Flags */ + u16 MaxLen; /* 2s complement of buffer length */ + u16 Len; /* packet length */ + } LANCE_RxDescr; + +#define RXDSCR_FLAGS_OWN 0x8000 /* LANCE owns descriptor */ +#define RXDSCR_FLAGS_ERR 0x4000 /* summary error flag */ +#define RXDSCR_FLAGS_FRAM 0x2000 /* framing error flag */ +#define RXDSCR_FLAGS_OFLO 0x1000 /* FIFO overflow? */ +#define RXDSCR_FLAGS_CRC 0x0800 /* CRC error? */ +#define RXDSCR_FLAGS_BUFF 0x0400 /* buffer error? */ +#define RXDSCR_FLAGS_STP 0x0200 /* first packet in chain? */ +#define RXDCSR_FLAGS_ENP 0x0100 /* last packet in chain? */ + +/* RAM layout */ + +#define TXCOUNT 4 /* length of TX descriptor queue */ +#define LTXCOUNT 2 /* log2 of it */ +#define RXCOUNT 4 /* length of RX descriptor queue */ +#define LRXCOUNT 2 /* log2 of it */ + +#define RAM_INITBASE 0 /* LANCE init block */ +#define RAM_TXBASE 24 /* Start of TX descriptor queue */ +#define RAM_RXBASE \ +(RAM_TXBASE + (TXCOUNT * 8)) /* Start of RX descriptor queue */ +#define RAM_DATABASE \ +(RAM_RXBASE + (RXCOUNT * 8)) /* Start of data area for frames */ +#define RAM_BUFSIZE 1580 /* max. frame size - should never be + reached */ + +#endif /* _SK_MCA_DRIVER_ */ + +extern int skmca_probe(struct device *); + + +#endif /* _SK_MCA_INCLUDE_ */
\ No newline at end of file diff --git a/drivers/sbus/char/aurora.c b/drivers/sbus/char/aurora.c new file mode 100644 index 000000000..607b0f230 --- /dev/null +++ b/drivers/sbus/char/aurora.c @@ -0,0 +1,2373 @@ +/* + * linux/drivers/sbus/char/aurora.c -- Aurora multiport driver + * + * Copyright (c) 1999 by Oliver Aldulea (oli@bv.ro) + * + * This code is based on the RISCom/8 multiport serial driver written + * by Dmitry Gorodchanin (pgmdsg@ibi.com), based on the Linux serial + * driver, written by Linus Torvalds, Theodore T'so and others. + * The Aurora multiport programming info was obtained mainly from the + * Cirrus Logic CD180 documentation (available on the web), and by + * doing heavy tests on the board. Many thanks to Eddie C. Dost for the + * help on the sbus interface. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Revision 1.0 + * + * This is the first public release. + * + * Most of the information you need is in the aurora.h file. Please + * read that file before reading this one. + * + * Several parts of the code do not have comments yet. + */ + +#include <linux/module.h> + +#include <linux/errno.h> +#include <linux/sched.h> +#ifdef AURORA_INT_DEBUG +#include <linux/timer.h> +#endif +#include <linux/interrupt.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/config.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/tqueue.h> +#include <linux/delay.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/oplib.h> +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/bitops.h> +#include <asm/kdebug.h> +#include <asm/sbus.h> +#include <asm/uaccess.h> + +#include "aurora.h" +#include "cd180.h" + +unsigned char irqs[4] = { + 0, 0, 0, 0 + }; + +#ifdef AURORA_INT_DEBUG +int irqhit=0; +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#define AURORA_TYPE_NORMAL 1 + +static struct tty_driver aurora_driver; +static struct Aurora_board aurora_board[AURORA_NBOARD] = { + {0,}, +}; + +static struct Aurora_port aurora_port[AURORA_TNPORTS] = { + { 0, }, +}; + +/* no longer used. static struct Aurora_board * IRQ_to_board[16] = { NULL, } ;*/ +static unsigned char * tmp_buf = NULL; +static DECLARE_MUTEX(tmp_buf_sem); +static int aurora_refcount = 0; +static struct tty_struct * aurora_table[AURORA_TNPORTS] = { NULL, }; +static struct termios * aurora_termios[AURORA_TNPORTS] = { NULL, }; +static struct termios * aurora_termios_locked[AURORA_TNPORTS] = { NULL, }; + +DECLARE_TASK_QUEUE(tq_aurora); + +/* Yes, the board can support 115.2 bit rates, but only on a few ports. The + * total badwidth of one chip (ports 0-7 or 8-15) is equal to OSC_FREQ div + * 16. In case of my board, each chip can take 6 channels of 115.2 kbaud. + * This information is not well-tested. + */ +static unsigned long baud_table[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200, 0, + }; + +static inline int aurora_paranoia_check(struct Aurora_port const * port, + kdev_t device, const char *routine) +{ +#ifdef AURORA_PARANOIA_CHECK + static const char *badmagic = + KERN_DEBUG "aurora: Warning: bad aurora port magic number for device %s in %s\n"; + static const char *badinfo = + KERN_DEBUG "aurora: Warning: null aurora port for device %s in %s\n"; + + if (!port) { + printk(badinfo, kdevname(device), routine); + return 1; + } + if (port->magic != AURORA_MAGIC) { + printk(badmagic, kdevname(device), routine); + return 1; + } +#endif + return 0; +} + +/* + * + * Service functions for aurora driver. + * + */ + +/* Get board number from pointer */ +extern inline int board_No (struct Aurora_board const * bp) +{ + return bp - aurora_board; +} + +/* Get port number from pointer */ +extern inline int port_No (struct Aurora_port const * port) +{ + return AURORA_PORT(port - aurora_port); +} + +/* Get pointer to board from pointer to port */ +extern inline struct Aurora_board * port_Board(struct Aurora_port const * port) +{ + return &aurora_board[AURORA_BOARD(port - aurora_port)]; +} + +/* Wait for Channel Command Register ready */ +extern inline void aurora_wait_CCR(struct aurora_reg128 * r) +{ + unsigned long delay; + +#ifdef AURORA_DEBUG +printk("aurora_wait_CCR\n"); +#endif + /* FIXME: need something more descriptive than 100000 :) */ + for (delay = 100000; delay; delay--) + if (!r->r[CD180_CCR]) + return; + printk(KERN_DEBUG "aurora: Timeout waiting for CCR.\n"); +} + +/* + * aurora probe functions. + */ + +/* Must be called with enabled interrupts */ +extern inline void aurora_long_delay(unsigned long delay) +{ + unsigned long i; +#ifdef AURORA_DEBUG +printk("aurora_long_delay: start\n"); +#endif + for (i = jiffies + delay; i > jiffies; ) ; +#ifdef AURORA_DEBUG +printk("aurora_long_delay: end\n"); +#endif +} + +/* Reset and setup CD180 chip */ +static int aurora_init_CD180(struct Aurora_board * bp, int chip) +{ + unsigned long flags; + int id; + +#ifdef AURORA_DEBUG +printk("aurora_init_CD180: start %d:%d\n",board_No(bp),chip); +#endif + save_flags(flags); cli(); + bp->r[chip]->r[CD180_CAR]=0; + bp->r[chip]->r[CD180_GSVR]=0; + aurora_wait_CCR(bp->r[chip]); /* Wait for CCR ready */ + bp->r[chip]->r[CD180_CCR]=CCR_HARDRESET; /* Reset CD180 chip */ + udelay(1); + sti(); + id=1000; + while((--id)&&(bp->r[chip]->r[CD180_GSVR]!=0xff))udelay(100); + if(!id) { + printk(KERN_ERR "aurora%d: Chip %d failed init.\n",board_No(bp),chip); + restore_flags(flags); + return(-1); + } + cli(); + bp->r[chip]->r[CD180_GSVR]=(board_No(bp)<<5)|((chip+1)<<3); /* Set ID for this chip */ + bp->r[chip]->r[CD180_MSMR]=0x80|bp->ACK_MINT; /* Prio for modem intr */ + bp->r[chip]->r[CD180_TSMR]=0x80|bp->ACK_TINT; /* Prio for transmitter intr */ + bp->r[chip]->r[CD180_RSMR]=0x80|bp->ACK_RINT; /* Prio for receiver intr */ + /* Setting up prescaler. We need 4 tick per 1 ms */ + bp->r[chip]->r[CD180_PPRH]=(bp->oscfreq/(1000000/AURORA_TPS)) >> 8; + bp->r[chip]->r[CD180_PPRL]=(bp->oscfreq/(1000000/AURORA_TPS)) & 0xff; + + bp->r[chip]->r[CD180_SRCR]=SRCR_AUTOPRI|SRCR_GLOBPRI; + + id=bp->r[chip]->r[CD180_GFRCR]; + printk(KERN_INFO "aurora%d: Chip %d id %02x: ",board_No(bp),chip,id); + if(bp->r[chip]->r[CD180_SRCR]&128) + switch(id){ + case 0x82:printk("CL-CD1864 rev A\n");break; + case 0x83:printk("CL-CD1865 rev A\n");break; + case 0x84:printk("CL-CD1865 rev B\n");break; + case 0x85:printk("CL-CD1865 rev C\n");break; + default:printk("Unknown.\n"); + }else + switch(id){ + case 0x81:printk("CL-CD180 rev B\n");break; + case 0x82:printk("CL-CD180 rev C\n");break; + default:printk("Unknown.\n"); + }; + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_init_CD180: end\n"); +#endif + return 0; +} + +static int valid_irq(unsigned char irq) +{ +int i; +for(i=0;i<TYPE_1_IRQS;i++) + if (type_1_irq[i]==irq) return 1; +return 0; +} + +static void aurora_interrupt(int irq, void * dev_id, struct pt_regs * regs); + +/* Main probing routine, also sets irq. */ +static int aurora_probe(void) { + struct linux_sbus *sbus; + struct linux_sbus_device *sdev; + int grrr; + char buf[30]; + int bn=0; + struct Aurora_board *bp; + + for_each_sbus(sbus) { + for_each_sbusdev(sdev, sbus) { +/* printk("Try: %x %s\n",sdev,sdev->prom_name);*/ + if (!strcmp(sdev->prom_name, "sio16")) { + #ifdef AURORA_DEBUG + printk(KERN_INFO "aurora: sio16 at %p\n",sdev); + #endif + prom_apply_sbus_ranges(sdev->my_bus, sdev->reg_addrs, sdev->num_registers, sdev); + if((sdev->reg_addrs[0].reg_size!=1)&&(sdev->reg_addrs[1].reg_size!=128)&& + (sdev->reg_addrs[2].reg_size!=128)&&(sdev->reg_addrs[3].reg_size!=4)){ + printk(KERN_ERR "aurora%d: registers' sizes do not match.\n",bn); + break; + } + bp=&aurora_board[bn]; + bp->r0 = (struct aurora_reg1 *) sparc_alloc_io(sdev->reg_addrs[0].phys_addr, 0, + sdev->reg_addrs[0].reg_size, "sio16",sdev->reg_addrs[0].which_io, 0x0); + if (!bp->r0) { + printk(KERN_ERR "aurora%d: can't map reg_addrs[0]\n",bn); + break; + } + #ifdef AURORA_DEBUG + printk("Map reg 0: %x\n",bp->r0); + #endif + bp->r[0] = (struct aurora_reg128 *) sparc_alloc_io(sdev->reg_addrs[1].phys_addr, 0, + sdev->reg_addrs[1].reg_size, "sio16", sdev->reg_addrs[1].which_io, 0x0); + if (!bp->r[0]) { + printk(KERN_ERR "aurora%d: can't map reg_addrs[1]\n",bn); + break; + } + #ifdef AURORA_DEBUG + printk("Map reg 1: %x\n",bp->r[0]); + #endif + bp->r[1] = (struct aurora_reg128 *) sparc_alloc_io(sdev->reg_addrs[2].phys_addr, 0, + sdev->reg_addrs[2].reg_size, "sio16", sdev->reg_addrs[2].which_io, 0x0); + if (!bp->r[1]) { + printk(KERN_ERR "aurora%d: can't map reg_addrs[2]\n",bn); + break; + } + #ifdef AURORA_DEBUG + printk("Map reg 2: %x\n",bp->r[1]); + #endif + bp->r3 = (struct aurora_reg4 *) sparc_alloc_io(sdev->reg_addrs[3].phys_addr, 0, + sdev->reg_addrs[3].reg_size, "sio16", sdev->reg_addrs[3].which_io, 0x0); + if (!bp->r3) { + printk(KERN_ERR "aurora%d: can't map reg_addrs[3]\n",bn); + break; + } + #ifdef AURORA_DEBUG + printk("Map reg 3: %x\n",bp->r3); + #endif + /* Variables setup */ + bp->flags = 0; + #ifdef AURORA_DEBUG + grrr=prom_getint(sdev->prom_node,"intr"); + printk("intr pri %d\n",grrr); + #endif + if ((bp->irq=irqs[bn]) && valid_irq(bp->irq) && + !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) { + free_irq(bp->irq|0x30, bp); + } else + if ((bp->irq=prom_getint(sdev->prom_node, "bintr")) && valid_irq(bp->irq) && + !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) { + free_irq(bp->irq|0x30, bp); + } else + if ((bp->irq=prom_getint(sdev->prom_node, "intr")) && valid_irq(bp->irq) && + !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) { + free_irq(bp->irq|0x30, bp); + } else + for(grrr=0;grrr<TYPE_1_IRQS;grrr++) { + if ((bp->irq=type_1_irq[grrr])&&!request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) { + free_irq(bp->irq|0x30, bp); + break; + } else { + printk(KERN_ERR "aurora%d: Could not get an irq for this board !!!\n",bn); + bp->flags=0xff; + } + } + if(bp->flags==0xff)break; + printk(KERN_INFO "aurora%d: irq %d\n",bn,bp->irq&0x0f); + buf[0]=0; + grrr=prom_getproperty(sdev->prom_node,"dtr_rts",buf,sizeof(buf)); + if(!strcmp(buf,"swapped")){ + printk(KERN_INFO "aurora%d: Swapped DTR and RTS\n",bn); + bp->DTR=MSVR_RTS; + bp->RTS=MSVR_DTR; + bp->MSVDTR=CD180_MSVRTS; + bp->MSVRTS=CD180_MSVDTR; + bp->flags|=AURORA_BOARD_DTR_FLOW_OK; + }else{ + #ifdef AURORA_FORCE_DTR_FLOW + printk(KERN_INFO "aurora%d: Forcing swapped DTR-RTS\n",bn); + bp->DTR=MSVR_RTS; + bp->RTS=MSVR_DTR; + bp->MSVDTR=CD180_MSVRTS; + bp->MSVRTS=CD180_MSVDTR; + bp->flags|=AURORA_BOARD_DTR_FLOW_OK; + #else + printk(KERN_INFO "aurora%d: Normal DTR and RTS\n",bn); + bp->DTR=MSVR_DTR; + bp->RTS=MSVR_RTS; + bp->MSVDTR=CD180_MSVDTR; + bp->MSVRTS=CD180_MSVRTS; + #endif + } + bp->oscfreq=prom_getint(sdev->prom_node,"clk")*100; + printk(KERN_INFO "aurora%d: Oscillator: %d Hz\n",bn,bp->oscfreq); + grrr=prom_getproperty(sdev->prom_node,"chip",buf,sizeof(buf)); + printk(KERN_INFO "aurora%d: Chips: %s\n",bn,buf); + grrr=prom_getproperty(sdev->prom_node,"manu",buf,sizeof(buf)); + printk(KERN_INFO "aurora%d: Manufacturer: %s\n",bn,buf); + grrr=prom_getproperty(sdev->prom_node,"model",buf,sizeof(buf)); + printk(KERN_INFO "aurora%d: Model: %s\n",bn,buf); + grrr=prom_getproperty(sdev->prom_node,"rev",buf,sizeof(buf)); + printk(KERN_INFO "aurora%d: Revision: %s\n",bn,buf); + grrr=prom_getproperty(sdev->prom_node,"mode",buf,sizeof(buf)); + printk(KERN_INFO "aurora%d: Mode: %s\n",bn,buf); + #ifdef MODULE + bp->count=0; + #endif + bp->flags = AURORA_BOARD_PRESENT; + /* hardware ack */ + bp->ACK_MINT=1; + bp->ACK_TINT=2; + bp->ACK_RINT=3; + bn++; + } + } + } + return bn; +} + +static void aurora_release_io_range(struct Aurora_board *bp) +{ +sparc_free_io(bp->r0,1); +sparc_free_io(bp->r[0],128); +sparc_free_io(bp->r[1],128); +sparc_free_io(bp->r3,4); +} + +extern inline void aurora_mark_event(struct Aurora_port * port, int event) +{ +#ifdef AURORA_DEBUG +printk("aurora_mark_event: start\n"); +#endif + set_bit(event, &port->event); + queue_task(&port->tqueue, &tq_aurora); + mark_bh(AURORA_BH); +#ifdef AURORA_DEBUG +printk("aurora_mark_event: end\n"); +#endif +} + +extern inline struct Aurora_port * aurora_get_port(struct Aurora_board const * bp, + int chip, unsigned char const * what) +{ + unsigned char channel; + struct Aurora_port * port; + + channel = (chip<<3)|((bp->r[chip]->r[CD180_GSCR]&GSCR_CHAN)>>GSCR_CHAN_OFF); + port = &aurora_port[board_No(bp) * AURORA_NPORT * AURORA_NCD180 + channel]; + if (port->flags & ASYNC_INITIALIZED) { + return port; + } + printk(KERN_DEBUG "aurora%d: %s interrupt from invalid port %d\n", + board_No(bp), what, channel); + return NULL; +} + +extern inline void aurora_receive_exc(struct Aurora_board const * bp, int chip) +{ + struct Aurora_port *port; + struct tty_struct *tty; + unsigned char status; + unsigned char ch; + + if (!(port = aurora_get_port(bp, chip, "Receive_x"))) + return; + + tty = port->tty; + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + #ifdef AURORA_INTNORM + printk("aurora%d: port %d: Working around flip buffer overflow.\n", + board_No(bp), port_No(port)); + #endif + return; + } + +#ifdef AURORA_REPORT_OVERRUN + status = bp->r[chip]->r[CD180_RCSR]; + if (status & RCSR_OE) { + port->overrun++; +#if 1 + printk("aurora%d: port %d: Overrun. Total %ld overruns.\n", + board_No(bp), port_No(port), port->overrun); +#endif + } + status &= port->mark_mask; +#else + status = bp->r[chip]->r[CD180_RCSR] & port->mark_mask; +#endif + ch = bp->r[chip]->r[CD180_RDR]; + if (!status) { + return; + } + if (status & RCSR_TOUT) { +/* printk("aurora%d: port %d: Receiver timeout. Hardware problems ?\n", + board_No(bp), port_No(port));*/ + return; + + } else if (status & RCSR_BREAK) { + printk(KERN_DEBUG "aurora%d: port %d: Handling break...\n", + board_No(bp), port_No(port)); + *tty->flip.flag_buf_ptr++ = TTY_BREAK; + if (port->flags & ASYNC_SAK) + do_SAK(tty); + + } else if (status & RCSR_PE) + *tty->flip.flag_buf_ptr++ = TTY_PARITY; + + else if (status & RCSR_FE) + *tty->flip.flag_buf_ptr++ = TTY_FRAME; + + else if (status & RCSR_OE) + *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; + + else + *tty->flip.flag_buf_ptr++ = 0; + + *tty->flip.char_buf_ptr++ = ch; + tty->flip.count++; + queue_task(&tty->flip.tqueue, &tq_timer); +} + +extern inline void aurora_receive(struct Aurora_board const * bp, int chip) +{ + struct Aurora_port *port; + struct tty_struct *tty; + unsigned char count,cnt; + + if (!(port = aurora_get_port(bp, chip, "Receive"))) + return; + + tty = port->tty; + + count = bp->r[chip]->r[CD180_RDCR]; + +#ifdef AURORA_REPORT_FIFO + port->hits[count > 8 ? 9 : count]++; +#endif + + while (count--) { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + #ifdef AURORA_INTNORM + printk("aurora%d: port %d: Working around flip buffer overflow.\n", + board_No(bp), port_No(port)); + #endif + break; + } + cnt=bp->r[chip]->r[CD180_RDR]; + *tty->flip.char_buf_ptr++ = cnt; + *tty->flip.flag_buf_ptr++ = 0; + tty->flip.count++; + } + queue_task(&tty->flip.tqueue, &tq_timer); +} + +extern inline void aurora_transmit(struct Aurora_board const * bp, int chip) +{ + struct Aurora_port *port; + struct tty_struct *tty; + unsigned char count; + + + if (!(port = aurora_get_port(bp, chip, "Transmit"))) + return; + + tty = port->tty; + + if (port->SRER & SRER_TXEMPTY) { + /* FIFO drained */ + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + port->SRER &= ~SRER_TXEMPTY; + bp->r[chip]->r[CD180_SRER]=port->SRER; + return; + } + + if ((port->xmit_cnt <= 0 && !port->break_length) + || tty->stopped || tty->hw_stopped) { + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + port->SRER &= ~SRER_TXRDY; + bp->r[chip]->r[CD180_SRER]=port->SRER; + return; + } + + if (port->break_length) { + if (port->break_length > 0) { + if (port->COR2 & COR2_ETC) { + bp->r[chip]->r[CD180_TDR]=CD180_C_ESC; + bp->r[chip]->r[CD180_TDR]=CD180_C_SBRK; + port->COR2 &= ~COR2_ETC; + } + count = MIN(port->break_length, 0xff); + bp->r[chip]->r[CD180_TDR]=CD180_C_ESC; + bp->r[chip]->r[CD180_TDR]=CD180_C_DELAY; + bp->r[chip]->r[CD180_TDR]=count; + if (!(port->break_length -= count)) + port->break_length--; + } else { + bp->r[chip]->r[CD180_TDR]=CD180_C_ESC; + bp->r[chip]->r[CD180_TDR]=CD180_C_EBRK; + bp->r[chip]->r[CD180_COR2]=port->COR2; + aurora_wait_CCR(bp->r[chip]); + bp->r[chip]->r[CD180_CCR]=CCR_CORCHG2; + port->break_length = 0; + } + return; + } + + count = CD180_NFIFO; + do { + bp->r[chip]->r[CD180_TDR]=port->xmit_buf[port->xmit_tail++]; + port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE-1); + if (--port->xmit_cnt <= 0) + break; + } while (--count > 0); + + if (port->xmit_cnt <= 0) { + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + port->SRER &= ~SRER_TXRDY; + bp->r[chip]->r[CD180_SRER]=port->SRER; + } + if (port->xmit_cnt <= port->wakeup_chars) + aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP); +} + +extern inline void aurora_check_modem(struct Aurora_board const * bp, int chip) +{ + struct Aurora_port *port; + struct tty_struct *tty; + unsigned char mcr; + + if (!(port = aurora_get_port(bp, chip, "Modem"))) + return; + + tty = port->tty; + + mcr = bp->r[chip]->r[CD180_MCR]; + if (mcr & MCR_CDCHG) { + if (bp->r[chip]->r[CD180_MSVR] & MSVR_CD) + wake_up_interruptible(&port->open_wait); + else if (!((port->flags & ASYNC_CALLOUT_ACTIVE) && + (port->flags & ASYNC_CALLOUT_NOHUP))) + queue_task(&port->tqueue_hangup, + &tq_scheduler); + } + +/* We don't have such things yet. My aurora board has DTR and RTS swapped, but that doesn't count in this driver. Let's hope + * Aurora didn't made any boards with CTS or DSR broken... + */ +/* #ifdef AURORA_BRAIN_DAMAGED_CTS + if (mcr & MCR_CTSCHG) { + if (aurora_in(bp, CD180_MSVR) & MSVR_CTS) { + tty->hw_stopped = 0; + port->SRER |= SRER_TXRDY; + if (port->xmit_cnt <= port->wakeup_chars) + aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP); + } else { + tty->hw_stopped = 1; + port->SRER &= ~SRER_TXRDY; + } + bp->r[chip]->r[CD180_SRER, port->SRER); + } + if (mcr & MCR_DSRCHG) { + if (aurora_in(bp, CD180_MSVR) & MSVR_DSR) { + tty->hw_stopped = 0; + port->SRER |= SRER_TXRDY; + if (port->xmit_cnt <= port->wakeup_chars) + aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP); + } else { + tty->hw_stopped = 1; + port->SRER &= ~SRER_TXRDY; + } + bp->r[chip]->r[CD180_SRER, port->SRER); + } +#endif AURORA_BRAIN_DAMAGED_CTS */ + + /* Clear change bits */ + bp->r[chip]->r[CD180_MCR]=0; +} + +/* The main interrupt processing routine */ +static void aurora_interrupt(int irq, void * dev_id, struct pt_regs * regs) +{ + unsigned char status; + unsigned char ack,chip/*,chip_id*/; + struct Aurora_board * bp = (struct Aurora_board *) dev_id; + unsigned long loop=0; + + #ifdef AURORA_INT_DEBUG + printk("IRQ%d %d\n",irq,++irqhit); + #ifdef AURORA_FLOODPRO + if (irqhit>=AURORA_FLOODPRO) + bp->r0->r=8; + #endif + #endif + +/* old bp = IRQ_to_board[irq&0x0f];*/ + + if (!bp || !(bp->flags & AURORA_BOARD_ACTIVE)) { + return; + } + +/* The while() below takes care of this. + status=bp->r[0]->r[CD180_SRSR]; + #ifdef AURORA_INT_DEBUG + printk("mumu: %02x\n",status); + #endif + if (!(status&SRSR_ANYINT)) return; * Nobody has anything to say, so exit * +*/ + while ((loop++ < 48)&&(status=bp->r[0]->r[CD180_SRSR]&SRSR_ANYINT)){ + #ifdef AURORA_INT_DEBUG + printk("SRSR: %02x\n",status); + #endif + if (status&SRSR_REXT) { + ack=bp->r3->r[bp->ACK_RINT]; + #ifdef AURORA_INT_DEBUG + printk("R-ACK %02x\n",ack); + #endif + if ((ack>>5)==board_No(bp)) { + if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) { + if ((ack&GSVR_ITMASK)==GSVR_IT_RGD) { + aurora_receive(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } else + if ((ack&GSVR_ITMASK)==GSVR_IT_REXC) { + aurora_receive_exc(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } + } + } + } else + if (status&SRSR_TEXT) { + ack=bp->r3->r[bp->ACK_TINT]; + #ifdef AURORA_INT_DEBUG + printk("T-ACK %02x\n",ack); + #endif + if ((ack>>5)==board_No(bp)) { + if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) { + if ((ack&GSVR_ITMASK)==GSVR_IT_TX) { + aurora_transmit(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } + } + } + } else + if (status&SRSR_MEXT) { + ack=bp->r3->r[bp->ACK_MINT]; + #ifdef AURORA_INT_DEBUG + printk("M-ACK %02x\n",ack); + #endif + if ((ack>>5)==board_No(bp)) { + if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) { + if ((ack&GSVR_ITMASK)==GSVR_IT_MDM) { + aurora_check_modem(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } + } + } + } + } +/* I guess this faster code can be used with CD1865, using AUROPRI and GLOBPRI. + while ((loop++ < 48)&&(status=bp->r[0]->r[CD180_SRSR]&SRSR_ANYINT)){ + #ifdef AURORA_INT_DEBUG + printk("SRSR: %02x\n",status); + #endif + ack=bp->r3->r[0]; + #ifdef AURORA_INT_DEBUG + printk("ACK: %02x\n",ack); + #endif + if ((ack>>5)==board_No(bp)) { + if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) { + ack&=GSVR_ITMASK; + if (ack==GSVR_IT_RGD) { + aurora_receive(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } else + if (ack==GSVR_IT_REXC) { + aurora_receive_exc(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } else + if (ack==GSVR_IT_TX) { + aurora_transmit(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } else + if (ack==GSVR_IT_MDM) { + aurora_check_modem(bp,chip); + bp->r[chip]->r[CD180_EOSRR]=0; + } + } + } + } +*/ +/* This is the old handling routine, used in riscom8 for only one CD180. I keep it here for reference. +for(chip=0;chip<AURORA_NCD180;chip++){ + chip_id=(board_No(bp)<<5)|((chip+1)<<3); + loop=0; + while ((loop++ < 1) && ((status = bp->r[chip]->r[CD180_SRSR]) & + (SRSR_TEXT | SRSR_MEXT | SRSR_REXT))) { + + if (status & SRSR_REXT) { + ack = bp->r3->r[bp->ACK_RINT]; + if (ack == (chip_id | GSVR_IT_RGD)){ + #ifdef AURORA_INTMSG + printk("RX ACK\n"); + #endif + aurora_receive(bp,chip); + } + else if (ack == (chip_id | GSVR_IT_REXC)){ + #ifdef AURORA_INTMSG + printk("RXC ACK\n"); + #endif + aurora_receive_exc(bp,chip); + } + else + #ifdef AURORA_INTNORM + printk("aurora%d-%d: Bad receive ack 0x%02x.\n", + board_No(bp), chip, ack) + #endif + ; + + } else if (status & SRSR_TEXT) { + ack = bp->r3->r[bp->ACK_TINT]; + if (ack == (chip_id | GSVR_IT_TX)){ + #ifdef AURORA_INTMSG + printk("TX ACK\n"); + #endif + aurora_transmit(bp,chip); + } + else{ + #ifdef AURORA_INTNORM + printk("aurora%d-%d: Bad transmit ack 0x%02x.\n", + board_No(bp), chip, ack); + #endif + } + + } else if (status & SRSR_MEXT) { + ack = bp->r3->r[bp->ACK_MINT]; + if (ack == (chip_id | GSVR_IT_MDM)){ + #ifdef AURORA_INTMSG + printk("MDM ACK\n"); + #endif + aurora_check_modem(bp,chip); + } + else + #ifdef AURORA_INTNORM + printk("aurora%d-%d: Bad modem ack 0x%02x.\n", + board_No(bp), chip, ack) + #endif + ; + + } + bp->r[chip]->r[CD180_EOSRR]=0; + } + } +*/ +} + + +#ifdef AURORA_INT_DEBUG +static void aurora_timer (unsigned long ignored); + +static struct timer_list +aurora_poll_timer = { NULL, NULL, 0, 0, aurora_timer }; + +static void +aurora_timer (unsigned long ignored) +{ + unsigned long flags; + int i; + + save_flags(flags); cli(); + +printk("SRSR: %02x,%02x - ",aurora_board[0].r[0]->r[CD180_SRSR],aurora_board[0].r[1]->r[CD180_SRSR]); +for(i=0;i<4;i++){ + udelay(1); + printk("%02x ",aurora_board[0].r3->r[i]); + } +printk("\n"); + + aurora_poll_timer.expires = jiffies + 300; + add_timer (&aurora_poll_timer); + + restore_flags(flags); +} +#endif + +/* + * Routines for open & close processing. + */ + +/* Called with disabled interrupts */ +extern inline int aurora_setup_board(struct Aurora_board * bp) +{ + int error; + +#ifdef AURORA_ALLIRQ + int i; + for(i=0;i<AURORA_ALLIRQ;i++){ + error = request_irq(allirq[i]|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp); + if (error){ + printk(KERN_ERR "IRQ%d request error %d\n",allirq[i],error); + } + } +#else + error = request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp); + if (error){ + printk(KERN_ERR "IRQ request error %d\n",error); + return error; + } +#endif + /* Board reset */ + bp->r0->r=0; + udelay(1); + if(bp->flags&AURORA_BOARD_TYPE_2){ + /* unknown yet */ + } else { + bp->r0->r=AURORA_CFG_ENABLE_IO|AURORA_CFG_ENABLE_IRQ|(((bp->irq)&0x0f)>>2); + } + udelay(10000); + + if (aurora_init_CD180(bp,0))error=1;error=0; + if (aurora_init_CD180(bp,1))error++; + if (error==AURORA_NCD180) { + printk(KERN_ERR "Both chips failed initialisation.\n"); + return -EIO; + } + +#ifdef AURORA_INT_DEBUG + aurora_poll_timer.expires=jiffies+1; + add_timer(&aurora_poll_timer); +#endif +#ifdef AURORA_DEBUG +printk("aurora_setup_board: end\n"); +#endif + return 0; +} + +/* Called with disabled interrupts */ +extern inline void aurora_shutdown_board(struct Aurora_board *bp) +{ + int i; + +#ifdef AURORA_DEBUG +printk("aurora_shutdown_board: start\n"); +#endif + +#ifdef AURORA_INT_DEBUG + del_timer(&aurora_poll_timer); +#endif + +#ifdef AURORA_ALLIRQ +for(i=0;i<AURORA_ALLIRQ;i++){ + free_irq(allirq[i]|0x30, bp); +/* IRQ_to_board[allirq[i]&0xf] = NULL;*/ + } +#else + free_irq(bp->irq|0x30, bp); +/* IRQ_to_board[bp->irq&0xf] = NULL;*/ +#endif + /* Drop all DTR's */ + for(i=0;i<16;i++){ + bp->r[i>>3]->r[CD180_CAR]=i&7; + udelay(1); + bp->r[i>>3]->r[CD180_MSVR]=0; + udelay(1); + } + /* Board shutdown */ + bp->r0->r=0; + +#ifdef AURORA_DEBUG +printk("aurora_shutdown_board: end\n"); +#endif +} + +/* + * Setting up port characteristics. + * Must be called with disabled interrupts + */ +static void aurora_change_speed(struct Aurora_board *bp, struct Aurora_port *port) +{ + struct tty_struct *tty; + unsigned long baud; + long tmp; + unsigned char cor1 = 0, cor3 = 0; + unsigned char mcor1 = 0, mcor2 = 0,chip; + +#ifdef AURORA_DEBUG +printk("aurora_change_speed: start\n"); +#endif + if (!(tty = port->tty) || !tty->termios) + return; + + chip=AURORA_CD180(port_No(port)); + + port->SRER = 0; + port->COR2 = 0; + port->MSVR = MSVR_RTS|MSVR_DTR; + + baud = C_BAUD(tty); + + if (baud & CBAUDEX) { + baud &= ~CBAUDEX; + if (baud < 1 || baud > 2) + port->tty->termios->c_cflag &= ~CBAUDEX; + else + baud += 15; + } + if (baud == 15) { + if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + baud ++; + if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + baud += 2; + } + + /* Select port on the board */ + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + + if (!baud_table[baud]) { + /* Drop DTR & exit */ + port->MSVR &= ~(bp->DTR|bp->RTS); + bp->r[chip]->r[CD180_MSVR]=port->MSVR; + return; + } else { + /* Set DTR on */ + port->MSVR |= bp->DTR; + bp->r[chip]->r[CD180_MSVR]=port->MSVR; + } + + /* + * Now we must calculate some speed depended things + */ + + /* Set baud rate for port */ + tmp = (((bp->oscfreq + baud_table[baud]/2) / baud_table[baud] + + CD180_TPC/2) / CD180_TPC); + +/* tmp = (bp->oscfreq/7)/baud_table[baud]; + if((tmp%10)>4)tmp=tmp/10+1;else tmp=tmp/10;*/ +/* printk("Prescaler period: %d\n",tmp);*/ + + bp->r[chip]->r[CD180_RBPRH]=(tmp >> 8) & 0xff; + bp->r[chip]->r[CD180_TBPRH]=(tmp >> 8) & 0xff; + bp->r[chip]->r[CD180_RBPRL]=tmp & 0xff; + bp->r[chip]->r[CD180_TBPRL]=tmp & 0xff; + + baud = (baud_table[baud] + 5) / 10; /* Estimated CPS */ + + /* Two timer ticks seems enough to wakeup something like SLIP driver */ + tmp = ((baud + HZ/2) / HZ) * 2 - CD180_NFIFO; + port->wakeup_chars = (tmp < 0) ? 0 : ((tmp >= SERIAL_XMIT_SIZE) ? + SERIAL_XMIT_SIZE - 1 : tmp); + + /* Receiver timeout will be transmission time for 1.5 chars */ + tmp = (AURORA_TPS + AURORA_TPS/2 + baud/2) / baud; + tmp = (tmp > 0xff) ? 0xff : tmp; + bp->r[chip]->r[CD180_RTPR]=tmp; + + switch (C_CSIZE(tty)) { + case CS5: + cor1 |= COR1_5BITS; + break; + case CS6: + cor1 |= COR1_6BITS; + break; + case CS7: + cor1 |= COR1_7BITS; + break; + case CS8: + cor1 |= COR1_8BITS; + break; + } + + if (C_CSTOPB(tty)) + cor1 |= COR1_2SB; + + cor1 |= COR1_IGNORE; + if (C_PARENB(tty)) { + cor1 |= COR1_NORMPAR; + if (C_PARODD(tty)) + cor1 |= COR1_ODDP; + if (I_INPCK(tty)) + cor1 &= ~COR1_IGNORE; + } + /* Set marking of some errors */ + port->mark_mask = RCSR_OE | RCSR_TOUT; + if (I_INPCK(tty)) + port->mark_mask |= RCSR_FE | RCSR_PE; + if (I_BRKINT(tty) || I_PARMRK(tty)) + port->mark_mask |= RCSR_BREAK; + if (I_IGNPAR(tty)) + port->mark_mask &= ~(RCSR_FE | RCSR_PE); + if (I_IGNBRK(tty)) { + port->mark_mask &= ~RCSR_BREAK; + if (I_IGNPAR(tty)) + /* Real raw mode. Ignore all */ + port->mark_mask &= ~RCSR_OE; + } + /* Enable Hardware Flow Control */ + if (C_CRTSCTS(tty)) { +/*#ifdef AURORA_BRAIN_DAMAGED_CTS + port->SRER |= SRER_DSR | SRER_CTS; + mcor1 |= MCOR1_DSRZD | MCOR1_CTSZD; + mcor2 |= MCOR2_DSROD | MCOR2_CTSOD; + tty->hw_stopped = !(aurora_in(bp, CD180_MSVR) & (MSVR_CTS|MSVR_DSR)); +#else*/ + port->COR2 |= COR2_CTSAE; +/*#endif*/ + if (bp->flags&AURORA_BOARD_DTR_FLOW_OK) { + mcor1 |= AURORA_RXTH; + } + } + /* Enable Software Flow Control. FIXME: I'm not sure about this */ + /* Some people reported that it works, but I still doubt */ + if (I_IXON(tty)) { + port->COR2 |= COR2_TXIBE; + cor3 |= (COR3_FCT | COR3_SCDE); + if (I_IXANY(tty)) + port->COR2 |= COR2_IXM; + bp->r[chip]->r[CD180_SCHR1]=START_CHAR(tty); + bp->r[chip]->r[CD180_SCHR2]=STOP_CHAR(tty); + bp->r[chip]->r[CD180_SCHR3]=START_CHAR(tty); + bp->r[chip]->r[CD180_SCHR4]=STOP_CHAR(tty); + } + if (!C_CLOCAL(tty)) { + /* Enable CD check */ + port->SRER |= SRER_CD; + mcor1 |= MCOR1_CDZD; + mcor2 |= MCOR2_CDOD; + } + + if (C_CREAD(tty)) + /* Enable receiver */ + port->SRER |= SRER_RXD; + + /* Set input FIFO size (1-8 bytes) */ + cor3 |= AURORA_RXFIFO; + /* Setting up CD180 channel registers */ + bp->r[chip]->r[CD180_COR1]=cor1; + bp->r[chip]->r[CD180_COR2]=port->COR2; + bp->r[chip]->r[CD180_COR3]=cor3; + /* Make CD180 know about registers change */ + aurora_wait_CCR(bp->r[chip]); + bp->r[chip]->r[CD180_CCR]=CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3; + /* Setting up modem option registers */ + bp->r[chip]->r[CD180_MCOR1]=mcor1; + bp->r[chip]->r[CD180_MCOR2]=mcor2; + /* Enable CD180 transmitter & receiver */ + aurora_wait_CCR(bp->r[chip]); + bp->r[chip]->r[CD180_CCR]=CCR_TXEN | CCR_RXEN; + /* Enable interrupts */ + bp->r[chip]->r[CD180_SRER]=port->SRER; + /* And finally set RTS on */ + bp->r[chip]->r[CD180_MSVR]=port->MSVR; +#ifdef AURORA_DEBUG +printk("aurora_change_speed: end\n"); +#endif +} + +/* Must be called with interrupts enabled */ +static int aurora_setup_port(struct Aurora_board *bp, struct Aurora_port *port) +{ + unsigned long flags; + +#ifdef AURORA_DEBUG +printk("aurora_setup_port: start %d\n",port_No(port)); +#endif + if (port->flags & ASYNC_INITIALIZED) + return 0; + + if (!port->xmit_buf) { + /* We may sleep in get_free_page() */ + unsigned long tmp; + + if (!(tmp = get_free_page(GFP_KERNEL))) + return -ENOMEM; + + if (port->xmit_buf) { + free_page(tmp); + return -ERESTARTSYS; + } + port->xmit_buf = (unsigned char *) tmp; + } + + save_flags(flags); cli(); + + if (port->tty) + clear_bit(TTY_IO_ERROR, &port->tty->flags); + +#ifdef MODULE + if (port->count == 1) { + MOD_INC_USE_COUNT; + if((++bp->count)==1) + bp->flags|=AURORA_BOARD_ACTIVE; + } +#endif + + port->xmit_cnt = port->xmit_head = port->xmit_tail = 0; + aurora_change_speed(bp, port); + port->flags |= ASYNC_INITIALIZED; + + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_setup_port: end\n"); +#endif + return 0; +} + +/* Must be called with interrupts disabled */ +static void aurora_shutdown_port(struct Aurora_board *bp, struct Aurora_port *port) +{ + struct tty_struct *tty; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_shutdown_port: start\n"); +#endif + if (!(port->flags & ASYNC_INITIALIZED)) + return; + + chip=AURORA_CD180(port_No(port)); + +#ifdef AURORA_REPORT_OVERRUN + printk("aurora%d: port %d: Total %ld overruns were detected.\n", + board_No(bp), port_No(port), port->overrun); +#endif +#ifdef AURORA_REPORT_FIFO + { + int i; + + printk("aurora%d: port %d: FIFO hits [ ", + board_No(bp), port_No(port)); + for (i = 0; i < 10; i++) { + printk("%ld ", port->hits[i]); + } + printk("].\n"); + } +#endif + if (port->xmit_buf) { + free_page((unsigned long) port->xmit_buf); + port->xmit_buf = NULL; + } + + if (!(tty = port->tty) || C_HUPCL(tty)) { + /* Drop DTR */ + port->MSVR &= ~(bp->DTR|bp->RTS); + bp->r[chip]->r[CD180_MSVR]=port->MSVR; + } + + /* Select port */ + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + /* Reset port */ + aurora_wait_CCR(bp->r[chip]); + bp->r[chip]->r[CD180_CCR]=CCR_SOFTRESET; + /* Disable all interrupts from this port */ + port->SRER = 0; + bp->r[chip]->r[CD180_SRER]=port->SRER; + + if (tty) + set_bit(TTY_IO_ERROR, &tty->flags); + port->flags &= ~ASYNC_INITIALIZED; + +#ifdef MODULE + if (--bp->count < 0) { + printk(KERN_DEBUG "aurora%d: aurora_shutdown_port: bad board count: %d\n", + board_No(bp), bp->count); + bp->count = 0; + } + + MOD_DEC_USE_COUNT; + if (!bp->count) + bp->flags&=~AURORA_BOARD_ACTIVE; +#endif + +#ifdef AURORA_DEBUG +printk("aurora_shutdown_port: end\n"); +#endif +} + + +static int block_til_ready(struct tty_struct *tty, struct file * filp, + struct Aurora_port *port) +{ + DECLARE_WAITQUEUE(wait, current); + struct Aurora_board *bp = port_Board(port); + int retval; + int do_clocal = 0; + int CD; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("block_til_ready: start\n"); +#endif + chip=AURORA_CD180(port_No(port)); + + /* + * If the device is in the middle of being closed, then block + * until it's done, and then try again. + */ + if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) { + interruptible_sleep_on(&port->close_wait); + if (port->flags & ASYNC_HUP_NOTIFY) + return -EAGAIN; + else + return -ERESTARTSYS; + } + + /* + * If non-blocking mode is set, or the port is not enabled, + * then make the check up front and then exit. + */ + if ((filp->f_flags & O_NONBLOCK) || + (tty->flags & (1 << TTY_IO_ERROR))) { + if (port->flags & ASYNC_CALLOUT_ACTIVE) + return -EBUSY; + port->flags |= ASYNC_NORMAL_ACTIVE; + return 0; + } + + if (port->flags & ASYNC_CALLOUT_ACTIVE) { + if (port->normal_termios.c_cflag & CLOCAL) + do_clocal = 1; + } else { + if (C_CLOCAL(tty)) + do_clocal = 1; + } + + /* + * Block waiting for the carrier detect and the line to become + * free (i.e., not in use by the callout). While we are in + * this loop, info->count is dropped by one, so that + * rs_close() knows when to free things. We restore it upon + * exit, either normal or abnormal. + */ + retval = 0; + add_wait_queue(&port->open_wait, &wait); + cli(); + if (!tty_hung_up_p(filp)) + port->count--; + sti(); + port->blocked_open++; + while (1) { + cli(); + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + CD = bp->r[chip]->r[CD180_MSVR] & MSVR_CD; + if (!(port->flags & ASYNC_CALLOUT_ACTIVE)) { + port->MSVR=bp->RTS; + bp->r[chip]->r[CD180_MSVR]=port->MSVR;/* auto drops DTR */ + } + sti(); + current->state = TASK_INTERRUPTIBLE; + if (tty_hung_up_p(filp) || + !(port->flags & ASYNC_INITIALIZED)) { + if (port->flags & ASYNC_HUP_NOTIFY) + retval = -EAGAIN; + else + retval = -ERESTARTSYS; + break; + } + if (/*!(port->flags & ASYNC_CALLOUT_ACTIVE) &&*/ + !(port->flags & ASYNC_CLOSING) && + (do_clocal || CD)) + break; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + } + current->state = TASK_RUNNING; + remove_wait_queue(&port->open_wait, &wait); + if (!tty_hung_up_p(filp)) + port->count++; + port->blocked_open--; + if (retval) + return retval; + + port->flags |= ASYNC_NORMAL_ACTIVE; +#ifdef AURORA_DEBUG +printk("block_til_ready: end\n"); +#endif + return 0; +} + +static int aurora_open(struct tty_struct * tty, struct file * filp) +{ + int board; + int error; + struct Aurora_port * port; + struct Aurora_board * bp; + unsigned long flags; + + #ifdef AURORA_DEBUG + printk("aurora_open: start\n"); + #endif + + board = AURORA_BOARD(MINOR(tty->device)); + if (board > AURORA_NBOARD || !(aurora_board[board].flags & AURORA_BOARD_PRESENT)){ + #ifdef AURORA_DEBUG + printk("aurora_open: error board %d present %d\n",board,aurora_board[board].flags &AURORA_BOARD_PRESENT); + #endif + return -ENODEV; + } + + bp = &aurora_board[board]; + port = aurora_port + board * AURORA_NPORT * AURORA_NCD180 + AURORA_PORT(MINOR(tty->device)); + if (aurora_paranoia_check(port, tty->device, "aurora_open")){ + #ifdef AURORA_DEBUG + printk("aurora_open: error paranoia check\n"); + #endif + return -ENODEV; + } + + port->count++; + tty->driver_data = port; + port->tty = tty; + + if ((error = aurora_setup_port(bp, port))) { + #ifdef AURORA_DEBUG + printk("aurora_open: error aurora_setup_port ret %d\n",error); + #endif + return error; + } + + if ((error = block_til_ready(tty, filp, port))){ + #ifdef AURORA_DEBUG + printk("aurora_open: error block_til_ready ret %d\n",error); + #endif + return error; + } + + if ((port->count == 1) && (port->flags & ASYNC_SPLIT_TERMIOS)) { + *tty->termios = port->normal_termios; + save_flags(flags); cli(); + aurora_change_speed(bp, port); + restore_flags(flags); + } + + port->session = current->session; + port->pgrp = current->pgrp; + #ifdef AURORA_DEBUG + printk("aurora_open: end\n"); + #endif + return 0; +} + +static void aurora_close(struct tty_struct * tty, struct file * filp) +{ + struct Aurora_port *port = (struct Aurora_port *) tty->driver_data; + struct Aurora_board *bp; + unsigned long flags; + unsigned long timeout; + unsigned char chip; + + #ifdef AURORA_DEBUG + printk("aurora_close: start\n"); + #endif + + if (!port || aurora_paranoia_check(port, tty->device, "close")) + return; + + chip=AURORA_CD180(port_No(port)); + + save_flags(flags); cli(); + if (tty_hung_up_p(filp)) { + restore_flags(flags); + return; + } + + bp = port_Board(port); + if ((tty->count == 1) && (port->count != 1)) { + printk(KERN_DEBUG "aurora%d: aurora_close: bad port count; tty->count is 1, port count is %d\n", + board_No(bp), port->count); + port->count = 1; + } + if (--port->count < 0) { + printk(KERN_DEBUG "aurora%d: aurora_close: bad port count for tty%d: %d\n", + board_No(bp), port_No(port), port->count); + port->count = 0; + } + if (port->count) { + restore_flags(flags); + return; + } + port->flags |= ASYNC_CLOSING; + /* + * Save the termios structure, since this port may have + * separate termios for callout and dialin. + */ + if (port->flags & ASYNC_NORMAL_ACTIVE) + port->normal_termios = *tty->termios; +/* if (port->flags & ASYNC_CALLOUT_ACTIVE) + port->callout_termios = *tty->termios;*/ + /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + tty->closing = 1; + if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE){ + #ifdef AURORA_DEBUG + printk("aurora_close: waiting to flush...\n"); + #endif + tty_wait_until_sent(tty, port->closing_wait); + } + /* + * At this point we stop accepting input. To do this, we + * disable the receive line status interrupts, and tell the + * interrupt driver to stop checking the data ready bit in the + * line status register. + */ + port->SRER &= ~SRER_RXD; + if (port->flags & ASYNC_INITIALIZED) { + port->SRER &= ~SRER_TXRDY; + port->SRER |= SRER_TXEMPTY; + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + bp->r[chip]->r[CD180_SRER]=port->SRER; + /* + * Before we drop DTR, make sure the UART transmitter + * has completely drained; this is especially + * important if there is a transmit FIFO! + */ + timeout = jiffies+HZ; + while(port->SRER & SRER_TXEMPTY) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(port->timeout); + if (time_after(jiffies, timeout)) + break; + } + } + #ifdef AURORA_DEBUG + printk("aurora_close: shutdown_port\n"); + #endif + aurora_shutdown_port(bp, port); + if (tty->driver.flush_buffer) + tty->driver.flush_buffer(tty); + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + tty->closing = 0; + port->event = 0; + port->tty = 0; + if (port->blocked_open) { + if (port->close_delay) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(port->close_delay); + } + wake_up_interruptible(&port->open_wait); + } + port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE| + ASYNC_CLOSING); + wake_up_interruptible(&port->close_wait); + restore_flags(flags); + #ifdef AURORA_DEBUG + printk("aurora_close: end\n"); + #endif +} + +static int aurora_write(struct tty_struct * tty, int from_user, + const unsigned char *buf, int count) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + struct Aurora_board *bp; + int c, total = 0; + unsigned long flags; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_write: start %d\n",count); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_write")) + return 0; + + chip=AURORA_CD180(port_No(port)); + + bp = port_Board(port); + + if (!tty || !port->xmit_buf || !tmp_buf) + return 0; + + if (from_user) + down(&tmp_buf_sem); + + save_flags(flags); + while (1) { + cli(); + c = MIN(count, MIN(SERIAL_XMIT_SIZE - port->xmit_cnt - 1, + SERIAL_XMIT_SIZE - port->xmit_head)); + if (c <= 0) + break; + + if (from_user) { + copy_from_user(tmp_buf, buf, c); + c = MIN(c, MIN(SERIAL_XMIT_SIZE - port->xmit_cnt - 1, + SERIAL_XMIT_SIZE - port->xmit_head)); + memcpy(port->xmit_buf + port->xmit_head, tmp_buf, c); + } else + memcpy(port->xmit_buf + port->xmit_head, buf, c); + port->xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE-1); + port->xmit_cnt += c; + restore_flags(flags); + buf += c; + count -= c; + total += c; + } + if (from_user) + up(&tmp_buf_sem); + if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped && + !(port->SRER & SRER_TXRDY)) { + port->SRER |= SRER_TXRDY; + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + bp->r[chip]->r[CD180_SRER]=port->SRER; + } + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_write: end %d\n",total); +#endif + return total; +} + +static void aurora_put_char(struct tty_struct * tty, unsigned char ch) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + unsigned long flags; + +#ifdef AURORA_DEBUG +printk("aurora_put_char: start %c\n",ch); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_put_char")) + return; + + if (!tty || !port->xmit_buf) + return; + + save_flags(flags); cli(); + + if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) { + restore_flags(flags); + return; + } + + port->xmit_buf[port->xmit_head++] = ch; + port->xmit_head &= SERIAL_XMIT_SIZE - 1; + port->xmit_cnt++; + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_put_char: end\n"); +#endif +} + +static void aurora_flush_chars(struct tty_struct * tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + unsigned long flags; + unsigned char chip; + +/*#ifdef AURORA_DEBUG +printk("aurora_flush_chars: start\n"); +#endif*/ + if (aurora_paranoia_check(port, tty->device, "aurora_flush_chars")) + return; + + chip=AURORA_CD180(port_No(port)); + + if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || + !port->xmit_buf) + return; + + save_flags(flags); cli(); + port->SRER |= SRER_TXRDY; + port_Board(port)->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + port_Board(port)->r[chip]->r[CD180_SRER]=port->SRER; + restore_flags(flags); +/*#ifdef AURORA_DEBUG +printk("aurora_flush_chars: end\n"); +#endif*/ +} + +static int aurora_write_room(struct tty_struct * tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + int ret; + +#ifdef AURORA_DEBUG +printk("aurora_write_room: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_write_room")) + return 0; + + ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1; + if (ret < 0) + ret = 0; +#ifdef AURORA_DEBUG +printk("aurora_write_room: end\n"); +#endif + return ret; +} + +static int aurora_chars_in_buffer(struct tty_struct *tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + + if (aurora_paranoia_check(port, tty->device, "aurora_chars_in_buffer")) + return 0; + + return port->xmit_cnt; +} + +static void aurora_flush_buffer(struct tty_struct *tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + unsigned long flags; + +#ifdef AURORA_DEBUG +printk("aurora_flush_buffer: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_flush_buffer")) + return; + + save_flags(flags); cli(); + port->xmit_cnt = port->xmit_head = port->xmit_tail = 0; + restore_flags(flags); + + wake_up_interruptible(&tty->write_wait); + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); +#ifdef AURORA_DEBUG +printk("aurora_flush_buffer: end\n"); +#endif +} + +static int aurora_get_modem_info(struct Aurora_port * port, unsigned int *value) +{ + struct Aurora_board * bp; + unsigned char status,chip; + unsigned int result; + unsigned long flags; + +#ifdef AURORA_DEBUG +printk("aurora_get_modem_info: start\n"); +#endif + chip=AURORA_CD180(port_No(port)); + + bp = port_Board(port); + save_flags(flags); cli(); + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + status = bp->r[chip]->r[CD180_MSVR]; + result = 0/*bp->r[chip]->r[AURORA_RI] & (1u << port_No(port)) ? 0 : TIOCM_RNG*/; + restore_flags(flags); + result |= ((status & bp->RTS) ? TIOCM_RTS : 0) + | ((status & bp->DTR) ? TIOCM_DTR : 0) + | ((status & MSVR_CD) ? TIOCM_CAR : 0) + | ((status & MSVR_DSR) ? TIOCM_DSR : 0) + | ((status & MSVR_CTS) ? TIOCM_CTS : 0); + put_user(result,(unsigned long *) value); +#ifdef AURORA_DEBUG +printk("aurora_get_modem_info: end\n"); +#endif + return 0; +} + +static int aurora_set_modem_info(struct Aurora_port * port, unsigned int cmd, + unsigned int *value) +{ + int error; + unsigned int arg; + unsigned long flags; + struct Aurora_board *bp = port_Board(port); + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_set_modem_info: start\n"); +#endif + error = get_user(arg, value); + if (error) + return error; + chip=AURORA_CD180(port_No(port)); + switch (cmd) { + case TIOCMBIS: + if (arg & TIOCM_RTS) + port->MSVR |= bp->RTS; + if (arg & TIOCM_DTR) + port->MSVR |= bp->DTR; + break; + case TIOCMBIC: + if (arg & TIOCM_RTS) + port->MSVR &= ~bp->RTS; + if (arg & TIOCM_DTR) + port->MSVR &= ~bp->DTR; + break; + case TIOCMSET: + port->MSVR = (arg & TIOCM_RTS) ? (port->MSVR | bp->RTS) : + (port->MSVR & ~bp->RTS); + port->MSVR = (arg & TIOCM_DTR) ? (port->MSVR | bp->RTS) : + (port->MSVR & ~bp->RTS); + break; + default: + return -EINVAL; + } + save_flags(flags); cli(); + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + bp->r[chip]->r[CD180_MSVR]=port->MSVR; + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_set_modem_info: end\n"); +#endif + return 0; +} + +extern inline void aurora_send_break(struct Aurora_port * port, unsigned long length) +{ + struct Aurora_board *bp = port_Board(port); + unsigned long flags; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_send_break: start\n"); +#endif + chip=AURORA_CD180(port_No(port)); + + save_flags(flags); cli(); + port->break_length = AURORA_TPS / HZ * length; + port->COR2 |= COR2_ETC; + port->SRER |= SRER_TXRDY; + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + bp->r[chip]->r[CD180_COR2]=port->COR2; + bp->r[chip]->r[CD180_SRER]=port->SRER; + aurora_wait_CCR(bp->r[chip]); + bp->r[chip]->r[CD180_CCR]=CCR_CORCHG2; + aurora_wait_CCR(bp->r[chip]); + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_send_break: end\n"); +#endif +} + +extern inline int aurora_set_serial_info(struct Aurora_port * port, + struct serial_struct * newinfo) +{ + struct serial_struct tmp; + struct Aurora_board *bp = port_Board(port); + int change_speed; + unsigned long flags; + int error; + +#ifdef AURORA_DEBUG +printk("aurora_set_serial_info: start\n"); +#endif + error = verify_area(VERIFY_READ, (void *) newinfo, sizeof(tmp)); + if (error) + return error; + copy_from_user(&tmp, newinfo, sizeof(tmp)); + +#if 0 + if ((tmp.irq != bp->irq) || + (tmp.port != bp->base) || + (tmp.type != PORT_CIRRUS) || + (tmp.baud_base != (bp->oscfreq + CD180_TPC/2) / CD180_TPC) || + (tmp.custom_divisor != 0) || + (tmp.xmit_fifo_size != CD180_NFIFO) || + (tmp.flags & ~AURORA_LEGAL_FLAGS)) + return -EINVAL; +#endif + + change_speed = ((port->flags & ASYNC_SPD_MASK) != + (tmp.flags & ASYNC_SPD_MASK)); + + if (!suser()) { + if ((tmp.close_delay != port->close_delay) || + (tmp.closing_wait != port->closing_wait) || + ((tmp.flags & ~ASYNC_USR_MASK) != + (port->flags & ~ASYNC_USR_MASK))) + return -EPERM; + port->flags = ((port->flags & ~ASYNC_USR_MASK) | + (tmp.flags & ASYNC_USR_MASK)); + } else { + port->flags = ((port->flags & ~ASYNC_FLAGS) | + (tmp.flags & ASYNC_FLAGS)); + port->close_delay = tmp.close_delay; + port->closing_wait = tmp.closing_wait; + } + if (change_speed) { + save_flags(flags); cli(); + aurora_change_speed(bp, port); + restore_flags(flags); + } +#ifdef AURORA_DEBUG +printk("aurora_set_serial_info: end\n"); +#endif + return 0; +} + +extern inline int aurora_get_serial_info(struct Aurora_port * port, + struct serial_struct * retinfo) +{ + struct serial_struct tmp; + struct Aurora_board *bp = port_Board(port); + int error; + +#ifdef AURORA_DEBUG +printk("aurora_get_serial_info: start\n"); +#endif + error = verify_area(VERIFY_WRITE, (void *) retinfo, sizeof(tmp)); + if (error) + return error; + + memset(&tmp, 0, sizeof(tmp)); + tmp.type = PORT_CIRRUS; + tmp.line = port - aurora_port; + tmp.port = 0; + tmp.irq = bp->irq; + tmp.flags = port->flags; + tmp.baud_base = (bp->oscfreq + CD180_TPC/2) / CD180_TPC; + tmp.close_delay = port->close_delay * HZ/100; + tmp.closing_wait = port->closing_wait * HZ/100; + tmp.xmit_fifo_size = CD180_NFIFO; + copy_to_user(retinfo, &tmp, sizeof(tmp)); +#ifdef AURORA_DEBUG +printk("aurora_get_serial_info: end\n"); +#endif + return 0; +} + +static int aurora_ioctl(struct tty_struct * tty, struct file * filp, + unsigned int cmd, unsigned long arg) + +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + int error; + int retval; + +#ifdef AURORA_DEBUG +printk("aurora_ioctl: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_ioctl")) + return -ENODEV; + + switch (cmd) { + case TCSBRK: /* SVID version: non-zero arg --> no break */ + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + if (!arg) + aurora_send_break(port, HZ/4); /* 1/4 second */ + return 0; + case TCSBRKP: /* support for POSIX tcsendbreak() */ + retval = tty_check_change(tty); + if (retval) + return retval; + tty_wait_until_sent(tty, 0); + aurora_send_break(port, arg ? arg*(HZ/10) : HZ/4); + return 0; + case TIOCGSOFTCAR: + error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(long)); + if (error) + return error; + put_user(C_CLOCAL(tty) ? 1 : 0, + (unsigned long *) arg); + return 0; + case TIOCSSOFTCAR: + retval = get_user(arg,(unsigned long *) arg); + if (retval) + return retval; + tty->termios->c_cflag = + ((tty->termios->c_cflag & ~CLOCAL) | + (arg ? CLOCAL : 0)); + return 0; + case TIOCMGET: + error = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(unsigned int)); + if (error) + return error; + return aurora_get_modem_info(port, (unsigned int *) arg); + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + return aurora_set_modem_info(port, cmd, (unsigned int *) arg); + case TIOCGSERIAL: + return aurora_get_serial_info(port, (struct serial_struct *) arg); + case TIOCSSERIAL: + return aurora_set_serial_info(port, (struct serial_struct *) arg); + default: + return -ENOIOCTLCMD; + } +#ifdef AURORA_DEBUG +printk("aurora_ioctl: end\n"); +#endif + return 0; +} + +static void aurora_throttle(struct tty_struct * tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + struct Aurora_board *bp; + unsigned long flags; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_throttle: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_throttle")) + return; + + bp = port_Board(port); + chip=AURORA_CD180(port_No(port)); + + save_flags(flags); cli(); + port->MSVR &= ~bp->RTS; + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + if (I_IXOFF(tty)) { + aurora_wait_CCR(bp->r[chip]); + bp->r[chip]->r[CD180_CCR]=CCR_SSCH2; + aurora_wait_CCR(bp->r[chip]); + } + bp->r[chip]->r[CD180_MSVR]=port->MSVR; + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_throttle: end\n"); +#endif +} + +static void aurora_unthrottle(struct tty_struct * tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + struct Aurora_board *bp; + unsigned long flags; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_unthrottle: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_unthrottle")) + return; + + bp = port_Board(port); + + chip=AURORA_CD180(port_No(port)); + + save_flags(flags); cli(); + port->MSVR |= bp->RTS; + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + if (I_IXOFF(tty)) { + aurora_wait_CCR(bp->r[chip]); + bp->r[chip]->r[CD180_CCR]=CCR_SSCH1; + aurora_wait_CCR(bp->r[chip]); + } + bp->r[chip]->r[CD180_MSVR]=port->MSVR; + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_unthrottle: end\n"); +#endif +} + +static void aurora_stop(struct tty_struct * tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + struct Aurora_board *bp; + unsigned long flags; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_stop: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_stop")) + return; + + bp = port_Board(port); + + chip=AURORA_CD180(port_No(port)); + + save_flags(flags); cli(); + port->SRER &= ~SRER_TXRDY; + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + bp->r[chip]->r[CD180_SRER]=port->SRER; + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_stop: end\n"); +#endif +} + +static void aurora_start(struct tty_struct * tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + struct Aurora_board *bp; + unsigned long flags; + unsigned char chip; + +#ifdef AURORA_DEBUG +printk("aurora_start: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_start")) + return; + + bp = port_Board(port); + + chip=AURORA_CD180(port_No(port)); + + save_flags(flags); cli(); + if (port->xmit_cnt && port->xmit_buf && !(port->SRER & SRER_TXRDY)) { + port->SRER |= SRER_TXRDY; + bp->r[chip]->r[CD180_CAR]=port_No(port)&7; + udelay(1); + bp->r[chip]->r[CD180_SRER]=port->SRER; + } + restore_flags(flags); +#ifdef AURORA_DEBUG +printk("aurora_start: end\n"); +#endif +} + +/* + * This routine is called from the scheduler tqueue when the interrupt + * routine has signalled that a hangup has occurred. The path of + * hangup processing is: + * + * serial interrupt routine -> (scheduler tqueue) -> + * do_aurora_hangup() -> tty->hangup() -> aurora_hangup() + * + */ +static void do_aurora_hangup(void *private_) +{ + struct Aurora_port *port = (struct Aurora_port *) private_; + struct tty_struct *tty; + +#ifdef AURORA_DEBUG +printk("do_aurora_hangup: start\n"); +#endif + tty = port->tty; + if (!tty) + return; + + tty_hangup(tty); +#ifdef AURORA_DEBUG +printk("do_aurora_hangup: end\n"); +#endif +} + +static void aurora_hangup(struct tty_struct * tty) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + struct Aurora_board *bp; + +#ifdef AURORA_DEBUG +printk("aurora_hangup: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_hangup")) + return; + + bp = port_Board(port); + + aurora_shutdown_port(bp, port); + port->event = 0; + port->count = 0; + port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE); + port->tty = 0; + wake_up_interruptible(&port->open_wait); +#ifdef AURORA_DEBUG +printk("aurora_hangup: end\n"); +#endif +} + +static void aurora_set_termios(struct tty_struct * tty, struct termios * old_termios) +{ + struct Aurora_port *port = (struct Aurora_port *)tty->driver_data; + unsigned long flags; + +#ifdef AURORA_DEBUG +printk("aurora_set_termios: start\n"); +#endif + if (aurora_paranoia_check(port, tty->device, "aurora_set_termios")) + return; + + if (tty->termios->c_cflag == old_termios->c_cflag && + tty->termios->c_iflag == old_termios->c_iflag) + return; + + save_flags(flags); cli(); + aurora_change_speed(port_Board(port), port); + restore_flags(flags); + + if ((old_termios->c_cflag & CRTSCTS) && + !(tty->termios->c_cflag & CRTSCTS)) { + tty->hw_stopped = 0; + aurora_start(tty); + } +#ifdef AURORA_DEBUG +printk("aurora_set_termios: end\n"); +#endif +} + +static void do_aurora_bh(void) +{ + run_task_queue(&tq_aurora); +} + +static void do_softint(void *private_) +{ + struct Aurora_port *port = (struct Aurora_port *) private_; + struct tty_struct *tty; + +#ifdef AURORA_DEBUG +printk("do_softint: start\n"); +#endif + if(!(tty = port->tty)) + return; + + if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) { + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); + wake_up_interruptible(&tty->write_wait); + } +#ifdef AURORA_DEBUG +printk("do_softint: end\n"); +#endif +} + +static int aurora_init_drivers(void) +{ + int error; + int i; + +#ifdef AURORA_DEBUG +printk("aurora_init_drivers: start\n"); +#endif + if (!(tmp_buf = (unsigned char *) get_free_page(GFP_KERNEL))) { + printk(KERN_ERR "aurora: Couldn't get free page.\n"); + return 1; + } + init_bh(AURORA_BH, do_aurora_bh); +/* memset(IRQ_to_board, 0, sizeof(IRQ_to_board));*/ + memset(&aurora_driver, 0, sizeof(aurora_driver)); + aurora_driver.magic = TTY_DRIVER_MAGIC; + aurora_driver.name = "ttyA"; + aurora_driver.major = AURORA_MAJOR; + aurora_driver.num = AURORA_TNPORTS; + aurora_driver.type = TTY_DRIVER_TYPE_SERIAL; + aurora_driver.subtype = AURORA_TYPE_NORMAL; + aurora_driver.init_termios = tty_std_termios; + aurora_driver.init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + aurora_driver.flags = TTY_DRIVER_REAL_RAW; + aurora_driver.refcount = &aurora_refcount; + aurora_driver.table = aurora_table; + aurora_driver.termios = aurora_termios; + aurora_driver.termios_locked = aurora_termios_locked; + + aurora_driver.open = aurora_open; + aurora_driver.close = aurora_close; + aurora_driver.write = aurora_write; + aurora_driver.put_char = aurora_put_char; + aurora_driver.flush_chars = aurora_flush_chars; + aurora_driver.write_room = aurora_write_room; + aurora_driver.chars_in_buffer = aurora_chars_in_buffer; + aurora_driver.flush_buffer = aurora_flush_buffer; + aurora_driver.ioctl = aurora_ioctl; + aurora_driver.throttle = aurora_throttle; + aurora_driver.unthrottle = aurora_unthrottle; + aurora_driver.set_termios = aurora_set_termios; + aurora_driver.stop = aurora_stop; + aurora_driver.start = aurora_start; + aurora_driver.hangup = aurora_hangup; + + if ((error = tty_register_driver(&aurora_driver))) { + free_page((unsigned long)tmp_buf); + printk(KERN_ERR "aurora: Couldn't register aurora driver, error = %d\n", + error); + return 1; + } + + memset(aurora_port, 0, sizeof(aurora_port)); + for (i = 0; i < AURORA_TNPORTS; i++) { + aurora_port[i].normal_termios = aurora_driver.init_termios; + aurora_port[i].magic = AURORA_MAGIC; + aurora_port[i].tqueue.routine = do_softint; + aurora_port[i].tqueue.data = &aurora_port[i]; + aurora_port[i].tqueue_hangup.routine = do_aurora_hangup; + aurora_port[i].tqueue_hangup.data = &aurora_port[i]; + aurora_port[i].close_delay = 50 * HZ/100; + aurora_port[i].closing_wait = 3000 * HZ/100; + init_waitqueue_head(&aurora_port[i].open_wait); + init_waitqueue_head(&aurora_port[i].close_wait); + } +#ifdef AURORA_DEBUG +printk("aurora_init_drivers: end\n"); +#endif + return 0; +} + +static void aurora_release_drivers(void) +{ +#ifdef AURORA_DEBUG +printk("aurora_release_drivers: start\n"); +#endif + free_page((unsigned long)tmp_buf); + tty_unregister_driver(&aurora_driver); +#ifdef AURORA_DEBUG +printk("aurora_release_drivers: end\n"); +#endif +} + +#ifndef MODULE +/* + * Called at boot time. + * + * You can specify IO base for up to RC_NBOARD cards, + * using line "riscom8=0xiobase1,0xiobase2,.." at LILO prompt. + * Note that there will be no probing at default + * addresses in this case. + * + */ +__init_func(void aurora_setup(char *str, int *ints)) +{ + int i; + + for(i=0;(i<ints[0])&&(i<4);i++) { + if (ints[i+1]) irqs[i]=ints[i+1]; + } +} + +__init_func(int aurora_init(void)) +#else +int aurora_init(void) +#endif +{ +int found; +int grrr,i; + +printk(KERN_INFO "aurora: Driver starting.\n"); +if(aurora_init_drivers())return -EIO; +found=aurora_probe(); +if(!found){ + aurora_release_drivers(); + printk(KERN_INFO "aurora: No Aurora Multiport boards detected.\n"); + return -EIO; + } else { + printk(KERN_INFO "aurora: %d boards found.\n",found); + } +for(i=0;i<found;i++){ + if ((grrr = aurora_setup_board(&aurora_board[i]))) { + #ifdef AURORA_DEBUG + printk(KERN_ERR "aurora_init: error aurora_setup_board ret %d\n",grrr); + #endif + return grrr; + } +} +return 0; +} + +#ifdef MODULE +int irq = 0; +int irq1 = 0; +int irq2 = 0; +int irq3 = 0; +MODULE_PARM(irq , "i"); +MODULE_PARM(irq1, "i"); +MODULE_PARM(irq2, "i"); +MODULE_PARM(irq3, "i"); + +int init_module(void) +{ + if (irq ) irqs[0]=irq ; + if (irq1) irqs[1]=irq1; + if (irq2) irqs[2]=irq2; + if (irq3) irqs[3]=irq3; + return aurora_init(); +} + +void cleanup_module(void) +{ + int i; + +#ifdef AURORA_DEBUG +printk("cleanup_module: aurora_release_drivers\n"); +#endif; + + aurora_release_drivers(); + for (i = 0; i < AURORA_NBOARD; i++) + if (aurora_board[i].flags & AURORA_BOARD_PRESENT) { + aurora_shutdown_board(&aurora_board[i]); + aurora_release_io_range(&aurora_board[i]); + } +} +#endif /* MODULE */ diff --git a/drivers/sbus/char/aurora.h b/drivers/sbus/char/aurora.h new file mode 100644 index 000000000..e8250ead4 --- /dev/null +++ b/drivers/sbus/char/aurora.h @@ -0,0 +1,278 @@ +/* + * linux/drivers/sbus/char/aurora.h -- Aurora multiport driver + * + * Copyright (c) 1999 by Oliver Aldulea (oli@bv.ro) + * + * This code is based on the RISCom/8 multiport serial driver written + * by Dmitry Gorodchanin (pgmdsg@ibi.com), based on the Linux serial + * driver, written by Linus Torvalds, Theodore T'so and others. + * The Aurora multiport programming info was obtained mainly from the + * Cirrus Logic CD180 documentation (available on the web), and by + * doing heavy tests on the board. Many thanks to Eddie C. Dost for the + * help on the sbus interface. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Revision 1.0 + * + * This is the first public release. + * + * This version needs a lot of feedback. This is the version that works + * with _my_ board. My board is model 1600se, revision '@(#)1600se.fth + * 1.2 3/28/95 1'. The driver might work with your board, but I do not + * guarantee it. If you have _any_ type of board, I need to know if the + * driver works or not, I need to know exactly your board parameters + * (get them with 'cd /proc/openprom/iommu/sbus/sio16/; ls *; cat *') + * Also, I need your board revision code, which is written on the board. + * Send me the output of my driver too (it outputs through klogd). + * + * If the driver does not work, you can try enabling the debug options + * to see what's wrong or what should be done. + * + * I'm sorry about the alignment of the code. It was written in a + * 128x48 environment. + * + * I must say that I do not like Aurora Technologies' policy. I asked + * them to help me do this driver faster, but they ended by something + * like "don't call us, we'll call you", and I never heard anything + * from them. They told me "knowing the way the board works, I don't + * doubt you and others on the net will make the driver." + * The truth about this board is that it has nothing intelligent on it. + * If you want to say to somebody what kind of board you have, say that + * it uses Cirrus Logic processors (CD180). The power of the board is + * in those two chips. The rest of the board is the interface to the + * sbus and to the peripherals. Still, they did something smart: they + * reversed DTR and RTS to make on-board automatic hardware flow + * control usable. + * Thanks to Aurora Technologies for wasting my time, nerves and money. + */ + +#ifndef __LINUX_AURORA_H +#define __LINUX_AURORA_H + +#include <linux/serial.h> + +#ifdef __KERNEL__ + +/* This is the number of boards to support. I've only tested this driver with + * one board, so it might not work. + */ +#define AURORA_NBOARD 1 + +/* Useful ? Yes. But you can safely comment the warnings if they annoy you + * (let me say that again: the warnings in the code, not this define). + */ +#define AURORA_PARANOIA_CHECK + +/* Well, after many lost nights, I found that the IRQ for this board is + * selected from four built-in values by writing some bits in the + * configuration register. This causes a little problem to occur: which + * IRQ to select ? Which one is the best for the user ? Well, I finally + * decided for the following algorithm: if the "bintr" value is not acceptable + * (not within type_1_irq[], then test the "intr" value, if that fails too, + * try each value from type_1_irq until succeded. Hope it's ok. + * You can safely reorder the irq's. + */ +#define TYPE_1_IRQS 4 +unsigned char type_1_irq[TYPE_1_IRQS] = { + 3, 5, 9, 13 + }; +/* I know something about another method of interrupt setting, but not enough. + * Also, this is for another type of board, so I first have to learn how to + * detect it. +#define TYPE_2_IRQS 3 +unsigned char type_2_irq[TYPE_2_IRQS] = { + 0, 0, 0 ** could anyone find these for me ? (see AURORA_ALLIRQ below) ** + }; +unsigned char type_2_mask[TYPE_2_IRQS] = { + 32, 64, 128 + }; +*/ + +/* The following section should only be modified by those who know what + * they're doing (or don't, but want to help with some feedback). Modifying + * anything raises a _big_ probability for your system to hang, but the + * sacrifice worths. (I sacrificed my ext2fs many, many times...) + */ + +/* This one tries to dump to console the name of almost every function called, + * and many other debugging info. + */ +#undef AURORA_DEBUG + +/* These are the most dangerous and useful defines. They do printk() during + * the interrupt processing routine(s), so if you manage to get "flooded" by + * irq's, start thinking about the "Power off/on" button... + */ +#undef AURORA_INTNORM /* This one enables the "normal" messages, but some + * of them cause flood, so I preffered putting + * them under a define */ +#undef AURORA_INT_DEBUG /* This one is really bad. */ + +/* Here's something helpful: after n irq's, the board will be disabled. This + * prevents irq flooding during debug (no need to think about power + * off/on anymore...) + */ +#define AURORA_FLOODPRO 10 + +/* This one helps finding which irq the board calls, in case of a strange/ + * unsupported board. AURORA_INT_DEBUG should be enabled, because I don't + * think /proc/interrupts or any command will be available in case of an irq + * flood... "allirq" is the list of all free irq's. + */ +/* +#define AURORA_ALLIRQ 6 +int allirq[AURORA_ALLIRQ]={ + 2,3,5,7,9,13 + }; +*/ + +/* These must not be modified. These values are assumed during the code for + * performance optimisations. + */ +#define AURORA_NCD180 2 /* two chips per board */ +#define AURORA_NPORT 8 /* 8 ports per chip */ + +/* several utilities */ +#define AURORA_BOARD(line) (((line) >> 4) & 0x01) +#define AURORA_CD180(line) (((line) >> 3) & 0x01) +#define AURORA_PORT(line) ((line) & 15) + +#define AURORA_TNPORTS (AURORA_NBOARD*AURORA_NCD180*AURORA_NPORT) + +/* Ticks per sec. Used for setting receiver timeout and break length */ +#define AURORA_TPS 4000 + +#define AURORA_MAGIC 0x0A18 + +/* Yeah, after heavy testing I decided it must be 6. + * Sure, You can change it if needed. + */ +#define AURORA_RXFIFO 6 /* Max. receiver FIFO size (1-8) */ + +#define AURORA_RXTH 7 + +struct aurora_reg1 { + __volatile__ unsigned char r; + }; + +struct aurora_reg128 { + __volatile__ unsigned char r[128]; + }; + +struct aurora_reg4 { + __volatile__ unsigned char r[4]; + }; + +struct Aurora_board { + unsigned long flags; + struct aurora_reg1 * r0; /* This is the board configuration + * register (write-only). */ + struct aurora_reg128 * r[2]; /* These are the registers for the + * two chips. */ + struct aurora_reg4 * r3; /* These are used for hardware-based + * acknowledge. Software-based ack is + * not supported by CD180. */ + unsigned int oscfreq; /* The on-board oscillator + * frequency, in Hz. */ + unsigned char irq; +#ifdef MODULE + signed char count; /* counts the use of the board */ +#endif + /* Values for the dtr_rts swapped mode. */ + unsigned char DTR; + unsigned char RTS; + unsigned char MSVDTR; + unsigned char MSVRTS; + /* Values for hardware acknowledge. */ + unsigned char ACK_MINT,ACK_TINT,ACK_RINT; +}; + +/* Board configuration register */ +#define AURORA_CFG_ENABLE_IO 8 +#define AURORA_CFG_ENABLE_IRQ 4 + +/* Board flags */ +#define AURORA_BOARD_PRESENT 0x00000001 +#define AURORA_BOARD_ACTIVE 0x00000002 +#define AURORA_BOARD_TYPE_2 0x00000004 /* don't know how to + * detect this yet */ +#define AURORA_BOARD_DTR_FLOW_OK 0x00000008 + +/* The story goes like this: Cirrus programmed the CD-180 chip to do automatic + * hardware flow control, and do it using CTS and DTR. CTS is ok, but, if you + * have a modem and the chip drops DTR, then the modem will drop the carrier + * (ain't that cute...). Luckily, the guys at Aurora decided to swap DTR and + * RTS, which makes the flow control usable. I hope that all the boards made + * by Aurora have these two signals swapped. If your's doesn't but you have a + * breakout box, you can try to reverse them yourself, then set the following + * flag. + */ +#undef AURORA_FORCE_DTR_FLOW + +/* In fact, a few more words have to be said about hardware flow control. + * This driver handles "output" flow control through the on-board facility + * CTS Auto Enable. For the "input" flow control there are two cases when + * the flow should be controlled. The first case is when the kernel is so + * busy that it cannot process IRQ's in time; this flow control can only be + * activated by the on-board chip, and if the board has RTS and DTR swapped, + * this facility is usable. The second case is when the application is so + * busy that it cannot receive bytes from the kernel, and this flow must be + * activated by software. This second case is not yet implemented in this + * driver. Unfortunately, I estimate that the second case is the one that + * occurs the most. + */ + + +struct Aurora_port { + int magic; + int baud_base; + int flags; + struct tty_struct * tty; + int count; + int blocked_open; + int event; + int timeout; + int close_delay; + long session; + long pgrp; + unsigned char * xmit_buf; + int custom_divisor; + int xmit_head; + int xmit_tail; + int xmit_cnt; + struct termios normal_termios; + wait_queue_head_t open_wait; + wait_queue_head_t close_wait; + struct tq_struct tqueue; + struct tq_struct tqueue_hangup; + short wakeup_chars; + short break_length; + unsigned short closing_wait; + unsigned char mark_mask; + unsigned char SRER; + unsigned char MSVR; + unsigned char COR2; +#ifdef AURORA_REPORT_OVERRUN + unsigned long overrun; +#endif +#ifdef AURORA_REPORT_FIFO + unsigned long hits[10]; +#endif +}; + +#endif +#endif /*__LINUX_AURORA_H*/ + diff --git a/drivers/sbus/char/cd180.h b/drivers/sbus/char/cd180.h new file mode 100644 index 000000000..445b86cc6 --- /dev/null +++ b/drivers/sbus/char/cd180.h @@ -0,0 +1,240 @@ + +/* Definitions for Cirrus Logic CL-CD180 8-port async mux chip */ +#define CD180_NCH 8 /* Total number of channels */ +#define CD180_TPC 16 /* Ticks per character */ +#define CD180_NFIFO 8 /* TX FIFO size */ + +/* Global registers */ +#define CD180_GFRCR 0x6b /* Global Firmware Revision Code Register */ +#define CD180_SRCR 0x66 /* Service Request Configuration Register */ +#define CD180_PPRH 0x70 /* Prescaler Period Register High */ +#define CD180_PPRL 0x71 /* Prescaler Period Register Low */ +#define CD180_MSMR 0x61 /* Modem Service Match Register */ +#define CD180_TSMR 0x62 /* Transmit Service Match Register */ +#define CD180_RSMR 0x63 /* Receive Service Match Register */ +#define CD180_GSVR 0x40 /* Global Service Vector Register */ +#define CD180_SRSR 0x65 /* Service Request Status Register */ +#define CD180_GSCR 0x41 /* Global Service Channel Register */ +#define CD180_CAR 0x64 /* Channel Access Register */ + +/* Indexed registers */ +#define CD180_RDCR 0x07 /* Receive Data Count Register */ +#define CD180_RDR 0x78 /* Receiver Data Register */ +#define CD180_RCSR 0x7a /* Receiver Character Status Register */ +#define CD180_TDR 0x7b /* Transmit Data Register */ +#define CD180_EOSRR 0x7f /* End of Service Request Register */ + +/* Channel Registers */ +#define CD180_SRER 0x02 /* Service Request Enable Register */ +#define CD180_CCR 0x01 /* Channel Command Register */ +#define CD180_COR1 0x03 /* Channel Option Register 1 */ +#define CD180_COR2 0x04 /* Channel Option Register 2 */ +#define CD180_COR3 0x05 /* Channel Option Register 3 */ +#define CD180_CCSR 0x06 /* Channel Control Status Register */ +#define CD180_RTPR 0x18 /* Receive Timeout Period Register */ +#define CD180_RBPRH 0x31 /* Receive Bit Rate Period Register High */ +#define CD180_RBPRL 0x32 /* Receive Bit Rate Period Register Low */ +#define CD180_TBPRH 0x39 /* Transmit Bit Rate Period Register High */ +#define CD180_TBPRL 0x3a /* Transmit Bit Rate Period Register Low */ +#define CD180_SCHR1 0x09 /* Special Character Register 1 */ +#define CD180_SCHR2 0x0a /* Special Character Register 2 */ +#define CD180_SCHR3 0x0b /* Special Character Register 3 */ +#define CD180_SCHR4 0x0c /* Special Character Register 4 */ +#define CD180_MCR 0x12 /* Modem Change Register */ +#define CD180_MCOR1 0x10 /* Modem Change Option 1 Register */ +#define CD180_MCOR2 0x11 /* Modem Change Option 2 Register */ +#define CD180_MSVR 0x28 /* Modem Signal Value Register */ +#define CD180_MSVRTS 0x29 /* Modem Signal Value RTS */ +#define CD180_MSVDTR 0x2a /* Modem Signal Value DTR */ + +/* Global Interrupt Vector Register (R/W) */ + +#define GSVR_ITMASK 0x07 /* Interrupt type mask */ +#define GSVR_IT_MDM 0x01 /* Modem Signal Change Interrupt */ +#define GSVR_IT_TX 0x02 /* Transmit Data Interrupt */ +#define GSVR_IT_RGD 0x03 /* Receive Good Data Interrupt */ +#define GSVR_IT_REXC 0x07 /* Receive Exception Interrupt */ + + +/* Global Interrupt Channel Register (R/W) */ + +#define GSCR_CHAN 0x1c /* Channel Number Mask */ +#define GSCR_CHAN_OFF 2 /* Channel Number Offset */ + + +/* Channel Address Register (R/W) */ + +#define CAR_CHAN 0x07 /* Channel Number Mask */ + + +/* Receive Character Status Register (R/O) */ + +#define RCSR_TOUT 0x80 /* Rx Timeout */ +#define RCSR_SCDET 0x70 /* Special Character Detected Mask */ +#define RCSR_NO_SC 0x00 /* No Special Characters Detected */ +#define RCSR_SC_1 0x10 /* Special Char 1 (or 1 & 3) Detected */ +#define RCSR_SC_2 0x20 /* Special Char 2 (or 2 & 4) Detected */ +#define RCSR_SC_3 0x30 /* Special Char 3 Detected */ +#define RCSR_SC_4 0x40 /* Special Char 4 Detected */ +#define RCSR_BREAK 0x08 /* Break has been detected */ +#define RCSR_PE 0x04 /* Parity Error */ +#define RCSR_FE 0x02 /* Frame Error */ +#define RCSR_OE 0x01 /* Overrun Error */ + + +/* Channel Command Register (R/W) (commands in groups can be OR-ed) */ + +#define CCR_HARDRESET 0x81 /* Reset the chip */ + +#define CCR_SOFTRESET 0x80 /* Soft Channel Reset */ + +#define CCR_CORCHG1 0x42 /* Channel Option Register 1 Changed */ +#define CCR_CORCHG2 0x44 /* Channel Option Register 2 Changed */ +#define CCR_CORCHG3 0x48 /* Channel Option Register 3 Changed */ + +#define CCR_SSCH1 0x21 /* Send Special Character 1 */ + +#define CCR_SSCH2 0x22 /* Send Special Character 2 */ + +#define CCR_SSCH3 0x23 /* Send Special Character 3 */ + +#define CCR_SSCH4 0x24 /* Send Special Character 4 */ + +#define CCR_TXEN 0x18 /* Enable Transmitter */ +#define CCR_RXEN 0x12 /* Enable Receiver */ + +#define CCR_TXDIS 0x14 /* Disable Transmitter */ +#define CCR_RXDIS 0x11 /* Disable Receiver */ + + +/* Service Request Enable Register (R/W) */ + +#define SRER_DSR 0x80 /* Enable interrupt on DSR change */ +#define SRER_CD 0x40 /* Enable interrupt on CD change */ +#define SRER_CTS 0x20 /* Enable interrupt on CTS change */ +#define SRER_RXD 0x10 /* Enable interrupt on Receive Data */ +#define SRER_RXSC 0x08 /* Enable interrupt on Receive Spec. Char */ +#define SRER_TXRDY 0x04 /* Enable interrupt on TX FIFO empty */ +#define SRER_TXEMPTY 0x02 /* Enable interrupt on TX completely empty */ +#define SRER_RET 0x01 /* Enable interrupt on RX Exc. Timeout */ + + +/* Channel Option Register 1 (R/W) */ + +#define COR1_ODDP 0x80 /* Odd Parity */ +#define COR1_PARMODE 0x60 /* Parity Mode mask */ +#define COR1_NOPAR 0x00 /* No Parity */ +#define COR1_FORCEPAR 0x20 /* Force Parity */ +#define COR1_NORMPAR 0x40 /* Normal Parity */ +#define COR1_IGNORE 0x10 /* Ignore Parity on RX */ +#define COR1_STOPBITS 0x0c /* Number of Stop Bits */ +#define COR1_1SB 0x00 /* 1 Stop Bit */ +#define COR1_15SB 0x04 /* 1.5 Stop Bits */ +#define COR1_2SB 0x08 /* 2 Stop Bits */ +#define COR1_CHARLEN 0x03 /* Character Length */ +#define COR1_5BITS 0x00 /* 5 bits */ +#define COR1_6BITS 0x01 /* 6 bits */ +#define COR1_7BITS 0x02 /* 7 bits */ +#define COR1_8BITS 0x03 /* 8 bits */ + + +/* Channel Option Register 2 (R/W) */ + +#define COR2_IXM 0x80 /* Implied XON mode */ +#define COR2_TXIBE 0x40 /* Enable In-Band (XON/XOFF) Flow Control */ +#define COR2_ETC 0x20 /* Embedded Tx Commands Enable */ +#define COR2_LLM 0x10 /* Local Loopback Mode */ +#define COR2_RLM 0x08 /* Remote Loopback Mode */ +#define COR2_RTSAO 0x04 /* RTS Automatic Output Enable */ +#define COR2_CTSAE 0x02 /* CTS Automatic Enable */ +#define COR2_DSRAE 0x01 /* DSR Automatic Enable */ + + +/* Channel Option Register 3 (R/W) */ + +#define COR3_XONCH 0x80 /* XON is a pair of characters (1 & 3) */ +#define COR3_XOFFCH 0x40 /* XOFF is a pair of characters (2 & 4) */ +#define COR3_FCT 0x20 /* Flow-Control Transparency Mode */ +#define COR3_SCDE 0x10 /* Special Character Detection Enable */ +#define COR3_RXTH 0x0f /* RX FIFO Threshold value (1-8) */ + + +/* Channel Control Status Register (R/O) */ + +#define CCSR_RXEN 0x80 /* Receiver Enabled */ +#define CCSR_RXFLOFF 0x40 /* Receive Flow Off (XOFF was sent) */ +#define CCSR_RXFLON 0x20 /* Receive Flow On (XON was sent) */ +#define CCSR_TXEN 0x08 /* Transmitter Enabled */ +#define CCSR_TXFLOFF 0x04 /* Transmit Flow Off (got XOFF) */ +#define CCSR_TXFLON 0x02 /* Transmit Flow On (got XON) */ + + +/* Modem Change Option Register 1 (R/W) */ + +#define MCOR1_DSRZD 0x80 /* Detect 0->1 transition of DSR */ +#define MCOR1_CDZD 0x40 /* Detect 0->1 transition of CD */ +#define MCOR1_CTSZD 0x20 /* Detect 0->1 transition of CTS */ +#define MCOR1_DTRTH 0x0f /* Auto DTR flow control Threshold (1-8) */ +#define MCOR1_NODTRFC 0x0 /* Automatic DTR flow control disabled */ + + +/* Modem Change Option Register 2 (R/W) */ + +#define MCOR2_DSROD 0x80 /* Detect 1->0 transition of DSR */ +#define MCOR2_CDOD 0x40 /* Detect 1->0 transition of CD */ +#define MCOR2_CTSOD 0x20 /* Detect 1->0 transition of CTS */ + + +/* Modem Change Register (R/W) */ + +#define MCR_DSRCHG 0x80 /* DSR Changed */ +#define MCR_CDCHG 0x40 /* CD Changed */ +#define MCR_CTSCHG 0x20 /* CTS Changed */ + + +/* Modem Signal Value Register (R/W) */ + +#define MSVR_DSR 0x80 /* Current state of DSR input */ +#define MSVR_CD 0x40 /* Current state of CD input */ +#define MSVR_CTS 0x20 /* Current state of CTS input */ +#define MSVR_DTR 0x02 /* Current state of DTR output */ +#define MSVR_RTS 0x01 /* Current state of RTS output */ + + +/* Service Request Status Register */ + +#define SRSR_CMASK 0xC0 /* Current Service Context Mask */ +#define SRSR_CNONE 0x00 /* Not in a service context */ +#define SRSR_CRX 0x40 /* Rx Context */ +#define SRSR_CTX 0x80 /* Tx Context */ +#define SRSR_CMDM 0xC0 /* Modem Context */ +#define SRSR_ANYINT 0x6F /* Any interrupt flag */ +#define SRSR_RINT 0x10 /* Receive Interrupt */ +#define SRSR_TINT 0x04 /* Transmit Interrupt */ +#define SRSR_MINT 0x01 /* Modem Interrupt */ +#define SRSR_REXT 0x20 /* Receive External Interrupt */ +#define SRSR_TEXT 0x08 /* Transmit External Interrupt */ +#define SRSR_MEXT 0x02 /* Modem External Interrupt */ + + +/* Service Request Configuration Register */ + +#define SRCR_PKGTYPE 0x80 +#define SRCR_REGACKEN 0x40 +#define SRCR_DAISYEN 0x20 +#define SRCR_GLOBPRI 0x10 +#define SRCR_UNFAIR 0x08 +#define SRCR_AUTOPRI 0x02 +#define SRCR_PRISEL 0x01 + +/* Values for register-based Interrupt ACKs */ +#define CD180_ACK_MINT 0x75 /* goes to MSMR */ +#define CD180_ACK_TINT 0x76 /* goes to TSMR */ +#define CD180_ACK_RINT 0x77 /* goes to RSMR */ + +/* Escape characters */ + +#define CD180_C_ESC 0x00 /* Escape character */ +#define CD180_C_SBRK 0x81 /* Start sending BREAK */ +#define CD180_C_DELAY 0x82 /* Delay output */ +#define CD180_C_EBRK 0x83 /* Stop sending BREAK */ diff --git a/drivers/sound/cmpci.c b/drivers/sound/cmpci.c new file mode 100644 index 000000000..f4befd5a3 --- /dev/null +++ b/drivers/sound/cmpci.c @@ -0,0 +1,2391 @@ +/*****************************************************************************/ + +/* + * cmpci.c -- C-Media PCI audio driver. + * + * Copyright (C) 1999 ChenLi Tien (cltien@home.com) + * + * Based on the PCI drivers by Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Special thanks to David C. Niemi + * + * + * Module command line parameters: + * none so far + * + * + * Supported devices: + * /dev/dsp standard /dev/dsp device, (mostly) OSS compatible + * /dev/mixer standard /dev/mixer device, (mostly) OSS compatible + * /dev/midi simple MIDI UART interface, no ioctl + * + * The card has both an FM and a Wavetable synth, but I have to figure + * out first how to drive them... + * + * Revision history + * 06.05.98 0.1 Initial release + * 10.05.98 0.2 Fixed many bugs, esp. ADC rate calculation + * First stab at a simple midi interface (no bells&whistles) + * 13.05.98 0.3 Fix stupid cut&paste error: set_adc_rate was called instead of + * set_dac_rate in the FMODE_WRITE case in cm_open + * Fix hwptr out of bounds (now mpg123 works) + * 14.05.98 0.4 Don't allow excessive interrupt rates + * 08.06.98 0.5 First release using Alan Cox' soundcore instead of miscdevice + * 03.08.98 0.6 Do not include modversions.h + * Now mixer behaviour can basically be selected between + * "OSS documented" and "OSS actual" behaviour + * 31.08.98 0.7 Fix realplayer problems - dac.count issues + * 10.12.98 0.8 Fix drain_dac trying to wait on not yet initialized DMA + * 16.12.98 0.9 Fix a few f_file & FMODE_ bugs + * 06.01.99 0.10 remove the silly SA_INTERRUPT flag. + * hopefully killed the egcs section type conflict + * 12.03.99 0.11 cinfo.blocks should be reset after GETxPTR ioctl. + * reported by Johan Maes <joma@telindus.be> + * 22.03.99 0.12 return EAGAIN instead of EBUSY when O_NONBLOCK + * read/write cannot be executed + * + */ + +/*****************************************************************************/ + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/sound.h> +#include <linux/malloc.h> +#include <linux/soundcard.h> +#include <linux/pci.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <asm/spinlock.h> +#include <asm/uaccess.h> +#include <asm/hardirq.h> + +#include "dm.h" + +/* --------------------------------------------------------------------- */ + +#undef OSS_DOCUMENTED_MIXER_SEMANTICS + +/* --------------------------------------------------------------------- */ + +#ifndef PCI_VENDOR_ID_CMEDIA +#define PCI_VENDOR_ID_CMEDIA 0x13F6 +#endif +#ifndef PCI_DEVICE_ID_CMEDIA_CM8338A +#define PCI_DEVICE_ID_CMEDIA_CM8338A 0x0100 +#endif +#ifndef PCI_DEVICE_ID_CMEDIA_CM8338B +#define PCI_DEVICE_ID_CMEDIA_CM8338B 0x0101 +#endif +#ifndef PCI_DEVICE_ID_CMEDIA_CM8738 +#define PCI_DEVICE_ID_CMEDIA_CM8738 0x0111 +#endif + +#define CM_MAGIC ((PCI_VENDOR_ID_CMEDIA<<16)|PCI_DEVICE_ID_CMEDIA_CM8338A) + +/* + * CM8338 registers definition + */ + +#define CODEC_CMI_FUNCTRL0 (0x00) +#define CODEC_CMI_FUNCTRL1 (0x04) +#define CODEC_CMI_CHFORMAT (0x08) +#define CODEC_CMI_INT_HLDCLR (0x0C) +#define CODEC_CMI_INT_STATUS (0x10) +#define CODEC_CMI_LEGACY_CTRL (0x14) +#define CODEC_CMI_MISC_CTRL (0x18) +#define CODEC_CMI_TDMA_POS (0x1C) +#define CODEC_CMI_MIXER (0x20) +#define CODEC_SB16_DATA (0x22) +#define CODEC_SB16_ADDR (0x23) +#define CODEC_CMI_MIXER1 (0x24) +#define CODEC_CMI_MIXER2 (0x25) +#define CODEC_CMI_AUX_VOL (0x26) +#define CODEC_CMI_MISC (0x27) +#define CODEC_CMI_AC97 (0x28) + +#define CODEC_CMI_CH0_FRAME1 (0x80) +#define CODEC_CMI_CH0_FRAME2 (0x84) +#define CODEC_CMI_CH1_FRAME1 (0x88) +#define CODEC_CMI_CH1_FRAME2 (0x8C) + +#define CODEC_CMI_EXT_REG (0xF0) +#define UCHAR unsigned char +/* +** Mixer registers for SB16 +*/ + +#define DSP_MIX_DATARESETIDX ((UCHAR)(0x00)) + +#define DSP_MIX_MASTERVOLIDX_L ((UCHAR)(0x30)) +#define DSP_MIX_MASTERVOLIDX_R ((UCHAR)(0x31)) +#define DSP_MIX_VOICEVOLIDX_L ((UCHAR)(0x32)) +#define DSP_MIX_VOICEVOLIDX_R ((UCHAR)(0x33)) +#define DSP_MIX_FMVOLIDX_L ((UCHAR)(0x34)) +#define DSP_MIX_FMVOLIDX_R ((UCHAR)(0x35)) +#define DSP_MIX_CDVOLIDX_L ((UCHAR)(0x36)) +#define DSP_MIX_CDVOLIDX_R ((UCHAR)(0x37)) +#define DSP_MIX_LINEVOLIDX_L ((UCHAR)(0x38)) +#define DSP_MIX_LINEVOLIDX_R ((UCHAR)(0x39)) + +#define DSP_MIX_MICVOLIDX ((UCHAR)(0x3A)) +#define DSP_MIX_SPKRVOLIDX ((UCHAR)(0x3B)) + +#define DSP_MIX_OUTMIXIDX ((UCHAR)(0x3C)) + +#define DSP_MIX_ADCMIXIDX_L ((UCHAR)(0x3D)) +#define DSP_MIX_ADCMIXIDX_R ((UCHAR)(0x3E)) + +#define DSP_MIX_INGAINIDX_L ((UCHAR)(0x3F)) +#define DSP_MIX_INGAINIDX_R ((UCHAR)(0x40)) +#define DSP_MIX_OUTGAINIDX_L ((UCHAR)(0x41)) +#define DSP_MIX_OUTGAINIDX_R ((UCHAR)(0x42)) + +#define DSP_MIX_AGCIDX ((UCHAR)(0x43)) + +#define DSP_MIX_TREBLEIDX_L ((UCHAR)(0x44)) +#define DSP_MIX_TREBLEIDX_R ((UCHAR)(0x45)) +#define DSP_MIX_BASSIDX_L ((UCHAR)(0x46)) +#define DSP_MIX_BASSIDX_R ((UCHAR)(0x47)) +#define CM_CH0_RESET 0x04 +#define CM_CH1_RESET 0x08 +#define CM_EXTENT_CODEC 0x100 +#define CM_EXTENT_MIDI 0x2 +#define CM_EXTENT_SYNTH 0x4 +#define CM_INT_CH0 1 +#define CM_INT_CH1 2 + +#define CM_CFMT_STEREO 0x01 +#define CM_CFMT_16BIT 0x02 +#define CM_CFMT_MASK 0x03 +#define CM_CFMT_DACSHIFT 0 +#define CM_CFMT_ADCSHIFT 2 + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +#define CM_CENABLE_RE 0x2 +#define CM_CENABLE_PE 0x1 + + +/* MIDI buffer sizes */ + +#define MIDIINBUF 256 +#define MIDIOUTBUF 256 + +#define FMODE_MIDI_SHIFT 2 +#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT) +#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT) + +#define FMODE_DMFM 0x10 + +#define SND_DEV_DSP16 5 + +/* --------------------------------------------------------------------- */ + +struct cm_state { + /* magic */ + unsigned int magic; + + /* we keep cm cards in a linked list */ + struct cm_state *next; + + /* soundcore stuff */ + int dev_audio; + int dev_mixer; + int dev_midi; + int dev_dmfm; + + /* hardware resources */ + unsigned int iosb, iobase, iosynth, iomidi, iogame, irq; + + /* mixer stuff */ + struct { + unsigned int modcnt; +#ifndef OSS_DOCUMENTED_MIXER_SEMANTICS + unsigned short vol[13]; +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + } mix; + + /* wave stuff */ + unsigned int rateadc, ratedac; + unsigned char fmt, enable; + + spinlock_t lock; + struct semaphore open_sem; + mode_t open_mode; + wait_queue_head_t open_wait; + + struct dmabuf { + void *rawbuf; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned hwptr, swptr; + unsigned total_bytes; + int count; + unsigned error; /* over/underrun */ + wait_queue_head_t wait; + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dma_dac, dma_adc; + + /* midi stuff */ + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t iwait; + wait_queue_head_t owait; + struct timer_list timer; + unsigned char ibuf[MIDIINBUF]; + unsigned char obuf[MIDIOUTBUF]; + } midi; +}; + +/* --------------------------------------------------------------------- */ + +static struct cm_state *devs = NULL; +static unsigned long wavetable_mem = 0; + +/* --------------------------------------------------------------------- */ + +extern __inline__ unsigned ld2(unsigned int x) +{ + unsigned r = 0; + + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 4) { + x >>= 2; + r += 2; + } + if (x >= 2) + r++; + return r; +} + +/* + * hweightN: returns the hamming weight (i.e. the number + * of bits set) of a N-bit word + */ + +#ifdef hweight32 +#undef hweight32 +#endif + +extern __inline__ unsigned int hweight32(unsigned int w) +{ + unsigned int res = (w & 0x55555555) + ((w >> 1) & 0x55555555); + res = (res & 0x33333333) + ((res >> 2) & 0x33333333); + res = (res & 0x0F0F0F0F) + ((res >> 4) & 0x0F0F0F0F); + res = (res & 0x00FF00FF) + ((res >> 8) & 0x00FF00FF); + return (res & 0x0000FFFF) + ((res >> 16) & 0x0000FFFF); +} + +/* --------------------------------------------------------------------- */ + +static void set_dmadac(struct cm_state *s, unsigned int addr, unsigned int count) +{ + count--; + outl(addr, s->iobase + CODEC_CMI_CH0_FRAME1); + outw(count, s->iobase + CODEC_CMI_CH0_FRAME2); + outb(inb(s->iobase + CODEC_CMI_FUNCTRL0) & ~1, s->iobase + CODEC_CMI_FUNCTRL0); + outb(inb(s->iobase + CODEC_CMI_FUNCTRL0 + 2) | 1, s->iobase + CODEC_CMI_FUNCTRL0 + 2); +} + +static void set_dmaadc(struct cm_state *s, unsigned int addr, unsigned int count) +{ + count--; + outl(addr, s->iobase + CODEC_CMI_CH1_FRAME1); + outw(count, s->iobase + CODEC_CMI_CH1_FRAME2); + outb(inb(s->iobase + CODEC_CMI_FUNCTRL0) | 2, s->iobase + CODEC_CMI_FUNCTRL0); + outb(inb(s->iobase + CODEC_CMI_FUNCTRL0 + 2) | 2, s->iobase + CODEC_CMI_FUNCTRL0 + 2); +} + +extern __inline__ unsigned get_dmadac(struct cm_state *s) +{ + unsigned int curr_addr; + + curr_addr = inl(s->iobase + CODEC_CMI_CH0_FRAME1); + curr_addr -= virt_to_bus(s->dma_dac.rawbuf); + curr_addr = s->dma_dac.dmasize - curr_addr; + curr_addr &= ~(sample_size[(s->fmt >> CM_CFMT_DACSHIFT) & CM_CFMT_MASK]-1); + return curr_addr; +} + +extern __inline__ unsigned get_dmaadc(struct cm_state *s) +{ + unsigned int curr_addr; + + curr_addr = inl(s->iobase + CODEC_CMI_CH1_FRAME1); + curr_addr -= virt_to_bus(s->dma_adc.rawbuf); + curr_addr = s->dma_adc.dmasize - curr_addr; + curr_addr &= ~(sample_size[(s->fmt >> CM_CFMT_ADCSHIFT) & CM_CFMT_MASK]-1); + return curr_addr; +} + +static void wrmixer(struct cm_state *s, unsigned char idx, unsigned char data) +{ + outb(idx, s->iobase + CODEC_SB16_ADDR); + udelay(10); + outb(data, s->iobase + CODEC_SB16_DATA); + udelay(10); +} + +static unsigned char rdmixer(struct cm_state *s, unsigned char idx) +{ + unsigned char v; + + outb(idx, s->iobase + CODEC_SB16_ADDR); + udelay(10); + v = inb(s->iobase + CODEC_SB16_DATA); + udelay(10); + return v; +} + +static void set_fmt(struct cm_state *s, unsigned char mask, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + if (mask) { + s->fmt = inb(s->iobase + CODEC_CMI_CHFORMAT); + udelay(10); + } + s->fmt = (s->fmt & mask) | data; + outb(s->fmt, s->iobase + CODEC_CMI_CHFORMAT); + spin_unlock_irqrestore(&s->lock, flags); + udelay(10); +} + +static void frobindir(struct cm_state *s, unsigned char idx, unsigned char mask, unsigned char data) +{ + outb(idx, s->iobase + CODEC_SB16_ADDR); + udelay(10); + outb((inb(s->iobase + CODEC_SB16_DATA) & mask) | data, s->iobase + CODEC_SB16_DATA); + udelay(10); +} + +static struct { + unsigned rate; + unsigned lower; + unsigned upper; + unsigned char freq; +} rate_lookup[] = +{ + { 5512, (0 + 5512) / 2, (5512 + 8000) / 2, 0 }, + { 8000, (5512 + 8000) / 2, (8000 + 11025) / 2, 4 }, + { 11025, (8000 + 11025) / 2, (11025 + 16000) / 2, 1 }, + { 16000, (11025 + 16000) / 2, (16000 + 22050) / 2, 5 }, + { 22050, (16000 + 22050) / 2, (22050 + 32000) / 2, 2 }, + { 32000, (22050 + 32000) / 2, (32000 + 44100) / 2, 6 }, + { 44100, (32000 + 44100) / 2, (44100 + 48000) / 2, 3 }, + { 48000, 48000, 48000, 7 } +}; + +static void set_dac_rate(struct cm_state *s, unsigned rate) +{ + unsigned long flags; + unsigned char freq = 4, val; + int i; + + if (rate > 48000) + rate = 48000; + if (rate < 5512) + rate = 5512; + for (i = 0; i < sizeof(rate_lookup) / sizeof(rate_lookup[0]); i++) + { + if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) + { + rate = rate_lookup[i].rate; + freq = rate_lookup[i].freq; + break; + } + } + s->ratedac = rate; + freq <<= 2; + spin_lock_irqsave(&s->lock, flags); + val = inb(s->iobase + CODEC_CMI_FUNCTRL1 + 1) & ~0x1c; + outb(val | freq, s->iobase + CODEC_CMI_FUNCTRL1 + 1); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void set_adc_rate(struct cm_state *s, unsigned rate) +{ + unsigned long flags; + unsigned char freq = 4, val; + int i; + + if (rate > 48000) + rate = 48000; + if (rate < 5512) + rate = 5512; + for (i = 0; i < sizeof(rate_lookup) / sizeof(rate_lookup[0]); i++) + { + if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) + { + rate = rate_lookup[i].rate; + freq = rate_lookup[i].freq; + break; + } + } + s->rateadc = rate; + freq <<= 5; + spin_lock_irqsave(&s->lock, flags); + val = inb(s->iobase + CODEC_CMI_FUNCTRL1 + 1) & ~0xe0; + outb(val | freq, s->iobase + CODEC_CMI_FUNCTRL1 + 1); + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +extern inline void stop_adc(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->enable &= ~CM_CENABLE_RE; + /* disable interrupt */ + outb(inb(s->iobase + CODEC_CMI_INT_HLDCLR + 2) & ~2, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + /* disable channel and reset */ + outb(s->enable | CM_CH1_RESET, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + udelay(10); + outb(s->enable & ~CM_CH1_RESET, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + spin_unlock_irqrestore(&s->lock, flags); +} + +extern inline void stop_dac(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->enable &= ~CM_CENABLE_PE; + /* disable interrupt */ + outb(inb(s->iobase + CODEC_CMI_INT_HLDCLR + 2) & ~1, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + /* disable channel and reset */ + outb(s->enable | CM_CH0_RESET, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + udelay(10); + outb(s->enable & ~CM_CH0_RESET, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_dac(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + if ((s->dma_dac.mapped || s->dma_dac.count > 0) && s->dma_dac.ready) { + s->enable |= CM_CENABLE_PE; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + } + outb(inb(s->iobase + CODEC_CMI_INT_HLDCLR + 2) | 1, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + spin_unlock_irqrestore(&s->lock, flags); +} + +static void start_adc(struct cm_state *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + if ((s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) + && s->dma_adc.ready) { + s->enable |= CM_CENABLE_RE; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + } + outb(inb(s->iobase + CODEC_CMI_INT_HLDCLR + 2) | 2, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + spin_unlock_irqrestore(&s->lock, flags); +} + +/* --------------------------------------------------------------------- */ + +#define DMABUF_DEFAULTORDER (17-PAGE_SHIFT) +#define DMABUF_MINORDER 1 + +static void dealloc_dmabuf(struct dmabuf *db) +{ + unsigned long map, mapend; + + if (db->rawbuf) { + /* undo marking the pages as reserved */ + mapend = MAP_NR(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (map = MAP_NR(db->rawbuf); map <= mapend; map++) + clear_bit(PG_reserved, &mem_map[map].flags); + free_pages((unsigned long)db->rawbuf, db->buforder); + } + db->rawbuf = NULL; + db->mapped = db->ready = 0; +} + + +/* Ch0 is used for playback, Ch1 is used for recording */ + +static int prog_dmabuf(struct cm_state *s, unsigned rec) +{ + struct dmabuf *db = rec ? &s->dma_adc : &s->dma_dac; + unsigned rate = rec ? s->rateadc : s->ratedac; + int order; + unsigned bytepersec; + unsigned bufs; + unsigned long map, mapend; + unsigned char fmt; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + fmt = s->fmt; + if (rec) { + s->enable &= ~CM_CENABLE_RE; + fmt >>= CM_CFMT_ADCSHIFT; + } else { + s->enable &= ~CM_CENABLE_PE; + fmt >>= CM_CFMT_DACSHIFT; + } + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + spin_unlock_irqrestore(&s->lock, flags); + fmt &= CM_CFMT_MASK; + db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; + if (!db->rawbuf) { + db->ready = db->mapped = 0; + for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER && !db->rawbuf; order--) + db->rawbuf = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA, order); + if (!db->rawbuf) + return -ENOMEM; + db->buforder = order; + if ((virt_to_bus(db->rawbuf) ^ (virt_to_bus(db->rawbuf) + (PAGE_SIZE << db->buforder) - 1)) & ~0xffff) + printk(KERN_DEBUG "cm: DMA buffer crosses 64k boundary: busaddr 0x%lx size %ld\n", + virt_to_bus(db->rawbuf), PAGE_SIZE << db->buforder); + if ((virt_to_bus(db->rawbuf) + (PAGE_SIZE << db->buforder) - 1) & ~0xffffff) + printk(KERN_DEBUG "cm: DMA buffer beyond 16MB: busaddr 0x%lx size %ld\n", + virt_to_bus(db->rawbuf), PAGE_SIZE << db->buforder); + /* now mark the pages as reserved; otherwise remap_page_range doesn't do what we want */ + mapend = MAP_NR(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); + for (map = MAP_NR(db->rawbuf); map <= mapend; map++) + set_bit(PG_reserved, &mem_map[map].flags); + } + bytepersec = rate << sample_shift[fmt]; + bufs = PAGE_SIZE << db->buforder; + if (db->ossfragshift) { + if ((1000 << db->ossfragshift) < bytepersec) + db->fragshift = ld2(bytepersec/1000); + else + db->fragshift = db->ossfragshift; + } else { + db->fragshift = ld2(bytepersec/100/(db->subdivision ? db->subdivision : 1)); + if (db->fragshift < 3) + db->fragshift = 3; + } + db->numfrag = bufs >> db->fragshift; + while (db->numfrag < 4 && db->fragshift > 3) { + db->fragshift--; + db->numfrag = bufs >> db->fragshift; + } + db->fragsize = 1 << db->fragshift; + if (db->ossmaxfrags >= 4 && db->ossmaxfrags < db->numfrag) + db->numfrag = db->ossmaxfrags; +#if 1 + /* to make fragsize >= 4096 */ + while (db->fragsize < 4096 && db->numfrag >= 4) + { + db->fragsize *= 2; + db->fragshift++; + db->numfrag /= 2; + } +#endif + db->fragsamples = db->fragsize >> sample_shift[fmt]; + db->dmasize = db->numfrag << db->fragshift; + memset(db->rawbuf, (fmt & CM_CFMT_16BIT) ? 0 : 0x80, db->dmasize); + spin_lock_irqsave(&s->lock, flags); + if (rec) { + set_dmaadc(s, virt_to_bus(db->rawbuf), db->dmasize >> sample_shift[fmt]); + /* program sample counts */ + outw(db->fragsamples-1, s->iobase + CODEC_CMI_CH1_FRAME2 + 2); + } else { + set_dmadac(s, virt_to_bus(db->rawbuf), db->dmasize >> sample_shift[fmt]); + /* program sample counts */ + outw(db->fragsamples-1, s->iobase + CODEC_CMI_CH0_FRAME2 + 2); + } + spin_unlock_irqrestore(&s->lock, flags); + db->ready = 1; + return 0; +} + +extern __inline__ void clear_advance(struct cm_state *s) +{ + unsigned char c = (s->fmt & (CM_CFMT_16BIT << CM_CFMT_DACSHIFT)) ? 0 : 0x80; + unsigned char *buf = s->dma_dac.rawbuf; + unsigned bsize = s->dma_dac.dmasize; + unsigned bptr = s->dma_dac.swptr; + unsigned len = s->dma_dac.fragsize; + + if (bptr + len > bsize) { + unsigned x = bsize - bptr; + memset(buf + bptr, c, x); + bptr = 0; + len -= x; + } + memset(buf + bptr, c, len); +} + +/* call with spinlock held! */ +static void cm_update_ptr(struct cm_state *s) +{ + unsigned hwptr; + int diff; + + /* update ADC pointer */ + if (s->dma_adc.ready) { + hwptr = (s->dma_adc.dmasize - get_dmaadc(s)) % s->dma_adc.dmasize; + diff = (s->dma_adc.dmasize + hwptr - s->dma_adc.hwptr) % s->dma_adc.dmasize; + s->dma_adc.hwptr = hwptr; + s->dma_adc.total_bytes += diff; + s->dma_adc.count += diff; + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + wake_up(&s->dma_adc.wait); + if (!s->dma_adc.mapped) { + if (s->dma_adc.count > (signed)(s->dma_adc.dmasize - ((3 * s->dma_adc.fragsize) >> 1))) { + s->enable &= ~CM_CENABLE_RE; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + s->dma_adc.error++; + } + } + } + /* update DAC pointer */ + if (s->dma_dac.ready) { + hwptr = (s->dma_dac.dmasize - get_dmadac(s)) % s->dma_dac.dmasize; + diff = (s->dma_dac.dmasize + hwptr - s->dma_dac.hwptr) % s->dma_dac.dmasize; + s->dma_dac.hwptr = hwptr; + s->dma_dac.total_bytes += diff; + if (s->dma_dac.mapped) { + s->dma_dac.count += diff; + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + wake_up(&s->dma_dac.wait); + } else { + s->dma_dac.count -= diff; + if (s->dma_dac.count <= 0) { + s->enable &= ~CM_CENABLE_PE; + outb(s->enable, s->iobase + CODEC_CMI_FUNCTRL0 + 2); + s->dma_dac.error++; + } else if (s->dma_dac.count <= (signed)s->dma_dac.fragsize && !s->dma_dac.endcleared) { + clear_advance(s); + s->dma_dac.endcleared = 1; + } + if (s->dma_dac.count + (signed)s->dma_dac.fragsize <= (signed)s->dma_dac.dmasize) + wake_up(&s->dma_dac.wait); + } + } +} + +/* hold spinlock for the following! */ +static void cm_handle_midi(struct cm_state *s) +{ + unsigned char ch; + int wake; + + wake = 0; + while (!(inb(s->iomidi+1) & 0x80)) { + ch = inb(s->iomidi); + if (s->midi.icnt < MIDIINBUF) { + s->midi.ibuf[s->midi.iwr] = ch; + s->midi.iwr = (s->midi.iwr + 1) % MIDIINBUF; + s->midi.icnt++; + } + wake = 1; + } + if (wake) + wake_up(&s->midi.iwait); + wake = 0; + while (!(inb(s->iomidi+1) & 0x40) && s->midi.ocnt > 0) { + outb(s->midi.obuf[s->midi.ord], s->iomidi); + s->midi.ord = (s->midi.ord + 1) % MIDIOUTBUF; + s->midi.ocnt--; + if (s->midi.ocnt < MIDIOUTBUF-16) + wake = 1; + } + if (wake) + wake_up(&s->midi.owait); +} + +static void cm_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct cm_state *s = (struct cm_state *)dev_id; + unsigned int intsrc, intstat; + + /* fastpath out, to ease interrupt sharing */ + intsrc = inb(s->iobase + CODEC_CMI_INT_STATUS); + if (!(intsrc & (CM_INT_CH0 | CM_INT_CH1))) + return; + spin_lock(&s->lock); + intstat = inb(s->iobase + CODEC_CMI_INT_HLDCLR + 2); + /* disable interrupt */ + if (intsrc & CM_INT_CH0) + outb(intstat & ~1, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + if (intsrc & CM_INT_CH1) + outb(intstat & ~2, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + cm_update_ptr(s); +#ifdef SOUND_CONFIG_CMPCI_MIDI + cm_handle_midi(s); +#endif + /* enable interrupt */ + if (intsrc & CM_INT_CH0) + outb(intstat | 1, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + if (intsrc & CM_INT_CH1) + outb(intstat | 2, s->iobase + CODEC_CMI_INT_HLDCLR + 2); + spin_unlock(&s->lock); +} + +static void cm_midi_timer(unsigned long data) +{ + struct cm_state *s = (struct cm_state *)data; + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + cm_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + s->midi.timer.expires = jiffies+1; + add_timer(&s->midi.timer); +} + +/* --------------------------------------------------------------------- */ + +static const char invalid_magic[] = KERN_CRIT "cm: invalid magic value\n"; + +#ifdef CONFIG_SOUND_CMPCI /* support multiple chips */ +#define VALIDATE_STATE(s) +#else +#define VALIDATE_STATE(s) \ +({ \ + if (!(s) || (s)->magic != CM_MAGIC) { \ + printk(invalid_magic); \ + return -ENXIO; \ + } \ +}) +#endif + +/* --------------------------------------------------------------------- */ + +#define MT_4 1 +#define MT_5MUTE 2 +#define MT_4MUTEMONO 3 +#define MT_6MUTE 4 + +static const struct { + unsigned left; + unsigned right; + unsigned type; + unsigned rec; + unsigned play; +} mixtable[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_CD] = { DSP_MIX_CDVOLIDX_L, DSP_MIX_CDVOLIDX_R, MT_5MUTE, 0x04, 0x02 }, + [SOUND_MIXER_LINE] = { DSP_MIX_LINEVOLIDX_L, DSP_MIX_LINEVOLIDX_R, MT_5MUTE, 0x10, 0x08 }, + [SOUND_MIXER_MIC] = { DSP_MIX_MICVOLIDX, CODEC_CMI_MIXER2, MT_4MUTEMONO, 0x01, 0x01 }, + [SOUND_MIXER_SYNTH] = { DSP_MIX_FMVOLIDX_L, DSP_MIX_FMVOLIDX_R, MT_5MUTE, 0x40, 0x00 }, + [SOUND_MIXER_VOLUME] = { DSP_MIX_MASTERVOLIDX_L, DSP_MIX_MASTERVOLIDX_R, MT_5MUTE, 0x00, 0x00 }, + [SOUND_MIXER_PCM] = { DSP_MIX_VOICEVOLIDX_L, DSP_MIX_VOICEVOLIDX_R, MT_5MUTE, 0x00, 0x00 } +}; + +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + +static int return_mixval(struct cm_state *s, unsigned i, int *arg) +{ + unsigned long flags; + unsigned char l, r, rl, rr; + + spin_lock_irqsave(&s->lock, flags); + l = rdmixer(s, mixtable[i].left); + r = rdmixer(s, mixtable[i].right); + spin_unlock_irqrestore(&s->lock, flags); + switch (mixtable[i].type) { + case MT_4: + r &= 0xf; + l &= 0xf; + rl = 10 + 6 * (l & 15); + rr = 10 + 6 * (r & 15); + break; + + case MT_4MUTEMONO: + rl = 55 - 3 * (l & 15); + if (r & 0x10) + rl += 45; + rr = rl; + r = l; + break; + + case MT_5MUTE: + default: + rl = 100 - 3 * (l & 31); + rr = 100 - 3 * (r & 31); + break; + + case MT_6MUTE: + rl = 100 - 3 * (l & 63) / 2; + rr = 100 - 3 * (r & 63) / 2; + break; + } + if (l & 0x80) + rl = 0; + if (r & 0x80) + rr = 0; + return put_user((rr << 8) | rl, arg); +} + +#else /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + +static const unsigned char volidx[SOUND_MIXER_NRDEVICES] = +{ + [SOUND_MIXER_CD] = 1, + [SOUND_MIXER_LINE] = 2, + [SOUND_MIXER_MIC] = 3, + [SOUND_MIXER_SYNTH] = 4, + [SOUND_MIXER_VOLUME] = 5, + [SOUND_MIXER_PCM] = 6 +}; + +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + +static unsigned mixer_recmask(struct cm_state *s) +{ + unsigned long flags; + int i, j, k; + + spin_lock_irqsave(&s->lock, flags); + j = rdmixer(s, DSP_MIX_ADCMIXIDX_L); + spin_unlock_irqrestore(&s->lock, flags); + j &= 0x7f; + for (k = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (j & mixtable[i].rec) + k |= 1 << i; + return k; +} + +static int mixer_ioctl(struct cm_state *s, unsigned int cmd, unsigned long arg) +{ + unsigned long flags; + int i, val, j; + unsigned char l, r, rl, rr; + + VALIDATE_STATE(s); + if (cmd == SOUND_MIXER_INFO) { + mixer_info info; + strncpy(info.id, "cmpci", sizeof(info.id)); + strncpy(info.name, "C-Media PCI", sizeof(info.name)); + info.modify_counter = s->mix.modcnt; + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == SOUND_OLD_MIXER_INFO) { + _old_mixer_info info; + strncpy(info.id, "cmpci", sizeof(info.id)); + strncpy(info.name, "C-Media cmpci", sizeof(info.name)); + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (cmd == OSS_GETVERSION) + return put_user(SOUND_VERSION, (int *)arg); + if (_IOC_TYPE(cmd) != 'M' || _IOC_SIZE(cmd) != sizeof(int)) + return -EINVAL; + if (_IOC_DIR(cmd) == _IOC_READ) { + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + return put_user(mixer_recmask(s), (int *)arg); + + case SOUND_MIXER_OUTSRC: /* Arg contains a bit for each recording source */ + return put_user(mixer_recmask(s), (int *)arg);//need fix + + case SOUND_MIXER_DEVMASK: /* Arg contains a bit for each supported device */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].type) + val |= 1 << i; + return put_user(val, (int *)arg); + + case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].rec) + val |= 1 << i; + return put_user(val, (int *)arg); + + case SOUND_MIXER_OUTMASK: /* Arg contains a bit for each supported recording source */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].play) + val |= 1 << i; + return put_user(val, (int *)arg); + + case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */ + for (val = i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (mixtable[i].type && mixtable[i].type != MT_4MUTEMONO) + val |= 1 << i; + return put_user(val, (int *)arg); + + case SOUND_MIXER_CAPS: + return put_user(0, (int *)arg); + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].type) + return -EINVAL; +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + return return_mixval(s, i, (int *)arg); +#else /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + if (!volidx[i]) + return -EINVAL; + return put_user(s->mix.vol[volidx[i]-1], (int *)arg); +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + } + } + if (_IOC_DIR(cmd) != (_IOC_READ|_IOC_WRITE)) + return -EINVAL; + s->mix.modcnt++; + switch (_IOC_NR(cmd)) { + case SOUND_MIXER_RECSRC: /* Arg contains a bit for each recording source */ + get_user_ret(val, (int *)arg, -EFAULT); + i = hweight32(val); + for (j = i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(val & (1 << i))) + continue; + if (!mixtable[i].rec) { + val &= ~(1 << i); + continue; + } + j |= mixtable[i].rec; + } + spin_lock_irqsave(&s->lock, flags); + wrmixer(s, DSP_MIX_ADCMIXIDX_L, j); + wrmixer(s, DSP_MIX_ADCMIXIDX_R, (j & 1) | j>>1); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + case SOUND_MIXER_OUTSRC: /* Arg contains a bit for each recording source */ + get_user_ret(val, (int *)arg, -EFAULT); + for (j = i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(val & (1 << i))) + continue; + if (!mixtable[i].play) { + val &= ~(1 << i); + continue; + } + j |= mixtable[i].play; + } + spin_lock_irqsave(&s->lock, flags); + frobindir(s, DSP_MIX_OUTMIXIDX, 0x1f, j); + spin_unlock_irqrestore(&s->lock, flags); + return 0; + + default: + i = _IOC_NR(cmd); + if (i >= SOUND_MIXER_NRDEVICES || !mixtable[i].type) + return -EINVAL; + get_user_ret(val, (int *)arg, -EFAULT); + l = val & 0xff; + r = (val >> 8) & 0xff; + if (l > 100) + l = 100; + if (r > 100) + r = 100; + spin_lock_irqsave(&s->lock, flags); + switch (mixtable[i].type) { + case MT_4: + if (l >= 10) + l -= 10; + if (r >= 10) + r -= 10; + frobindir(s, mixtable[i].left, 0xf0, l / 6); + frobindir(s, mixtable[i].right, 0xf0, l / 6); + break; + + case MT_4MUTEMONO: + rl = (l < 4 ? 0 : (l - 5) / 3) & 31; + rr = (rl >> 2) & 7; + wrmixer(s, mixtable[i].left, rl<<3); + outb((inb(s->iobase + CODEC_CMI_MIXER2) & ~0x0e) | rr<<1, s->iobase + CODEC_CMI_MIXER2); + break; + + case MT_5MUTE: + rl = l < 4 ? 0 : (l - 5) / 3; + rr = r < 4 ? 0 : (r - 5) / 3; + wrmixer(s, mixtable[i].left, rl<<3); + wrmixer(s, mixtable[i].right, rr<<3); + break; + + case MT_6MUTE: + if (l < 6) + rl = 0x00; + else + rl = l * 2 / 3; + if (r < 6) + rr = 0x00; + else + rr = r * 2 / 3; + wrmixer(s, mixtable[i].left, rl); + wrmixer(s, mixtable[i].right, rr); + break; + } + spin_unlock_irqrestore(&s->lock, flags); +#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS + return return_mixval(s, i, (int *)arg); +#else /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + if (!volidx[i]) + return -EINVAL; + s->mix.vol[volidx[i]-1] = val; + return put_user(s->mix.vol[volidx[i]-1], (int *)arg); +#endif /* OSS_DOCUMENTED_MIXER_SEMANTICS */ + } +} + +/* --------------------------------------------------------------------- */ + +static loff_t cm_llseek(struct file *file, loff_t offset, int origin) +{ + return -ESPIPE; +} + +/* --------------------------------------------------------------------- */ + +static int cm_open_mixdev(struct inode *inode, struct file *file) +{ + int minor = MINOR(inode->i_rdev); + struct cm_state *s = devs; + + while (s && s->dev_mixer != minor) + s = s->next; + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + MOD_INC_USE_COUNT; + return 0; +} + +static int cm_release_mixdev(struct inode *inode, struct file *file) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + + VALIDATE_STATE(s); + MOD_DEC_USE_COUNT; + return 0; +} + +static int cm_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + return mixer_ioctl((struct cm_state *)file->private_data, cmd, arg); +} + +static /*const*/ struct file_operations cm_mixer_fops = { + &cm_llseek, + NULL, /* read */ + NULL, /* write */ + NULL, /* readdir */ + NULL, /* poll */ + &cm_ioctl_mixdev, + NULL, /* mmap */ + &cm_open_mixdev, + NULL, /* flush */ + &cm_release_mixdev, + NULL, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL, /* revalidate */ + NULL, /* lock */ +}; + +/* --------------------------------------------------------------------- */ + +static int drain_dac(struct cm_state *s, int nonblock) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int count, tmo; + + if (s->dma_dac.mapped || !s->dma_dac.ready) + return 0; + current->state = TASK_INTERRUPTIBLE; + add_wait_queue(&s->dma_dac.wait, &wait); + for (;;) { + spin_lock_irqsave(&s->lock, flags); + count = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (nonblock) { + remove_wait_queue(&s->dma_dac.wait, &wait); + current->state = TASK_RUNNING; + return -EBUSY; + } + tmo = (count * HZ) / s->ratedac; + tmo >>= sample_shift[(s->fmt >> CM_CFMT_DACSHIFT) & CM_CFMT_MASK]; + if (!schedule_timeout(tmo ? : 1) && tmo) + printk(KERN_DEBUG "cm: dma timed out??\n"); + } + remove_wait_queue(&s->dma_dac.wait, &wait); + current->state = TASK_RUNNING; + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; +} + +/* --------------------------------------------------------------------- */ + +static ssize_t cm_read(struct file *file, char *buffer, size_t count, loff_t *ppos) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (ppos != &file->f_pos) + return -ESPIPE; + if (s->dma_adc.mapped) + return -ENXIO; + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; +#if 0 + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + spin_unlock_irqrestore(&s->lock, flags); +#endif + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + swptr = s->dma_adc.swptr; + cnt = s->dma_adc.dmasize-swptr; + if (s->dma_adc.count < cnt) + cnt = s->dma_adc.count; + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + start_adc(s); + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&s->dma_adc.wait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_to_user(buffer, s->dma_adc.rawbuf + swptr, cnt)) + return ret ? ret : -EFAULT; + swptr = (swptr + cnt) % s->dma_adc.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_adc.swptr = swptr; + s->dma_adc.count -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + start_adc(s); + } + return ret; +} + +static ssize_t cm_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned swptr; + int cnt; + + VALIDATE_STATE(s); + if (ppos != &file->f_pos) + return -ESPIPE; + if (s->dma_dac.mapped) + return -ENXIO; + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; +#if 0 + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + spin_unlock_irqrestore(&s->lock, flags); +#endif + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + if (s->dma_dac.count < 0) { + s->dma_dac.count = 0; + s->dma_dac.swptr = s->dma_dac.hwptr; + } + swptr = s->dma_dac.swptr; + cnt = s->dma_dac.dmasize-swptr; + if (s->dma_dac.count + cnt > s->dma_dac.dmasize) + cnt = s->dma_dac.dmasize - s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + start_dac(s); + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&s->dma_dac.wait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_from_user(s->dma_dac.rawbuf + swptr, buffer, cnt)) + return ret ? ret : -EFAULT; + swptr = (swptr + cnt) % s->dma_dac.dmasize; + spin_lock_irqsave(&s->lock, flags); + s->dma_dac.swptr = swptr; + s->dma_dac.count += cnt; + s->dma_dac.endcleared = 0; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + start_dac(s); + } + return ret; +} + +static unsigned int cm_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->dma_dac.wait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->dma_adc.wait, wait); + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + if (file->f_mode & FMODE_READ) { + if (s->dma_adc.count >= (signed)s->dma_adc.fragsize) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->dma_dac.mapped) { + if (s->dma_dac.count >= (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } else { + if ((signed)s->dma_dac.dmasize >= s->dma_dac.count + (signed)s->dma_dac.fragsize) + mask |= POLLOUT | POLLWRNORM; + } + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int cm_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + struct dmabuf *db; + int ret; + unsigned long size; + + VALIDATE_STATE(s); + if (vma->vm_flags & VM_WRITE) { + if ((ret = prog_dmabuf(s, 1)) != 0) + return ret; + db = &s->dma_dac; + } else if (vma->vm_flags & VM_READ) { + if ((ret = prog_dmabuf(s, 0)) != 0) + return ret; + db = &s->dma_adc; + } else + return -EINVAL; + if (vma->vm_offset != 0) + return -EINVAL; + size = vma->vm_end - vma->vm_start; + if (size > (PAGE_SIZE << db->buforder)) + return -EINVAL; + if (remap_page_range(vma->vm_start, virt_to_phys(db->rawbuf), size, vma->vm_page_prot)) + return -EAGAIN; + db->mapped = 1; + return 0; +} + +static int cm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + unsigned long flags; + audio_buf_info abinfo; + count_info cinfo; + int val, mapped, ret; + unsigned char fmtm, fmtd; + + VALIDATE_STATE(s); + mapped = ((file->f_mode & FMODE_WRITE) && s->dma_dac.mapped) || + ((file->f_mode & FMODE_READ) && s->dma_adc.mapped); + switch (cmd) { + case OSS_GETVERSION: + return put_user(SOUND_VERSION, (int *)arg); + + case SNDCTL_DSP_SYNC: + if (file->f_mode & FMODE_WRITE) + return drain_dac(s, 0/*file->f_flags & O_NONBLOCK*/); + return 0; + + case SNDCTL_DSP_SETDUPLEX: + return 0; + + case SNDCTL_DSP_GETCAPS: + return put_user(DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | DSP_CAP_MMAP, (int *)arg); + + case SNDCTL_DSP_RESET: + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + synchronize_irq(); + s->dma_dac.swptr = s->dma_dac.hwptr = s->dma_dac.count = s->dma_dac.total_bytes = 0; + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + synchronize_irq(); + s->dma_adc.swptr = s->dma_adc.hwptr = s->dma_adc.count = s->dma_adc.total_bytes = 0; + } + return 0; + + case SNDCTL_DSP_SPEED: + get_user_ret(val, (int *)arg, -EFAULT); + if (val >= 0) { + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + set_adc_rate(s, val); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + set_dac_rate(s, val); + } + } + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, (int *)arg); + + case SNDCTL_DSP_STEREO: + get_user_ret(val, (int *)arg, -EFAULT); + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val) + fmtd |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_ADCSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val) + fmtd |= CM_CFMT_STEREO << CM_CFMT_DACSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_DACSHIFT); + } + set_fmt(s, fmtm, fmtd); + return 0; + + case SNDCTL_DSP_CHANNELS: + get_user_ret(val, (int *)arg, -EFAULT); + if (val != 0) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val >= 2) + fmtd |= CM_CFMT_STEREO << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_ADCSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val >= 2) + fmtd |= CM_CFMT_STEREO << CM_CFMT_DACSHIFT; + else + fmtm &= ~(CM_CFMT_STEREO << CM_CFMT_DACSHIFT); + } + set_fmt(s, fmtm, fmtd); + } + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_STEREO << CM_CFMT_ADCSHIFT) + : (CM_CFMT_STEREO << CM_CFMT_DACSHIFT))) ? 2 : 1, (int *)arg); + + case SNDCTL_DSP_GETFMTS: /* Returns a mask */ + return put_user(AFMT_S16_LE|AFMT_U8, (int *)arg); + + case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/ + get_user_ret(val, (int *)arg, -EFAULT); + if (val != AFMT_QUERY) { + fmtd = 0; + fmtm = ~0; + if (file->f_mode & FMODE_READ) { + stop_adc(s); + s->dma_adc.ready = 0; + if (val == AFMT_S16_LE) + fmtd |= CM_CFMT_16BIT << CM_CFMT_ADCSHIFT; + else + fmtm &= ~(CM_CFMT_16BIT << CM_CFMT_ADCSHIFT); + } + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + s->dma_dac.ready = 0; + if (val == AFMT_S16_LE) + fmtd |= CM_CFMT_16BIT << CM_CFMT_DACSHIFT; + else + fmtm &= ~(CM_CFMT_16BIT << CM_CFMT_DACSHIFT); + } + set_fmt(s, fmtm, fmtd); + } + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_16BIT << CM_CFMT_ADCSHIFT) + : (CM_CFMT_16BIT << CM_CFMT_DACSHIFT))) ? AFMT_S16_LE : AFMT_U8, (int *)arg); + + case SNDCTL_DSP_POST: + return 0; + + case SNDCTL_DSP_GETTRIGGER: + val = 0; + if (file->f_mode & FMODE_READ && s->enable & CM_CENABLE_RE) + val |= PCM_ENABLE_INPUT; + if (file->f_mode & FMODE_WRITE && s->enable & CM_CENABLE_PE) + val |= PCM_ENABLE_OUTPUT; + return put_user(val, (int *)arg); + + case SNDCTL_DSP_SETTRIGGER: + get_user_ret(val, (int *)arg, -EFAULT); + if (file->f_mode & FMODE_READ) { + if (val & PCM_ENABLE_INPUT) { + if (!s->dma_adc.ready && (ret = prog_dmabuf(s, 1))) + return ret; + start_adc(s); + } else + stop_adc(s); + } + if (file->f_mode & FMODE_WRITE) { + if (val & PCM_ENABLE_OUTPUT) { + if (!s->dma_dac.ready && (ret = prog_dmabuf(s, 0))) + return ret; + start_dac(s); + } else + stop_dac(s); + } + return 0; + + case SNDCTL_DSP_GETOSPACE: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + if (!(s->enable & CM_CENABLE_PE) && (val = prog_dmabuf(s, 0)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + abinfo.fragsize = s->dma_dac.fragsize; + abinfo.bytes = s->dma_dac.dmasize - s->dma_dac.count; + abinfo.fragstotal = s->dma_dac.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_dac.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_GETISPACE: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + if (!(s->enable & CM_CENABLE_RE) && (val = prog_dmabuf(s, 1)) != 0) + return val; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + abinfo.fragsize = s->dma_adc.fragsize; + abinfo.bytes = s->dma_adc.count; + abinfo.fragstotal = s->dma_adc.numfrag; + abinfo.fragments = abinfo.bytes >> s->dma_adc.fragshift; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user((void *)arg, &abinfo, sizeof(abinfo)) ? -EFAULT : 0; + + case SNDCTL_DSP_NONBLOCK: + file->f_flags |= O_NONBLOCK; + return 0; + + case SNDCTL_DSP_GETODELAY: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + val = s->dma_dac.count; + spin_unlock_irqrestore(&s->lock, flags); + return put_user(val, (int *)arg); + + case SNDCTL_DSP_GETIPTR: + if (!(file->f_mode & FMODE_READ)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + cinfo.bytes = s->dma_adc.total_bytes; + cinfo.blocks = s->dma_adc.count >> s->dma_adc.fragshift; + cinfo.ptr = s->dma_adc.hwptr; + if (s->dma_adc.mapped) + s->dma_adc.count &= s->dma_adc.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user((void *)arg, &cinfo, sizeof(cinfo)); + + case SNDCTL_DSP_GETOPTR: + if (!(file->f_mode & FMODE_WRITE)) + return -EINVAL; + spin_lock_irqsave(&s->lock, flags); + cm_update_ptr(s); + cinfo.bytes = s->dma_dac.total_bytes; + cinfo.blocks = s->dma_dac.count >> s->dma_dac.fragshift; + cinfo.ptr = s->dma_dac.hwptr; + if (s->dma_dac.mapped) + s->dma_dac.count &= s->dma_dac.fragsize-1; + spin_unlock_irqrestore(&s->lock, flags); + return copy_to_user((void *)arg, &cinfo, sizeof(cinfo)); + + case SNDCTL_DSP_GETBLKSIZE: + if (file->f_mode & FMODE_WRITE) { + if ((val = prog_dmabuf(s, 0))) + return val; + return put_user(s->dma_dac.fragsize, (int *)arg); + } + if ((val = prog_dmabuf(s, 1))) + return val; + return put_user(s->dma_adc.fragsize, (int *)arg); + + case SNDCTL_DSP_SETFRAGMENT: + get_user_ret(val, (int *)arg, -EFAULT); + if (file->f_mode & FMODE_READ) { + s->dma_adc.ossfragshift = val & 0xffff; + s->dma_adc.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_adc.ossfragshift < 4) + s->dma_adc.ossfragshift = 4; + if (s->dma_adc.ossfragshift > 15) + s->dma_adc.ossfragshift = 15; + if (s->dma_adc.ossmaxfrags < 4) + s->dma_adc.ossmaxfrags = 4; + } + if (file->f_mode & FMODE_WRITE) { + s->dma_dac.ossfragshift = val & 0xffff; + s->dma_dac.ossmaxfrags = (val >> 16) & 0xffff; + if (s->dma_dac.ossfragshift < 4) + s->dma_dac.ossfragshift = 4; + if (s->dma_dac.ossfragshift > 15) + s->dma_dac.ossfragshift = 15; + if (s->dma_dac.ossmaxfrags < 4) + s->dma_dac.ossmaxfrags = 4; + } + return 0; + + case SNDCTL_DSP_SUBDIVIDE: + if ((file->f_mode & FMODE_READ && s->dma_adc.subdivision) || + (file->f_mode & FMODE_WRITE && s->dma_dac.subdivision)) + return -EINVAL; + get_user_ret(val, (int *)arg, -EFAULT); + if (val != 1 && val != 2 && val != 4) + return -EINVAL; + if (file->f_mode & FMODE_READ) + s->dma_adc.subdivision = val; + if (file->f_mode & FMODE_WRITE) + s->dma_dac.subdivision = val; + return 0; + + case SOUND_PCM_READ_RATE: + return put_user((file->f_mode & FMODE_READ) ? s->rateadc : s->ratedac, (int *)arg); + + case SOUND_PCM_READ_CHANNELS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_STEREO << CM_CFMT_ADCSHIFT) : (CM_CFMT_STEREO << CM_CFMT_DACSHIFT))) ? 2 : 1, (int *)arg); + + case SOUND_PCM_READ_BITS: + return put_user((s->fmt & ((file->f_mode & FMODE_READ) ? (CM_CFMT_16BIT << CM_CFMT_ADCSHIFT) : (CM_CFMT_16BIT << CM_CFMT_DACSHIFT))) ? 16 : 8, (int *)arg); + + case SOUND_PCM_WRITE_FILTER: + case SNDCTL_DSP_SETSYNCRO: + case SOUND_PCM_READ_FILTER: + return -EINVAL; + + } + return mixer_ioctl(s, cmd, arg); +} + +static int cm_open(struct inode *inode, struct file *file) +{ + int minor = MINOR(inode->i_rdev); + struct cm_state *s = devs; + unsigned char fmtm = ~0, fmts = 0; + + while (s && ((s->dev_audio ^ minor) & ~0xf)) + s = s->next; + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & file->f_mode) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + up(&s->open_sem); + interruptible_sleep_on(&s->open_wait); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + if (file->f_mode & FMODE_READ) { + fmtm &= ~((CM_CFMT_STEREO | CM_CFMT_16BIT) << CM_CFMT_ADCSHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= CM_CFMT_16BIT << CM_CFMT_ADCSHIFT; + s->dma_adc.ossfragshift = s->dma_adc.ossmaxfrags = s->dma_adc.subdivision = 0; + set_adc_rate(s, 8000); + } + if (file->f_mode & FMODE_WRITE) { + fmtm &= ~((CM_CFMT_STEREO | CM_CFMT_16BIT) << CM_CFMT_DACSHIFT); + if ((minor & 0xf) == SND_DEV_DSP16) + fmts |= CM_CFMT_16BIT << CM_CFMT_DACSHIFT; + s->dma_dac.ossfragshift = s->dma_dac.ossmaxfrags = s->dma_dac.subdivision = 0; + set_dac_rate(s, 8000); + } + set_fmt(s, fmtm, fmts); + s->open_mode |= file->f_mode & (FMODE_READ | FMODE_WRITE); + up(&s->open_sem); + MOD_INC_USE_COUNT; + return 0; +} + +static int cm_release(struct inode *inode, struct file *file) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + drain_dac(s, file->f_flags & O_NONBLOCK); + down(&s->open_sem); + if (file->f_mode & FMODE_WRITE) { + stop_dac(s); + dealloc_dmabuf(&s->dma_dac); + } + if (file->f_mode & FMODE_READ) { + stop_adc(s); + dealloc_dmabuf(&s->dma_adc); + } + s->open_mode &= (~file->f_mode) & (FMODE_READ|FMODE_WRITE); + up(&s->open_sem); + wake_up(&s->open_wait); + MOD_DEC_USE_COUNT; + return 0; +} + +static /*const*/ struct file_operations cm_audio_fops = { + &cm_llseek, + &cm_read, + &cm_write, + NULL, /* readdir */ + &cm_poll, + &cm_ioctl, + &cm_mmap, + &cm_open, + NULL, /* flush */ + &cm_release, + NULL, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL, /* revalidate */ + NULL, /* lock */ +}; + +#ifdef CONFIG_SOUND_CMPCI_MIDI +/* --------------------------------------------------------------------- */ + +static ssize_t cm_midi_read(struct file *file, char *buffer, size_t count, loff_t *ppos) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (ppos != &file->f_pos) + return -ESPIPE; + if (!access_ok(VERIFY_WRITE, buffer, count)) + return -EFAULT; + ret = 0; + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.ird; + cnt = MIDIINBUF - ptr; + if (s->midi.icnt < cnt) + cnt = s->midi.icnt; + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&s->midi.iwait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_to_user(buffer, s->midi.ibuf + ptr, cnt)) + return ret ? ret : -EFAULT; + ptr = (ptr + cnt) % MIDIINBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.ird = ptr; + s->midi.icnt -= cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + } + return ret; +} + +static ssize_t cm_midi_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + ssize_t ret; + unsigned long flags; + unsigned ptr; + int cnt; + + VALIDATE_STATE(s); + if (ppos != &file->f_pos) + return -ESPIPE; + if (!access_ok(VERIFY_READ, buffer, count)) + return -EFAULT; + ret = 0; + while (count > 0) { + spin_lock_irqsave(&s->lock, flags); + ptr = s->midi.owr; + cnt = MIDIOUTBUF - ptr; + if (s->midi.ocnt + cnt > MIDIOUTBUF) + cnt = MIDIOUTBUF - s->midi.ocnt; + if (cnt <= 0) + cm_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + if (cnt > count) + cnt = count; + if (cnt <= 0) { + if (file->f_flags & O_NONBLOCK) + return ret ? ret : -EAGAIN; + interruptible_sleep_on(&s->midi.owait); + if (signal_pending(current)) + return ret ? ret : -ERESTARTSYS; + continue; + } + if (copy_from_user(s->midi.obuf + ptr, buffer, cnt)) + return ret ? ret : -EFAULT; + ptr = (ptr + cnt) % MIDIOUTBUF; + spin_lock_irqsave(&s->lock, flags); + s->midi.owr = ptr; + s->midi.ocnt += cnt; + spin_unlock_irqrestore(&s->lock, flags); + count -= cnt; + buffer += cnt; + ret += cnt; + spin_lock_irqsave(&s->lock, flags); + cm_handle_midi(s); + spin_unlock_irqrestore(&s->lock, flags); + } + return ret; +} + +static unsigned int cm_midi_poll(struct file *file, struct poll_table_struct *wait) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + unsigned long flags; + unsigned int mask = 0; + + VALIDATE_STATE(s); + if (file->f_mode & FMODE_WRITE) + poll_wait(file, &s->midi.owait, wait); + if (file->f_mode & FMODE_READ) + poll_wait(file, &s->midi.iwait, wait); + spin_lock_irqsave(&s->lock, flags); + if (file->f_mode & FMODE_READ) { + if (s->midi.icnt > 0) + mask |= POLLIN | POLLRDNORM; + } + if (file->f_mode & FMODE_WRITE) { + if (s->midi.ocnt < MIDIOUTBUF) + mask |= POLLOUT | POLLWRNORM; + } + spin_unlock_irqrestore(&s->lock, flags); + return mask; +} + +static int cm_midi_open(struct inode *inode, struct file *file) +{ + int minor = MINOR(inode->i_rdev); + struct cm_state *s = devs; + unsigned long flags; + + while (s && s->dev_midi != minor) + s = s->next; + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & (file->f_mode << FMODE_MIDI_SHIFT)) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + up(&s->open_sem); + interruptible_sleep_on(&s->open_wait); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + /* enable MPU-401 */ + outb(inb(s->iobase + CODEC_CMI_FUNCTRL1) | 4, s->iobase + CODEC_CMI_FUNCTRL1); + outb(0xff, s->iomidi+1); /* reset command */ + if (!(inb(s->iomidi+1) & 0x80)) + inb(s->iomidi); + outb(0x3f, s->iomidi+1); /* uart command */ + if (!(inb(s->iomidi+1) & 0x80)) + inb(s->iomidi); + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + init_timer(&s->midi.timer); + s->midi.timer.expires = jiffies+1; + s->midi.timer.data = (unsigned long)s; + s->midi.timer.function = cm_midi_timer; + add_timer(&s->midi.timer); + } + if (file->f_mode & FMODE_READ) { + s->midi.ird = s->midi.iwr = s->midi.icnt = 0; + } + if (file->f_mode & FMODE_WRITE) { + s->midi.ord = s->midi.owr = s->midi.ocnt = 0; + } + spin_unlock_irqrestore(&s->lock, flags); + s->open_mode |= (file->f_mode << FMODE_MIDI_SHIFT) & (FMODE_MIDI_READ | FMODE_MIDI_WRITE); + up(&s->open_sem); + MOD_INC_USE_COUNT; + return 0; +} + +static int cm_midi_release(struct inode *inode, struct file *file) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + unsigned count, tmo; + + VALIDATE_STATE(s); + + if (file->f_mode & FMODE_WRITE) { + current->state = TASK_INTERRUPTIBLE; + add_wait_queue(&s->midi.owait, &wait); + for (;;) { + spin_lock_irqsave(&s->lock, flags); + count = s->midi.ocnt; + spin_unlock_irqrestore(&s->lock, flags); + if (count <= 0) + break; + if (signal_pending(current)) + break; + if (file->f_flags & O_NONBLOCK) { + remove_wait_queue(&s->midi.owait, &wait); + current->state = TASK_RUNNING; + return -EBUSY; + } + tmo = (count * HZ) / 3100; + if (!schedule_timeout(tmo ? : 1) && tmo) + printk(KERN_DEBUG "cm: midi timed out??\n"); + } + remove_wait_queue(&s->midi.owait, &wait); + current->state = TASK_RUNNING; + } + down(&s->open_sem); + s->open_mode &= (~(file->f_mode << FMODE_MIDI_SHIFT)) & (FMODE_MIDI_READ|FMODE_MIDI_WRITE); + spin_lock_irqsave(&s->lock, flags); + if (!(s->open_mode & (FMODE_MIDI_READ | FMODE_MIDI_WRITE))) { + del_timer(&s->midi.timer); + outb(0xff, s->iomidi+1); /* reset command */ + if (!(inb(s->iomidi+1) & 0x80)) + inb(s->iomidi); + /* disable MPU-401 */ + outb(inb(s->iobase + CODEC_CMI_FUNCTRL1) & ~4, s->iobase + CODEC_CMI_FUNCTRL1); + } + spin_unlock_irqrestore(&s->lock, flags); + up(&s->open_sem); + wake_up(&s->open_wait); + MOD_DEC_USE_COUNT; + return 0; +} + +static /*const*/ struct file_operations cm_midi_fops = { + &cm_llseek, + &cm_midi_read, + &cm_midi_write, + NULL, /* readdir */ + &cm_midi_poll, + NULL, /* ioctl */ + NULL, /* mmap */ + &cm_midi_open, + NULL, /* flush */ + &cm_midi_release, + NULL, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL, /* revalidate */ + NULL, /* lock */ +}; +#endif + +/* --------------------------------------------------------------------- */ + +#ifdef CONFIG_SOUND_CMPCI_FM +static int cm_dmfm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + static const unsigned char op_offset[18] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 + }; + struct cm_state *s = (struct cm_state *)file->private_data; + struct dm_fm_voice v; + struct dm_fm_note n; + struct dm_fm_params p; + unsigned int io; + unsigned int regb; + + switch (cmd) { + case FM_IOCTL_RESET: + for (regb = 0xb0; regb < 0xb9; regb++) { + outb(regb, s->iosynth); + outb(0, s->iosynth+1); + outb(regb, s->iosynth+2); + outb(0, s->iosynth+3); + } + return 0; + + case FM_IOCTL_PLAY_NOTE: + if (copy_from_user(&n, (void *)arg, sizeof(n))) + return -EFAULT; + if (n.voice >= 18) + return -EINVAL; + if (n.voice >= 9) { + regb = n.voice - 9; + io = s->iosynth+2; + } else { + regb = n.voice; + io = s->iosynth; + } + outb(0xa0 + regb, io); + outb(n.fnum & 0xff, io+1); + outb(0xb0 + regb, io); + outb(((n.fnum >> 8) & 3) | ((n.octave & 7) << 2) | ((n.key_on & 1) << 5), io+1); + return 0; + + case FM_IOCTL_SET_VOICE: + if (copy_from_user(&v, (void *)arg, sizeof(v))) + return -EFAULT; + if (v.voice >= 18) + return -EINVAL; + regb = op_offset[v.voice]; + io = s->iosynth + ((v.op & 1) << 1); + outb(0x20 + regb, io); + outb(((v.am & 1) << 7) | ((v.vibrato & 1) << 6) | ((v.do_sustain & 1) << 5) | + ((v.kbd_scale & 1) << 4) | (v.harmonic & 0xf), io+1); + outb(0x40 + regb, io); + outb(((v.scale_level & 0x3) << 6) | (v.volume & 0x3f), io+1); + outb(0x60 + regb, io); + outb(((v.attack & 0xf) << 4) | (v.decay & 0xf), io+1); + outb(0x80 + regb, io); + outb(((v.sustain & 0xf) << 4) | (v.release & 0xf), io+1); + outb(0xe0 + regb, io); + outb(v.waveform & 0x7, io+1); + if (n.voice >= 9) { + regb = n.voice - 9; + io = s->iosynth+2; + } else { + regb = n.voice; + io = s->iosynth; + } + outb(0xc0 + regb, io); + outb(((v.right & 1) << 5) | ((v.left & 1) << 4) | ((v.feedback & 7) << 1) | + (v.connection & 1), io+1); + return 0; + + case FM_IOCTL_SET_PARAMS: + if (copy_from_user(&p, (void *)arg, sizeof(p))) + return -EFAULT; + outb(0x08, s->iosynth); + outb((p.kbd_split & 1) << 6, s->iosynth+1); + outb(0xbd, s->iosynth); + outb(((p.am_depth & 1) << 7) | ((p.vib_depth & 1) << 6) | ((p.rhythm & 1) << 5) | ((p.bass & 1) << 4) | + ((p.snare & 1) << 3) | ((p.tomtom & 1) << 2) | ((p.cymbal & 1) << 1) | (p.hihat & 1), s->iosynth+1); + return 0; + + case FM_IOCTL_SET_OPL: + outb(4, s->iosynth+2); + outb(arg, s->iosynth+3); + return 0; + + case FM_IOCTL_SET_MODE: + outb(5, s->iosynth+2); + outb(arg & 1, s->iosynth+3); + return 0; + + default: + return -EINVAL; + } +} + +static int cm_dmfm_open(struct inode *inode, struct file *file) +{ + int minor = MINOR(inode->i_rdev); + struct cm_state *s = devs; + + while (s && s->dev_dmfm != minor) + s = s->next; + if (!s) + return -ENODEV; + VALIDATE_STATE(s); + file->private_data = s; + /* wait for device to become free */ + down(&s->open_sem); + while (s->open_mode & FMODE_DMFM) { + if (file->f_flags & O_NONBLOCK) { + up(&s->open_sem); + return -EBUSY; + } + up(&s->open_sem); + interruptible_sleep_on(&s->open_wait); + if (signal_pending(current)) + return -ERESTARTSYS; + down(&s->open_sem); + } + /* init the stuff */ + outb(1, s->iosynth); + outb(0x20, s->iosynth+1); /* enable waveforms */ + outb(4, s->iosynth+2); + outb(0, s->iosynth+3); /* no 4op enabled */ + outb(5, s->iosynth+2); + outb(1, s->iosynth+3); /* enable OPL3 */ + s->open_mode |= FMODE_DMFM; + up(&s->open_sem); + MOD_INC_USE_COUNT; + return 0; +} + +static int cm_dmfm_release(struct inode *inode, struct file *file) +{ + struct cm_state *s = (struct cm_state *)file->private_data; + unsigned int regb; + + VALIDATE_STATE(s); + down(&s->open_sem); + s->open_mode &= ~FMODE_DMFM; + for (regb = 0xb0; regb < 0xb9; regb++) { + outb(regb, s->iosynth); + outb(0, s->iosynth+1); + outb(regb, s->iosynth+2); + outb(0, s->iosynth+3); + } + up(&s->open_sem); + wake_up(&s->open_wait); + MOD_DEC_USE_COUNT; + return 0; +} + +static /*const*/ struct file_operations cm_dmfm_fops = { + &cm_llseek, + NULL, /* read */ + NULL, /* write */ + NULL, /* readdir */ + NULL, /* poll */ + &cm_dmfm_ioctl, + NULL, /* mmap */ + &cm_dmfm_open, + NULL, /* flush */ + &cm_dmfm_release, + NULL, /* fsync */ + NULL, /* fasync */ + NULL, /* check_media_change */ + NULL, /* revalidate */ + NULL, /* lock */ +}; +#endif /* CONFIG_SOUND_CMPCI_FM */ + +/* --------------------------------------------------------------------- */ + +/* maximum number of devices */ +#define NR_DEVICE 5 + +#if 0 +static int reverb[NR_DEVICE] = { 0, }; + +static int wavetable[NR_DEVICE] = { 0, }; +#endif + +/* --------------------------------------------------------------------- */ + +static struct initvol { + int mixch; + int vol; +} initvol[] __initdata = { + { SOUND_MIXER_WRITE_CD, 0x4040 }, + { SOUND_MIXER_WRITE_LINE, 0x4040 }, + { SOUND_MIXER_WRITE_MIC, 0x4040 }, + { SOUND_MIXER_WRITE_SYNTH, 0x4040 }, + { SOUND_MIXER_WRITE_VOLUME, 0x4040 }, + { SOUND_MIXER_WRITE_PCM, 0x4040 } +}; + +#ifdef MODULE +__initfunc(int init_module(void)) +#else +__initfunc(int init_cmpci(void)) +#endif +{ + struct cm_state *s; + struct pci_dev *pcidev = NULL; + mm_segment_t fs; + int i, val, index = 0; + struct { + unsigned short deviceid; + char *devicename; + } devicetable[] = + { + { PCI_DEVICE_ID_CMEDIA_CM8338A, "CM8338A" }, + { PCI_DEVICE_ID_CMEDIA_CM8338B, "CM8338B" }, + { PCI_DEVICE_ID_CMEDIA_CM8738, "CM8738" }, + }; + char *devicename = "unknown"; + +#ifdef CONFIG_PCI + if (!pci_present()) /* No PCI bus in this machine! */ +#endif + return -ENODEV; + printk(KERN_INFO "cm: version v1.1 time " __TIME__ " " __DATE__ "\n"); +#if 0 + if (!(wavetable_mem = __get_free_pages(GFP_KERNEL, 20-PAGE_SHIFT))) + printk(KERN_INFO "cm: cannot allocate 1MB of contiguous nonpageable memory for wavetable data\n"); +#endif + while (index < NR_DEVICE && pcidev == NULL && ( + (pcidev = pci_find_device(PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338A, pcidev)) || + (pcidev = pci_find_device(PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8338B, pcidev)) || + (pcidev = pci_find_device(PCI_VENDOR_ID_CMEDIA, PCI_DEVICE_ID_CMEDIA_CM8738, pcidev)))) { + if (pcidev->irq == 0) + continue; + if (!(s = kmalloc(sizeof(struct cm_state), GFP_KERNEL))) { + printk(KERN_WARNING "cm: out of memory\n"); + continue; + } + /* search device name */ + for (i = 0; i < sizeof(devicetable) / sizeof(devicetable[0]); i++) + { + if (devicetable[i].deviceid == pcidev->device) + { + devicename = devicetable[i].devicename; + break; + } + } + memset(s, 0, sizeof(struct cm_state)); + init_waitqueue_head(&s->dma_adc.wait); + init_waitqueue_head(&s->dma_dac.wait); + init_waitqueue_head(&s->open_wait); + init_waitqueue_head(&s->midi.iwait); + init_waitqueue_head(&s->midi.owait); + init_MUTEX(&s->open_sem); + s->magic = CM_MAGIC; + s->iobase = pcidev->base_address[0] & PCI_BASE_ADDRESS_IO_MASK; +#ifdef CONFIG_SOUND_CMPCI_FM + s->iosynth = 0x388; +#endif +#ifdef CONFIG_SOUND_CMPCI_MIDI + s->iomidi = 0x330; +#endif + if (s->iobase == 0) + continue; + s->irq = pcidev->irq; + + if (check_region(s->iobase, CM_EXTENT_CODEC)) { + printk(KERN_ERR "cm: io ports %#x-%#x in use\n", s->iobase, s->iobase+CM_EXTENT_CODEC-1); + goto err_region5; + } + request_region(s->iobase, CM_EXTENT_CODEC, "cmpci"); +#ifdef CONFIG_SOUND_CMPCI_MIDI + if (check_region(s->iomidi, CM_EXTENT_MIDI)) { + printk(KERN_ERR "cm: io ports %#x-%#x in use\n", s->iomidi, s->iomidi+CM_EXTENT_MIDI-1); + goto err_region4; + } + request_region(s->iomidi, CM_EXTENT_MIDI, "cmpci Midi"); + /* set IO based at 0x330 */ + outb(inb(s->iobase + CODEC_CMI_LEGACY_CTRL + 3) & ~0x60, s->iobase + CODEC_CMI_LEGACY_CTRL + 3); +#endif +#ifdef CONFIG_SOUND_CMPCI_FM + if (check_region(s->iosynth, CM_EXTENT_SYNTH)) { + printk(KERN_ERR "cm: io ports %#x-%#x in use\n", s->iosynth, s->iosynth+CM_EXTENT_SYNTH-1); + goto err_region1; + } + request_region(s->iosynth, CM_EXTENT_SYNTH, "cmpci FM"); + /* enable FM */ + outb(inb(s->iobase + CODEC_CMI_MISC_CTRL + 2) | 8, s->iobase + CODEC_CMI_MISC_CTRL); +#endif + /* initialize codec registers */ + outb(0, s->iobase + CODEC_CMI_INT_HLDCLR + 2); /* disable ints */ + outb(0, s->iobase + CODEC_CMI_FUNCTRL0 + 2); /* reset channels */ + /* reset mixer */ + wrmixer(s, DSP_MIX_DATARESETIDX, 0); + + /* request irq */ + if (request_irq(s->irq, cm_interrupt, SA_SHIRQ, "cmpci", s)) { + printk(KERN_ERR "cm: irq %u in use\n", s->irq); + goto err_irq; + } + printk(KERN_INFO "cm: found %s adapter at io %#06x irq %u\n", + devicename, s->iobase, s->irq); + /* register devices */ + if ((s->dev_audio = register_sound_dsp(&cm_audio_fops, -1)) < 0) + goto err_dev1; + if ((s->dev_mixer = register_sound_mixer(&cm_mixer_fops, -1)) < 0) + goto err_dev2; +#ifdef CONFIG_SOUND_CMPCI_MIDI + if ((s->dev_midi = register_sound_midi(&cm_midi_fops, -1)) < 0) + goto err_dev3; +#endif +#ifdef CONFIG_SOUND_CMPCI_FM + if ((s->dev_dmfm = register_sound_special(&cm_dmfm_fops, 15 /* ?? */)) < 0) + goto err_dev4; +#endif + /* initialize the chips */ + fs = get_fs(); + set_fs(KERNEL_DS); + /* set mixer output */ + frobindir(s, DSP_MIX_OUTMIXIDX, 0x1f, 0x1f); + /* set mixer input */ + val = SOUND_MASK_LINE|SOUND_MASK_SYNTH|SOUND_MASK_CD|SOUND_MASK_MIC; + mixer_ioctl(s, SOUND_MIXER_WRITE_RECSRC, (unsigned long)&val); + for (i = 0; i < sizeof(initvol)/sizeof(initvol[0]); i++) { + val = initvol[i].vol; + mixer_ioctl(s, initvol[i].mixch, (unsigned long)&val); + } + set_fs(fs); + /* queue it for later freeing */ + s->next = devs; + devs = s; + index++; + continue; + + err_dev4: + unregister_sound_midi(s->dev_midi); + err_dev3: + unregister_sound_mixer(s->dev_mixer); + err_dev2: + unregister_sound_dsp(s->dev_audio); + err_dev1: + printk(KERN_ERR "cm: cannot register misc device\n"); + free_irq(s->irq, s); + err_irq: +#ifdef CONFIG_SOUND_CMPCI_FM + release_region(s->iosynth, CM_EXTENT_SYNTH); + err_region1: +#endif +#ifdef CONFIG_SOUND_CMPCI_MIDI + release_region(s->iomidi, CM_EXTENT_MIDI); +#endif + err_region4: + release_region(s->iobase, CM_EXTENT_CODEC); + err_region5: + kfree_s(s, sizeof(struct cm_state)); + } + if (!devs) { + if (wavetable_mem) + free_pages(wavetable_mem, 20-PAGE_SHIFT); + return -ENODEV; + } + return 0; +} + +/* --------------------------------------------------------------------- */ + +#ifdef MODULE + +#if 0 +MODULE_PARM(wavetable, "1-" __MODULE_STRING(NR_DEVICE) "i"); +MODULE_PARM_DESC(wavetable, "if 1 the wavetable synth is enabled"); +#endif + +MODULE_AUTHOR("ChenLi Tien, cltien@home.com"); +MODULE_DESCRIPTION("CMPCI Audio Driver"); + +void cleanup_module(void) +{ + struct cm_state *s; + + while ((s = devs)) { + devs = devs->next; + outb(0, s->iobase + CODEC_CMI_INT_HLDCLR + 2); /* disable ints */ + synchronize_irq(); + outb(0, s->iobase + CODEC_CMI_FUNCTRL0 + 2); /* reset channels */ + free_irq(s->irq, s); + + /* reset mixer */ + wrmixer(s, DSP_MIX_DATARESETIDX, 0); + + release_region(s->iobase, CM_EXTENT_CODEC); +#ifdef CONFIG_SOUND_CMPCI_MIDI + release_region(s->iomidi, CM_EXTENT_MIDI); +#endif +#ifdef CONFIG_SOUND_CMPCI_FM + release_region(s->iosynth, CM_EXTENT_SYNTH); +#endif + unregister_sound_dsp(s->dev_audio); + unregister_sound_mixer(s->dev_mixer); +#ifdef CONFIG_SOUND_CMPCI_MIDI + unregister_sound_midi(s->dev_midi); +#endif +#ifdef CONFIG_SOUND_CMPCI_FM + unregister_sound_special(s->dev_dmfm); +#endif + kfree_s(s, sizeof(struct cm_state)); + } + if (wavetable_mem) + free_pages(wavetable_mem, 20-PAGE_SHIFT); + printk(KERN_INFO "cm: unloading\n"); +} + +#endif /* MODULE */ diff --git a/drivers/usb/acm.c b/drivers/usb/acm.c new file mode 100644 index 000000000..d4796e28c --- /dev/null +++ b/drivers/usb/acm.c @@ -0,0 +1,293 @@ +/* + * USB Abstract Control Model based on Brad Keryan's USB busmouse driver + * + * Armin Fuerst 5/8/1999 + * + * version 0.2: Improved Bulk transfer. TX led now flashes every time data is + * sent. Send Encapsulated Data is not needed, nor does it do anything. + * Why's that ?!? Thanks to Thomas Sailer for his close look at the bulk code. + * He told me about some importand bugs. (5/21/99) + * + * version 0.1: Bulk transfer for uhci seems to work now, no dangling tds any + * more. TX led of the ISDN TA flashed the first time. Does this mean it works? + * The interrupt of the ctrl endpoint crashes the kernel => no read possible + * (5/19/99) + * + * version 0.0: Driver sets up configuration, sets up data pipes, opens misc + * device. No actual data transfer is done, since we don't have bulk transfer, + * yet. Purely skeleton for now. (5/8/99) + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/malloc.h> +#include <linux/config.h> +#include <linux/module.h> + +#include <asm/spinlock.h> + +#include "usb.h" + +#define USB_ACM_MINOR 32 + +struct acm_state { + int present; /* this acm is plugged in */ + int active; /* someone is has this acm's device open */ + int serstate; /* Status of the serial port (rate, handshakelines,...) */ + struct usb_device *dev; + unsigned ctrlbuffer; /*buffer for control messages*/ + unsigned int readendp,writeendp,ctrlendp; + unsigned int readpipe,writepipe,ctrlpipe; + char buffer; +}; + +static struct acm_state static_acm_state; + +spinlock_t usb_acm_lock = SPIN_LOCK_UNLOCKED; + +static int acm_irq(int state, void *__buffer, void *dev_id) +{ +// unsigned char *data = __buffer; + struct acm_state *acm = &static_acm_state; + devrequest *dr; + + dr=__buffer; + printk("ACM_USB_IRQ\n"); + printk("reqtype: %02X\n",dr->requesttype); + printk("request: %02X\n",dr->request); + printk("wValue: %02X\n",dr->value); + printk("wIndex: %02X\n",dr->index); + printk("wLength: %02X\n",dr->length); + + switch(dr->request) { + //Network connection + case 0x00: + printk("Network connection: "); + if (dr->request==0) printk("disconnected\n"); + if (dr->request==1) printk("connected\n"); + break; + + //Response available + case 0x01: + printk("Response available\n"); + acm->buffer=1; + break; + + //Set serial line state + case 0x20: + if ((dr->index==1)&&(dr->length==2)) { + acm->serstate=acm->ctrlbuffer; + printk("Serstate: %02X\n",acm->ctrlbuffer); + } + break; + } +/* + if(!acm->active) + return 1; +*/ + return 1; +} + +static int release_acm(struct inode * inode, struct file * file) +{ + struct acm_state *acm = &static_acm_state; + printk("ACM_FILE_RELEASE\n"); + +// fasync_acm(-1, file, 0); + if (--acm->active) + return 0; + return 0; +} + +static int open_acm(struct inode * inode, struct file * file) +{ + struct acm_state *acm = &static_acm_state; + printk("USB_FILE_OPEN\n"); + + if (!acm->present) + return -EINVAL; + if (acm->active++) + return 0; + return 0; +} + +static ssize_t write_acm(struct file * file, + const char * buffer, size_t count, loff_t *ppos) +{ + devrequest dr; + struct acm_state *acm = &static_acm_state; + unsigned long retval; + + printk("USB_FILE_WRITE\n"); +//Huh, i seem to got that wrong, we don't need this ?!? +/* + dr.requesttype = USB_TYPE_CLASS | USB_RT_ENDPOINT; + dr.request = 0; + dr.value = 0; + dr.index = acm->writeendp; + dr.length = count; + acm->dev->bus->op->control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), &dr, NULL, 0); +*/ + + acm->dev->bus->op->bulk_msg(acm->dev,&acm->writepipe,buffer, count, &retval); + return -EINVAL; +} + + +static ssize_t read_acm(struct file * file, const char * buffer, size_t count, loff_t *ppos) +{ + devrequest dr; + struct acm_state *acm = &static_acm_state; + unsigned long retval; + printk("USB_FILE_READ\n"); +// if (!acm->buffer) return -1; + acm->buffer=0; +//We don't need this +/* + printk("writing control msg\n"); + dr.requesttype = USB_TYPE_CLASS | USB_RT_ENDPOINT | 0x80; + dr.request = 1; + dr.value = 0; + dr.index = acm->readendp; + dr.length = 0; + acm->dev->bus->op->control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), &dr, NULL, 0); +*/ + printk("reading:>%s<\n",buffer); + acm->dev->bus->op->bulk_msg(acm->dev,&acm->readpipe,buffer, 1,&retval); + printk("done:>%s<\n",buffer); + return 1; +} + +struct file_operations usb_acm_fops = { + NULL, /* acm_seek */ + read_acm, + write_acm, + NULL, /* acm_readdir */ + NULL, /* acm_poll */ + NULL, /* acm_ioctl */ + NULL, /* acm_mmap */ + open_acm, + NULL, /* flush */ + release_acm, + NULL, + NULL, /*fasync*/ +}; + +static struct miscdevice usb_acm = { + USB_ACM_MINOR, "USB ACM", &usb_acm_fops +}; + +static int acm_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *endpoint; + struct acm_state *acm = &static_acm_state; + int cfgnum; + + /* Only use CDC */ + if (dev->descriptor.bDeviceClass != 2 || + dev->descriptor.bDeviceSubClass != 0 || + dev->descriptor.bDeviceProtocol != 0) + return -1; + + /*Now scan all configs for a ACM configuration*/ + for (cfgnum=0;cfgnum<dev->descriptor.bNumConfigurations;cfgnum++) { + /* The first one should be Communications interface? */ + interface = &dev->config[cfgnum].altsetting[0].interface[0]; + if (interface->bInterfaceClass != 2 || + interface->bInterfaceSubClass != 2 || + interface->bInterfaceProtocol != 1 || + interface->bNumEndpoints != 1) + continue; + + /*Which uses an interrupt input */ + endpoint = &interface->endpoint[0]; + if ((endpoint->bEndpointAddress & 0x80) != 0x80 || + (endpoint->bmAttributes & 3) != 3) + continue; + + /* The second one should be a Data interface? */ + interface = &dev->config[cfgnum].altsetting[0].interface[1]; + if (interface->bInterfaceClass != 10 || + interface->bInterfaceSubClass != 0 || + interface->bInterfaceProtocol != 0 || + interface->bNumEndpoints != 2) + continue; + + /*With a bulk input */ + endpoint = &interface->endpoint[0]; + if ((endpoint->bEndpointAddress & 0x80) != 0x80 || + (endpoint->bmAttributes & 3) != 2) + continue; + + /*And a bulk output */ + endpoint = &interface->endpoint[1]; + if ((endpoint->bEndpointAddress & 0x80) == 0x80 || + (endpoint->bmAttributes & 3) != 2) + continue; + + printk("USB ACM found\n"); + usb_set_configuration(dev, dev->config[cfgnum].bConfigurationValue); + acm->dev=dev; + acm->readendp=dev->config[cfgnum].altsetting[0].interface[1].endpoint[0].bEndpointAddress; + acm->writeendp=dev->config[cfgnum].altsetting[0].interface[1].endpoint[1].bEndpointAddress; + acm->ctrlendp=dev->config[cfgnum].altsetting[0].interface[0].endpoint[0].bEndpointAddress; + acm->readpipe=usb_rcvbulkpipe(dev,acm->readendp); + acm->writepipe=usb_sndbulkpipe(dev,acm->writeendp); + usb_request_irq(dev,acm->ctrlpipe=usb_rcvctrlpipe(dev,acm->ctrlendp), acm_irq, dev->config[cfgnum].altsetting[0].interface[0].endpoint[0].bInterval, &acm->ctrlbuffer); + acm->present = 1; + acm->buffer=0; + return 0; + } + + return -1; +} + +static void acm_disconnect(struct usb_device *dev) +{ + struct acm_state *acm = &static_acm_state; + + /* this might need work */ + acm->present = 0; +} + +static struct usb_driver acm_driver = { + "acm", + acm_probe, + acm_disconnect, + { NULL, NULL } +}; + +int usb_acm_init(void) +{ + struct acm_state *acm = &static_acm_state; + + misc_register(&usb_acm); + + acm->present = acm->active = 0; + + usb_register(&acm_driver); + printk(KERN_INFO "USB ACM registered.\n"); + return 0; +} + +#ifdef MODULE + +int init_module(void) +{ + return usb_acm_init(); +} + +void cleanup_module(void) +{ + /* this, too, probably needs work */ + usb_deregister(&acm_driver); + misc_deregister(&usb_acm); +} + +#endif diff --git a/drivers/usb/cpia.c b/drivers/usb/cpia.c new file mode 100644 index 000000000..87e1e4254 --- /dev/null +++ b/drivers/usb/cpia.c @@ -0,0 +1,1267 @@ +/* + * USB CPiA Video Camera driver + * + * Supports CPiA based Video Camera's. Many manufacturers use this chipset. + * There's a good chance, if you have a USB video camera, it's a CPiA based + * one + * + * (C) Copyright 1999 Johannes Erdfelt + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/smp_lock.h> +#include <linux/videodev.h> +#include <linux/vmalloc.h> +#include <linux/wrapper.h> +#include <linux/config.h> +#include <linux/module.h> + +#include <asm/spinlock.h> +#include <asm/io.h> + +#include "usb.h" +#include "uhci.h" +#include "cpia.h" + +#define MAX_FRAME_SIZE (384 * 288 * 3) + +/*******************************/ +/* Memory management functions */ +/*******************************/ + +/* convert virtual user memory address to physical address */ +/* (virt_to_phys only works for kmalloced kernel memory) */ + +static inline unsigned long uvirt_to_phys(unsigned long adr) +{ + pgd_t *pgd; + pmd_t *pmd; + pte_t *ptep, pte; + + pgd = pgd_offset(current->mm, adr); + if (pgd_none(*pgd)) + return 0; + pmd = pmd_offset(pgd, adr); + if (pmd_none(*pmd)) + return 0; + ptep = pte_offset(pmd, adr/*&(~PGDIR_MASK)*/); + pte = *ptep; + if(pte_present(pte)) + return + virt_to_phys((void *)(pte_page(pte)|(adr&(PAGE_SIZE-1)))); + return 0; +} + +static inline unsigned long uvirt_to_bus(unsigned long adr) +{ + return virt_to_bus(phys_to_virt(uvirt_to_phys(adr))); +} + +/* convert virtual kernel memory address to physical address */ +/* (virt_to_phys only works for kmalloced kernel memory) */ + +static inline unsigned long kvirt_to_phys(unsigned long adr) +{ + return uvirt_to_phys(VMALLOC_VMADDR(adr)); +} + +static inline unsigned long kvirt_to_bus(unsigned long adr) +{ + return uvirt_to_bus(VMALLOC_VMADDR(adr)); +} + + +static void * rvmalloc(unsigned long size) +{ + void * mem; + unsigned long adr, page; + + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + mem=vmalloc(size); + if (mem) + { + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_phys(adr); + mem_map_reserve(MAP_NR(phys_to_virt(page))); + adr+=PAGE_SIZE; + if (size > PAGE_SIZE) + size-=PAGE_SIZE; + else + size=0; + } + } + return mem; +} + +static void rvfree(void * mem, unsigned long size) +{ + unsigned long adr, page; + + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + if (mem) + { + adr=(unsigned long) mem; + while (size > 0) + { + page = kvirt_to_phys(adr); + mem_map_unreserve(MAP_NR(phys_to_virt(page))); + adr+=PAGE_SIZE; + if (size > PAGE_SIZE) + size-=PAGE_SIZE; + else + size=0; + } + vfree(mem); + } +} + +int usb_cpia_get_version(struct usb_device *dev, void *buf) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE) | 0x80; + dr.request = USB_REQ_CPIA_GET_VERSION; + dr.value = 0; + dr.index = 0; + dr.length = 4; + + return dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, buf, 4); +} + +int usb_cpia_get_pnp_id(struct usb_device *dev, void *buf) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE) | 0x80; + dr.request = USB_REQ_CPIA_GET_PNP_ID; + dr.value = 0; + dr.index = 0; + dr.length = 6; + + return dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, buf, 6); +} + +int usb_cpia_get_camera_status(struct usb_device *dev, void *buf) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE) | 0x80; + dr.request = USB_REQ_CPIA_GET_CAMERA_STATUS; + dr.value = 0; + dr.index = 0; + dr.length = 8; + + return dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, buf, 8); +} + +int usb_cpia_goto_hi_power(struct usb_device *dev) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_GOTO_HI_POWER; + dr.value = 0; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_get_vp_version(struct usb_device *dev, void *buf) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_GET_VP_VERSION; + dr.value = 0; + dr.index = 0; + dr.length = 4; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, buf, 4); +} + +int usb_cpia_set_sensor_fps(struct usb_device *dev, int sensorbaserate, int sensorclkdivisor) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_SET_SENSOR_FPS; + dr.value = (sensorclkdivisor << 8) + sensorbaserate; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_grab_frame(struct usb_device *dev, int streamstartline) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_GRAB_FRAME; + dr.value = streamstartline << 8; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_upload_frame(struct usb_device *dev, int forceupload) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_UPLOAD_FRAME; + dr.value = forceupload; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_set_grab_mode(struct usb_device *dev, int continuousgrab) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_SET_GRAB_MODE; + dr.value = continuousgrab; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_set_format(struct usb_device *dev, int size, int subsample, int order) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_SET_FORMAT; + dr.value = (subsample << 8) + size; + dr.index = order; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_set_compression(struct usb_device *dev, int compmode, int decimation) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_SET_COMPRESSION; + dr.value = (decimation << 8) + compmode; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_initstreamcap(struct usb_device *dev, int skipframes, int streamstartline) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_INIT_STREAM_CAP; + dr.value = (streamstartline << 8) + skipframes; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_finistreamcap(struct usb_device *dev) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_FINI_STREAM_CAP; + dr.value = 0; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_startstreamcap(struct usb_device *dev) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_START_STREAM_CAP; + dr.value = 0; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +int usb_cpia_endstreamcap(struct usb_device *dev) +{ + devrequest dr; + + dr.requesttype = (USB_TYPE_VENDOR | USB_RECIP_DEVICE); + dr.request = USB_REQ_CPIA_END_STREAM_CAP; + dr.value = 0; + dr.index = 0; + dr.length = 0; + + return dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +#define scratch_left(x) (cpia->scratchlen - (int)((char *)x - (char *)cpia->scratch)) + +static void cpia_parse_data(struct usb_cpia *cpia) +{ + unsigned char *data = cpia->scratch; + int done; + + done = 0; + while (!done && scratch_left(data)) { + switch (cpia->state) { + case STATE_SCANNING: + { + unsigned char *begin = data; + + /* We need atleast 2 bytes for the magic value */ + if (scratch_left(data) < 2) { + done = 1; + break; + } + + printk("header: %X\n", (*data << 8) + *(data + 1)); + if ((*data == 0x19) && (*(data + 1) == 0x68)) { + cpia->state = STATE_HEADER; + printk("moving to header\n"); + break; + } + + if (scratch_left(data) < 4) { + done = 1; + break; + } + + printk("Scanning for end of frame\n"); + while (scratch_left(data) >= 4) { + if ((*data == 0xFF) && + (*(data + 1) == 0xFF) && + (*(data + 2) == 0xFF) && + (*(data + 3) == 0xFF)) { + data += 4; + break; + } + data++; + } +printk("scan: scanned %d bytes\n", data-begin); + break; + } + case STATE_HEADER: + /* We need atleast 64 bytes for the header */ + if (scratch_left(data) < 64) { + done = 1; + break; + } + +printk("header: framerate %d\n", data[41]); + + data += 64; + + cpia->state = STATE_LINES; + + break; + case STATE_LINES: + { + unsigned char *begin = data; + int found = 0; + + while (scratch_left(data)) { + if (*data == 0xFD) { + data++; + found = 1; + break; + } else if ((*data == 0xFF) && + (scratch_left(data) >= 3) && + (*(data + 1) == 0xFF) && + (*(data + 2) == 0xFF) && + (*(data + 3) == 0xFF)) { + data+=4; + cpia->curline = 144; + found = 1; + break; + } + + data++; + } +#if 0 +printk("line %d: scanned %d bytes\n", cpia->curline, data-begin); +#endif +if (data-begin == 355 && cpia->frame[cpia->curframe].width != 64) { + int i; + char *f = cpia->frame[cpia->curframe].data, *b = begin; + +#if 0 +printk("copying\n"); +#endif + + b+=2; + f+=(cpia->frame[cpia->curframe].width*3)*cpia->curline; + for (i = 0; i < 176; i++) + f[(i * 3) + 0] = + f[(i * 3) + 1] = + f[(i * 3) + 2] = + b[(i * 2)]; +} + if (found) { + cpia->curline++; + if (cpia->curline >= 144) { + wake_up(&cpia->wq); + cpia->state = STATE_SCANNING; + cpia->curline = 0; + cpia->curframe = -1; + done = 1; + } + } else { + data = begin; + done = 1; + } + + break; + } + } + } + + { + int l; + + l = scratch_left(data); + memmove(cpia->scratch, data, l); + cpia->scratchlen = l; + } +} + +static int cpia_isoc_irq(int status, void *__buffer, void *dev_id) +{ + struct usb_cpia *cpia = dev_id; + struct usb_device *dev = cpia->dev; + struct cpia_sbuf *sbuf; + int i; + char *p; + + if (!cpia->streaming) { + printk("oops, not streaming, but interrupt\n"); + return 0; + } + + if (cpia->curframe < 0) { + if (cpia->frame[0].state == FRAME_READY) { + cpia->curframe = 0; + cpia->frame[0].state = FRAME_GRABBING; +printk("capturing to frame 0\n"); + } else if (cpia->frame[1].state == FRAME_READY) { + cpia->curframe = 1; + cpia->frame[1].state = FRAME_GRABBING; +printk("capturing to frame 1\n"); + } else +printk("no frame available\n"); + } + + sbuf = &cpia->sbuf[cpia->receivesbuf]; + + uhci_unsched_isochronous(dev, sbuf->isodesc); + + /* Do something to it now */ + sbuf->len = uhci_compress_isochronous(dev, sbuf->isodesc); + + if (sbuf->len) + printk("%d bytes received\n", sbuf->len); + + if (sbuf->len && cpia->curframe >= 0) { + if (sbuf->len > (SCRATCH_BUF_SIZE - cpia->scratchlen)) { + printk("overflow!\n"); + return 0; + } + memcpy(cpia->scratch + cpia->scratchlen, sbuf->data, sbuf->len); + cpia->scratchlen += sbuf->len; + + cpia_parse_data(cpia); + } + + /* Reschedule this block of Isochronous desc */ + uhci_sched_isochronous(dev, sbuf->isodesc, cpia->sbuf[(cpia->receivesbuf + 2) % 3].isodesc); + + /* Move to the next one */ + cpia->receivesbuf = (cpia->receivesbuf + 1) % 3; + + return 1; +} + +int cpia_init_isoc(struct usb_cpia *cpia) +{ + struct usb_device *dev = cpia->dev; + + cpia->receivesbuf = 0; + +#if 0 + cpia->parsesbuf = 0; + cpia->parsepos = 0; +#endif + cpia->scratchlen = 0; + cpia->curline = 0; + cpia->state = STATE_SCANNING; + + /* Allocate all of the memory necessary */ + cpia->sbuf[0].isodesc = uhci_alloc_isochronous(dev, usb_rcvisocpipe(dev,1), cpia->sbuf[0].data, STREAM_BUF_SIZE, 960, cpia_isoc_irq, cpia); + cpia->sbuf[1].isodesc = uhci_alloc_isochronous(dev, usb_rcvisocpipe(dev,1), cpia->sbuf[1].data, STREAM_BUF_SIZE, 960, cpia_isoc_irq, cpia); + cpia->sbuf[2].isodesc = uhci_alloc_isochronous(dev, usb_rcvisocpipe(dev,1), cpia->sbuf[2].data, STREAM_BUF_SIZE, 960, cpia_isoc_irq, cpia); + + printk("isodesc[0] @ %p\n", cpia->sbuf[0].isodesc); + printk("isodesc[1] @ %p\n", cpia->sbuf[1].isodesc); + printk("isodesc[2] @ %p\n", cpia->sbuf[2].isodesc); + + /* Schedule the queues */ + uhci_sched_isochronous(dev, cpia->sbuf[0].isodesc, NULL); + uhci_sched_isochronous(dev, cpia->sbuf[1].isodesc, cpia->sbuf[0].isodesc); + uhci_sched_isochronous(dev, cpia->sbuf[2].isodesc, cpia->sbuf[1].isodesc); + + if (usb_set_interface(cpia->dev, 1, 3)) { + printk("cpia_set_interface error\n"); + return -EINVAL; + } + + usb_cpia_startstreamcap(cpia->dev); + + cpia->streaming = 1; + + return 0; +} + +void cpia_stop_isoc(struct usb_cpia *cpia) +{ + struct usb_device *dev = cpia->dev; + + if (!cpia->streaming) + return; + + cpia->streaming = 0; + + /* Stop the streaming */ + usb_cpia_endstreamcap(cpia->dev); + + /* Set packet size to 0 */ + if (usb_set_interface(cpia->dev, 1, 0)) { + printk("cpia_set_interface error\n"); + return -EINVAL; + } + + /* Unschedule all of the iso td's */ + uhci_unsched_isochronous(dev, cpia->sbuf[2].isodesc); + uhci_unsched_isochronous(dev, cpia->sbuf[1].isodesc); + uhci_unsched_isochronous(dev, cpia->sbuf[0].isodesc); + + /* Delete them all */ + uhci_delete_isochronous(dev, cpia->sbuf[2].isodesc); + uhci_delete_isochronous(dev, cpia->sbuf[1].isodesc); + uhci_delete_isochronous(dev, cpia->sbuf[0].isodesc); +} + +/* Video 4 Linux API */ +static int cpia_open(struct video_device *dev, int flags) +{ + struct usb_cpia *cpia = (struct usb_cpia *)dev; + +printk("cpia_open\n"); + + cpia->fbuf = rvmalloc(2 * MAX_FRAME_SIZE); + if (!cpia->fbuf) + return -ENOMEM; + + cpia->frame[0].state = FRAME_DONE; + cpia->frame[1].state = FRAME_DONE; + + cpia->frame[0].data = cpia->fbuf; + cpia->frame[1].data = cpia->fbuf + MAX_FRAME_SIZE; + printk("frame [0] @ %p\n", cpia->frame[0].data); + printk("frame [1] @ %p\n", cpia->frame[1].data); + + cpia->sbuf[0].data = kmalloc(STREAM_BUF_SIZE, GFP_KERNEL); + if (!cpia->sbuf[0].data) + return -ENOMEM; + + cpia->sbuf[1].data = kmalloc(STREAM_BUF_SIZE, GFP_KERNEL); + if (!cpia->sbuf[1].data) + return -ENOMEM; + + cpia->sbuf[2].data = kmalloc(STREAM_BUF_SIZE, GFP_KERNEL); + if (!cpia->sbuf[2].data) + return -ENOMEM; + + printk("sbuf[0] @ %p\n", cpia->sbuf[0].data); + printk("sbuf[1] @ %p\n", cpia->sbuf[1].data); + printk("sbuf[2] @ %p\n", cpia->sbuf[2].data); + + cpia->curframe = -1; + cpia->receivesbuf = 0; + + usb_cpia_initstreamcap(cpia->dev, 0, 60); + + cpia_init_isoc(cpia); + + return 0; +} + +static void cpia_close(struct video_device *dev) +{ + struct usb_cpia *cpia = (struct usb_cpia *)dev; + +printk("cpia_close\n"); + + cpia_stop_isoc(cpia); + + usb_cpia_finistreamcap(cpia->dev); + + rvfree(cpia->fbuf, 2 * MAX_FRAME_SIZE); + + kfree(cpia->sbuf[2].data); + kfree(cpia->sbuf[1].data); + kfree(cpia->sbuf[0].data); +} + +static int cpia_init_done(struct video_device *dev) +{ + return 0; +} + +static long cpia_write(struct video_device *dev, const char *buf, unsigned long count, int noblock) +{ + return -EINVAL; +} + +#if 0 + if (usb_set_interface(dev, 1, 3)) { + printk("cpia_set_interface error\n"); + return -EINVAL; + } + + if (usb_cpia_grab_frame(dev, 0)) { + printk("cpia_grab_frame error\n"); + return -EINVAL; + } + + if (usb_cpia_upload_frame(dev, 0)) { + printk("cpia_upload_frame error\n"); + return -EINVAL; + } + + buf = cpia->ibuf; + uhci_receive_isochronous(dev, usb_rcvisocpipe(dev,1), buf, 176*144*4); + + { + printk("header magic: %X\n", (buf[0] << 8) + buf[1]); + + while ((buf[0] != 0x19) || (buf[1] != 0x68)) { + int i; + + printk("resync'ing\n"); + for (i=0;i<(176*144*4)-4;i++, buf++) + if ( + (buf[0] == 0xFF) && + (buf[1] == 0xFF) && + (buf[2] == 0xFF) && + (buf[3] == 0xFF)) { + buf+=4; + i+=4; + break; + } + + memmove(cpia->ibuf, buf, (176*144*4) - i); + uhci_receive_isochronous(dev, usb_rcvisocpipe(dev,1), cpia->ibuf + (176*144*4) - i, i); + buf = cpia->ibuf; + +#if 0 + printk("header magic: %X\n", (buf[0] << 8) + buf[1]); +#endif + } + + printk("size: %d, sample: %d, order: %d\n", buf[16], buf[17], buf[18]); + printk("comp: %d, decimation: %d\n", buf[28], buf[29]); + printk("roi: top left: %d, %d bottom right: %d, %d\n", + buf[26] * 4, buf[24] * 8, + buf[27] * 4, buf[25] * 8); + + printk("vm->frame: %d\n", vm->frame); + + { + int i, i1; + char *b = buf + 64, *fbuf = &cpia->fbuffer[MAX_FRAME_SIZE * (vm->frame & 1)]; + for (i=0;i<144;i++) { +#if 0 + printk("line len: %d\n", (b[1] << 8) + b[0]); +#endif + b += 2; + for (i1=0;i1<176;i1++) { + fbuf[(i * vm->width * 3) + (i1 * 3)] = + fbuf[(i * vm->width * 3) + (i1 * 3) + 1] = + fbuf[(i * vm->width * 3) + (i1 * 3) + 2] = + b[i1 * 2]; +#if 0 + *((short *)&fbuf[(i * vm->width * 2) + (i1 * 2)]) = + ((b[i1 * 2] >> 3) << 11) + ((b[i1 * 2] >> 2) << 6) + (b[i1 * 2] >> 3); +#endif + } + b += (176 * 2) + 1; + } + } + + } + + if (usb_set_interface(dev, 1, 0)) { + printk("cpia_set_interface error\n"); + return -EINVAL; + } +#endif + +static int cpia_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct usb_cpia *cpia = (struct usb_cpia *)dev; + + switch (cmd) { + case VIDIOCGCAP: + { + struct video_capability b; + + strcpy(b.name, "CPiA USB Camera"); + b.type = VID_TYPE_CAPTURE /* | VID_TYPE_SUBCAPTURE */; + b.channels = 1; + b.audios = 0; + b.maxwidth = 176 /* 352 */; + b.maxheight = 144 /* 240 */; + b.minwidth = 176 /* (Something small?) */; + b.minheight = 144 /* " " */; + + if (copy_to_user(arg, &b, sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if (v.channel != 0) + return -EINVAL; + + v.flags = 0; + v.tuners = 0; + v.type = VIDEO_TYPE_CAMERA; + strcpy(v.name, "Camera"); + + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSCHAN: + { + int v; + + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + if (v != 0) + return -EINVAL; + + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + if (v.tuner) + return -EINVAL; + + strcpy(v.name, "Format"); + + v.rangelow = 0; + v.rangehigh = 0; + v.flags = 0; + v.mode = VIDEO_MODE_AUTO; + + if (copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + + return 0; + } + case VIDIOCSTUNER: + { + struct video_tuner v; + + if (copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + + if (v.tuner) + return -EINVAL; + + if (v.mode != VIDEO_MODE_AUTO) + return -EINVAL; + + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p; + + p.colour = 0x8000; /* Damn British people :) */ + p.hue = 0x8000; + p.brightness = 180 << 8; /* XXX */ + p.contrast = 192 << 8; /* XXX */ + p.whiteness = 105 << 8; /* XXX */ +#if 0 + p.depth = 24; +#endif + p.depth = 16; + p.palette = VIDEO_PALETTE_YUYV; + + if (copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + + return 0; + } + case VIDIOCSPICT: + { + struct video_picture p; + + if (copy_from_user(&p, arg, sizeof(p))) + return -EFAULT; + +printk("Attempting to set palette %d, depth %d\n", p.palette, p.depth); + +#if 0 + if (p.palette != VIDEO_PALETTE_YUYV) + return -EINVAL; + if (p.depth != 16) + return -EINVAL; +#endif + + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + +printk("VIDIOCSWIN\n"); + if (copy_from_user(&vw, arg, sizeof(vw))) + return -EFAULT; + if (vw.flags) + return -EINVAL; + if (vw.clipcount) + return -EINVAL; + if (vw.height != 176) + return -EINVAL; + if (vw.width != 144) + return -EINVAL; + + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + +printk("VIDIOCGWIN\n"); + vw.x = 0; + vw.y = 0; + vw.width = 176; + vw.height = 144; + vw.chromakey = 0; + vw.flags = 0; + + if (copy_to_user(arg, &vw, sizeof(vw))) + return -EFAULT; + + return 0; + } + case VIDIOCGMBUF: + { + struct video_mbuf vm; + + memset(&vm, 0, sizeof(vm)); + vm.size = MAX_FRAME_SIZE * 2; + vm.frames = 2; + vm.offsets[0] = 0; + vm.offsets[1] = MAX_FRAME_SIZE; + + if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm))) + return -EFAULT; + + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + + if (copy_from_user((void *)&vm, (void *)arg, sizeof(vm))) + return -EFAULT; + +printk("MCAPTURE\n"); +printk("frame: %d, size: %dx%d, format: %d\n", vm.frame, vm.width, vm.height, vm.format); + + if (vm.format != VIDEO_PALETTE_RGB24) + return -EINVAL; + + if ((vm.frame != 0) && (vm.frame != 1)) + return -EINVAL; + + cpia->frame[vm.frame].width = vm.width; + cpia->frame[vm.frame].height = vm.height; + + /* Mark it as free */ + cpia->frame[vm.frame].state = FRAME_READY; + + return 0; + } + case VIDIOCSYNC: + { + int frame; + + if (copy_from_user((void *)&frame, arg, sizeof(int))) + return -EFAULT; + + printk("syncing to frame %d\n", frame); + switch (cpia->frame[frame].state) { + case FRAME_UNUSED: + return -EINVAL; + case FRAME_READY: + case FRAME_GRABBING: + interruptible_sleep_on(&cpia->wq); + case FRAME_DONE: + cpia->frame[frame].state = FRAME_UNUSED; + break; + } + printk("synced to frame %d\n", frame); + return 0; + } + case VIDIOCCAPTURE: + return -EINVAL; + case VIDIOCGFBUF: + return -EINVAL; + case VIDIOCSFBUF: + return -EINVAL; + case VIDIOCKEY: + return 0; + case VIDIOCGFREQ: + return -EINVAL; + case VIDIOCSFREQ: + return -EINVAL; + case VIDIOCGAUDIO: + return -EINVAL; + case VIDIOCSAUDIO: + return -EINVAL; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static long cpia_read(struct video_device *dev, char *buf, unsigned long count, int noblock) +{ + struct usb_cpia *cpia = (struct usb_cpia *)dev; + int len; + + printk("cpia_read: %d bytes\n", count); +#if 0 + len = cpia_capture(cpia, buf, count); + + return len; +#endif + return 0; +} + +static int cpia_mmap(struct video_device *dev, const char *adr, unsigned long size) +{ + struct usb_cpia *cpia = (struct usb_cpia *)dev; + unsigned long start = (unsigned long)adr; + unsigned long page, pos; + + printk("mmap: %d (%X) bytes\n", size, size); + if (size > (((2 * MAX_FRAME_SIZE) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) + return -EINVAL; + +#if 0 + if (!cpia->fbuffer) { + if ((cpia->fbuffer = rvmalloc(2 * MAX_FRAME_SIZE)) == NULL) + return -EINVAL; + } +#endif + + pos = (unsigned long)cpia->fbuf; + while (size > 0) + { + page = kvirt_to_phys(pos); + if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) + return -EAGAIN; + start+=PAGE_SIZE; + pos+=PAGE_SIZE; + if (size > PAGE_SIZE) + size-=PAGE_SIZE; + else + size=0; + } + + return 0; +} + +static struct video_device cpia_template = { + "CPiA USB Camera", + VID_TYPE_CAPTURE, + VID_HARDWARE_CPIA, + cpia_open, + cpia_close, + cpia_read, + cpia_write, + NULL, + cpia_ioctl, + cpia_mmap, + cpia_init_done, + NULL, + 0, + 0 +}; + +static void usb_cpia_configure(struct usb_cpia *cpia) +{ + struct usb_device *dev = cpia->dev; + unsigned char version[4]; + unsigned char pnpid[6]; + unsigned char camerastat[8]; + unsigned char *buf; + + usb_set_configuration(dev, dev->config[0].bConfigurationValue); + + if (usb_cpia_get_version(dev, version)) { + printk("cpia_get_version error\n"); + return; + } + + printk("cpia: Firmware v%d.%d, VC Hardware v%d.%d\n", + version[0], version[1], version[2], version[3]); + + if (usb_cpia_get_pnp_id(dev, pnpid)) { + printk("cpia_get_pnp_id error\n"); + return; + } + + printk("cpia: PnP Id: Vendor: %X, Product: %X, Revision: %X\n", + (pnpid[1] << 8) + pnpid[0], (pnpid[3] << 8) + pnpid[2], + (pnpid[5] << 8) + pnpid[4]); + + memcpy(&cpia->vdev, &cpia_template, sizeof(cpia_template)); + + init_waitqueue_head(&cpia->wq); + + if (video_register_device(&cpia->vdev, VFL_TYPE_GRABBER) == -1) { + printk("video_register_device failed\n"); + return; + } + + if (usb_cpia_goto_hi_power(dev)) { + printk("cpia_goto_hi_power error\n"); + return; + } + + if (usb_cpia_get_vp_version(dev, version)) { + printk("cpia_get_vp_version error\n"); + return; + } + + printk("cpia: VP v%d rev %d\n", version[0], version[1]); + printk("cpia: Camera Head ID %04X\n", (version[3] << 8) + version[2]); + + /* Turn off continuous grab */ + if (usb_cpia_set_grab_mode(dev, 1)) { + printk("cpia_set_grab_mode error\n"); + return; + } + + /* Set up the sensor to be 30fps */ + if (usb_cpia_set_sensor_fps(dev, 1, 0)) { + printk("cpia_set_sensor_fps error\n"); + return; + } + + /* Set video into QCIF mode, and order into YUYV mode */ + if (usb_cpia_set_format(dev, CPIA_QCIF, 1, CPIA_YUYV)) { + printk("cpia_set_format error\n"); + return; + } + + /* Turn off compression */ + if (usb_cpia_set_compression(dev, 0, 0)) { + printk("cpia_set_compression error\n"); + return; + } + +#if 0 + if (usb_cpia_grab_frame(dev, 0)) { + printk("cpia_grab_frame error\n"); + return; + } + + if (usb_cpia_upload_frame(dev, 1)) { + printk("cpia_upload_frame error\n"); + return; + } + + buf = (void *)__get_free_page(GFP_KERNEL); + + { + int i; + for (i=0;i<448;i++) + buf[i]=0; + } + uhci_receive_isochronous(dev, usb_rcvisocpipe(dev,1), buf, 448); + + { + int i; + for (i=0;i<448;i++) { + printk("%02X ", buf[i]); + if ((i % 16) == 15) + printk("\n"); + } + printk("\n"); + } + + free_page((unsigned long)buf); +#endif +} + +static int cpia_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_cpia *cpia; + + /* We don't handle multi-config cameras */ + if (dev->descriptor.bNumConfigurations != 1) + return -1; + +#if 0 + /* We don't handle multi-interface hubs */ + if (dev->config[0].bNumInterfaces != 1) + return -1; +#endif + + interface = &dev->config[0].altsetting[0].interface[0]; + + /* Is it a CPiA? */ +/* +Apr 24 17:49:04 bjorn kernel: Vendor: 0545 +Apr 24 17:49:04 bjorn kernel: Product: 8080 +*/ +/* + if (dev->descriptor.idVendor != 0x0545) + return -1; + if (dev->descriptor.idProduct != 0x8080) + return -1; + if (interface->bInterfaceClass != 0xFF) + return -1; + if (interface->bInterfaceSubClass != 0xFF) + return -1; +*/ + if (dev->descriptor.idVendor != 0x0553) + return -1; + if (dev->descriptor.idProduct != 0x0002) + return -1; + if (interface->bInterfaceClass != 0xFF) + return -1; + if (interface->bInterfaceSubClass != 0x00) + return -1; + +#if 0 + /* Multiple endpoints? What kind of mutant ninja-hub is this? */ + if (interface->bNumEndpoints != 1) + return -1; + + endpoint = &interface->endpoint[0]; + + /* Output endpoint? Curiousier and curiousier.. */ + if (!(endpoint->bEndpointAddress & 0x80)) + return -1; + + /* If it's not an interrupt endpoint, we'd better punt! */ + if ((endpoint->bmAttributes & 3) != 3) + return -1; +#endif + + /* We found a CPiA */ + printk("USB CPiA camera found\n"); + + if ((cpia = kmalloc(sizeof(*cpia), GFP_KERNEL)) == NULL) { + printk("couldn't kmalloc cpia struct\n"); + return -1; + } + + memset(cpia, 0, sizeof(*cpia)); + + dev->private = cpia; + cpia->dev = dev; + + usb_cpia_configure(cpia); + +#if 0 + usb_request_irq(dev, usb_rcvctrlpipe(dev, endpoint->bEndpointAddress), pport_irq, endpoint->bInterval, pport); +#endif + + return 0; +} + +static void cpia_disconnect(struct usb_device *dev) +{ + struct usb_cpia *cpia = dev->private; + + video_unregister_device(&cpia->vdev); + + /* Free the memory */ + kfree(cpia); +} + +static struct usb_driver cpia_driver = { + "cpia", + cpia_probe, + cpia_disconnect, + { NULL, NULL } +}; + +/* + * This should be a separate module. + */ +int usb_cpia_init(void) +{ + usb_register(&cpia_driver); + + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + return usb_cpia_init(); +} +void cleanup_module(void) +{ +} +#endif + diff --git a/drivers/usb/cpia.h b/drivers/usb/cpia.h new file mode 100644 index 000000000..51ff43e79 --- /dev/null +++ b/drivers/usb/cpia.h @@ -0,0 +1,139 @@ +#ifndef __LINUX_CPIA_H +#define __LINUX_CPIA_H + +#include <linux/list.h> + +#define USB_REQ_CPIA_GET_VERSION 0x01 +#define USB_REQ_CPIA_GET_PNP_ID 0x02 +#define USB_REQ_CPIA_GET_CAMERA_STATUS 0x03 +#define USB_REQ_CPIA_GOTO_HI_POWER 0x04 +#define USB_REQ_CPIA_GOTO_LO_POWER 0x05 +/* No 0x06 */ +#define USB_REQ_CPIA_GOTO_SUSPEND 0x07 +#define USB_REQ_CPIA_GOTO_PASS_THROUGH 0x08 +/* No 0x09 */ +#define USB_REQ_CPIA_MODIFY_CAMERA_STATUS 0x0A + +#define USB_REQ_CPIA_READ_VC_REGS 0x21 +#define USB_REQ_CPIA_WRITE_BC_REG 0x22 +#define USB_REQ_CPIA_READ_MC_PORTS 0x23 +#define USB_REQ_CPIA_WRITE_MC_PORT 0x24 +#define USB_REQ_CPIA_SET_BAUD_RATE 0x25 +#define USB_REQ_CPIA_SET_ECP_TIMING 0x26 +#define USB_REQ_CPIA_READ_IDATA 0x27 +#define USB_REQ_CPIA_WRITE_IDATA 0x28 +#define USB_REQ_CPIA_GENERIC_CALL 0x29 +#define USB_REQ_CPIA_I2CSTART 0x2A +#define USB_REQ_CPIA_I2CSTOP 0x2B +#define USB_REQ_CPIA_I2CWRITE 0x2C +#define USB_REQ_CPIA_I2CREAD 0x2D + +#define USB_REQ_CPIA_GET_VP_VERSION 0xA1 +#define USB_REQ_CPIA_SET_COLOUR_PARAMS 0xA3 +#define USB_REQ_CPIA_SET_EXPOSURE 0xA4 +/* No 0xA5 */ +#define USB_REQ_CPIA_SET_COLOUR_BALANCE 0xA6 +#define USB_REQ_CPIA_SET_SENSOR_FPS 0xA7 +#define USB_REQ_CPIA_SET_VP_DEFAULTS 0xA8 +#define USB_REQ_CPIA_SET_APCOR 0xA9 +#define USB_REQ_CPIA_SET_FLICKER_CTRL 0xAA +#define USB_REQ_CPIA_SET_VL_OFFSET 0xAB + +#define USB_REQ_CPIA_GET_COLOUR_PARAMETERS 0xB0 +#define USB_REQ_CPIA_GET_COLOUR_BALANCE 0xB1 +#define USB_REQ_CPIA_GET_EXPOSURE 0xB2 +#define USB_REQ_CPIA_SET_SENSOR_MATRIX 0xB3 + +#define USB_REQ_CPIA_COLOUR_BARS 0xBD +#define USB_REQ_CPIA_READ_VP_REGS 0xBE +#define USB_REQ_CPIA_WRITE_VP_REGS 0xBF + +#define USB_REQ_CPIA_GRAB_FRAME 0xC1 +#define USB_REQ_CPIA_UPLOAD_FRAME 0xC2 +#define USB_REQ_CPIA_SET_GRAB_MODE 0xC3 +#define USB_REQ_CPIA_INIT_STREAM_CAP 0xC4 +#define USB_REQ_CPIA_FINI_STREAM_CAP 0xC5 +#define USB_REQ_CPIA_START_STREAM_CAP 0xC6 +#define USB_REQ_CPIA_END_STREAM_CAP 0xC7 +#define USB_REQ_CPIA_SET_FORMAT 0xC8 +#define USB_REQ_CPIA_SET_ROI 0xC9 +#define USB_REQ_CPIA_SET_COMPRESSION 0xCA +#define USB_REQ_CPIA_SET_COMPRESSION_TARGET 0xCB +#define USB_REQ_CPIA_SET_YUV_THRESH 0xCC +#define USB_REQ_CPIA_SET_COMPRESSION_PARAMS 0xCD +#define USB_REQ_CPIA_DISCARD_FRAME 0xCE + +#define USB_REQ_CPIA_OUTPUT_RS232 0xE1 +#define USB_REQ_CPIA_ABORT_PROCESS 0xE4 +#define USB_REQ_CPIA_SET_DRAM_PAGE 0xE5 +#define USB_REQ_CPIA_START_DRAM_UPLOAD 0xE6 +#define USB_REQ_CPIA_START_DUMMY_STREAM 0xE8 +#define USB_REQ_CPIA_ABORT_STREAM 0xE9 +#define USB_REQ_CPIA_DOWNLOAD_DRAM 0xEA +/* #define USB_REQ_CPIA_NULL_CMD 0x?? */ + +#define CPIA_QCIF 0 +#define CPIA_CIF 1 + +#define CPIA_YUYV 0 +#define CPIA_UYVY 1 + +#define STREAM_BUF_SIZE (PAGE_SIZE * 4) + +#define SCRATCH_BUF_SIZE (STREAM_BUF_SIZE * 2) + +enum { + STATE_SCANNING, /* Scanning for start */ + STATE_HEADER, /* Parsing header */ + STATE_LINES, /* Parsing lines */ +}; + +struct usb_device; + +struct cpia_sbuf { + char *data; + int len; + void *isodesc; +}; + +enum { + FRAME_READY, /* Ready to grab into */ + FRAME_GRABBING, /* In the process of being grabbed into */ + FRAME_DONE, /* Finished grabbing, but not been synced yet */ + FRAME_UNUSED, /* Unused (no MCAPTURE) */ +}; + +struct cpia_frame { + char *data; + int width; + int height; + int state; +}; + +struct usb_cpia { + struct video_device vdev; + + /* Device structure */ + struct usb_device *dev; + + int streaming; + + char *fbuf; /* Videodev buffer area */ + + int curframe; + struct cpia_frame frame[2]; /* Double buffering */ + + int receivesbuf; /* Current receiving sbuf */ + struct cpia_sbuf sbuf[3]; /* Triple buffering */ + + int state; /* Current scanning state */ + int curline; + + char scratch[SCRATCH_BUF_SIZE]; + int scratchlen; + + wait_queue_head_t wq; +}; + +#endif + diff --git a/drivers/usb/keymap-mac.c b/drivers/usb/keymap-mac.c new file mode 100644 index 000000000..48fc25e9b --- /dev/null +++ b/drivers/usb/keymap-mac.c @@ -0,0 +1,50 @@ +unsigned char usb_kbd_map[256] = +{ + 0x00, 0x00, 0x00, 0x00, 0x80, 0x0b, 0x08, 0x02, + 0x0e, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, + + 0x2e, 0x2d, 0x1f, 0x23, 0x0c, 0x0f, 0x01, 0x11, + 0x20, 0x09, 0x0d, 0x07, 0x10, 0x06, 0x12, 0x13, + + 0x14, 0x15, 0x17, 0x16, 0x1a, 0x1c, 0x19, 0x1d, + 0x24, 0x35, 0x33, 0x30, 0x31, 0x1b, 0x18, 0x21, + + 0x1e, 0x2a, 0x00, 0x29, 0x27, 0x32, 0x2b, 0x2f, + 0x2c, 0x39, 0x7a, 0x78, 0x63, 0x76, 0x60, 0x61, + + 0x62, 0x64, 0x65, 0x6d, 0x67, 0x6f, 0x69, 0x6b, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x77, 0x79, 0x3c, + + 0x3b, 0x3d, 0x3e, 0x47, 0x4b, 0x43, 0x4e, 0x45, + 0x4c, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + + 0x5b, 0x5c, 0x52, 0x41, 0x00, 0x00, 0x00, 0x00, + 0x69, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x38, 0x3a, 0x37, 0x7d, 0x7b, 0x7c, 0x37, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/drivers/usb/maps/mac.map b/drivers/usb/maps/mac.map new file mode 100644 index 000000000..dd601f76d --- /dev/null +++ b/drivers/usb/maps/mac.map @@ -0,0 +1,350 @@ +# Kernel keymap for Macintoshes. This uses 7 modifier combinations. +keymaps 0-2,4-5,8,12 +# +# Fixups: +keycode 0x69 = Print_Screen +keycode 0x6b = F14 +keycode 0x37 = Window_R +# +#keycode 0x00 = a +# hack! +keycode 0x80 = a + altgr keycode 0x00 = Hex_A +keycode 0x01 = s +keycode 0x02 = d + altgr keycode 0x02 = Hex_D +keycode 0x03 = f + altgr keycode 0x03 = Hex_F +keycode 0x04 = h +keycode 0x05 = g +keycode 0x06 = z +keycode 0x07 = x +keycode 0x08 = c + altgr keycode 0x08 = Hex_C +keycode 0x09 = v +keycode 0x0a = +keycode 0x0b = b + altgr keycode 0x0b = Hex_B +keycode 0x0c = q +keycode 0x0d = w +keycode 0x0e = e + altgr keycode 0x0e = Hex_E +keycode 0x0f = r +keycode 0x10 = y +keycode 0x11 = t +keycode 0x12 = one exclam + alt keycode 0x12 = Meta_one +keycode 0x13 = two at at + control keycode 0x13 = nul + shift control keycode 0x13 = nul + alt keycode 0x13 = Meta_two +keycode 0x14 = three numbersign + control keycode 0x14 = Escape + alt keycode 0x14 = Meta_three +keycode 0x15 = four dollar dollar + control keycode 0x15 = Control_backslash + alt keycode 0x15 = Meta_four +keycode 0x16 = six asciicircum + control keycode 0x16 = Control_asciicircum + alt keycode 0x16 = Meta_six +keycode 0x17 = five percent + control keycode 0x17 = Control_bracketright + alt keycode 0x17 = Meta_five +keycode 0x18 = equal plus + alt keycode 0x18 = Meta_equal +keycode 0x19 = nine parenleft bracketright + alt keycode 0x19 = Meta_nine +keycode 0x1a = seven ampersand braceleft + control keycode 0x1a = Control_underscore + alt keycode 0x1a = Meta_seven +keycode 0x1b = minus underscore backslash + control keycode 0x1b = Control_underscore + shift control keycode 0x1b = Control_underscore + alt keycode 0x1b = Meta_minus +keycode 0x1c = eight asterisk bracketleft + control keycode 0x1c = Delete + alt keycode 0x1c = Meta_eight +keycode 0x1d = zero parenright braceright + alt keycode 0x1d = Meta_zero +keycode 0x1e = bracketright braceright asciitilde + control keycode 0x1e = Control_bracketright + alt keycode 0x1e = Meta_bracketright +keycode 0x1f = o +keycode 0x20 = u +keycode 0x21 = bracketleft braceleft + control keycode 0x21 = Escape + alt keycode 0x21 = Meta_bracketleft +keycode 0x22 = i +keycode 0x23 = p +keycode 0x24 = Return + alt keycode 0x24 = Meta_Control_m +keycode 0x25 = l +keycode 0x26 = j +keycode 0x27 = apostrophe quotedbl + control keycode 0x27 = Control_g + alt keycode 0x27 = Meta_apostrophe +keycode 0x28 = k +keycode 0x29 = semicolon colon + alt keycode 0x29 = Meta_semicolon +keycode 0x2a = backslash bar + control keycode 0x2a = Control_backslash + alt keycode 0x2a = Meta_backslash +keycode 0x2b = comma less + alt keycode 0x2b = Meta_comma +keycode 0x2c = slash question + control keycode 0x2c = Delete + alt keycode 0x2c = Meta_slash +keycode 0x2d = n +keycode 0x2e = m +keycode 0x2f = period greater + control keycode 0x2f = Compose + alt keycode 0x2f = Meta_period +keycode 0x30 = Tab Tab + alt keycode 0x30 = Meta_Tab +keycode 0x31 = space space + control keycode 0x31 = nul + alt keycode 0x31 = Meta_space +keycode 0x32 = grave asciitilde + control keycode 0x32 = nul + alt keycode 0x32 = Meta_grave +keycode 0x33 = Delete Delete + control keycode 0x33 = BackSpace + alt keycode 0x33 = Meta_Delete +keycode 0x34 = +keycode 0x35 = Escape Escape + alt keycode 0x35 = Meta_Escape +keycode 0x36 = Control +keycode 0x37 = Window +keycode 0x38 = Shift +keycode 0x39 = Caps_Lock +keycode 0x3a = Alt +keycode 0x3b = Left + alt keycode 0x3b = Decr_Console +keycode 0x3c = Right + alt keycode 0x3c = Incr_Console +keycode 0x3d = Down +keycode 0x3e = Up +keycode 0x3f = +keycode 0x40 = +keycode 0x41 = KP_Period +keycode 0x42 = +keycode 0x43 = KP_Multiply +keycode 0x44 = +keycode 0x45 = KP_Add +keycode 0x46 = +keycode 0x47 = Num_Lock +# shift keycode 0x47 = Bare_Num_Lock +keycode 0x48 = +keycode 0x49 = +keycode 0x4a = +keycode 0x4b = KP_Divide +keycode 0x4c = KP_Enter +keycode 0x4d = +keycode 0x4e = KP_Subtract +keycode 0x4f = +keycode 0x50 = +keycode 0x51 = +#keycode 0x51 = KP_Equals +keycode 0x52 = KP_0 + alt keycode 0x52 = Ascii_0 + altgr keycode 0x52 = Hex_0 +keycode 0x53 = KP_1 + alt keycode 0x53 = Ascii_1 + altgr keycode 0x53 = Hex_1 +keycode 0x54 = KP_2 + alt keycode 0x54 = Ascii_2 + altgr keycode 0x54 = Hex_2 +keycode 0x55 = KP_3 + alt keycode 0x55 = Ascii_3 + altgr keycode 0x55 = Hex_3 +keycode 0x56 = KP_4 + alt keycode 0x56 = Ascii_4 + altgr keycode 0x56 = Hex_4 +keycode 0x57 = KP_5 + alt keycode 0x57 = Ascii_5 + altgr keycode 0x57 = Hex_5 +keycode 0x58 = KP_6 + alt keycode 0x58 = Ascii_6 + altgr keycode 0x58 = Hex_6 +keycode 0x59 = KP_7 + alt keycode 0x59 = Ascii_7 + altgr keycode 0x59 = Hex_7 +keycode 0x5b = KP_8 + alt keycode 0x5b = Ascii_8 + altgr keycode 0x5b = Hex_8 +keycode 0x5c = KP_9 + alt keycode 0x5c = Ascii_9 + altgr keycode 0x5c = Hex_9 +keycode 0x5d = +keycode 0x5e = +keycode 0x5f = +keycode 0x60 = F5 F15 Console_17 + control keycode 0x60 = F5 + alt keycode 0x60 = Console_5 + control alt keycode 0x60 = Console_5 +keycode 0x61 = F6 F16 Console_18 + control keycode 0x61 = F6 + alt keycode 0x61 = Console_6 + control alt keycode 0x61 = Console_6 +keycode 0x62 = F7 F17 Console_19 + control keycode 0x62 = F7 + alt keycode 0x62 = Console_7 + control alt keycode 0x62 = Console_7 +keycode 0x63 = F3 F13 Console_15 + control keycode 0x63 = F3 + alt keycode 0x63 = Console_3 + control alt keycode 0x63 = Console_3 +keycode 0x64 = F8 F18 Console_20 + control keycode 0x64 = F8 + alt keycode 0x64 = Console_8 + control alt keycode 0x64 = Console_8 +keycode 0x65 = F9 F19 Console_21 + control keycode 0x65 = F9 + alt keycode 0x65 = Console_9 + control alt keycode 0x65 = Console_9 +keycode 0x66 = +keycode 0x67 = F11 F11 Console_23 + control keycode 0x67 = F11 + alt keycode 0x67 = Console_11 + control alt keycode 0x67 = Console_11 +keycode 0x68 = +keycode 0x69 = F13 +keycode 0x6a = +keycode 0x6b = Scroll_Lock Show_Memory Show_Registers + control keycode 0x6b = Show_State + alt keycode 0x6b = Scroll_Lock +keycode 0x6c = +keycode 0x6d = F10 F20 Console_22 + control keycode 0x6d = F10 + alt keycode 0x6d = Console_10 + control alt keycode 0x6d = Console_10 +keycode 0x6e = +keycode 0x6f = F12 F12 Console_24 + control keycode 0x6f = F12 + alt keycode 0x6f = Console_12 + control alt keycode 0x6f = Console_12 +keycode 0x70 = +keycode 0x71 = Pause +keycode 0x72 = Insert +keycode 0x73 = Home +keycode 0x74 = Prior + shift keycode 0x74 = Scroll_Backward +keycode 0x75 = Remove +keycode 0x76 = F4 F14 Console_16 + control keycode 0x76 = F4 + alt keycode 0x76 = Console_4 + control alt keycode 0x76 = Console_4 +keycode 0x77 = End +keycode 0x78 = F2 F12 Console_14 + control keycode 0x78 = F2 + alt keycode 0x78 = Console_2 + control alt keycode 0x78 = Console_2 +keycode 0x79 = Next + shift keycode 0x79 = Scroll_Forward +keycode 0x7a = F1 F11 Console_13 + control keycode 0x7a = F1 + alt keycode 0x7a = Console_1 + control alt keycode 0x7a = Console_1 +keycode 0x7b = Shift_R +keycode 0x7c = Alt_R +keycode 0x7d = Control_R +keycode 0x7e = +keycode 0x7f = +#keycode 0x7f = Power + control shift keycode 0x7f = Boot +string F1 = "\033[[A" +string F2 = "\033[[B" +string F3 = "\033[[C" +string F4 = "\033[[D" +string F5 = "\033[[E" +string F6 = "\033[17~" +string F7 = "\033[18~" +string F8 = "\033[19~" +string F9 = "\033[20~" +string F10 = "\033[21~" +string F11 = "\033[23~" +string F12 = "\033[24~" +string F13 = "\033[25~" +string F14 = "\033[26~" +string F15 = "\033[28~" +string F16 = "\033[29~" +string F17 = "\033[31~" +string F18 = "\033[32~" +string F19 = "\033[33~" +string F20 = "\033[34~" +string Find = "\033[1~" +string Insert = "\033[2~" +string Remove = "\033[3~" +string Select = "\033[4~" +string Prior = "\033[5~" +string Next = "\033[6~" +string Macro = "\033[M" +string Pause = "\033[P" +compose '`' 'A' to 'À' +compose '`' 'a' to 'à' +compose '\'' 'A' to 'Á' +compose '\'' 'a' to 'á' +compose '^' 'A' to 'Â' +compose '^' 'a' to 'â' +compose '~' 'A' to 'Ã' +compose '~' 'a' to 'ã' +compose '"' 'A' to 'Ä' +compose '"' 'a' to 'ä' +compose 'O' 'A' to 'Å' +compose 'o' 'a' to 'å' +compose '0' 'A' to 'Å' +compose '0' 'a' to 'å' +compose 'A' 'A' to 'Å' +compose 'a' 'a' to 'å' +compose 'A' 'E' to 'Æ' +compose 'a' 'e' to 'æ' +compose ',' 'C' to 'Ç' +compose ',' 'c' to 'ç' +compose '`' 'E' to 'È' +compose '`' 'e' to 'è' +compose '\'' 'E' to 'É' +compose '\'' 'e' to 'é' +compose '^' 'E' to 'Ê' +compose '^' 'e' to 'ê' +compose '"' 'E' to 'Ë' +compose '"' 'e' to 'ë' +compose '`' 'I' to 'Ì' +compose '`' 'i' to 'ì' +compose '\'' 'I' to 'Í' +compose '\'' 'i' to 'í' +compose '^' 'I' to 'Î' +compose '^' 'i' to 'î' +compose '"' 'I' to 'Ï' +compose '"' 'i' to 'ï' +compose '-' 'D' to 'Ð' +compose '-' 'd' to 'ð' +compose '~' 'N' to 'Ñ' +compose '~' 'n' to 'ñ' +compose '`' 'O' to 'Ò' +compose '`' 'o' to 'ò' +compose '\'' 'O' to 'Ó' +compose '\'' 'o' to 'ó' +compose '^' 'O' to 'Ô' +compose '^' 'o' to 'ô' +compose '~' 'O' to 'Õ' +compose '~' 'o' to 'õ' +compose '"' 'O' to 'Ö' +compose '"' 'o' to 'ö' +compose '/' 'O' to 'Ø' +compose '/' 'o' to 'ø' +compose '`' 'U' to 'Ù' +compose '`' 'u' to 'ù' +compose '\'' 'U' to 'Ú' +compose '\'' 'u' to 'ú' +compose '^' 'U' to 'Û' +compose '^' 'u' to 'û' +compose '"' 'U' to 'Ü' +compose '"' 'u' to 'ü' +compose '\'' 'Y' to 'Ý' +compose '\'' 'y' to 'ý' +compose 'T' 'H' to 'Þ' +compose 't' 'h' to 'þ' +compose 's' 's' to 'ß' +compose '"' 'y' to 'ÿ' +compose 's' 'z' to 'ß' +compose 'i' 'j' to 'ÿ' diff --git a/drivers/usb/mkmap.adb b/drivers/usb/mkmap.adb new file mode 100644 index 000000000..45020d5cc --- /dev/null +++ b/drivers/usb/mkmap.adb @@ -0,0 +1,88 @@ +#!/usr/bin/perl + +($ME = $0) =~ s|.*/||; + +$file = "maps/mac.map"; +$line = 1; +open(PC, $file) || die("$!"); +while(<PC>) +{ + if(/^\s*keycode\s+(\d+|0x[0-9a-fA-F]+)\s*=\s*(\S+)/) + { + my($idx) = $1; + my($sym) = $2; + if ($idx =~ "0x.*") { + $idx = hex($idx); + } else { + $idx = int($idx); + } + if(defined($map{uc($sym)})) + { + # print STDERR "$file:$line: warning: `$sym' redefined\n"; + } + $map{uc($sym)} = $idx; + } + $line++; +} +close(PC); + +# $file = "maps/fixup.map"; +# $line = 1; +# open(FIXUP, $file) || die("$!"); +# while(<FIXUP>) +# { +# if(/^\s*keycode\s+(\d+)\s*=\s*/) +# { +# my($idx) = int($1); +# for $sym (split(/\s+/, $')) +# { +# $map{uc($sym)} = $idx; +# } +# } +# $line++; +# } +# close(FIXUP); + +$file = "maps/usb.map"; +$line = 1; +open(USB, $file) || die("$!"); +while(<USB>) +{ + if(/^\s*keycode\s+(\d+)\s*=\s*/) + { + my($idx) = int($1); + for $sym (split(/\s+/, $')) + { + my($val) = $map{uc($sym)}; + $map[$idx] = $val; + if(!defined($val)) + { + print STDERR "$file:$line: warning: `$sym' undefined\n"; + } + else + { + last; + } + } + } + $line++; +} +close(USB); + +print "unsigned char usb_kbd_map[256] = \n{\n"; +for($x = 0; $x < 32; $x++) +{ + if($x && !($x % 2)) + { + print "\n"; + } + print " "; + for($y = 0; $y < 8; $y++) + { + my($idx) = $x * 8 + $y; + print sprintf(" 0x%02x,", + int(defined($map[$idx]) ? $map[$idx]:0)); + } + print "\n"; +} +print "};\n"; diff --git a/drivers/usb/printer.c b/drivers/usb/printer.c new file mode 100644 index 000000000..88723c6ff --- /dev/null +++ b/drivers/usb/printer.c @@ -0,0 +1,413 @@ + +/* Driver for USB Printers + * + * (C) Michael Gee (michael@linuxspecific.com) 1999 + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/random.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/malloc.h> +#include <linux/lp.h> + +#include <asm/spinlock.h> + +#include "usb.h" + +#define NAK_TIMEOUT (HZ) /* stall wait for printer */ +#define MAX_RETRY_COUNT ((60*60*HZ)/NAK_TIMEOUT) /* should not take 1 minute a page! */ + +#ifndef USB_PRINTER_MAJOR +#define USB_PRINTER_MAJOR 0 +#endif + +static int mymajor = USB_PRINTER_MAJOR; + +#define MAX_PRINTERS 8 + +struct pp_usb_data { + struct usb_device *pusb_dev; + __u8 isopen; /* nz if open */ + __u8 noinput; /* nz if no input stream */ + __u8 minor; /* minor number of device */ + __u8 status; /* last status from device */ + int maxin, maxout; /* max transfer size in and out */ + char *obuf; /* transfer buffer (out only) */ + wait_queue_head_t wait_q; /* for timeouts */ + unsigned int last_error; /* save for checking */ +}; + +static struct pp_usb_data *minor_data[MAX_PRINTERS]; + +#define PPDATA(x) ((struct pp_usb_data *)(x)) + +unsigned char printer_read_status(struct pp_usb_data *p) +{ + __u8 status; + devrequest dr; + struct usb_device *dev = p->pusb_dev; + + dr.requesttype = USB_TYPE_CLASS | USB_RT_INTERFACE | 0x80; + dr.request = 1; + dr.value = 0; + dr.index = 0; + dr.length = 1; + if (dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, &status, 1)) { + return 0; + } + return status; +} + +static int printer_check_status(struct pp_usb_data *p) +{ + unsigned int last = p->last_error; + unsigned char status = printer_read_status(p); + + if (status & LP_PERRORP) + /* No error. */ + last = 0; + else if ((status & LP_POUTPA)) { + if (last != LP_POUTPA) { + last = LP_POUTPA; + printk(KERN_INFO "usblp%d out of paper\n", p->minor); + } + } else if (!(status & LP_PSELECD)) { + if (last != LP_PSELECD) { + last = LP_PSELECD; + printk(KERN_INFO "usblp%d off-line\n", p->minor); + } + } else { + if (last != LP_PERRORP) { + last = LP_PERRORP; + printk(KERN_INFO "usblp%d on fire\n", p->minor); + } + } + + p->last_error = last; + + return status; +} + +void printer_reset(struct pp_usb_data *p) +{ + devrequest dr; + struct usb_device *dev = p->pusb_dev; + + dr.requesttype = USB_TYPE_CLASS | USB_RECIP_OTHER; + dr.request = 2; + dr.value = 0; + dr.index = 0; + dr.length = 0; + dev->bus->op->control_msg(dev, usb_sndctrlpipe(dev,0), &dr, NULL, 0); +} + +static int open_printer(struct inode * inode, struct file * file) +{ + struct pp_usb_data *p; + + if(MINOR(inode->i_rdev) >= MAX_PRINTERS || + !minor_data[MINOR(inode->i_rdev)]) { + return -ENODEV; + } + + p = minor_data[MINOR(inode->i_rdev)]; + p->minor = MINOR(inode->i_rdev); + + if (p->isopen++) { + return -EBUSY; + } + if (!(p->obuf = (char *)__get_free_page(GFP_KERNEL))) { + return -ENOMEM; + } + + printer_check_status(p); + + + file->private_data = p; +// printer_reset(p); + init_waitqueue_head(&p->wait_q); + return 0; +} + +static int close_printer(struct inode * inode, struct file * file) +{ + struct pp_usb_data *p = file->private_data; + + free_page((unsigned long)p->obuf); + p->isopen = 0; + file->private_data = NULL; + if(!p->pusb_dev) { + minor_data[p->minor] = NULL; + kfree(p); + + MOD_DEC_USE_COUNT; + + } + return 0; +} + +static ssize_t write_printer(struct file * file, + const char * buffer, size_t count, loff_t *ppos) +{ + struct pp_usb_data *p = file->private_data; + unsigned long copy_size; + unsigned long bytes_written = 0; + unsigned long partial; + int result; + int maxretry; + + do { + char *obuf = p->obuf; + unsigned long thistime; + + thistime = copy_size = (count > p->maxout) ? p->maxout : count; + if (copy_from_user(p->obuf, buffer, copy_size)) + return -EFAULT; + maxretry = MAX_RETRY_COUNT; + while (thistime) { + if (!p->pusb_dev) + return -ENODEV; + if (signal_pending(current)) { + return bytes_written ? bytes_written : -EINTR; + } + result = p->pusb_dev->bus->op->bulk_msg(p->pusb_dev, + usb_sndbulkpipe(p->pusb_dev, 1), obuf, thistime, &partial); + if (result == USB_ST_TIMEOUT) { /* NAK - so hold for a while */ + if(!maxretry--) + return -ETIME; + interruptible_sleep_on_timeout(&p->wait_q, NAK_TIMEOUT); + continue; + } else if (!result & partial) { + obuf += partial; + thistime -= partial; + } else + break; + }; + if (result) { + /* whoops - let's reset and fail the request */ +// printk("Whoops - %x\n", result); + printer_reset(p); + interruptible_sleep_on_timeout(&p->wait_q, 5*HZ); /* let reset do its stuff */ + return -EIO; + } + bytes_written += copy_size; + count -= copy_size; + buffer += copy_size; + } while ( count > 0 ); + + return bytes_written ? bytes_written : -EIO; +} + +static ssize_t read_printer(struct file * file, + char * buffer, size_t count, loff_t *ppos) +{ + struct pp_usb_data *p = file->private_data; + int read_count; + int this_read; + char buf[64]; + unsigned long partial; + int result; + + if (p->noinput) + return -EINVAL; + + read_count = 0; + while (count) { + if (signal_pending(current)) { + return read_count ? read_count : -EINTR; + } + if (!p->pusb_dev) + return -ENODEV; + this_read = (count > sizeof(buf)) ? sizeof(buf) : count; + + result = p->pusb_dev->bus->op->bulk_msg(p->pusb_dev, + usb_rcvbulkpipe(p->pusb_dev, 2), buf, this_read, &partial); + + /* unlike writes, we don't retry a NAK, just stop now */ + if (!result & partial) + count = this_read = partial; + else if (result) + return -EIO; + + if (this_read) { + if (copy_to_user(buffer, p->obuf, this_read)) + return -EFAULT; + count -= this_read; + read_count += this_read; + buffer += this_read; + } + } + return read_count; +} + +static int printer_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + int i; + + /* + * FIXME - this will not cope with combined printer/scanners + */ + if (dev->descriptor.bDeviceClass != 7 || + dev->descriptor.bNumConfigurations != 1 || + dev->config[0].bNumInterfaces != 1) { + return -1; + } + + interface = dev->config->altsetting->interface; + + /* Lets be paranoid (for the moment)*/ + if (interface->bInterfaceClass != 7 || + interface->bInterfaceSubClass != 1 || + (interface->bInterfaceProtocol != 2 && interface->bInterfaceProtocol != 1)|| + interface->bNumEndpoints > 2) { + return -1; + } + + if (interface->endpoint[0].bEndpointAddress != 0x01 || + interface->endpoint[0].bmAttributes != 0x02 || + (interface->bNumEndpoints > 1 && ( + interface->endpoint[1].bEndpointAddress != 0x82 || + interface->endpoint[1].bmAttributes != 0x02))) { + return -1; + } + + for (i=0; i<MAX_PRINTERS; i++) { + if (!minor_data[i]) + break; + } + if (i >= MAX_PRINTERS) { + return -1; + } + + printk(KERN_INFO "USB Printer found at address %d\n", dev->devnum); + + if (!(dev->private = kmalloc(sizeof(struct pp_usb_data), GFP_KERNEL))) { + printk( KERN_DEBUG "usb_printer: no memory!\n"); + return -1; + } + + memset(dev->private, 0, sizeof(struct pp_usb_data)); + minor_data[i] = PPDATA(dev->private); + minor_data[i]->minor = i; + minor_data[i]->pusb_dev = dev; + minor_data[i]->maxout = interface->endpoint[0].wMaxPacketSize * 16; + if (minor_data[i]->maxout > PAGE_SIZE) { + minor_data[i]->maxout = PAGE_SIZE; + } + if (interface->bInterfaceProtocol != 2) + minor_data[i]->noinput = 1; + else { + minor_data[i]->maxin = interface->endpoint[1].wMaxPacketSize; + } + + if (usb_set_configuration(dev, dev->config[0].bConfigurationValue)) { + printk(KERN_INFO " Failed to set configuration\n"); + return -1; + } +#if 0 + { + __u8 status; + __u8 ieee_id[64]; + devrequest dr; + + /* Lets get the device id if possible */ + dr.requesttype = USB_TYPE_CLASS | USB_RT_INTERFACE | 0x80; + dr.request = 0; + dr.value = 0; + dr.index = 0; + dr.length = sizeof(ieee_id) - 1; + if (dev->bus->op->control_msg(dev, usb_rcvctrlpipe(dev,0), &dr, ieee_id, sizeof(ieee_id)-1) == 0) { + if (ieee_id[1] < sizeof(ieee_id) - 1) + ieee_id[ieee_id[1]+2] = '\0'; + else + ieee_id[sizeof(ieee_id)-1] = '\0'; + printk(KERN_INFO " Printer ID is %s\n", &ieee_id[2]); + } + status = printer_read_status(PPDATA(dev->private)); + printk(KERN_INFO " Status is %s,%s,%s\n", + (status & 0x10) ? "Selected" : "Not Selected", + (status & 0x20) ? "No Paper" : "Paper", + (status & 0x08) ? "No Error" : "Error"); + } +#endif + return 0; +} + +static void printer_disconnect(struct usb_device *dev) +{ + struct pp_usb_data *pp = dev->private; + + if (pp->isopen) { + /* better let it finish - the release will do whats needed */ + pp->pusb_dev = NULL; + return; + } + minor_data[pp->minor] = NULL; + kfree(pp); + dev->private = NULL; /* just in case */ + MOD_DEC_USE_COUNT; +} + +static struct usb_driver printer_driver = { + "printer", + printer_probe, + printer_disconnect, + { NULL, NULL } +}; + +static struct file_operations usb_printer_fops = { + NULL, /* seek */ + read_printer, + write_printer, + NULL, /* readdir */ + NULL, /* poll - out for the moment */ + NULL, /* ioctl */ + NULL, /* mmap */ + open_printer, + NULL, /* flush ? */ + close_printer, + NULL, + NULL +}; + +int usb_printer_init(void) +{ + int result; + + MOD_INC_USE_COUNT; + + if ((result = register_chrdev(USB_PRINTER_MAJOR, "usblp", &usb_printer_fops)) < 0) { + printk(KERN_WARNING "usbprinter: Cannot register device\n"); + return result; + } + if (mymajor == 0) { + mymajor = result; + } + usb_register(&printer_driver); + printk(KERN_INFO "USB Printer support registered.\n"); + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + + return usb_printer_init(); +} + +void cleanup_module(void) +{ + unsigned int offset; + + usb_deregister(&printer_driver); + unregister_chrdev(mymajor, "usblplp"); +} +#endif diff --git a/drivers/usb/usb-core.c b/drivers/usb/usb-core.c new file mode 100644 index 000000000..f9f73a051 --- /dev/null +++ b/drivers/usb/usb-core.c @@ -0,0 +1,96 @@ +/* + * driver/usb/usb-core.c + * + * (C) Copyright David Waite 1999 + * based on code from usb.c, by Linus Torvolds + * + * The purpose of this file is to pull any and all generic modular code from + * usb.c and put it in a separate file. This way usb.c is kept as a generic + * library, while this file handles starting drivers, etc. + * + */ +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/module.h> + +#include "inits.h" +#include "usb.h" + +#ifndef CONFIG_USB_MODULE +# ifdef CONFIG_USB_UHCI + int uhci_init(void); +# endif +# ifdef CONFIG_USB_OHCI + int ohci_init(void); +# endif +# ifdef CONFIG_USB_OHCI_HCD + int ohci_hcd_init(void); +# endif +#endif + +int usb_init(void) +{ +#ifndef CONFIG_USB_MODULE +# ifdef CONFIG_USB_UHCI + uhci_init(); +# endif +# ifdef CONFIG_USB_OHCI + ohci_init(); +# endif +# ifdef CONFIG_USB_OHCI_HCD + ohci_hcd_init(); +# endif +# ifdef CONFIG_USB_MOUSE + usb_mouse_init(); +# endif +# ifdef CONFIG_USB_KBD + usb_kbd_init(); +# endif +# ifdef CONFIG_USB_AUDIO + usb_audio_init(); +# endif +# ifdef CONFIG_USB_ACM + usb_acm_init(); +# endif +# ifdef CONFIG_USB_PRINTER + usb_print_init(); +# endif +# ifdef CONFIG_USB_CPIA + usb_cpia_init(); +# endif +# ifdef CONFIG_USB_HUB + usb_hub_init(); +# endif +# ifdef CONFIG_USB_SCSI + usb_scsi_init(); +# endif +#endif + return 0; +} +/* + * Clean up when unloading the module + */ +void cleanup_drivers(void) +{ +#ifndef MODULE +# ifdef CONFIG_USB_HUB + usb_hub_cleanup(); +# endif +# ifdef CONFIG_USB_MOUSE + usb_mouse_cleanup(); +# endif +#endif +} + +#ifdef MODULE +int init_module(void) +{ + return usb_init(); +} +void cleanup_module(void) +{ + cleanup_drivers(); +} +#endif + + diff --git a/drivers/usb/usb_scsi.c b/drivers/usb/usb_scsi.c new file mode 100644 index 000000000..655045bea --- /dev/null +++ b/drivers/usb/usb_scsi.c @@ -0,0 +1,1098 @@ + +/* Driver for USB scsi like devices + * + * (C) Michael Gee (michael@linuxspecific.com) 1999 + * + * This driver is scitzoid - it makes a USB device appear as both a SCSI device + * and a character device. The latter is only available if the device has an + * interrupt endpoint, and is used specifically to receive interrupt events. + * + * In order to support various 'strange' devices, this module supports plug in + * device specific filter modules, which can do their own thing when required. + * + * Further reference. + * This driver is based on the 'USB Mass Storage Class' document. This + * describes in detail the transformation of SCSI command blocks to the + * equivalent USB control and data transfer required. + * It is important to note that in a number of cases this class exhibits + * class-specific exemptions from the USB specification. Notably the + * usage of NAK, STALL and ACK differs from the norm, in that they are + * used to communicate wait, failed and OK on SCSI commands. + * Also, for certain devices, the interrupt endpoint is used to convey + * status of a command. + * + * Basically, this stuff is WIERD!! + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/random.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/malloc.h> + +#include <asm/spinlock.h> +#include <linux/smp_lock.h> + +#include <linux/blk.h> +#include "../scsi/scsi.h" +#include "../scsi/hosts.h" +#include "../scsi/sd.h" + +#include "usb.h" +#include "usb_scsi.h" + +/* direction table (what a pain) */ + +unsigned char us_direction[256/8] = { + +#include "usb_scsi_dt.c" + +}; + +/* + * Per device data + */ + +static int my_host_number; + +int usbscsi_debug = 1; + +struct us_data { + struct us_data *next; /* next device */ + struct usb_device *pusb_dev; + struct usb_scsi_filter *filter; /* filter driver */ + void *fdata; /* filter data */ + unsigned int flags; /* from filter initially*/ + __u8 ep_in; /* in endpoint */ + __u8 ep_out; /* out ....... */ + __u8 ep_int; /* interrupt . */ + __u8 subclass; /* as in overview */ + __u8 protocol; /* .............. */ + int (*pop)(Scsi_Cmnd *); /* protocol specific do cmd */ + GUID(guid); /* unique dev id */ + struct Scsi_Host *host; /* our dummy host data */ + Scsi_Host_Template *htmplt; /* own host template */ + int host_number; /* to find us */ + int host_no; /* allocated by scsi */ + int fixedlength; /* expand commands */ + Scsi_Cmnd *srb; /* current srb */ + int action; /* what to do */ + wait_queue_head_t waitq; /* thread waits */ + wait_queue_head_t ip_waitq; /* for CBI interrupts */ + __u16 ip_data; /* interrupt data */ + int ip_wanted; /* needed */ + int pid; /* control thread */ + struct semaphore *notify; /* wait for thread to begin */ +}; + +/* + * kernel thread actions + */ + +#define US_ACT_COMMAND 1 +#define US_ACT_ABORT 2 +#define US_ACT_DEVICE_RESET 3 +#define US_ACT_BUS_RESET 4 +#define US_ACT_HOST_RESET 5 + +static struct proc_dir_entry proc_usb_scsi = +{ + PROC_SCSI_USB_SCSI, + 0, + NULL, + S_IFDIR | S_IRUGO | S_IXUGO, + 2 +}; + +static struct us_data *us_list; + +static struct usb_scsi_filter *filters; + +static int scsi_probe(struct usb_device *dev); +static void scsi_disconnect(struct usb_device *dev); +static struct usb_driver scsi_driver = { + "usb_scsi", + scsi_probe, + scsi_disconnect, + { NULL, NULL } +}; + +/* Data handling, using SG if required */ + +static int us_one_transfer(struct us_data *us, int pipe, char *buf, int length) +{ + int max_size = usb_maxpacket(us->pusb_dev, pipe) * 16; + int this_xfer; + int result; + unsigned long partial; + int maxtry = 100; + while (length) { + this_xfer = length > max_size ? max_size : length; + length -= this_xfer; + do { + US_DEBUGP("Bulk xfer %x(%d)\n", (unsigned int)buf, this_xfer); + result = us->pusb_dev->bus->op->bulk_msg(us->pusb_dev, pipe, buf, + this_xfer, &partial); + + /* we want to retry if the device reported NAK */ + if (result == USB_ST_TIMEOUT) { + if (!maxtry--) + break; + this_xfer -= partial; + buf += partial; + } else if (!result && partial != this_xfer) { + /* short data - assume end */ + result = USB_ST_DATAUNDERRUN; + break; + } else + break; + } while ( this_xfer ); + if (result) + return result; + buf += this_xfer; + } + return 0; + +} +static int us_transfer(Scsi_Cmnd *srb, int dir_in) +{ + struct us_data *us = (struct us_data *)srb->host_scribble; + int i; + int result = -1; + + if (srb->use_sg) { + struct scatterlist *sg = (struct scatterlist *) srb->request_buffer; + + for (i = 0; i < srb->use_sg; i++) { + result = us_one_transfer(us, dir_in ? usb_rcvbulkpipe(us->pusb_dev, us->ep_in) : + usb_sndbulkpipe(us->pusb_dev, us->ep_out), + sg[i].address, sg[i].length); + if (result) + break; + } + return result; + } + else + return us_one_transfer(us, dir_in ? usb_rcvbulkpipe(us->pusb_dev, us->ep_in) : + usb_sndbulkpipe(us->pusb_dev, us->ep_out), + srb->request_buffer, srb->request_bufflen); +} + +static unsigned int us_transfer_length(Scsi_Cmnd *srb) +{ + int i; + unsigned int total = 0; + + /* always zero for some commands */ + switch (srb->cmnd[0]) { + case SEEK_6: + case SEEK_10: + case REZERO_UNIT: + case ALLOW_MEDIUM_REMOVAL: + case START_STOP: + case TEST_UNIT_READY: + return 0; + + default: + break; + } + + if (srb->use_sg) { + struct scatterlist *sg = (struct scatterlist *) srb->request_buffer; + + for (i = 0; i < srb->use_sg; i++) { + total += sg[i].length; + } + return total; + } + else + return srb->request_bufflen; + +} + +static int pop_CBI_irq(int state, void *buffer, void *dev_id) +{ + struct us_data *us = (struct us_data *)dev_id; + + if (state != USB_ST_REMOVED) { + us->ip_data = *(__u16 *)buffer; + us->ip_wanted = 0; + } + wake_up(&us->ip_waitq); + + /* we dont want another interrupt */ + + return 0; +} +static int pop_CB_command(Scsi_Cmnd *srb) +{ + struct us_data *us = (struct us_data *)srb->host_scribble; + devrequest dr; + unsigned char cmd[16]; + int result; + int retry = 1; + int done_start = 0; + + while (retry--) { + dr.requesttype = USB_TYPE_CLASS | USB_RT_INTERFACE; + dr.request = US_CBI_ADSC; + dr.value = 0; + dr.index = us->pusb_dev->ifnum; + dr.length = srb->cmd_len; + + if (us->flags & US_FL_FIXED_COMMAND) { + dr.length = us->fixedlength; + memset(cmd, 0, us->fixedlength); + + /* fix some commands */ + + switch (srb->cmnd[0]) { + case WRITE_6: + case READ_6: + cmd[0] = srb->cmnd[0] | 0x20; + cmd[1] = srb->cmnd[1] & 0xE0; + cmd[2] = 0; + cmd[3] = srb->cmnd[1] & 0x1F; + cmd[4] = srb->cmnd[2]; + cmd[5] = srb->cmnd[3]; + cmd[8] = srb->cmnd[4]; + break; + + case MODE_SENSE: + case MODE_SELECT: + cmd[0] = srb->cmnd[0] | 0x40; + cmd[1] = srb->cmnd[1]; + cmd[2] = srb->cmnd[2]; + cmd[8] = srb->cmnd[4]; + break; + + default: + memcpy(cmd, srb->cmnd, srb->cmd_len); + break; + } + result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, + usb_sndctrlpipe(us->pusb_dev,0), + &dr, cmd, us->fixedlength); + if (!done_start && us->subclass == US_SC_UFI && cmd[0] == TEST_UNIT_READY && result) { + /* as per spec try a start command, wait and retry */ + + done_start++; + cmd[0] = START_STOP; + cmd[4] = 1; /* start */ + result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, + usb_sndctrlpipe(us->pusb_dev,0), + &dr, cmd, us->fixedlength); + wait_ms(100); + retry++; + continue; + } + } else + result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, + usb_sndctrlpipe(us->pusb_dev,0), + &dr, srb->cmnd, srb->cmd_len); + if (result != USB_ST_STALL && result != USB_ST_TIMEOUT) + return result; + } + return result; +} + +/* Protocol command handlers */ + +static int pop_CBI(Scsi_Cmnd *srb) +{ + struct us_data *us = (struct us_data *)srb->host_scribble; + int result; + + /* run the command */ + + if ((result = pop_CB_command(srb))) { + US_DEBUGP("CBI command %x\n", result); + if (result == USB_ST_STALL || result == USB_ST_TIMEOUT) + return (DID_OK << 16) | 2; + return DID_ABORT << 16; + } + + /* transfer the data */ + + if (us_transfer_length(srb)) { + result = us_transfer(srb, US_DIRECTION(srb->cmnd[0])); + if (result && result != USB_ST_DATAUNDERRUN) { + US_DEBUGP("CBI transfer %x\n", result); + return DID_ABORT << 16; + } + } + + /* get status */ + + if (us->protocol == US_PR_CBI) { + /* get from interrupt pipe */ + + /* add interrupt transfer, marked for removal */ + us->ip_wanted = 1; + result = us->pusb_dev->bus->op->request_irq(us->pusb_dev, + usb_rcvctrlpipe(us->pusb_dev, us->ep_int), + pop_CBI_irq, 0, (void *)us); + if (result) { + US_DEBUGP("No interrupt for CBI %x\n", result); + return DID_ABORT << 16; + } + sleep_on(&us->ip_waitq); + if (us->ip_wanted) { + US_DEBUGP("Did not get interrupt on CBI\n"); + us->ip_wanted = 0; + return DID_ABORT << 16; + } + + US_DEBUGP("Got interrupt data %x\n", us->ip_data); + + /* sort out what it means */ + + if (us->subclass == US_SC_UFI) { + /* gives us asc and ascq, as per request sense */ + + if (srb->cmnd[0] == REQUEST_SENSE || + srb->cmnd[0] == INQUIRY) + return DID_OK << 16; + else + return (DID_OK << 16) + ((us->ip_data & 0xff) ? 2 : 0); + } + if (us->ip_data & 0xff) { + US_DEBUGP("Bad CBI interrupt data %x\n", us->ip_data); + return DID_ABORT << 16; + } + return (DID_OK << 16) + ((us->ip_data & 0x300) ? 2 : 0); + } else { + /* get from where? */ + } + return DID_ERROR << 16; +} + +static int pop_Bulk_reset(struct us_data *us) +{ + devrequest dr; + int result; + + dr.requesttype = USB_TYPE_CLASS | USB_RT_INTERFACE; + dr.request = US_BULK_RESET; + dr.value = US_BULK_RESET_SOFT; + dr.index = 0; + dr.length = 0; + + US_DEBUGP("Bulk soft reset\n"); + result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, usb_sndctrlpipe(us->pusb_dev,0), &dr, NULL, 0); + if (result) { + US_DEBUGP("Bulk soft reset failed %d\n", result); + dr.value = US_BULK_RESET_HARD; + result = us->pusb_dev->bus->op->control_msg(us->pusb_dev, usb_sndctrlpipe(us->pusb_dev,0), &dr, NULL, 0); + if (result) + US_DEBUGP("Bulk hard reset failed %d\n", result); + } + usb_clear_halt(us->pusb_dev, us->ep_in | 0x80); + usb_clear_halt(us->pusb_dev, us->ep_out); + return result; +} +/* + * The bulk only protocol handler. + * Uses the in and out endpoints to transfer commands and data (nasty) + */ +static int pop_Bulk(Scsi_Cmnd *srb) +{ + struct us_data *us = (struct us_data *)srb->host_scribble; + struct bulk_cb_wrap bcb; + struct bulk_cs_wrap bcs; + int result; + unsigned long partial; + int stall; + + /* set up the command wrapper */ + + bcb.Signature = US_BULK_CB_SIGN; + bcb.DataTransferLength = us_transfer_length(srb);; + bcb.Flags = US_DIRECTION(srb->cmnd[0]) << 7; + bcb.Tag = srb->serial_number; + bcb.Lun = 0; + memset(bcb.CDB, 0, sizeof(bcb.CDB)); + memcpy(bcb.CDB, srb->cmnd, srb->cmd_len); + if (us->flags & US_FL_FIXED_COMMAND) { + bcb.Length = us->fixedlength; + } else { + bcb.Length = srb->cmd_len; + } + + /* send it to out endpoint */ + + US_DEBUGP("Bulk command S %x T %x L %d F %d CL %d\n", bcb.Signature, + bcb.Tag, bcb.DataTransferLength, bcb.Flags, bcb.Length); + result = us->pusb_dev->bus->op->bulk_msg(us->pusb_dev, + usb_sndbulkpipe(us->pusb_dev, us->ep_out), &bcb, + US_BULK_CB_WRAP_LEN, &partial); + if (result) { + US_DEBUGP("Bulk command result %x\n", result); + return DID_ABORT << 16; + } + + //return DID_BAD_TARGET << 16; + /* send/receive data */ + + if (bcb.DataTransferLength) { + result = us_transfer(srb, bcb.Flags); + if (result && result != USB_ST_DATAUNDERRUN && result != USB_ST_STALL) { + US_DEBUGP("Bulk transfer result %x\n", result); + return DID_ABORT << 16; + } + } + + /* get status */ + + + stall = 0; + do { + //usb_settoggle(us->pusb_dev, us->ep_in, 0); /* AAARgh!! */ + US_DEBUGP("Toggle is %d\n", usb_gettoggle(us->pusb_dev, us->ep_in)); + result = us->pusb_dev->bus->op->bulk_msg(us->pusb_dev, + usb_rcvbulkpipe(us->pusb_dev, us->ep_in), &bcs, + US_BULK_CS_WRAP_LEN, &partial); + if (result == USB_ST_STALL || result == USB_ST_TIMEOUT) + stall++; + else + break; + } while ( stall < 3); + if (result && result != USB_ST_DATAUNDERRUN) { + US_DEBUGP("Bulk status result = %x\n", result); + return DID_ABORT << 16; + } + + /* check bulk status */ + + US_DEBUGP("Bulk status S %x T %x R %d V %x\n", bcs.Signature, bcs.Tag, + bcs.Residue, bcs.Status); + if (bcs.Signature != US_BULK_CS_SIGN || bcs.Tag != bcb.Tag || + bcs.Status > US_BULK_STAT_PHASE) { + US_DEBUGP("Bulk logical error\n"); + return DID_ABORT << 16; + } + switch (bcs.Status) { + case US_BULK_STAT_OK: + return DID_OK << 16; + + case US_BULK_STAT_FAIL: + /* check for underrun - dont report */ + if (bcs.Residue) + return DID_OK << 16; + //pop_Bulk_reset(us); + break; + + case US_BULK_STAT_PHASE: + return DID_ERROR << 16; + } + return (DID_OK << 16) | 2; /* check sense required */ + +} + +/* Host functions */ + +/* detect adapter (always true ) */ +static int us_detect(struct SHT *sht) +{ + /* FIXME - not nice at all, but how else ? */ + struct us_data *us = (struct us_data *)sht->proc_dir; + char name[32]; + + sprintf(name, "usbscsi%d", us->host_number); + proc_usb_scsi.namelen = strlen(name); + proc_usb_scsi.name = kmalloc(proc_usb_scsi.namelen+1, GFP_KERNEL); + if (!proc_usb_scsi.name) + return 0; + strcpy((char *)proc_usb_scsi.name, name); + sht->proc_dir = kmalloc(sizeof(*sht->proc_dir), GFP_KERNEL); + if (!sht->proc_dir) { + kfree(proc_usb_scsi.name); + return 0; + } + *sht->proc_dir = proc_usb_scsi; + sht->name = proc_usb_scsi.name; + us->host = scsi_register(sht, sizeof(us)); + if (us->host) { + us->host->hostdata[0] = (unsigned long)us; + us->host_no = us->host->host_no; + return 1; + } + kfree(proc_usb_scsi.name); + kfree(sht->proc_dir); + return 0; +} + +/* release - must be here to stop scsi + * from trying to release IRQ etc. + * Kill off our data + */ +static int us_release(struct Scsi_Host *psh) +{ + struct us_data *us = (struct us_data *)psh->hostdata[0]; + struct us_data *prev = (struct us_data *)&us_list; + + if (us->filter) + us->filter->release(us->fdata); + if (us->pusb_dev) + usb_deregister(&scsi_driver); + + /* FIXME - leaves hanging host template copy */ + /* (bacause scsi layer uses it after removal !!!) */ + while(prev->next != us) + prev = prev->next; + prev->next = us->next; + return 0; +} + +/* run command */ +static int us_command( Scsi_Cmnd *srb ) +{ + US_DEBUGP("Bad use of us_command\n"); + + return DID_BAD_TARGET << 16; +} + +/* run command */ +static int us_queuecommand( Scsi_Cmnd *srb , void (*done)(Scsi_Cmnd *)) +{ + struct us_data *us = (struct us_data *)srb->host->hostdata[0]; + + US_DEBUGP("Command wakeup\n"); + srb->host_scribble = (unsigned char *)us; + us->srb = srb; + srb->scsi_done = done; + us->action = US_ACT_COMMAND; + + /* wake up the process task */ + + wake_up_interruptible(&us->waitq); + + return 0; +} + +static int us_abort( Scsi_Cmnd *srb ) +{ + return 0; +} + +static int us_device_reset( Scsi_Cmnd *srb ) +{ + return 0; +} + +static int us_host_reset( Scsi_Cmnd *srb ) +{ + return 0; +} + +static int us_bus_reset( Scsi_Cmnd *srb ) +{ + return 0; +} + +#undef SPRINTF +#define SPRINTF(args...) { if (pos < (buffer + length)) pos += sprintf (pos, ## args); } + +int usb_scsi_proc_info (char *buffer, char **start, off_t offset, int length, int hostno, int inout) +{ + struct us_data *us = us_list; + char *pos = buffer; + char *vendor; + char *product; + char *style = ""; + + /* find our data from hostno */ + + while (us) { + if (us->host_no == hostno) + break; + us = us->next; + } + + if (!us) + return -ESRCH; + + /* null on outward */ + + if (inout) + return length; + + if (!(vendor = usb_string(us->pusb_dev, us->pusb_dev->descriptor.iManufacturer))) + vendor = "?"; + if (!(product = usb_string(us->pusb_dev, us->pusb_dev->descriptor.iProduct))) + product = "?"; + + switch (us->protocol) { + case US_PR_CB: + style = "Control/Bulk"; + break; + + case US_PR_CBI: + style = "Control/Bulk/Interrupt"; + break; + + case US_PR_ZIP: + style = "Bulk only"; + break; + + } + SPRINTF ("Host scsi%d: usb-scsi\n", hostno); + SPRINTF ("Device: %s %s - GUID " GUID_FORMAT "\n", vendor, product, GUID_ARGS(us->guid) ); + SPRINTF ("Style: %s\n", style); + + /* + * Calculate start of next buffer, and return value. + */ + *start = buffer + offset; + + if ((pos - buffer) < offset) + return (0); + else if ((pos - buffer - offset) < length) + return (pos - buffer - offset); + else + return (length); +} + +/* + * this defines our 'host' + */ + +static Scsi_Host_Template my_host_template = { + NULL, /* next */ + NULL, /* module */ + NULL, /* proc_dir */ + usb_scsi_proc_info, + NULL, /* name - points to unique */ + us_detect, + us_release, + NULL, /* info */ + NULL, /* ioctl */ + us_command, + us_queuecommand, + NULL, /* eh_strategy */ + us_abort, + us_device_reset, + us_bus_reset, + us_host_reset, + NULL, /* abort */ + NULL, /* reset */ + NULL, /* slave_attach */ + NULL, /* bios_param */ + 1, /* can_queue */ + -1, /* this_id */ + SG_ALL, /* sg_tablesize */ + 1, /* cmd_per_lun */ + 0, /* present */ + FALSE, /* unchecked_isa_dma */ + FALSE, /* use_clustering */ + TRUE, /* use_new_eh_code */ + TRUE /* emulated */ +}; + +static int usbscsi_control_thread(void * __us) +{ + struct us_data *us = (struct us_data *)__us; + int action; + + lock_kernel(); + + /* + * This thread doesn't need any user-level access, + * so get rid of all our resources.. + */ + exit_mm(current); + exit_files(current); + //exit_fs(current); + + sprintf(current->comm, "usbscsi%d", us->host_no); + + unlock_kernel(); + + up(us->notify); + + for(;;) { + siginfo_t info; + int unsigned long signr; + + interruptible_sleep_on(&us->waitq); + + action = us->action; + us->action = 0; + + switch (action) { + case US_ACT_COMMAND : + if (!us->pusb_dev || us->srb->target || us->srb->lun) { + /* bad device */ + US_DEBUGP( "Bad device number (%d/%d) or dev %x\n", us->srb->target, us->srb->lun, (unsigned int)us->pusb_dev); + us->srb->result = DID_BAD_TARGET << 16; + } else { + US_DEBUG(us_show_command(us->srb)); + if (us->filter && us->filter->command) + us->srb->result = us->filter->command(us->fdata, us->srb); + else + us->srb->result = us->pop(us->srb); + } + us->srb->scsi_done(us->srb); + break; + + case US_ACT_ABORT : + break; + + case US_ACT_DEVICE_RESET : + break; + + case US_ACT_BUS_RESET : + break; + + case US_ACT_HOST_RESET : + break; + + } + + if(signal_pending(current)) { + /* sending SIGUSR1 makes us print out some info */ + spin_lock_irq(¤t->sigmask_lock); + signr = dequeue_signal(¤t->blocked, &info); + spin_unlock_irq(¤t->sigmask_lock); + + if (signr == SIGUSR2) { + printk("USBSCSI debug toggle\n"); + usbscsi_debug = !usbscsi_debug; + } else { + break; + } + } + } + + MOD_DEC_USE_COUNT; + + printk("usbscsi_control_thread exiting\n"); + + return 0; +} + +static int scsi_probe(struct usb_device *dev) +{ + struct usb_interface_descriptor *interface; + int i; + char *mf; /* manufacturer */ + char *prod; /* product */ + char *serial; /* serial number */ + struct us_data *ss = NULL; + struct usb_scsi_filter *filter = filters; + void *fdata = NULL; + unsigned int flags = 0; + GUID(guid); + struct us_data *prev; + Scsi_Host_Template *htmplt; + int protocol = 0; + int subclass = 0; + + GUID_CLEAR(guid); + mf = usb_string(dev, dev->descriptor.iManufacturer); + prod = usb_string(dev, dev->descriptor.iProduct); + serial = usb_string(dev, dev->descriptor.iSerialNumber); + + /* probe with filters first */ + + if (mf && prod) { + while (filter) { + if ((fdata = filter->probe(dev, mf, prod, serial)) != NULL) { + flags = filter->flags; + printk(KERN_INFO "USB Scsi filter %s\n", filter->name); + break; + } + filter = filter->next; + } + } + + /* generic devices next */ + + if (fdata == NULL) { + + /* some exceptions */ + if (dev->descriptor.idVendor == 0x04e6 && + dev->descriptor.idProduct == 0x0001) { + /* shuttle E-USB */ + protocol = US_PR_ZIP; + subclass = US_SC_8070; /* an assumption */ + } else if (dev->descriptor.bDeviceClass != 0 || + dev->config->altsetting->interface->bInterfaceClass != 8 || + dev->config->altsetting->interface->bInterfaceSubClass < US_SC_MIN || + dev->config->altsetting->interface->bInterfaceSubClass > US_SC_MAX) { + return -1; + } + + /* now check if we have seen it before */ + + if (dev->descriptor.iSerialNumber && + usb_string(dev, dev->descriptor.iSerialNumber) ) { + make_guid(guid, dev->descriptor.idVendor, dev->descriptor.idProduct, + usb_string(dev, dev->descriptor.iSerialNumber)); + for (ss = us_list; ss; ss = ss->next) { + if (GUID_EQUAL(guid, ss->guid)) { + US_DEBUGP("Found existing GUID " GUID_FORMAT "\n", GUID_ARGS(guid)); + break; + } + } + } + } + + if (!ss) { + if ((ss = (struct us_data *)kmalloc(sizeof(*ss), GFP_KERNEL)) == NULL) { + printk(KERN_WARNING USB_SCSI "Out of memory\n"); + if (filter) + filter->release(fdata); + return -1; + } + memset(ss, 0, sizeof(struct us_data)); + } + + interface = dev->config->altsetting->interface; + ss->filter = filter; + ss->fdata = fdata; + ss->flags = flags; + if (subclass) { + ss->subclass = subclass; + ss->protocol = protocol; + } else { + ss->subclass = interface->bInterfaceSubClass; + ss->protocol = interface->bInterfaceProtocol; + } + + /* set the protocol op */ + + US_DEBUGP("Protocol "); + switch (ss->protocol) { + case US_PR_CB: + US_DEBUGPX("Control/Bulk\n"); + ss->pop = pop_CBI; + break; + + case US_PR_CBI: + US_DEBUGPX("Control/Bulk/Interrupt\n"); + ss->pop = pop_CBI; + break; + + default: + US_DEBUGPX("Bulk\n"); + ss->pop = pop_Bulk; + break; + } + + /* + * we are expecting a minimum of 2 endpoints - in and out (bulk) + * an optional interrupt is OK (necessary for CBI protocol) + * we will ignore any others + */ + + for (i = 0; i < interface->bNumEndpoints; i++) { + if (interface->endpoint[i].bmAttributes == 0x02) { + if (interface->endpoint[i].bEndpointAddress & 0x80) + ss->ep_in = interface->endpoint[i].bEndpointAddress & 0x0f; + else + ss->ep_out = interface->endpoint[i].bEndpointAddress & 0x0f; + } else if (interface->endpoint[i].bmAttributes == 0x03) { + ss->ep_int = interface->endpoint[i].bEndpointAddress & 0x0f; + } + } + US_DEBUGP("Endpoints In %d Out %d Int %d\n", ss->ep_in, ss->ep_out, ss->ep_int); + + /* exit if strange looking */ + + if (usb_set_configuration(dev, dev->config[0].bConfigurationValue) || + !ss->ep_in || !ss->ep_out || (ss->protocol == US_PR_CBI && ss->ep_int == 0)) { + US_DEBUGP("Problems with device\n"); + if (ss->host) { + scsi_unregister_module(MODULE_SCSI_HA, ss->htmplt); + kfree(ss->htmplt->name); + kfree(ss->htmplt); + } + if (filter) + filter->release(fdata); + kfree(ss); + return -1; /* no endpoints */ + } + + if (dev->config[0].iConfiguration && usb_string(dev, dev->config[0].iConfiguration)) + US_DEBUGP("Configuration %s\n", usb_string(dev, dev->config[0].iConfiguration)); + if (interface->iInterface && usb_string(dev, interface->iInterface)) + US_DEBUGP("Interface %s\n", usb_string(dev, interface->iInterface)); + + ss->pusb_dev = dev; + + /* Now generate a scsi host definition, and register with scsi above us */ + + if (!ss->host) { + + /* make unique id if possible */ + + if (dev->descriptor.iSerialNumber && + usb_string(dev, dev->descriptor.iSerialNumber) ) { + make_guid(ss->guid, dev->descriptor.idVendor, dev->descriptor.idProduct, + usb_string(dev, dev->descriptor.iSerialNumber)); + } + + US_DEBUGP("New GUID " GUID_FORMAT "\n", GUID_ARGS(guid)); + + /* set class specific stuff */ + + US_DEBUGP("SubClass "); + switch (ss->subclass) { + case US_SC_RBC: + US_DEBUGPX("Reduced Block Commands\n"); + break; + case US_SC_8020: + US_DEBUGPX("8020\n"); + break; + case US_SC_QIC: + US_DEBUGPX("QIC157\n"); + break; + case US_SC_8070: + US_DEBUGPX("8070\n"); + ss->flags |= US_FL_FIXED_COMMAND; + ss->fixedlength = 12; + break; + case US_SC_SCSI: + US_DEBUGPX("Transparent SCSI\n"); + break; + case US_SC_UFI: + US_DEBUGPX(" UFF\n"); + ss->flags |= US_FL_FIXED_COMMAND; + ss->fixedlength = 12; + break; + + default: + break; + } + + /* create unique host template */ + + if ((htmplt = (Scsi_Host_Template *)kmalloc(sizeof(*ss->htmplt), GFP_KERNEL)) == NULL ) { + printk(KERN_WARNING USB_SCSI "Out of memory\n"); + if (filter) + filter->release(fdata); + kfree(ss); + return -1; + } + memcpy(htmplt, &my_host_template, sizeof(my_host_template)); + ss->host_number = my_host_number++; + + + (struct us_data *)htmplt->proc_dir = ss; + if (ss->protocol == US_PR_CBI) + init_waitqueue_head(&ss->ip_waitq); + + /* start up our thread */ + + { + DECLARE_MUTEX_LOCKED(sem); + + init_waitqueue_head(&ss->waitq); + + ss->notify = &sem; + ss->pid = kernel_thread(usbscsi_control_thread, ss, + CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (ss->pid < 0) { + printk(KERN_WARNING USB_SCSI "Unable to start control thread\n"); + kfree(htmplt); + if (filter) + filter->release(fdata); + kfree(ss); + return -1; + } + + /* wait for it to start */ + + down(&sem); + } + + /* now register - our detect function will be called */ + + scsi_register_module(MODULE_SCSI_HA, htmplt); + + /* put us in the list */ + + prev = (struct us_data *)&us_list; + while (prev->next) + prev = prev->next; + prev->next = ss; + + } + + + printk(KERN_INFO "USB SCSI device found at address %d\n", dev->devnum); + + dev->private = ss; + return 0; +} + +static void scsi_disconnect(struct usb_device *dev) +{ + struct us_data *ss = dev->private; + + if (!ss) + return; + if (ss->filter) + ss->filter->release(ss->fdata); + ss->pusb_dev = NULL; + dev->private = NULL; /* just in case */ + MOD_DEC_USE_COUNT; +} + +int usb_scsi_init(void) +{ + + MOD_INC_USE_COUNT; +#ifdef CONFIG_USB_HP4100 + hp4100_init(); +#endif +#ifdef CONFIG_USB_ZIP + usb_zip_init(); +#endif + usb_register(&scsi_driver); + printk(KERN_INFO "USB SCSI support registered.\n"); + return 0; +} + + +int usb_scsi_register(struct usb_scsi_filter *filter) +{ + struct usb_scsi_filter *prev = (struct usb_scsi_filter *)&filters; + + while (prev->next) + prev = prev->next; + prev->next = filter; + return 0; +} + +void usb_scsi_deregister(struct usb_scsi_filter *filter) +{ + struct usb_scsi_filter *prev = (struct usb_scsi_filter *)&filters; + + while (prev->next && prev->next != filter) + prev = prev->next; + if (prev->next) + prev->next = filter->next; +} + +#ifdef MODULE +int init_module(void) +{ + + return usb_scsi_init(); +} + +void cleanup_module(void) +{ + unsigned int offset; + + usb_deregister(&scsi_driver); +} +#endif diff --git a/drivers/usb/usb_scsi.h b/drivers/usb/usb_scsi.h new file mode 100644 index 000000000..58367d852 --- /dev/null +++ b/drivers/usb/usb_scsi.h @@ -0,0 +1,145 @@ +/* Driver for USB scsi - include file + * + * (C) Michael Gee (michael@linuxspecific.com) 1999 + * + * This driver is scitzoid - it make a USB scanner appear as both a SCSI device + * and a character device. The latter is only available if the device has an + * interrupt endpoint, and is used specifically to receive interrupt events. + * + * In order to support various 'strange' scanners, this module supports plug in + * device specific filter modules, which can do their own thing when required. + * + */ + +#define USB_SCSI "usbscsi: " + +extern int usbscsi_debug; + +#ifdef CONFIG_USB_SCSI_DEBUG +void us_show_command(Scsi_Cmnd *srb); +#define US_DEBUGP(x...) { if(usbscsi_debug) printk( KERN_DEBUG USB_SCSI ## x ); } +#define US_DEBUGPX(x...) { if(usbscsi_debug) printk( ## x ); } +#define US_DEBUG(x) { if(usbscsi_debug) x; } +#else +#define US_DEBUGP(x...) +#define US_DEBUGPX(x...) +#define US_DEBUG(x) +#endif + +/* bit set if input */ +extern unsigned char us_direction[256/8]; +#define US_DIRECTION(x) ((us_direction[x>>3] >> (x & 7)) & 1) + +/* Sub Classes */ + +#define US_SC_RBC 1 /* Typically, flash devices */ +#define US_SC_8020 2 /* CD-ROM */ +#define US_SC_QIC 3 /* QIC-157 Tapes */ +#define US_SC_UFI 4 /* Floppy */ +#define US_SC_8070 5 /* Removable media */ +#define US_SC_SCSI 6 /* Transparent */ +#define US_SC_MIN US_SC_RBC +#define US_SC_MAX US_SC_SCSI + +/* Protocols */ + +#define US_PR_CB 1 /* Control/Bulk w/o interrupt */ +#define US_PR_CBI 0 /* Control/Bulk/Interrupt */ +#define US_PR_ZIP 0x50 /* bulk only */ +/* #define US_PR_BULK ?? */ + +/* + * Bulk only data structures (Zip 100, for example) + */ + +struct bulk_cb_wrap { + __u32 Signature; /* contains 'USBC' */ + __u32 Tag; /* unique per command id */ + __u32 DataTransferLength; /* size of data */ + __u8 Flags; /* direction in bit 0 */ + __u8 Lun; /* LUN normally 0 */ + __u8 Length; /* of of the CDB */ + __u8 CDB[16]; /* max command */ +}; + +#define US_BULK_CB_WRAP_LEN 31 +#define US_BULK_CB_SIGN 0x43425355 +#define US_BULK_FLAG_IN 1 +#define US_BULK_FLAG_OUT 0 + +struct bulk_cs_wrap { + __u32 Signature; /* should = 'USBS' */ + __u32 Tag; /* same as original command */ + __u32 Residue; /* amount not transferred */ + __u8 Status; /* see below */ + __u8 Filler[18]; +}; + +#define US_BULK_CS_WRAP_LEN 31 +#define US_BULK_CS_SIGN 0x53425355 +#define US_BULK_STAT_OK 0 +#define US_BULK_STAT_FAIL 1 +#define US_BULK_STAT_PHASE 2 + +#define US_BULK_RESET 0xff +#define US_BULK_RESET_SOFT 1 +#define US_BULK_RESET_HARD 0 + +/* + * CBI style + */ + +#define US_CBI_ADSC 0 + +/* + * Filter device definitions + */ +struct usb_scsi_filter { + + struct usb_scsi_filter * next; /* usb_scsi driver only */ + char *name; /* not really required */ + + unsigned int flags; /* Filter flags */ + void * (* probe) (struct usb_device *, char *, char *, char *); /* probe device */ + void (* release)(void *); /* device gone */ + int (* command)(void *, Scsi_Cmnd *); /* all commands */ +}; + +#define GUID(x) __u32 x[3] +#define GUID_EQUAL(x, y) (x[0] == y[0] && x[1] == y[1] && x[2] == y[2]) +#define GUID_CLEAR(x) x[0] = x[1] = x[2] = 0; +#define GUID_NONE(x) (!x[0] && !x[1] && !x[2]) +#define GUID_FORMAT "%08x%08x%08x" +#define GUID_ARGS(x) x[0], x[1], x[2] + +static inline void make_guid( __u32 *pg, __u16 vendor, __u16 product, char *serial) +{ + pg[0] = (vendor << 16) | product; + pg[1] = pg[2] = 0; + while (*serial) { + pg[1] <<= 4; + pg[1] |= pg[2] >> 28; + pg[2] <<= 4; + if (*serial >= 'a') + *serial -= 'a' - 'A'; + pg[2] |= (*serial <= '9' && *serial >= '0') ? *serial - '0' + : *serial - 'A' + 10; + serial++; + } +} + +/* Flag definitions */ +#define US_FL_IP_STATUS 0x00000001 /* status uses interrupt */ +#define US_FL_FIXED_COMMAND 0x00000002 /* expand commands to fixed size */ + +/* + * Called by filters to register/unregister the mini driver + * + * WARNING - the supplied probe function may be called before exiting this fn + */ +int usb_scsi_register(struct usb_scsi_filter *); +void usb_scsi_deregister(struct usb_scsi_filter *); + +#ifdef CONFIG_USB_HP4100 +int hp4100_init(void); +#endif diff --git a/drivers/usb/usb_scsi_debug.c b/drivers/usb/usb_scsi_debug.c new file mode 100644 index 000000000..2ca847c08 --- /dev/null +++ b/drivers/usb/usb_scsi_debug.c @@ -0,0 +1,104 @@ + +/* Driver for USB scsi like devices + * + * (C) Michael Gee (michael@linuxspecific.com) 1999 + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/random.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/malloc.h> + +#include <asm/spinlock.h> + +#include <linux/blk.h> +#include "../scsi/scsi.h" +#include "../scsi/hosts.h" +#include "../scsi/sd.h" + +#include "usb.h" +#include "usb_scsi.h" + +void us_show_command(Scsi_Cmnd *srb) +{ + char *what; + + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: what = "TEST_UNIT_READY"; break; + case REZERO_UNIT: what = "REZERO_UNIT"; break; + case REQUEST_SENSE: what = "REQUEST_SENSE"; break; + case FORMAT_UNIT: what = "FORMAT_UNIT"; break; + case READ_BLOCK_LIMITS: what = "READ_BLOCK_LIMITS"; break; + case REASSIGN_BLOCKS: what = "REASSIGN_BLOCKS"; break; + case READ_6: what = "READ_6"; break; + case WRITE_6: what = "WRITE_6"; break; + case SEEK_6: what = "SEEK_6"; break; + case READ_REVERSE: what = "READ_REVERSE"; break; + case WRITE_FILEMARKS: what = "WRITE_FILEMARKS"; break; + case SPACE: what = "SPACE"; break; + case INQUIRY: what = "INQUIRY"; break; + case RECOVER_BUFFERED_DATA: what = "RECOVER_BUFFERED_DATA"; break; + case MODE_SELECT: what = "MODE_SELECT"; break; + case RESERVE: what = "RESERVE"; break; + case RELEASE: what = "RELEASE"; break; + case COPY: what = "COPY"; break; + case ERASE: what = "ERASE"; break; + case MODE_SENSE: what = "MODE_SENSE"; break; + case START_STOP: what = "START_STOP"; break; + case RECEIVE_DIAGNOSTIC: what = "RECEIVE_DIAGNOSTIC"; break; + case SEND_DIAGNOSTIC: what = "SEND_DIAGNOSTIC"; break; + case ALLOW_MEDIUM_REMOVAL: what = "ALLOW_MEDIUM_REMOVAL"; break; + case SET_WINDOW: what = "SET_WINDOW"; break; + case READ_CAPACITY: what = "READ_CAPACITY"; break; + case READ_10: what = "READ_10"; break; + case WRITE_10: what = "WRITE_10"; break; + case SEEK_10: what = "SEEK_10"; break; + case WRITE_VERIFY: what = "WRITE_VERIFY"; break; + case VERIFY: what = "VERIFY"; break; + case SEARCH_HIGH: what = "SEARCH_HIGH"; break; + case SEARCH_EQUAL: what = "SEARCH_EQUAL"; break; + case SEARCH_LOW: what = "SEARCH_LOW"; break; + case SET_LIMITS: what = "SET_LIMITS"; break; + case READ_POSITION: what = "READ_POSITION"; break; + case SYNCHRONIZE_CACHE: what = "SYNCHRONIZE_CACHE"; break; + case LOCK_UNLOCK_CACHE: what = "LOCK_UNLOCK_CACHE"; break; + case READ_DEFECT_DATA: what = "READ_DEFECT_DATA"; break; + case MEDIUM_SCAN: what = "MEDIUM_SCAN"; break; + case COMPARE: what = "COMPARE"; break; + case COPY_VERIFY: what = "COPY_VERIFY"; break; + case WRITE_BUFFER: what = "WRITE_BUFFER"; break; + case READ_BUFFER: what = "READ_BUFFER"; break; + case UPDATE_BLOCK: what = "UPDATE_BLOCK"; break; + case READ_LONG: what = "READ_LONG"; break; + case WRITE_LONG: what = "WRITE_LONG"; break; + case CHANGE_DEFINITION: what = "CHANGE_DEFINITION"; break; + case WRITE_SAME: what = "WRITE_SAME"; break; + case READ_TOC: what = "READ_TOC"; break; + case LOG_SELECT: what = "LOG_SELECT"; break; + case LOG_SENSE: what = "LOG_SENSE"; break; + case MODE_SELECT_10: what = "MODE_SELECT_10"; break; + case MODE_SENSE_10: what = "MODE_SENSE_10"; break; + case MOVE_MEDIUM: what = "MOVE_MEDIUM"; break; + case READ_12: what = "READ_12"; break; + case WRITE_12: what = "WRITE_12"; break; + case WRITE_VERIFY_12: what = "WRITE_VERIFY_12"; break; + case SEARCH_HIGH_12: what = "SEARCH_HIGH_12"; break; + case SEARCH_EQUAL_12: what = "SEARCH_EQUAL_12"; break; + case SEARCH_LOW_12: what = "SEARCH_LOW_12"; break; + case READ_ELEMENT_STATUS: what = "READ_ELEMENT_STATUS"; break; + case SEND_VOLUME_TAG: what = "SEND_VOLUME_TAG"; break; + case WRITE_LONG_2: what = "WRITE_LONG_2"; break; + default: what = "??"; break; + } + printk(KERN_DEBUG USB_SCSI "Command %s (%d bytes)\n", what, srb->cmd_len); + printk(KERN_DEBUG USB_SCSI " %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + srb->cmnd[0], srb->cmnd[1], srb->cmnd[2], srb->cmnd[3], srb->cmnd[4], srb->cmnd[5], + srb->cmnd[6], srb->cmnd[7], srb->cmnd[8], srb->cmnd[9]); +} diff --git a/drivers/usb/usb_scsi_dt.c b/drivers/usb/usb_scsi_dt.c new file mode 100644 index 000000000..5d615bdff --- /dev/null +++ b/drivers/usb/usb_scsi_dt.c @@ -0,0 +1,4 @@ +0x28, 0x81, 0x14, 0x14, 0x20, 0x01, 0x90, 0x77, +0x0C, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 diff --git a/drivers/video/cyber2000fb.c b/drivers/video/cyber2000fb.c new file mode 100644 index 000000000..c24288124 --- /dev/null +++ b/drivers/video/cyber2000fb.c @@ -0,0 +1,1057 @@ +/* + * linux/drivers/video/cyber2000fb.c + * + * Integraphics Cyber2000 frame buffer device + * + * Based on cyberfb.c + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/malloc.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +#include <video/fbcon.h> +#include <video/fbcon-cfb8.h> +#include <video/fbcon-cfb16.h> +#include <video/fbcon-cfb24.h> + +/* + * Some defaults + */ +#define DEFAULT_XRES 640 +#define DEFAULT_YRES 480 +#define DEFAULT_BPP 8 + +static volatile unsigned char *CyberRegs; + +#include "cyber2000fb.h" + +static struct display global_disp; +static struct fb_info fb_info; +static struct cyber2000fb_par current_par; +static struct display_switch *dispsw; +static struct fb_var_screeninfo __initdata init_var = {}; + +#ifdef DEBUG +static void debug_printf(char *fmt, ...) +{ + char buffer[128]; + va_list ap; + + va_start(ap, fmt); + vsprintf(buffer, fmt, ap); + va_end(ap); + + printascii(buffer); +} +#else +#define debug_printf(x...) do { } while (0) +#endif + +/* + * Predefined Video Modes + */ +static const struct res cyber2000_res[] = { + { + 640, 480, + { + 0x5f, 0x4f, 0x50, 0x80, 0x52, 0x9d, 0x0b, 0x3e, + 0x00, 0x40, + 0xe9, 0x8b, 0xdf, 0x50, 0x00, 0xe6, 0x04, 0xc3 + }, + 0x00, + { 0xd2, 0xce, 0xdb, 0x54 } + }, + + { + 800, 600, + { + 0x7f, 0x63, 0x64, 0x00, 0x66, 0x10, 0x6f, 0xf0, + 0x00, 0x60, + 0x5b, 0x8f, 0x57, 0x64, 0x00, 0x59, 0x6e, 0xe3 + }, + 0x00, + { 0x52, 0x85, 0xdb, 0x54 } + }, + + { + 1024, 768, + { + 0x9f, 0x7f, 0x80, 0x80, 0x8b, 0x94, 0x1e, 0xfd, + 0x00, 0x60, + 0x03, 0x86, 0xff, 0x80, 0x0f, 0x00, 0x1e, 0xe3 + }, + 0x00, + { 0xd0, 0x52, 0xdb, 0x54 } + }, +#if 0 + { + 1152, 886, + { + }, + { + } + }, +#endif + { + 1280, 1024, + { + 0xce, 0x9f, 0xa0, 0x8f, 0xa2, 0x1f, 0x28, 0x52, + 0x00, 0x40, + 0x08, 0x8f, 0xff, 0xa0, 0x00, 0x03, 0x27, 0xe3 + }, + 0x1d, + { 0xb4, 0x4b, 0xdb, 0x54 } + }, + + { + 1600, 1200, + { + 0xff, 0xc7, 0xc9, 0x9f, 0xcf, 0xa0, 0xfe, 0x10, + 0x00, 0x40, + 0xcf, 0x89, 0xaf, 0xc8, 0x00, 0xbc, 0xf1, 0xe3 + }, + 0x1f, + { 0xbd, 0x10, 0xdb, 0x54 } + } +}; + +#define NUM_TOTAL_MODES arraysize(cyber2000_res) + +static const char igs_regs[] = { + 0x10, 0x10, 0x12, 0x00, 0x13, 0x00, + 0x30, 0x21, 0x31, 0x00, 0x32, 0x00, 0x33, 0x01, + 0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53, 0x00, + 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, 0x01, + 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, + 0x70, 0x0b, 0x71, 0x10, 0x72, 0x45, 0x73, 0x30, + 0x74, 0x1b, 0x75, 0x1e, 0x76, 0x00, 0x7a, 0xc8 +}; + +static const char crtc_idx[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 +}; + +static void cyber2000_init_hw(const struct res *res) +{ + int i; + + debug_printf("init vga hw for %dx%d\n", res->xres, res->yres); + + cyber2000_outb(0xef, 0x3c2); + cyber2000_crtcw(0x0b, 0x11); + cyber2000_attrw(0x00, 0x11); + + cyber2000_seqw(0x01, 0x00); + cyber2000_seqw(0x01, 0x01); + cyber2000_seqw(0x0f, 0x02); + cyber2000_seqw(0x00, 0x03); + cyber2000_seqw(0x0e, 0x04); + cyber2000_seqw(0x03, 0x00); + + for (i = 0; i < sizeof(crtc_idx); i++) + cyber2000_crtcw(res->crtc_regs[i], crtc_idx[i]); + + for (i = 0x0a; i < 0x10; i++) + cyber2000_crtcw(0, i); + + cyber2000_crtcw(0xff, 0x18); + + cyber2000_grphw(0x00, 0x00); + cyber2000_grphw(0x00, 0x01); + cyber2000_grphw(0x00, 0x02); + cyber2000_grphw(0x00, 0x03); + cyber2000_grphw(0x00, 0x04); + cyber2000_grphw(0x60, 0x05); + cyber2000_grphw(0x05, 0x06); + cyber2000_grphw(0x0f, 0x07); + cyber2000_grphw(0xff, 0x08); + + for (i = 0; i < 16; i++) + cyber2000_attrw(i, i); + + cyber2000_attrw(0x01, 0x10); + cyber2000_attrw(0x00, 0x11); + cyber2000_attrw(0x0f, 0x12); + cyber2000_attrw(0x00, 0x13); + cyber2000_attrw(0x00, 0x14); + + for (i = 0; i < sizeof(igs_regs); i += 2) + cyber2000_grphw(igs_regs[i+1], igs_regs[i]); + + cyber2000_grphw(res->crtc_ofl, 0x11); + + for (i = 0; i < 4; i += 1) + cyber2000_grphw(res->clk_regs[i], 0xb0 + i); + + cyber2000_grphw(0x01, 0x90); + cyber2000_grphw(0x80, 0xb9); + cyber2000_grphw(0x00, 0xb9); + + cyber2000_outb(0x56, 0x3ce); + i = cyber2000_inb(0x3cf); + cyber2000_outb(i | 4, 0x3cf); + cyber2000_outb(0x04, 0x3c6); + cyber2000_outb(i, 0x3cf); + + cyber2000_outb(0x20, 0x3c0); + cyber2000_outb(0xff, 0x3c6); + + for (i = 0; i < 256; i++) { + cyber2000_outb(i, 0x3c8); + cyber2000_outb(0, 0x3c9); + cyber2000_outb(0, 0x3c9); + cyber2000_outb(0, 0x3c9); + } +} + + +static struct fb_ops cyber2000fb_ops; + +/* -------------------- Hardware specific routines ------------------------- */ + +/* + * Hardware Cyber2000 Acceleration + */ +static void cyber2000_accel_wait(void) +{ + int count = 10000; + + while (cyber2000_inb(0xbf011) & 0x80) { + if (!count--) { + debug_printf("accel_wait timed out\n"); + cyber2000_outb(0, 0xbf011); + return; + } + udelay(10); + } +} + +static void +cyber2000_accel_setup(struct display *p) +{ + dispsw->setup(p); +} + +static void +cyber2000_accel_bmove(struct display *p, int sy, int sx, int dy, int dx, + int height, int width) +{ + unsigned long src, dst, chwidth = p->var.xres_virtual * fontheight(p); + int v = 0x8000; + + if (sx < dx) { + sx += width - 1; + dx += width - 1; + v |= 4; + } + + if (sy < dy) { + sy += height - 1; + dy += height - 1; + v |= 2; + } + + sx *= fontwidth(p); + dx *= fontwidth(p); + src = sx + sy * chwidth; + dst = dx + dy * chwidth; + width = width * fontwidth(p) - 1; + height = height * fontheight(p) - 1; + + cyber2000_accel_wait(); + cyber2000_outb(0x00, 0xbf011); + cyber2000_outb(0x03, 0xbf048); + cyber2000_outw(width, 0xbf060); + + if (p->var.bits_per_pixel != 24) { + cyber2000_outl(dst, 0xbf178); + cyber2000_outl(src, 0xbf170); + } else { + cyber2000_outl(dst * 3, 0xbf178); + cyber2000_outb(dst, 0xbf078); + cyber2000_outl(src * 3, 0xbf170); + } + + cyber2000_outw(height, 0xbf062); + cyber2000_outw(v, 0xbf07c); + cyber2000_outw(0x2800, 0xbf07e); +} + +static void +cyber2000_accel_clear(struct vc_data *conp, struct display *p, int sy, int sx, + int height, int width) +{ + unsigned long dst; + u32 bgx = attr_bgcol_ec(p, conp); + + dst = sx * fontwidth(p) + sy * p->var.xres_virtual * fontheight(p); + width = width * fontwidth(p) - 1; + height = height * fontheight(p) - 1; + + cyber2000_accel_wait(); + cyber2000_outb(0x00, 0xbf011); + cyber2000_outb(0x03, 0xbf048); + cyber2000_outw(width, 0xbf060); + cyber2000_outw(height, 0xbf062); + + switch (p->var.bits_per_pixel) { + case 16: + bgx = ((u16 *)p->dispsw_data)[bgx]; + case 8: + cyber2000_outl(dst, 0xbf178); + break; + + case 24: + cyber2000_outl(dst * 3, 0xbf178); + cyber2000_outb(dst, 0xbf078); + bgx = ((u32 *)p->dispsw_data)[bgx]; + break; + } + + cyber2000_outl(bgx, 0xbf058); + cyber2000_outw(0x8000, 0xbf07c); + cyber2000_outw(0x0800, 0xbf07e); +} + +static void +cyber2000_accel_putc(struct vc_data *conp, struct display *p, int c, int yy, int xx) +{ + cyber2000_accel_wait(); + dispsw->putc(conp, p, c, yy, xx); +} + +static void +cyber2000_accel_putcs(struct vc_data *conp, struct display *p, + const unsigned short *s, int count, int yy, int xx) +{ + cyber2000_accel_wait(); + dispsw->putcs(conp, p, s, count, yy, xx); +} + +static void +cyber2000_accel_revc(struct display *p, int xx, int yy) +{ + cyber2000_accel_wait(); + dispsw->revc(p, xx, yy); +} + +static void +cyber2000_accel_clear_margins(struct vc_data *conp, struct display *p, int bottom_only) +{ + dispsw->clear_margins(conp, p, bottom_only); +} + +static struct display_switch fbcon_cyber_accel = { + cyber2000_accel_setup, + cyber2000_accel_bmove, + cyber2000_accel_clear, + cyber2000_accel_putc, + cyber2000_accel_putcs, + cyber2000_accel_revc, + NULL, + NULL, + cyber2000_accel_clear_margins, + FONTWIDTH(8)|FONTWIDTH(16) +}; + +/* + * Palette + */ +static int +cyber2000_getcolreg(u_int regno, u_int * red, u_int * green, u_int * blue, + u_int * transp, struct fb_info *fb_info) +{ + int t; + + if (regno >= 256) + return 1; + + t = current_par.palette[regno].red; + *red = t << 10 | t << 4 | t >> 2; + + t = current_par.palette[regno].green; + *green = t << 10 | t << 4 | t >> 2; + + t = current_par.palette[regno].blue; + *blue = t << 10 | t << 4 | t >> 2; + + *transp = 0; + + return 0; +} + +/* + * Set a single color register. Return != 0 for invalid regno. + */ +static int +cyber2000_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *fb_info) +{ + if (regno > 255) + return 1; + + red >>= 10; + green >>= 10; + blue >>= 10; + + current_par.palette[regno].red = red; + current_par.palette[regno].green = green; + current_par.palette[regno].blue = blue; + + switch (fb_display[current_par.currcon].var.bits_per_pixel) { + case 8: + cyber2000_outb(regno, 0x3c8); + cyber2000_outb(red, 0x3c9); + cyber2000_outb(green, 0x3c9); + cyber2000_outb(blue, 0x3c9); + break; + +#ifdef FBCON_HAS_CFB16 + case 16: + if (regno < 64) { + /* write green */ + cyber2000_outb(regno << 2, 0x3c8); + cyber2000_outb(current_par.palette[regno >> 1].red, 0x3c9); + cyber2000_outb(green, 0x3c9); + cyber2000_outb(current_par.palette[regno >> 1].blue, 0x3c9); + } + + if (regno < 32) { + /* write red,blue */ + cyber2000_outb(regno << 3, 0x3c8); + cyber2000_outb(red, 0x3c9); + cyber2000_outb(current_par.palette[regno << 1].green, 0x3c9); + cyber2000_outb(blue, 0x3c9); + } + + if (regno < 16) + current_par.c_table.cfb16[regno] = regno | regno << 5 | regno << 11; + break; +#endif + +#ifdef FBCON_HAS_CFB24 + case 24: + cyber2000_outb(regno, 0x3c8); + cyber2000_outb(red, 0x3c9); + cyber2000_outb(green, 0x3c9); + cyber2000_outb(blue, 0x3c9); + + if (regno < 16) + current_par.c_table.cfb24[regno] = regno | regno << 8 | regno << 16; + break; +#endif + + default: + return 1; + } + + return 0; +} + +static int cyber2000fb_set_timing(struct fb_var_screeninfo *var) +{ + int width = var->xres_virtual; + int scr_pitch, fetchrow; + int i; + char b, col; + + switch (var->bits_per_pixel) { + case 8: /* PSEUDOCOLOUR, 256 */ + b = 0; + col = 1; + scr_pitch = var->xres_virtual / 8; + break; + + case 16:/* DIRECTCOLOUR, 64k */ + b = 1; + col = 2; + scr_pitch = var->xres_virtual / 8 * 2; + break; + case 24:/* TRUECOLOUR, 16m */ + b = 2; + col = 4; + scr_pitch = var->xres_virtual / 8 * 3; + width *= 3; + break; + + default: + return 1; + } + + for (i = 0; i < NUM_TOTAL_MODES; i++) + if (var->xres == cyber2000_res[i].xres && + var->yres == cyber2000_res[i].yres) + break; + + if (i < NUM_TOTAL_MODES) + cyber2000_init_hw(cyber2000_res + i); + + fetchrow = scr_pitch + 1; + + debug_printf("Setting regs: pitch=%X, fetchrow=%X, col=%X, b=%X\n", + scr_pitch, fetchrow, col, b); + + cyber2000_outb(0x13, 0x3d4); + cyber2000_outb(scr_pitch, 0x3d5); + cyber2000_outb(0x14, 0x3ce); + cyber2000_outb(fetchrow, 0x3cf); + cyber2000_outb(0x15, 0x3ce); + /* FIXME: is this the right way round? */ + cyber2000_outb(((fetchrow >> 4) & 0xf0) | ((scr_pitch >> 8) & 0x0f), 0x3cf); + cyber2000_outb(0x77, 0x3ce); + cyber2000_outb(col, 0x3cf); + + + cyber2000_outb(0x33, 0x3ce); + cyber2000_outb(0x1c, 0x3cf); + + cyber2000_outw(width - 1, 0xbf018); + cyber2000_outw(width - 1, 0xbf218); + cyber2000_outb(b, 0xbf01c); + + return 0; +} + +static inline void +cyber2000fb_update_start(struct fb_var_screeninfo *var) +{ +#if 0 + unsigned int base; + + base = var->yoffset * var->xres_virtual + var->xoffset; + + cyber2000_outb(0x0c, 0x3d4); + cyber2000_outb(base, 0x3d5); + cyber2000_outb(0x0d, 0x3d4); + cyber2000_outb(base >> 8, 0x3d5); + /* FIXME: need the upper bits of the start offset */ +/* cyber2000_outb(0x??, 0x3d4); + cyber2000_outb(base >> 16, 0x3d5);*/ +#endif +} + +/* + * Open/Release the frame buffer device + */ +static int cyber2000fb_open(struct fb_info *info, int user) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static int cyber2000fb_release(struct fb_info *info, int user) +{ + MOD_DEC_USE_COUNT; + return 0; +} + +/* + * Get the Colormap + */ +static int +cyber2000fb_get_cmap(struct fb_cmap *cmap, int kspc, int con, + struct fb_info *info) +{ + int err = 0; + + if (con == current_par.currcon) /* current console? */ + err = fb_get_cmap(cmap, kspc, cyber2000_getcolreg, info); + else if (fb_display[con].cmap.len) /* non default colormap? */ + fb_copy_cmap(&fb_display[con].cmap, cmap, kspc ? 0 : 2); + else + fb_copy_cmap(fb_default_cmap(1 << fb_display[con].var.bits_per_pixel), + cmap, kspc ? 0 : 2); + return err; +} + + +/* + * Set the Colormap + */ +static int +cyber2000fb_set_cmap(struct fb_cmap *cmap, int kspc, int con, + struct fb_info *info) +{ + struct display *disp = &fb_display[con]; + int err = 0; + + if (!disp->cmap.len) { /* no colormap allocated? */ + int size; + + if (disp->var.bits_per_pixel == 16) + size = 32; + else + size = 256; + + err = fb_alloc_cmap(&disp->cmap, size, 0); + } + if (!err) { + if (con == current_par.currcon) /* current console? */ + err = fb_set_cmap(cmap, kspc, cyber2000_setcolreg, + info); + else + fb_copy_cmap(cmap, &disp->cmap, kspc ? 0 : 1); + } + + return err; +} + +static int +cyber2000fb_decode_var(struct fb_var_screeninfo *var, int con, int *visual) +{ + switch (var->bits_per_pixel) { +#ifdef FBCON_HAS_CFB8 + case 8: + *visual = FB_VISUAL_PSEUDOCOLOR; + break; +#endif +#ifdef FBCON_HAS_CFB16 + case 16: + *visual = FB_VISUAL_DIRECTCOLOR; + break; +#endif +#ifdef FBCON_HAS_CFB24 + case 24: + *visual = FB_VISUAL_TRUECOLOR; + break; +#endif + default: + return -EINVAL; + } + + return 0; +} + +/* + * Get the Fixed Part of the Display + */ +static int +cyber2000fb_get_fix(struct fb_fix_screeninfo *fix, int con, + struct fb_info *fb_info) +{ + struct display *display; + + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + strcpy(fix->id, "Cyber2000"); + + if (con >= 0) + display = fb_display + con; + else + display = &global_disp; + + fix->smem_start = (char *)current_par.screen_base_p; + fix->smem_len = current_par.screen_size; + fix->mmio_start = (char *)current_par.regs_base_p; + fix->mmio_len = 0x000c0000; + fix->type = display->type; + fix->type_aux = display->type_aux; + fix->xpanstep = 0; + fix->ypanstep = display->ypanstep; + fix->ywrapstep = display->ywrapstep; + fix->visual = display->visual; + fix->line_length = display->line_length; + fix->accel = 22; /*FB_ACCEL_IGS_CYBER2000*/ + + return 0; +} + + +/* + * Get the User Defined Part of the Display + */ +static int +cyber2000fb_get_var(struct fb_var_screeninfo *var, int con, + struct fb_info *fb_info) +{ + if (con == -1) + *var = global_disp.var; + else + *var = fb_display[con].var; + + return 0; +} + +/* + * Set the User Defined Part of the Display + */ +static int +cyber2000fb_set_var(struct fb_var_screeninfo *var, int con, struct fb_info *info) +{ + struct display *display; + int err, chgvar = 0, visual; + + if (con >= 0) + display = fb_display + con; + else + display = &global_disp; + + err = cyber2000fb_decode_var(var, con, &visual); + if (err) + return err; + + switch (var->activate & FB_ACTIVATE_MASK) { + case FB_ACTIVATE_TEST: + return 0; + + case FB_ACTIVATE_NXTOPEN: + case FB_ACTIVATE_NOW: + break; + + default: + return -EINVAL; + } + + if (con >= 0) { + if (display->var.xres != var->xres) + chgvar = 1; + if (display->var.yres != var->yres) + chgvar = 1; + if (display->var.xres_virtual != var->xres_virtual) + chgvar = 1; + if (display->var.yres_virtual != var->yres_virtual) + chgvar = 1; + if (display->var.accel_flags != var->accel_flags) + chgvar = 1; + if (memcmp(&display->var.red, &var->red, sizeof(var->red))) + chgvar = 1; + if (memcmp(&display->var.green, &var->green, sizeof(var->green))) + chgvar = 1; + if (memcmp(&display->var.blue, &var->blue, sizeof(var->green))) + chgvar = 1; + } + + display->var = *var; + + display->screen_base = (char *)current_par.screen_base; + display->visual = visual; + display->type = FB_TYPE_PACKED_PIXELS; + display->type_aux = 0; + display->ypanstep = 0; + display->ywrapstep = 0; + display->line_length = + display->next_line = (var->xres_virtual * var->bits_per_pixel) / 8; + display->can_soft_blank = 1; + display->inverse = 0; + + switch (display->var.bits_per_pixel) { +#ifdef FBCON_HAS_CFB8 + case 8: + dispsw = &fbcon_cfb8; + display->dispsw_data = NULL; + break; +#endif +#ifdef FBCON_HAS_CFB16 + case 16: + dispsw = &fbcon_cfb16; + display->dispsw_data = current_par.c_table.cfb16; + break; +#endif +#ifdef FBCON_HAS_CFB24 + case 24: + dispsw = &fbcon_cfb24; + display->dispsw_data = current_par.c_table.cfb24; + break; +#endif + default: + printk(KERN_WARNING "cyber2000: no support for %dbpp\n", + display->var.bits_per_pixel); + dispsw = &fbcon_dummy; + break; + } + + if (display->var.accel_flags & FB_ACCELF_TEXT && + dispsw != &fbcon_dummy) + display->dispsw = &fbcon_cyber_accel; + else + display->dispsw = dispsw; + + if (chgvar && info && info->changevar) + info->changevar(con); + + if (con == current_par.currcon) { + struct fb_cmap *cmap; + + cyber2000fb_update_start(var); + cyber2000fb_set_timing(var); + + if (display->cmap.len) + cmap = &display->cmap; + else + cmap = fb_default_cmap(current_par.palette_size); + + fb_set_cmap(cmap, 1, cyber2000_setcolreg, info); + } + return 0; +} + + +/* + * Pan or Wrap the Display + */ +static int cyber2000fb_pan_display(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + u_int y_bottom; + + y_bottom = var->yoffset; + + if (!(var->vmode & FB_VMODE_YWRAP)) + y_bottom += var->yres; + + if (var->xoffset > (var->xres_virtual - var->xres)) + return -EINVAL; + if (y_bottom > fb_display[con].var.yres_virtual) + return -EINVAL; +return -EINVAL; + + cyber2000fb_update_start(var); + + fb_display[con].var.xoffset = var->xoffset; + fb_display[con].var.yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + fb_display[con].var.vmode |= FB_VMODE_YWRAP; + else + fb_display[con].var.vmode &= ~FB_VMODE_YWRAP; + + return 0; +} + + +static int cyber2000fb_ioctl(struct inode *inode, struct file *file, + u_int cmd, u_long arg, int con, struct fb_info *info) +{ + return -EINVAL; +} + + +/* + * Update the `var' structure (called by fbcon.c) + * + * This call looks only at yoffset and the FB_VMODE_YWRAP flag in `var'. + * Since it's called by a kernel driver, no range checking is done. + */ +static int +cyber2000fb_updatevar(int con, struct fb_info *info) +{ + if (con == current_par.currcon) + cyber2000fb_update_start(&fb_display[con].var); + return 0; +} + +static int +cyber2000fb_switch(int con, struct fb_info *info) +{ + struct fb_cmap *cmap; + + if (current_par.currcon >= 0) { + cmap = &fb_display[current_par.currcon].cmap; + + if (cmap->len) + fb_get_cmap(cmap, 1, cyber2000_getcolreg, info); + } + + current_par.currcon = con; + + fb_display[con].var.activate = FB_ACTIVATE_NOW; + + cyber2000fb_set_var(&fb_display[con].var, con, info); + + return 0; +} + +/* + * (Un)Blank the display. + */ +static void cyber2000fb_blank(int blank, struct fb_info *fb_info) +{ + int i; + + if (blank) { + for (i = 0; i < 256; i++) { + cyber2000_outb(i, 0x3c8); + cyber2000_outb(0, 0x3c9); + cyber2000_outb(0, 0x3c9); + cyber2000_outb(0, 0x3c9); + } + } else { + for (i = 0; i < 256; i++) { + cyber2000_outb(i, 0x3c8); + cyber2000_outb(current_par.palette[i].red, 0x3c9); + cyber2000_outb(current_par.palette[i].green, 0x3c9); + cyber2000_outb(current_par.palette[i].blue, 0x3c9); + } + } +} + +__initfunc(void cyber2000fb_setup(char *options, int *ints)) +{ +} + +static struct fb_ops cyber2000fb_ops = +{ + cyber2000fb_open, + cyber2000fb_release, + cyber2000fb_get_fix, + cyber2000fb_get_var, + cyber2000fb_set_var, + cyber2000fb_get_cmap, + cyber2000fb_set_cmap, + cyber2000fb_pan_display, + cyber2000fb_ioctl +}; + +__initfunc(static void +cyber2000fb_init_fbinfo(void)) +{ + static int first = 1; + + if (!first) + return; + first = 0; + + strcpy(fb_info.modename, "Cyber2000"); + strcpy(fb_info.fontname, "Acorn8x8"); + + fb_info.node = -1; + fb_info.fbops = &cyber2000fb_ops; + fb_info.disp = &global_disp; + fb_info.changevar = NULL; + fb_info.switch_con = cyber2000fb_switch; + fb_info.updatevar = cyber2000fb_updatevar; + fb_info.blank = cyber2000fb_blank; + fb_info.flags = FBINFO_FLAG_DEFAULT; + + /* + * setup initial parameters + */ + memset(&init_var, 0, sizeof(init_var)); + init_var.xres_virtual = + init_var.xres = DEFAULT_XRES; + init_var.yres_virtual = + init_var.yres = DEFAULT_YRES; + init_var.bits_per_pixel = DEFAULT_BPP; + + init_var.red.msb_right = 0; + init_var.green.msb_right = 0; + init_var.blue.msb_right = 0; + + switch(init_var.bits_per_pixel) { + case 8: + init_var.bits_per_pixel = 8; + init_var.red.offset = 0; + init_var.red.length = 8; + init_var.green.offset = 0; + init_var.green.length = 8; + init_var.blue.offset = 0; + init_var.blue.length = 8; + break; + + case 16: + init_var.bits_per_pixel = 16; + init_var.red.offset = 11; + init_var.red.length = 5; + init_var.green.offset = 5; + init_var.green.length = 6; + init_var.blue.offset = 0; + init_var.blue.length = 5; + break; + + case 24: + init_var.bits_per_pixel = 24; + init_var.red.offset = 16; + init_var.red.length = 8; + init_var.green.offset = 8; + init_var.green.length = 8; + init_var.blue.offset = 0; + init_var.blue.length = 8; + break; + } + + init_var.nonstd = 0; + init_var.activate = FB_ACTIVATE_NOW; + init_var.height = -1; + init_var.width = -1; + init_var.accel_flags = FB_ACCELF_TEXT; + init_var.sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT; + init_var.vmode = FB_VMODE_NONINTERLACED; +} + +/* + * Initialization + */ +__initfunc(void cyber2000fb_init(void)) +{ + struct pci_dev *dev; + u_int h_sync, v_sync; + + dev = pci_find_device(PCI_VENDOR_ID_INTERG, 0x2000, NULL); + if (!dev) + return; + + CyberRegs = bus_to_virt(dev->base_address[0]) + 0x00800000;/*FIXME*/ + + cyber2000_outb(0x18, 0x46e8); + cyber2000_outb(0x01, 0x102); + cyber2000_outb(0x08, 0x46e8); + + cyber2000fb_init_fbinfo(); + + current_par.currcon = -1; + current_par.screen_base_p = 0x80000000 + dev->base_address[0]; + current_par.screen_base = (u_int)bus_to_virt(dev->base_address[0]); + current_par.screen_size = 0x00200000; + current_par.regs_base_p = 0x80800000 + dev->base_address[0]; + + cyber2000fb_set_var(&init_var, -1, &fb_info); + + h_sync = 1953125000 / init_var.pixclock; + h_sync = h_sync * 512 / (init_var.xres + init_var.left_margin + + init_var.right_margin + init_var.hsync_len); + v_sync = h_sync / (init_var.yres + init_var.upper_margin + + init_var.lower_margin + init_var.vsync_len); + + printk("Cyber2000: %ldkB VRAM, using %dx%d, %d.%03dkHz, %dHz\n", + current_par.screen_size >> 10, + init_var.xres, init_var.yres, + h_sync / 1000, h_sync % 1000, v_sync); + + if (register_framebuffer(&fb_info) < 0) + return; + + MOD_INC_USE_COUNT; /* TODO: This driver cannot be unloaded yet */ +} + + + +#ifdef MODULE +int init_module(void) +{ + cyber2000fb_init(); + return 0; +} + +void cleanup_module(void) +{ + /* Not reached because the usecount will never be + decremented to zero */ + unregister_framebuffer(&fb_info); + /* TODO: clean up ... */ +} + +#endif /* MODULE */ diff --git a/drivers/video/cyber2000fb.h b/drivers/video/cyber2000fb.h new file mode 100644 index 000000000..f1e81dfa0 --- /dev/null +++ b/drivers/video/cyber2000fb.h @@ -0,0 +1,78 @@ +/* + * linux/drivers/video/cyber2000fb.h + * + * Integraphics Cyber2000 frame buffer device + */ + +#define arraysize(x) (sizeof(x)/sizeof(*(x))) +#define cyber2000_outb(dat,reg) (CyberRegs[reg] = dat) +#define cyber2000_outw(dat,reg) (*(unsigned short *)&CyberRegs[reg] = dat) +#define cyber2000_outl(dat,reg) (*(unsigned long *)&CyberRegs[reg] = dat) + +#define cyber2000_inb(reg) (CyberRegs[reg]) +#define cyber2000_inw(reg) (*(unsigned short *)&CyberRegs[reg]) +#define cyber2000_inl(reg) (*(unsigned long *)&CyberRegs[reg]) + +static inline void cyber2000_crtcw(int val, int reg) +{ + cyber2000_outb(reg, 0x3d4); + cyber2000_outb(val, 0x3d5); +} + +static inline void cyber2000_grphw(int val, int reg) +{ + cyber2000_outb(reg, 0x3ce); + cyber2000_outb(val, 0x3cf); +} + +static inline void cyber2000_attrw(int val, int reg) +{ + cyber2000_inb(0x3da); + cyber2000_outb(reg, 0x3c0); + cyber2000_inb(0x3c1); + cyber2000_outb(val, 0x3c0); +} + +static inline void cyber2000_seqw(int val, int reg) +{ + cyber2000_outb(reg, 0x3c4); + cyber2000_outb(val, 0x3c5); +} + +struct cyber2000fb_par { + unsigned long screen_base; + unsigned long screen_base_p; + unsigned long regs_base; + unsigned long regs_base_p; + unsigned long screen_end; + unsigned long screen_size; + unsigned int palette_size; + signed int currcon; + /* + * palette + */ + struct { + u8 red; + u8 green; + u8 blue; + } palette[256]; + /* + * colour mapping table + */ + union { +#ifdef FBCON_HAS_CFB16 + u16 cfb16[16]; +#endif +#ifdef FBCON_HAS_CFB24 + u32 cfb24[16]; +#endif + } c_table; +}; + +struct res { + int xres; + int yres; + unsigned char crtc_regs[18]; + unsigned char crtc_ofl; + unsigned char clk_regs[4]; +}; diff --git a/drivers/video/fbcon-vga-planes.c b/drivers/video/fbcon-vga-planes.c new file mode 100644 index 000000000..391ceb22e --- /dev/null +++ b/drivers/video/fbcon-vga-planes.c @@ -0,0 +1,364 @@ +/* + * linux/drivers/video/fbcon-vga-planes.c -- Low level frame buffer operations + * for VGA 4-plane modes + * + * Copyright 1999 Ben Pfaff <pfaffben@debian.org> and Petr Vandrovec <VANDROVE@vc.cvut.cz> + * Based on code by Michael Schmitz + * Based on the old macfb.c 4bpp code by Alan Cox + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. */ + +#include <linux/module.h> +#include <linux/tty.h> +#include <linux/console.h> +#include <linux/string.h> +#include <linux/fb.h> +#include <linux/vt_buffer.h> + +#include <asm/io.h> + +#include <video/fbcon.h> +#include <video/fbcon-vga-planes.h> + +#define GRAPHICS_ADDR_REG 0x3ce /* Graphics address register. */ +#define GRAPHICS_DATA_REG 0x3cf /* Graphics data register. */ + +#define SET_RESET_INDEX 0 /* Set/Reset Register index. */ +#define ENABLE_SET_RESET_INDEX 1 /* Enable Set/Reset Register index. */ +#define DATA_ROTATE_INDEX 3 /* Data Rotate Register index. */ +#define GRAPHICS_MODE_INDEX 5 /* Graphics Mode Register index. */ +#define BIT_MASK_INDEX 8 /* Bit Mask Register index. */ + +/* The VGA's weird architecture often requires that we read a byte and + write a byte to the same location. It doesn't matter *what* byte + we write, however. This is because all the action goes on behind + the scenes in the VGA's 32-bit latch register, and reading and writing + video memory just invokes latch behavior. + + To avoid race conditions (is this necessary?), reading and writing + the memory byte should be done with a single instruction. One + suitable instruction is the x86 bitwise OR. The following + read-modify-write routine should optimize to one such bitwise + OR. */ +static inline void rmw(volatile char *p) +{ + *p |= 1; +} + +/* Set the Graphics Mode Register. Bits 0-1 are write mode, bit 3 is + read mode. */ +static inline void setmode(int mode) +{ + outb(GRAPHICS_MODE_INDEX, GRAPHICS_ADDR_REG); + outb(mode, GRAPHICS_DATA_REG); +} + +/* Select the Bit Mask Register. */ +static inline void selectmask(void) +{ + outb(BIT_MASK_INDEX, GRAPHICS_ADDR_REG); +} + +/* Set the value of the Bit Mask Register. It must already have been + selected with selectmask(). */ +static inline void setmask(int mask) +{ + outb(mask, GRAPHICS_DATA_REG); +} + +/* Set the Data Rotate Register. Bits 0-2 are rotate count, bits 3-4 + are logical operation (0=NOP, 1=AND, 2=OR, 3=XOR). */ +static inline void setop(int op) +{ + outb(DATA_ROTATE_INDEX, GRAPHICS_ADDR_REG); + outb(op, GRAPHICS_DATA_REG); +} + +/* Set the Enable Set/Reset Register. The code here always uses value + 0xf for this register. */ +static inline void setsr(int sr) +{ + outb(ENABLE_SET_RESET_INDEX, GRAPHICS_ADDR_REG); + outb(sr, GRAPHICS_DATA_REG); +} + +/* Set the Set/Reset Register. */ +static inline void setcolor(int color) +{ + outb(SET_RESET_INDEX, GRAPHICS_ADDR_REG); + outb(color, GRAPHICS_DATA_REG); +} + +/* Set the value in the Graphics Address Register. */ +static inline void setindex(int index) +{ + outb(index, GRAPHICS_ADDR_REG); +} + +void fbcon_vga_planes_setup(struct display *p) +{ +} + +void fbcon_vga_planes_bmove(struct display *p, int sy, int sx, int dy, int dx, + int height, int width) +{ + char *src; + char *dest; + int line_ofs; + int x; + + setmode(1); + setop(0); + setsr(0xf); + + sy *= fontheight(p); + dy *= fontheight(p); + height *= fontheight(p); + + if (dy < sy || (dy == sy && dx < sx)) { + line_ofs = p->line_length - width; + dest = p->screen_base + dx + dy * p->line_length; + src = p->screen_base + sx + sy * p->line_length; + while (height--) { + for (x = 0; x < width; x++) + *dest++ = *src++; + src += line_ofs; + dest += line_ofs; + } + } else { + line_ofs = p->line_length - width; + dest = p->screen_base + dx + width + (dy + height - 1) * p->line_length; + src = p->screen_base + sx + width + (sy + height - 1) * p->line_length; + while (height--) { + for (x = 0; x < width; x++) + *--dest = *--src; + src -= line_ofs; + dest -= line_ofs; + } + } +} + +void fbcon_vga_planes_clear(struct vc_data *conp, struct display *p, int sy, int sx, + int height, int width) +{ + int line_ofs = p->line_length - width; + char *where; + int x; + + setmode(0); + setop(0); + setsr(0xf); + setcolor(attr_bgcol_ec(p, conp)); + selectmask(); + + setmask(0xff); + + sy *= fontheight(p); + height *= fontheight(p); + + where = p->screen_base + sx + sy * p->line_length; + while (height--) { + for (x = 0; x < width; x++) + *where++ = 0; + where += line_ofs; + } +} + +void fbcon_ega_planes_putc(struct vc_data *conp, struct display *p, int c, int yy, int xx) +{ + int fg = attr_fgcol(p,c); + int bg = attr_bgcol(p,c); + + int y; + u8 *cdat = p->fontdata + (c & p->charmask) * fontheight(p); + char *where = p->screen_base + xx + yy * p->line_length * fontheight(p); + + setmode(0); + setop(0); + setsr(0xf); + setcolor(bg); + selectmask(); + + setmask(0xff); + for (y = 0; y < fontheight(p); y++, where += p->line_length) + rmw(where); + + where -= p->line_length * y; + setcolor(fg); + selectmask(); + for (y = 0; y < fontheight(p); y++, where += p->line_length) + if (cdat[y]) { + setmask(cdat[y]); + rmw(where); + } +} + +void fbcon_vga_planes_putc(struct vc_data *conp, struct display *p, int c, int yy, int xx) +{ + int fg = attr_fgcol(p,c); + int bg = attr_bgcol(p,c); + + int y; + u8 *cdat = p->fontdata + (c & p->charmask) * fontheight(p); + char *where = p->screen_base + xx + yy * p->line_length * fontheight(p); + + setmode(2); + setop(0); + setsr(0xf); + setcolor(fg); + selectmask(); + + setmask(0xff); + *where = bg; + rmb(); + *(volatile char*)where; /* fill latches */ + setmode(3); + wmb(); + for (y = 0; y < fontheight(p); y++, where += p->line_length) + *where = cdat[y]; + wmb(); +} + +/* 28.50 in my test */ +void fbcon_ega_planes_putcs(struct vc_data *conp, struct display *p, const unsigned short *s, + int count, int yy, int xx) +{ + int fg = attr_fgcol(p,scr_readw(s)); + int bg = attr_bgcol(p,scr_readw(s)); + + char *where; + int n; + + setmode(2); + setop(0); + selectmask(); + + setmask(0xff); + where = p->screen_base + xx + yy * p->line_length * fontheight(p); + *where = bg; + rmb(); + *(volatile char*)where; + wmb(); + selectmask(); + for (n = 0; n < count; n++) { + int c = scr_readw(s++) & p->charmask; + u8 *cdat = p->fontdata + c * fontheight(p); + u8 *end = cdat + fontheight(p); + + while (cdat < end) { + outb(*cdat++, GRAPHICS_DATA_REG); + wmb(); + *where = fg; + where += p->line_length; + } + where += 1 - p->line_length * fontheight(p); + } + + wmb(); +} + +/* 6.96 in my test */ +void fbcon_vga_planes_putcs(struct vc_data *conp, struct display *p, const unsigned short *s, + int count, int yy, int xx) +{ + int fg = attr_fgcol(p,*s); + int bg = attr_bgcol(p,*s); + + char *where; + int n; + + setmode(2); + setop(0); + setsr(0xf); + setcolor(fg); + selectmask(); + + setmask(0xff); + where = p->screen_base + xx + yy * p->line_length * fontheight(p); + *where = bg; + rmb(); + *(volatile char*)where; /* fill latches with background */ + setmode(3); + wmb(); + for (n = 0; n < count; n++) { + int y; + int c = *s++ & p->charmask; + u8 *cdat = p->fontdata + (c & p->charmask) * fontheight(p); + + for (y = 0; y < fontheight(p); y++, cdat++) { + *where = *cdat; + where += p->line_length; + } + where += 1 - p->line_length * fontheight(p); + } + + wmb(); +} + +void fbcon_vga_planes_revc(struct display *p, int xx, int yy) +{ + char *where = p->screen_base + xx + yy * p->line_length * fontheight(p); + int y; + + setmode(0); + setop(0x18); + setsr(0xf); + setcolor(0xf); + selectmask(); + + setmask(0xff); + for (y = 0; y < fontheight(p); y++) { + rmw(where); + where += p->line_length; + } +} + +struct display_switch fbcon_vga_planes = { + fbcon_vga_planes_setup, fbcon_vga_planes_bmove, fbcon_vga_planes_clear, + fbcon_vga_planes_putc, fbcon_vga_planes_putcs, fbcon_vga_planes_revc, + NULL, NULL, NULL, FONTWIDTH(8) +}; + +struct display_switch fbcon_ega_planes = { + fbcon_vga_planes_setup, fbcon_vga_planes_bmove, fbcon_vga_planes_clear, + fbcon_ega_planes_putc, fbcon_ega_planes_putcs, fbcon_vga_planes_revc, + NULL, NULL, NULL, FONTWIDTH(8) +}; + +#ifdef MODULE +int init_module(void) +{ + return 0; +} + +void cleanup_module(void) +{} +#endif /* MODULE */ + + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(fbcon_vga_planes); +EXPORT_SYMBOL(fbcon_vga_planes_setup); +EXPORT_SYMBOL(fbcon_vga_planes_bmove); +EXPORT_SYMBOL(fbcon_vga_planes_clear); +EXPORT_SYMBOL(fbcon_vga_planes_putc); +EXPORT_SYMBOL(fbcon_vga_planes_putcs); +EXPORT_SYMBOL(fbcon_vga_planes_revc); + +EXPORT_SYMBOL(fbcon_ega_planes); +EXPORT_SYMBOL(fbcon_ega_planes_putc); +EXPORT_SYMBOL(fbcon_ega_planes_putcs); + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ + diff --git a/drivers/video/vga16fb.c b/drivers/video/vga16fb.c new file mode 100644 index 000000000..7f7b1af4d --- /dev/null +++ b/drivers/video/vga16fb.c @@ -0,0 +1,1064 @@ +/* + * linux/drivers/video/vga16.c -- VGA 16-color framebuffer driver + * + * Copyright 1999 Ben Pfaff <pfaffben@debian.org> and Petr Vandrovec <VANDROVE@vc.cvut.cz> + * Based on VGA info at http://www.goodnet.com/~tinara/FreeVGA/home.htm + * Based on VESA framebuffer (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file COPYING in the main directory of this + * archive for more details. */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/tty.h> +#include <linux/malloc.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/console.h> +#include <linux/selection.h> +#include <linux/ioport.h> +#include <linux/init.h> + +#include <asm/io.h> + +#include <video/fbcon.h> +#include <video/fbcon-vga-planes.h> + +#define dac_reg (0x3c8) +#define dac_val (0x3c9) + +/* --------------------------------------------------------------------- */ + +/* + * card parameters + */ + +static struct vga16fb_info { + struct fb_info fb_info; + char *video_vbase; /* 0xa0000 map address */ + int isVGA; + + /* structure holding original VGA register settings when the + screen is blanked */ + struct { + unsigned char SeqCtrlIndex; /* Sequencer Index reg. */ + unsigned char CrtCtrlIndex; /* CRT-Contr. Index reg. */ + unsigned char CrtMiscIO; /* Miscellaneous register */ + unsigned char HorizontalTotal; /* CRT-Controller:00h */ + unsigned char HorizDisplayEnd; /* CRT-Controller:01h */ + unsigned char StartHorizRetrace; /* CRT-Controller:04h */ + unsigned char EndHorizRetrace; /* CRT-Controller:05h */ + unsigned char Overflow; /* CRT-Controller:07h */ + unsigned char StartVertRetrace; /* CRT-Controller:10h */ + unsigned char EndVertRetrace; /* CRT-Controller:11h */ + unsigned char ModeControl; /* CRT-Controller:17h */ + unsigned char ClockingMode; /* Seq-Controller:01h */ + } vga_state; + + int palette_blanked; + int vesa_blanked; +} vga16fb; + +/* Some of the code below is taken from SVGAlib. The original, + unmodified copyright notice for that code is below. */ +/* VGAlib version 1.2 - (c) 1993 Tommy Frandsen */ +/* */ +/* This library is free software; you can redistribute it and/or */ +/* modify it without any restrictions. This library is distributed */ +/* in the hope that it will be useful, but without any warranty. */ + +/* Multi-chipset support Copyright 1993 Harm Hanemaayer */ +/* partially copyrighted (C) 1993 by Hartmut Schirmer */ + +/* VGA data register ports */ +#define CRT_DC 0x3D5 /* CRT Controller Data Register - color emulation */ +#define CRT_DM 0x3B5 /* CRT Controller Data Register - mono emulation */ +#define ATT_R 0x3C1 /* Attribute Controller Data Read Register */ +#define GRA_D 0x3CF /* Graphics Controller Data Register */ +#define SEQ_D 0x3C5 /* Sequencer Data Register */ +#define MIS_R 0x3CC /* Misc Output Read Register */ +#define MIS_W 0x3C2 /* Misc Output Write Register */ +#define IS1_RC 0x3DA /* Input Status Register 1 - color emulation */ +#define IS1_RM 0x3BA /* Input Status Register 1 - mono emulation */ +#define PEL_D 0x3C9 /* PEL Data Register */ +#define PEL_MSK 0x3C6 /* PEL mask register */ + +/* EGA-specific registers */ +#define GRA_E0 0x3CC /* Graphics enable processor 0 */ +#define GRA_E1 0x3CA /* Graphics enable processor 1 */ + + +/* VGA index register ports */ +#define CRT_IC 0x3D4 /* CRT Controller Index - color emulation */ +#define CRT_IM 0x3B4 /* CRT Controller Index - mono emulation */ +#define ATT_IW 0x3C0 /* Attribute Controller Index & Data Write Register */ +#define GRA_I 0x3CE /* Graphics Controller Index */ +#define SEQ_I 0x3C4 /* Sequencer Index */ +#define PEL_IW 0x3C8 /* PEL Write Index */ +#define PEL_IR 0x3C7 /* PEL Read Index */ + +/* standard VGA indexes max counts */ +#define CRT_C 24 /* 24 CRT Controller Registers */ +#define ATT_C 21 /* 21 Attribute Controller Registers */ +#define GRA_C 9 /* 9 Graphics Controller Registers */ +#define SEQ_C 5 /* 5 Sequencer Registers */ +#define MIS_C 1 /* 1 Misc Output Register */ + +#define CRTC_H_TOTAL 0 +#define CRTC_H_DISP 1 +#define CRTC_H_BLANK_START 2 +#define CRTC_H_BLANK_END 3 +#define CRTC_H_SYNC_START 4 +#define CRTC_H_SYNC_END 5 +#define CRTC_V_TOTAL 6 +#define CRTC_OVERFLOW 7 +#define CRTC_PRESET_ROW 8 +#define CRTC_MAX_SCAN 9 +#define CRTC_CURSOR_START 0x0A +#define CRTC_CURSOR_END 0x0B +#define CRTC_START_HI 0x0C +#define CRTC_START_LO 0x0D +#define CRTC_CURSOR_HI 0x0E +#define CRTC_CURSOR_LO 0x0F +#define CRTC_V_SYNC_START 0x10 +#define CRTC_V_SYNC_END 0x11 +#define CRTC_V_DISP_END 0x12 +#define CRTC_OFFSET 0x13 +#define CRTC_UNDERLINE 0x14 +#define CRTC_V_BLANK_START 0x15 +#define CRTC_V_BLANK_END 0x16 +#define CRTC_MODE 0x17 +#define CRTC_LINE_COMPARE 0x18 +#define CRTC_REGS 0x19 + +#define ATC_MODE 0x10 +#define ATC_OVERSCAN 0x11 +#define ATC_PLANE_ENABLE 0x12 +#define ATC_PEL 0x13 +#define ATC_COLOR_PAGE 0x14 + +#define SEQ_CLOCK_MODE 0x01 +#define SEQ_PLANE_WRITE 0x02 +#define SEQ_CHARACTER_MAP 0x03 +#define SEQ_MEMORY_MODE 0x04 + +#define GDC_SR_VALUE 0x00 +#define GDC_SR_ENABLE 0x01 +#define GDC_COMPARE_VALUE 0x02 +#define GDC_DATA_ROTATE 0x03 +#define GDC_PLANE_READ 0x04 +#define GDC_MODE 0x05 +#define GDC_MISC 0x06 +#define GDC_COMPARE_MASK 0x07 +#define GDC_BIT_MASK 0x08 + +struct vga16fb_par { + u8 crtc[CRTC_REGS]; + u8 atc[ATT_C]; + u8 gdc[GRA_C]; + u8 seq[SEQ_C]; + u8 misc; + u8 vss; + struct fb_var_screeninfo var; +}; + +/* --------------------------------------------------------------------- */ + +static struct fb_var_screeninfo vga16fb_defined = { + 640,480,640,480,/* W,H, W, H (virtual) load xres,xres_virtual*/ + 0,0, /* virtual -> visible no offset */ + 4, /* depth -> load bits_per_pixel */ + 0, /* greyscale ? */ + {0,0,0}, /* R */ + {0,0,0}, /* G */ + {0,0,0}, /* B */ + {0,0,0}, /* transparency */ + 0, /* standard pixel format */ + FB_ACTIVATE_NOW, + -1,-1, + 0, + 39721, 48, 16, 39, 8, + 96, 2, 0, /* No sync info */ + FB_VMODE_NONINTERLACED, + {0,0,0,0,0,0} +}; + +static struct display disp; +static struct { u_short blue, green, red, pad; } palette[256]; + +static int currcon = 0; + +/* --------------------------------------------------------------------- */ + + /* + * Open/Release the frame buffer device + */ + +static int vga16fb_open(struct fb_info *info, int user) +{ + /* + * Nothing, only a usage count for the moment + */ + MOD_INC_USE_COUNT; + return(0); +} + +static int vga16fb_release(struct fb_info *info, int user) +{ + MOD_DEC_USE_COUNT; + return(0); +} + +static void vga16fb_pan_var(struct fb_info *info, struct fb_var_screeninfo *var) +{ + u32 pos = (var->xres_virtual * var->yoffset + var->xoffset) >> 3; + outb(CRTC_START_HI, CRT_IC); + outb(pos >> 8, CRT_DC); + outb(CRTC_START_LO, CRT_IC); + outb(pos & 0xFF, CRT_DC); +#if 0 + /* if someone supports xoffset in bit resolution */ + inb(IS1_RC); /* reset flip-flop */ + outb(ATC_PEL, ATT_IW); + outb(xoffset & 7, ATT_IW); + inb(IS1_RC); + outb(0x20, ATT_IW); +#endif +} + +static int vga16fb_update_var(int con, struct fb_info *info) +{ + vga16fb_pan_var(info, &fb_display[con].var); + return 0; +} + +static int vga16fb_get_fix(struct fb_fix_screeninfo *fix, int con, + struct fb_info *info) +{ + struct display *p; + + if (con < 0) + p = &disp; + else + p = fb_display + con; + + memset(fix, 0, sizeof(struct fb_fix_screeninfo)); + strcpy(fix->id,"VGA16 VGA"); + + fix->smem_start = (char *) 0xa0000; + fix->smem_len = 65536; + fix->type = FB_TYPE_VGA_PLANES; + fix->visual = FB_VISUAL_PSEUDOCOLOR; + fix->xpanstep = 8; + fix->ypanstep = 1; + fix->ywrapstep = 0; + fix->line_length = p->var.xres_virtual / 8; + return 0; +} + +static int vga16fb_get_var(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + if(con==-1) + memcpy(var, &vga16fb_defined, sizeof(struct fb_var_screeninfo)); + else + *var=fb_display[con].var; + return 0; +} + +static void vga16fb_set_disp(int con, struct vga16fb_info *info) +{ + struct fb_fix_screeninfo fix; + struct display *display; + + if (con < 0) + display = &disp; + else + display = fb_display + con; + + + vga16fb_get_fix(&fix, con, &info->fb_info); + + display->screen_base = info->video_vbase; + display->visual = fix.visual; + display->type = fix.type; + display->type_aux = fix.type_aux; + display->ypanstep = fix.ypanstep; + display->ywrapstep = fix.ywrapstep; + display->line_length = fix.line_length; + display->next_line = fix.line_length; + display->can_soft_blank = 1; + display->inverse = 0; + + if (info->isVGA) + display->dispsw = &fbcon_vga_planes; + else + display->dispsw = &fbcon_ega_planes; + display->scrollmode = SCROLL_YREDRAW; +} + +static void vga16fb_encode_var(struct fb_var_screeninfo *var, + const struct vga16fb_par *par, + const struct vga16fb_info *info) +{ + *var = par->var; +} + +static void vga16fb_clock_chip(struct vga16fb_par *par, + unsigned int pixclock, + const struct vga16fb_info *info) +{ + static struct { + u32 pixclock; + u8 misc; + u8 seq_clock_mode; + } *ptr, *best, vgaclocks[] = { + { 79442 /* 12.587 */, 0x00, 0x08}, + { 70616 /* 14.161 */, 0x04, 0x08}, + { 39721 /* 25.175 */, 0x00, 0x00}, + { 35308 /* 28.322 */, 0x04, 0x00}, + { 0 /* bad */, 0x00, 0x00}}; + int err; + + best = vgaclocks; + err = pixclock - best->pixclock; + if (err < 0) err = -err; + for (ptr = vgaclocks + 1; ptr->pixclock; ptr++) { + int tmp; + + tmp = pixclock - ptr->pixclock; + if (tmp < 0) tmp = -tmp; + if (tmp < err) { + err = tmp; + best = ptr; + } + } + par->misc |= best->misc; + par->seq[SEQ_CLOCK_MODE] |= best->seq_clock_mode; + par->var.pixclock = best->pixclock; +} + +#define FAIL(X) return -EINVAL + +static int vga16fb_decode_var(const struct fb_var_screeninfo *var, + struct vga16fb_par *par, + const struct vga16fb_info *info) +{ + u32 xres, right, hslen, left, xtotal; + u32 yres, lower, vslen, upper, ytotal; + u32 vxres, xoffset, vyres, yoffset; + u32 pos; + u8 r7, rMode; + int i; + + if (var->bits_per_pixel != 4) + return -EINVAL; + xres = (var->xres + 7) & ~7; + vxres = (var->xres_virtual + 0xF) & ~0xF; + xoffset = (var->xoffset + 7) & ~7; + left = (var->left_margin + 7) & ~7; + right = (var->right_margin + 7) & ~7; + hslen = (var->hsync_len + 7) & ~7; + + if (vxres < xres) + vxres = xres; + if (xres + xoffset > vxres) + xoffset = vxres - xres; + + par->var.xres = xres; + par->var.right_margin = right; + par->var.hsync_len = hslen; + par->var.left_margin = left; + par->var.xres_virtual = vxres; + par->var.xoffset = xoffset; + + xres >>= 3; + right >>= 3; + hslen >>= 3; + left >>= 3; + vxres >>= 3; + xtotal = xres + right + hslen + left; + if (xtotal >= 256) + FAIL("xtotal too big"); + if (hslen > 32) + FAIL("hslen too big"); + if (right + hslen + left > 64) + FAIL("hblank too big"); + par->crtc[CRTC_H_TOTAL] = xtotal - 5; + par->crtc[CRTC_H_BLANK_START] = xres - 1; + par->crtc[CRTC_H_DISP] = xres - 1; + pos = xres + right; + par->crtc[CRTC_H_SYNC_START] = pos; + pos += hslen; + par->crtc[CRTC_H_SYNC_END] = pos & 0x1F; + pos += left - 2; /* blank_end + 2 <= total + 5 */ + par->crtc[CRTC_H_BLANK_END] = (pos & 0x1F) | 0x80; + if (pos & 0x20) + par->crtc[CRTC_H_SYNC_END] |= 0x80; + + yres = var->yres; + lower = var->lower_margin; + vslen = var->vsync_len; + upper = var->upper_margin; + vyres = var->yres_virtual; + yoffset = var->yoffset; + + if (yres > vyres) + vyres = yres; + if (vxres * vyres > 65536) { + vyres = 65536 / vxres; + if (vyres < yres) + return -ENOMEM; + } + if (yoffset + yres > vyres) + yoffset = vyres - yres; + par->var.yres = yres; + par->var.lower_margin = lower; + par->var.vsync_len = vslen; + par->var.upper_margin = upper; + par->var.yres_virtual = vyres; + par->var.yoffset = yoffset; + + if (var->vmode & FB_VMODE_DOUBLE) { + yres <<= 1; + lower <<= 1; + vslen <<= 1; + upper <<= 1; + } + ytotal = yres + lower + vslen + upper; + if (ytotal > 1024) { + ytotal >>= 1; + yres >>= 1; + lower >>= 1; + vslen >>= 1; + upper >>= 1; + rMode = 0x04; + } else + rMode = 0x00; + if (ytotal > 1024) + FAIL("ytotal too big"); + if (vslen > 16) + FAIL("vslen too big"); + par->crtc[CRTC_V_TOTAL] = ytotal - 2; + r7 = 0x10; /* disable linecompare */ + if (ytotal & 0x100) r7 |= 0x01; + if (ytotal & 0x200) r7 |= 0x20; + par->crtc[CRTC_PRESET_ROW] = 0; + par->crtc[CRTC_MAX_SCAN] = 0x40; /* 1 scanline, no linecmp */ + par->var.vmode = var->vmode; + if (var->vmode & FB_VMODE_DOUBLE) + par->crtc[CRTC_MAX_SCAN] |= 0x80; + par->crtc[CRTC_CURSOR_START] = 0x20; + par->crtc[CRTC_CURSOR_END] = 0x00; + pos = yoffset * vxres + (xoffset >> 3); + par->crtc[CRTC_START_HI] = pos >> 8; + par->crtc[CRTC_START_LO] = pos & 0xFF; + par->crtc[CRTC_CURSOR_HI] = 0x00; + par->crtc[CRTC_CURSOR_LO] = 0x00; + pos = yres - 1; + par->crtc[CRTC_V_DISP_END] = pos & 0xFF; + par->crtc[CRTC_V_BLANK_START] = pos & 0xFF; + if (pos & 0x100) + r7 |= 0x0A; /* 0x02 -> DISP_END, 0x08 -> BLANK_START */ + if (pos & 0x200) { + r7 |= 0x40; /* 0x40 -> DISP_END */ + par->crtc[CRTC_MAX_SCAN] |= 0x20; /* BLANK_START */ + } + pos += lower; + par->crtc[CRTC_V_SYNC_START] = pos & 0xFF; + if (pos & 0x100) + r7 |= 0x04; + if (pos & 0x200) + r7 |= 0x80; + pos += vslen; + par->crtc[CRTC_V_SYNC_END] = (pos & 0x0F) | 0x10; /* disabled IRQ */ + pos += upper - 1; /* blank_end + 1 <= ytotal + 2 */ + par->crtc[CRTC_V_BLANK_END] = pos & 0xFF; /* 0x7F for original VGA, + but some SVGA chips requires all 8 bits to set */ + if (vxres >= 512) + FAIL("vxres too long"); + par->crtc[CRTC_OFFSET] = vxres >> 1; + par->crtc[CRTC_UNDERLINE] = 0x1F; + par->crtc[CRTC_MODE] = rMode | 0xE3; + par->crtc[CRTC_LINE_COMPARE] = 0xFF; + par->crtc[CRTC_OVERFLOW] = r7; + + par->vss = 0x00; /* 3DA */ + + for (i = 0x00; i < 0x10; i++) + par->atc[i] = i; + par->atc[ATC_MODE] = 0x81; + par->atc[ATC_OVERSCAN] = 0x00; /* 0 for EGA, 0xFF for VGA */ + par->atc[ATC_PLANE_ENABLE] = 0x0F; + par->atc[ATC_PEL] = xoffset & 7; + par->atc[ATC_COLOR_PAGE] = 0x00; + + par->misc = 0xC3; /* enable CPU, ports 0x3Dx, positive sync */ + par->var.sync = var->sync; + if (var->sync & FB_SYNC_HOR_HIGH_ACT) + par->misc &= ~0x40; + if (var->sync & FB_SYNC_VERT_HIGH_ACT) + par->misc &= ~0x80; + + par->seq[SEQ_CLOCK_MODE] = 0x01; + par->seq[SEQ_PLANE_WRITE] = 0x0F; + par->seq[SEQ_CHARACTER_MAP] = 0x00; + par->seq[SEQ_MEMORY_MODE] = 0x06; + + par->gdc[GDC_SR_VALUE] = 0x00; + par->gdc[GDC_SR_ENABLE] = 0x0F; + par->gdc[GDC_COMPARE_VALUE] = 0x00; + par->gdc[GDC_DATA_ROTATE] = 0x20; + par->gdc[GDC_PLANE_READ] = 0; + par->gdc[GDC_MODE] = 0x00; + par->gdc[GDC_MISC] = 0x05; + par->gdc[GDC_COMPARE_MASK] = 0x0F; + par->gdc[GDC_BIT_MASK] = 0xFF; + + vga16fb_clock_chip(par, var->pixclock, info); + + par->var.bits_per_pixel = 4; + par->var.grayscale = var->grayscale; + par->var.red.offset = par->var.green.offset = par->var.blue.offset = + par->var.transp.offset = 0; + par->var.red.length = par->var.green.length = par->var.blue.length = + (info->isVGA) ? 6 : 2; + par->var.transp.length = 0; + par->var.nonstd = 0; + par->var.activate = FB_ACTIVATE_NOW; + par->var.height = -1; + par->var.width = -1; + par->var.accel_flags = 0; + + return 0; +} +#undef FAIL + +static int vga16fb_set_par(const struct vga16fb_par *par, + struct vga16fb_info *info) +{ + int i; + + outb(inb(MIS_R) | 0x01, MIS_W); + + /* Enable graphics register modification */ + if (!info->isVGA) { + outb(0x00, GRA_E0); + outb(0x01, GRA_E1); + } + + /* update misc output register */ + outb(par->misc, MIS_W); + + /* synchronous reset on */ + outb(0x00, SEQ_I); + outb(0x01, SEQ_D); + + /* write sequencer registers */ + outb(1, SEQ_I); + outb(par->seq[1] | 0x20, SEQ_D); + for (i = 2; i < SEQ_C; i++) { + outb(i, SEQ_I); + outb(par->seq[i], SEQ_D); + } + + /* synchronous reset off */ + outb(0x00, SEQ_I); + outb(0x03, SEQ_D); + + /* deprotect CRT registers 0-7 */ + outb(0x11, CRT_IC); + outb(par->crtc[0x11], CRT_DC); + + /* write CRT registers */ + for (i = 0; i < CRTC_REGS; i++) { + outb(i, CRT_IC); + outb(par->crtc[i], CRT_DC); + } + + /* write graphics controller registers */ + for (i = 0; i < GRA_C; i++) { + outb(i, GRA_I); + outb(par->gdc[i], GRA_D); + } + + /* write attribute controller registers */ + for (i = 0; i < ATT_C; i++) { + inb_p(IS1_RC); /* reset flip-flop */ + outb_p(i, ATT_IW); + outb_p(par->atc[i], ATT_IW); + } + + /* Wait for screen to stabilize. */ + mdelay(50); + + outb(0x01, SEQ_I); + outb(par->seq[1], SEQ_D); + + inb(IS1_RC); + outb(0x20, ATT_IW); + + return 0; +} + +static int vga16fb_set_var(struct fb_var_screeninfo *var, int con, + struct fb_info *fb) +{ + struct vga16fb_info *info = (struct vga16fb_info*)fb; + struct vga16fb_par par; + struct display *display; + int err; + + if (con < 0) + display = fb->disp; + else + display = fb_display + con; + if ((err = vga16fb_decode_var(var, &par, info)) != 0) + return err; + vga16fb_encode_var(var, &par, info); + + if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_TEST) + return 0; + + if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) { + u32 oldxres, oldyres, oldvxres, oldvyres, oldbpp; + + oldxres = display->var.xres; + oldyres = display->var.yres; + oldvxres = display->var.xres_virtual; + oldvyres = display->var.yres_virtual; + oldbpp = display->var.bits_per_pixel; + + display->var = *var; + + if (oldxres != var->xres || oldyres != var->yres || + oldvxres != var->xres_virtual || oldvyres != var->yres_virtual || + oldbpp != var->bits_per_pixel) { + vga16fb_set_disp(con, info); + if (info->fb_info.changevar) + info->fb_info.changevar(con); + } + if (con == currcon) + vga16fb_set_par(&par, info); + } + + return 0; +} + +static void ega16_setpalette(int regno, unsigned red, unsigned green, unsigned blue) +{ + static unsigned char map[] = { 000, 001, 010, 011 }; + int val; + + val = map[red>>14] | ((map[green>>14]) << 1) | ((map[blue>>14]) << 2); + inb_p(0x3DA); /* ! 0x3BA */ + outb_p(regno, 0x3C0); + outb_p(val, 0x3C0); + inb_p(0x3DA); /* some clones need it */ + outb_p(0x20, 0x3C0); /* unblank screen */ +} + +static int vga16_getcolreg(unsigned regno, unsigned *red, unsigned *green, + unsigned *blue, unsigned *transp, + struct fb_info *fb_info) +{ + /* + * Read a single color register and split it into colors/transparent. + * Return != 0 for invalid regno. + */ + + if (regno >= 16) + return 1; + + *red = palette[regno].red; + *green = palette[regno].green; + *blue = palette[regno].blue; + *transp = 0; + return 0; +} + +static void vga16_setpalette(int regno, unsigned red, unsigned green, unsigned blue) +{ + outb(regno, dac_reg); + outb(red >> 10, dac_val); + outb(green >> 10, dac_val); + outb(blue >> 10, dac_val); +} + +static int vga16_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *fb_info) +{ + int gray; + + /* + * Set a single color register. The values supplied are + * already rounded down to the hardware's capabilities + * (according to the entries in the `var' structure). Return + * != 0 for invalid regno. + */ + + if (regno >= 16) + return 1; + + palette[regno].red = red; + palette[regno].green = green; + palette[regno].blue = blue; + + if (currcon < 0) + gray = disp.var.grayscale; + else + gray = fb_display[currcon].var.grayscale; + if (gray) { + /* gray = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + if (((struct vga16fb_info *) fb_info)->isVGA) + vga16_setpalette(regno,red,green,blue); + else + ega16_setpalette(regno,red,green,blue); + + return 0; +} + +static void do_install_cmap(int con, struct fb_info *info) +{ + if (con != currcon) + return; + if (fb_display[con].cmap.len) + fb_set_cmap(&fb_display[con].cmap, 1, vga16_setcolreg, info); + else + fb_set_cmap(fb_default_cmap(16), 1, vga16_setcolreg, + info); +} + +static int vga16fb_get_cmap(struct fb_cmap *cmap, int kspc, int con, + struct fb_info *info) +{ + if (con == currcon) /* current console? */ + return fb_get_cmap(cmap, kspc, vga16_getcolreg, info); + else if (fb_display[con].cmap.len) /* non default colormap? */ + fb_copy_cmap(&fb_display[con].cmap, cmap, kspc ? 0 : 2); + else + fb_copy_cmap(fb_default_cmap(16), + cmap, kspc ? 0 : 2); + return 0; +} + +static int vga16fb_set_cmap(struct fb_cmap *cmap, int kspc, int con, + struct fb_info *info) +{ + int err; + + if (!fb_display[con].cmap.len) { /* no colormap allocated? */ + err = fb_alloc_cmap(&fb_display[con].cmap,16,0); + if (err) + return err; + } + if (con == currcon) /* current console? */ + return fb_set_cmap(cmap, kspc, vga16_setcolreg, info); + else + fb_copy_cmap(cmap, &fb_display[con].cmap, kspc ? 0 : 1); + return 0; +} + +static int vga16fb_pan_display(struct fb_var_screeninfo *var, int con, + struct fb_info *info) +{ + if (var->xoffset + fb_display[con].var.xres > fb_display[con].var.xres_virtual || + var->yoffset + fb_display[con].var.yres > fb_display[con].var.yres_virtual) + return -EINVAL; + if (con == currcon) + vga16fb_pan_var(info, var); + fb_display[con].var.xoffset = var->xoffset; + fb_display[con].var.yoffset = var->yoffset; + fb_display[con].var.vmode &= ~FB_VMODE_YWRAP; + return 0; +} + +static int vga16fb_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, int con, + struct fb_info *info) +{ + return -EINVAL; +} + +static struct fb_ops vga16fb_ops = { + vga16fb_open, + vga16fb_release, + vga16fb_get_fix, + vga16fb_get_var, + vga16fb_set_var, + vga16fb_get_cmap, + vga16fb_set_cmap, + vga16fb_pan_display, + vga16fb_ioctl +}; + +void vga16fb_setup(char *options, int *ints) +{ + char *this_opt; + + vga16fb.fb_info.fontname[0] = '\0'; + + if (!options || !*options) + return; + + for(this_opt=strtok(options,","); this_opt; this_opt=strtok(NULL,",")) { + if (!*this_opt) continue; + + if (!strncmp(this_opt, "font:", 5)) + strcpy(vga16fb.fb_info.fontname, this_opt+5); + } +} + +static int vga16fb_switch(int con, struct fb_info *fb) +{ + struct vga16fb_par par; + struct vga16fb_info *info = (struct vga16fb_info*)fb; + + /* Do we have to save the colormap? */ + if (fb_display[currcon].cmap.len) + fb_get_cmap(&fb_display[currcon].cmap, 1, vga16_getcolreg, + fb); + + currcon = con; + vga16fb_decode_var(&fb_display[con].var, &par, info); + vga16fb_set_par(&par, info); + vga16fb_set_disp(con, info); + + /* Install new colormap */ + do_install_cmap(con, fb); +/* vga16fb_update_var(con, fb); */ + return 1; +} + +/* The following VESA blanking code is taken from vgacon.c. The VGA + blanking code was originally by Huang shi chao, and modified by + Christoph Rimek (chrimek@toppoint.de) and todd j. derr + (tjd@barefoot.org) for Linux. */ +#define attrib_port 0x3c0 +#define seq_port_reg 0x3c4 +#define seq_port_val 0x3c5 +#define gr_port_reg 0x3ce +#define gr_port_val 0x3cf +#define video_misc_rd 0x3cc +#define video_misc_wr 0x3c2 +#define vga_video_port_reg 0x3d4 +#define vga_video_port_val 0x3d5 + +static void vga_vesa_blank(struct vga16fb_info *info, int mode) +{ + unsigned char SeqCtrlIndex; + unsigned char CrtCtrlIndex; + + cli(); + SeqCtrlIndex = inb_p(seq_port_reg); + CrtCtrlIndex = inb_p(vga_video_port_reg); + + /* save original values of VGA controller registers */ + if(!info->vesa_blanked) { + info->vga_state.CrtMiscIO = inb_p(video_misc_rd); + sti(); + + outb_p(0x00,vga_video_port_reg); /* HorizontalTotal */ + info->vga_state.HorizontalTotal = inb_p(vga_video_port_val); + outb_p(0x01,vga_video_port_reg); /* HorizDisplayEnd */ + info->vga_state.HorizDisplayEnd = inb_p(vga_video_port_val); + outb_p(0x04,vga_video_port_reg); /* StartHorizRetrace */ + info->vga_state.StartHorizRetrace = inb_p(vga_video_port_val); + outb_p(0x05,vga_video_port_reg); /* EndHorizRetrace */ + info->vga_state.EndHorizRetrace = inb_p(vga_video_port_val); + outb_p(0x07,vga_video_port_reg); /* Overflow */ + info->vga_state.Overflow = inb_p(vga_video_port_val); + outb_p(0x10,vga_video_port_reg); /* StartVertRetrace */ + info->vga_state.StartVertRetrace = inb_p(vga_video_port_val); + outb_p(0x11,vga_video_port_reg); /* EndVertRetrace */ + info->vga_state.EndVertRetrace = inb_p(vga_video_port_val); + outb_p(0x17,vga_video_port_reg); /* ModeControl */ + info->vga_state.ModeControl = inb_p(vga_video_port_val); + outb_p(0x01,seq_port_reg); /* ClockingMode */ + info->vga_state.ClockingMode = inb_p(seq_port_val); + } + + /* assure that video is enabled */ + /* "0x20" is VIDEO_ENABLE_bit in register 01 of sequencer */ + cli(); + outb_p(0x01,seq_port_reg); + outb_p(info->vga_state.ClockingMode | 0x20,seq_port_val); + + /* test for vertical retrace in process.... */ + if ((info->vga_state.CrtMiscIO & 0x80) == 0x80) + outb_p(info->vga_state.CrtMiscIO & 0xef,video_misc_wr); + + /* + * Set <End of vertical retrace> to minimum (0) and + * <Start of vertical Retrace> to maximum (incl. overflow) + * Result: turn off vertical sync (VSync) pulse. + */ + if (mode & VESA_VSYNC_SUSPEND) { + outb_p(0x10,vga_video_port_reg); /* StartVertRetrace */ + outb_p(0xff,vga_video_port_val); /* maximum value */ + outb_p(0x11,vga_video_port_reg); /* EndVertRetrace */ + outb_p(0x40,vga_video_port_val); /* minimum (bits 0..3) */ + outb_p(0x07,vga_video_port_reg); /* Overflow */ + outb_p(info->vga_state.Overflow | 0x84,vga_video_port_val); /* bits 9,10 of vert. retrace */ + } + + if (mode & VESA_HSYNC_SUSPEND) { + /* + * Set <End of horizontal retrace> to minimum (0) and + * <Start of horizontal Retrace> to maximum + * Result: turn off horizontal sync (HSync) pulse. + */ + outb_p(0x04,vga_video_port_reg); /* StartHorizRetrace */ + outb_p(0xff,vga_video_port_val); /* maximum */ + outb_p(0x05,vga_video_port_reg); /* EndHorizRetrace */ + outb_p(0x00,vga_video_port_val); /* minimum (0) */ + } + + /* restore both index registers */ + outb_p(SeqCtrlIndex,seq_port_reg); + outb_p(CrtCtrlIndex,vga_video_port_reg); + sti(); +} + +static void vga_vesa_unblank(struct vga16fb_info *info) +{ + unsigned char SeqCtrlIndex; + unsigned char CrtCtrlIndex; + + cli(); + SeqCtrlIndex = inb_p(seq_port_reg); + CrtCtrlIndex = inb_p(vga_video_port_reg); + + /* restore original values of VGA controller registers */ + outb_p(info->vga_state.CrtMiscIO,video_misc_wr); + + outb_p(0x00,vga_video_port_reg); /* HorizontalTotal */ + outb_p(info->vga_state.HorizontalTotal,vga_video_port_val); + outb_p(0x01,vga_video_port_reg); /* HorizDisplayEnd */ + outb_p(info->vga_state.HorizDisplayEnd,vga_video_port_val); + outb_p(0x04,vga_video_port_reg); /* StartHorizRetrace */ + outb_p(info->vga_state.StartHorizRetrace,vga_video_port_val); + outb_p(0x05,vga_video_port_reg); /* EndHorizRetrace */ + outb_p(info->vga_state.EndHorizRetrace,vga_video_port_val); + outb_p(0x07,vga_video_port_reg); /* Overflow */ + outb_p(info->vga_state.Overflow,vga_video_port_val); + outb_p(0x10,vga_video_port_reg); /* StartVertRetrace */ + outb_p(info->vga_state.StartVertRetrace,vga_video_port_val); + outb_p(0x11,vga_video_port_reg); /* EndVertRetrace */ + outb_p(info->vga_state.EndVertRetrace,vga_video_port_val); + outb_p(0x17,vga_video_port_reg); /* ModeControl */ + outb_p(info->vga_state.ModeControl,vga_video_port_val); + outb_p(0x01,seq_port_reg); /* ClockingMode */ + outb_p(info->vga_state.ClockingMode,seq_port_val); + + /* restore index/control registers */ + outb_p(SeqCtrlIndex,seq_port_reg); + outb_p(CrtCtrlIndex,vga_video_port_reg); + sti(); +} + +static void vga_pal_blank(void) +{ + int i; + + for (i=0; i<16; i++) { + outb_p (i, dac_reg) ; + outb_p (0, dac_val) ; + outb_p (0, dac_val) ; + outb_p (0, dac_val) ; + } +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ +static void vga16fb_blank(int blank, struct fb_info *fb_info) +{ + struct vga16fb_info *info = (struct vga16fb_info*)fb_info; + + switch (blank) { + case 0: /* Unblank */ + if (info->vesa_blanked) { + vga_vesa_unblank(info); + info->vesa_blanked = 0; + } + if (info->palette_blanked) { + do_install_cmap(currcon, fb_info); + info->palette_blanked = 0; + } + break; + case 1: /* blank */ + vga_pal_blank(); + info->palette_blanked = 1; + break; + default: /* VESA blanking */ + vga_vesa_blank(info, blank-1); + info->vesa_blanked = 1; + break; + } +} + +__initfunc(void vga16fb_init(void)) +{ + int i,j; + + printk("vga16fb: initializing\n"); + + vga16fb.video_vbase = ioremap((unsigned long)0xa0000, 65536); + printk("vga16fb: mapped to 0x%p\n", vga16fb.video_vbase); + + vga16fb.isVGA = ORIG_VIDEO_ISVGA; + vga16fb.palette_blanked = 0; + vga16fb.vesa_blanked = 0; + + i = vga16fb.isVGA? 6 : 2; + + vga16fb_defined.red.length = i; + vga16fb_defined.green.length = i; + vga16fb_defined.blue.length = i; + for(i = 0; i < 16; i++) { + j = color_table[i]; + palette[i].red = default_red[j]; + palette[i].green = default_grn[j]; + palette[i].blue = default_blu[j]; + } + if (vga16fb.isVGA) + request_region(0x3c0, 32, "vga+"); + else + request_region(0x3C0, 32, "ega"); + + disp.var = vga16fb_defined; + + /* name should not depend on EGA/VGA */ + strcpy(vga16fb.fb_info.modename, "VGA16 VGA"); + vga16fb.fb_info.changevar = NULL; + vga16fb.fb_info.node = -1; + vga16fb.fb_info.fbops = &vga16fb_ops; + vga16fb.fb_info.disp=&disp; + vga16fb.fb_info.switch_con=&vga16fb_switch; + vga16fb.fb_info.updatevar=&vga16fb_update_var; + vga16fb.fb_info.blank=&vga16fb_blank; + vga16fb.fb_info.flags=FBINFO_FLAG_DEFAULT; + vga16fb_set_disp(-1, &vga16fb); + + if (register_framebuffer(&vga16fb.fb_info)<0) + return; + + printk("fb%d: %s frame buffer device\n", + GET_FB_IDX(vga16fb.fb_info.node), vga16fb.fb_info.modename); +} + + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * --------------------------------------------------------------------------- + * Local variables: + * c-basic-offset: 8 + * End: + */ + |