diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/parisc |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/parisc')
-rw-r--r-- | drivers/parisc/Kconfig | 169 | ||||
-rw-r--r-- | drivers/parisc/Makefile | 27 | ||||
-rw-r--r-- | drivers/parisc/README.dino | 28 | ||||
-rw-r--r-- | drivers/parisc/asp.c | 132 | ||||
-rw-r--r-- | drivers/parisc/ccio-dma.c | 1593 | ||||
-rw-r--r-- | drivers/parisc/ccio-rm-dma.c | 201 | ||||
-rw-r--r-- | drivers/parisc/dino.c | 1044 | ||||
-rw-r--r-- | drivers/parisc/eisa.c | 464 | ||||
-rw-r--r-- | drivers/parisc/eisa_eeprom.c | 134 | ||||
-rw-r--r-- | drivers/parisc/eisa_enumerator.c | 521 | ||||
-rw-r--r-- | drivers/parisc/gsc.c | 245 | ||||
-rw-r--r-- | drivers/parisc/gsc.h | 47 | ||||
-rw-r--r-- | drivers/parisc/hppb.c | 109 | ||||
-rw-r--r-- | drivers/parisc/iommu-helpers.h | 171 | ||||
-rw-r--r-- | drivers/parisc/iosapic.c | 921 | ||||
-rw-r--r-- | drivers/parisc/iosapic_private.h | 188 | ||||
-rw-r--r-- | drivers/parisc/lasi.c | 240 | ||||
-rw-r--r-- | drivers/parisc/lba_pci.c | 1649 | ||||
-rw-r--r-- | drivers/parisc/led.c | 760 | ||||
-rw-r--r-- | drivers/parisc/pdc_stable.c | 735 | ||||
-rw-r--r-- | drivers/parisc/power.c | 278 | ||||
-rw-r--r-- | drivers/parisc/sba_iommu.c | 2165 | ||||
-rw-r--r-- | drivers/parisc/superio.c | 508 | ||||
-rw-r--r-- | drivers/parisc/wax.c | 140 |
24 files changed, 12469 insertions, 0 deletions
diff --git a/drivers/parisc/Kconfig b/drivers/parisc/Kconfig new file mode 100644 index 00000000000..3f5de867acd --- /dev/null +++ b/drivers/parisc/Kconfig @@ -0,0 +1,169 @@ +menu "Bus options (PCI, PCMCIA, EISA, GSC, ISA)" + +config GSC + bool "VSC/GSC/HSC bus support" + default y + help + The VSC, GSC and HSC busses were used from the earliest 700-series + workstations up to and including the C360/J2240 workstations. They + were also used in servers from the E-class to the K-class. They + are not found in B1000, C3000, J5000, A500, L1000, N4000 and upwards. + If in doubt, say "Y". + +config HPPB + bool "HP-PB bus support" + depends on GSC + help + The HP-PB bus was used in the Nova class and K-class servers. + If in doubt, say "Y" + +config IOMMU_CCIO + bool "U2/Uturn I/O MMU" + depends on GSC + help + Say Y here to enable DMA management routines for the first + generation of PA-RISC cache-coherent machines. Programs the + U2/Uturn chip in "Virtual Mode" and use the I/O MMU. + +config GSC_LASI + bool "Lasi I/O support" + depends on GSC + help + Say Y here to support the Lasi multifunction chip found in + many PA-RISC workstations & servers. It includes interfaces + for a parallel port, serial port, NCR 53c710 SCSI, Apricot + Ethernet, Harmony audio, PS/2 keyboard & mouse, ISDN, telephony + and floppy. Note that you must still enable all the individual + drivers for these chips. + +config GSC_WAX + bool "Wax I/O support" + depends on GSC + help + Say Y here to support the Wax multifunction chip found in some + older systems, including B/C/D/R class and 715/64, 715/80 and + 715/100. Wax includes an EISA adapter, a serial port (not always + used), a HIL interface chip and is also known to be used as the + GSC bridge for an X.25 GSC card. + +config EISA + bool "EISA support" + depends on GSC + help + Say Y here if you have an EISA bus in your machine. This code + supports both the Mongoose & Wax EISA adapters. It is sadly + incomplete and lacks support for card-to-host DMA. + +source "drivers/eisa/Kconfig" + +config ISA + bool "ISA support" + depends on EISA + help + If you want to plug an ISA card into your EISA bus, say Y here. + Most people should say N. + +config PCI + bool "PCI support" + help + All recent HP machines have PCI slots, and you should say Y here + if you have a recent machine. If you are convinced you do not have + PCI slots in your machine (eg a 712), then you may say "N" here. + Beware that some GSC cards have a Dino onboard and PCI inside them, + so it may be safest to say "Y" anyway. + +source "drivers/pci/Kconfig" + +config GSC_DINO + bool "GSCtoPCI/Dino PCI support" + depends on PCI && GSC + help + Say Y here to support the Dino & Cujo GSC to PCI bridges found in + machines from the B132 to the C360, the J2240 and the A180. Some + GSC/HSC cards (eg gigabit & dual 100 Mbit Ethernet) have a Dino on + the card, and you also need to say Y here if you have such a card. + Note that Dino also supplies one of the serial ports on certain + machines. If in doubt, say Y. + +config PCI_LBA + bool "LBA/Elroy PCI support" + depends on PCI + help + Say Y here to support the Elroy PCI Lower Bus Adapter. This is + present on B, C, J, L and N-class machines with 4-digit model + numbers and the A400/A500. + +config IOSAPIC + bool + depends on PCI_LBA + default PCI_LBA + +config IOMMU_SBA + bool + depends on PCI_LBA + default PCI_LBA + +#config PCI_EPIC +# bool "EPIC/SAGA PCI support" +# depends on PCI +# default y +# help +# Say Y here for V-class PCI, DMA/IOMMU, IRQ subsystem support. + +source "drivers/pcmcia/Kconfig" + +source "drivers/pci/hotplug/Kconfig" + +endmenu + +menu "PA-RISC specific drivers" + +config SUPERIO + bool "SuperIO (SuckyIO) support" + depends on PCI_LBA + default y + help + Say Y here to support the SuperIO chip found in Bxxxx, C3xxx and + J5xxx+ machines. This enables IDE, Floppy, Parallel Port, and + Serial port on those machines. + +config CHASSIS_LCD_LED + bool "Chassis LCD and LED support" + default y + help + Say Y here if you want to enable support for the Heartbeat, + Disk/Network activities LEDs on some PA-RISC machines, + or support for the LCD that can be found on recent material. + + This has nothing to do with LED State support for A and E class. + + If unsure, say Y. + +config PDC_CHASSIS + bool "PDC chassis State Panel support" + default y + help + Say Y here if you want to enable support for the LED State front + panel as found on E class, and support for the GSP Virtual Front + Panel (LED State and message logging) as found on high end + servers such as A, L and N-class. + + This has nothing to do with Chassis LCD and LED support. + + If unsure, say Y. + +config PDC_STABLE + tristate "PDC Stable Storage support" + depends on SYSFS + default y + help + Say Y here if you want to enable support for accessing Stable Storage + variables (PDC non volatile variables such as Primary Boot Path, + Console Path, Autoboot, Autosearch, etc) through SysFS. + + If unsure, say Y. + + To compile this driver as a module, choose M here. + The module will be called pdc_stable. + +endmenu diff --git a/drivers/parisc/Makefile b/drivers/parisc/Makefile new file mode 100644 index 00000000000..f95cab57133 --- /dev/null +++ b/drivers/parisc/Makefile @@ -0,0 +1,27 @@ +# +# Makefile for most of the non-PCI devices in PA-RISC machines +# + +# I/O SAPIC is also on IA64 platforms. +# The two could be merged into a common source some day. +obj-$(CONFIG_IOSAPIC) += iosapic.o +obj-$(CONFIG_IOMMU_SBA) += sba_iommu.o +obj-$(CONFIG_PCI_LBA) += lba_pci.o + +# Only use one of them: ccio-rm-dma is for PCX-W systems *only* +# obj-$(CONFIG_IOMMU_CCIO) += ccio-rm-dma.o +obj-$(CONFIG_IOMMU_CCIO) += ccio-dma.o + +obj-$(CONFIG_GSC) += gsc.o + +obj-$(CONFIG_HPPB) += hppb.o +obj-$(CONFIG_GSC_DINO) += dino.o +obj-$(CONFIG_GSC_LASI) += lasi.o asp.o +obj-$(CONFIG_GSC_WAX) += wax.o +obj-$(CONFIG_EISA) += eisa.o eisa_enumerator.o eisa_eeprom.o + +obj-$(CONFIG_SUPERIO) += superio.o +obj-$(CONFIG_CHASSIS_LCD_LED) += led.o +obj-$(CONFIG_PDC_STABLE) += pdc_stable.o +obj-y += power.o + diff --git a/drivers/parisc/README.dino b/drivers/parisc/README.dino new file mode 100644 index 00000000000..097324f34bb --- /dev/null +++ b/drivers/parisc/README.dino @@ -0,0 +1,28 @@ +/* +** HP VISUALIZE Workstation PCI Bus Defect +** +** "HP has discovered a potential system defect that can affect +** the behavior of five models of HP VISUALIZE workstations when +** equipped with third-party or customer-installed PCI I/O expansion +** cards. The defect is limited to the HP C180, C160, C160L, B160L, +** and B132L VISUALIZE workstations, and will only be encountered +** when data is transmitted through PCI I/O expansion cards on the +** PCI bus. HP-supplied graphics cards that utilize the PCI bus are +** not affected." +** +** REVISIT: "go/pci_defect" link below is stale. +** HP Internal can use <http://hpfcdma.fc.hp.com:80/Dino/> +** +** Product First Good Serial Number +** C200/C240 (US) US67350000 +**B132L+/B180 (US) US67390000 +** C200 (Europe) 3713G01000 +** B180L (Europe) 3720G01000 +** +** Note that many boards were fixed/replaced under a free replacement +** program. Assume a machine is only "suspect" until proven otherwise. +** +** "The pci_check program will also be available as application +** patch PHSS_12295" +*/ + diff --git a/drivers/parisc/asp.c b/drivers/parisc/asp.c new file mode 100644 index 00000000000..38860996713 --- /dev/null +++ b/drivers/parisc/asp.c @@ -0,0 +1,132 @@ +/* + * ASP Device Driver + * + * (c) Copyright 2000 The Puffin Group Inc. + * + * 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. + * + * by Helge Deller <deller@gmx.de> + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <asm/io.h> +#include <asm/led.h> + +#include "gsc.h" + +#define ASP_GSC_IRQ 3 /* hardcoded interrupt for GSC */ + +#define ASP_VER_OFFSET 0x20 /* offset of ASP version */ + +#define ASP_LED_ADDR 0xf0800020 + +#define VIPER_INT_WORD 0xFFFBF088 /* addr of viper interrupt word */ + +static struct gsc_asic asp; + +static void asp_choose_irq(struct parisc_device *dev, void *ctrl) +{ + int irq; + + switch (dev->id.sversion) { + case 0x71: irq = 9; break; /* SCSI */ + case 0x72: irq = 8; break; /* LAN */ + case 0x73: irq = 1; break; /* HIL */ + case 0x74: irq = 7; break; /* Centronics */ + case 0x75: irq = (dev->hw_path == 4) ? 5 : 6; break; /* RS232 */ + case 0x76: irq = 10; break; /* EISA BA */ + case 0x77: irq = 11; break; /* Graphics1 */ + case 0x7a: irq = 13; break; /* Audio (Bushmaster) */ + case 0x7b: irq = 13; break; /* Audio (Scorpio) */ + case 0x7c: irq = 3; break; /* FW SCSI */ + case 0x7d: irq = 4; break; /* FDDI */ + case 0x7f: irq = 13; break; /* Audio (Outfield) */ + default: return; /* Unknown */ + } + + gsc_asic_assign_irq(ctrl, irq, &dev->irq); + + switch (dev->id.sversion) { + case 0x73: irq = 2; break; /* i8042 High-priority */ + case 0x76: irq = 0; break; /* EISA BA */ + default: return; /* Other */ + } + + gsc_asic_assign_irq(ctrl, irq, &dev->aux_irq); +} + +/* There are two register ranges we're interested in. Interrupt / + * Status / LED are at 0xf080xxxx and Asp special registers are at + * 0xf082fxxx. PDC only tells us that Asp is at 0xf082f000, so for + * the purposes of interrupt handling, we have to tell other bits of + * the kernel to look at the other registers. + */ +#define ASP_INTERRUPT_ADDR 0xf0800000 + +int __init +asp_init_chip(struct parisc_device *dev) +{ + struct gsc_irq gsc_irq; + int ret; + + asp.version = gsc_readb(dev->hpa + ASP_VER_OFFSET) & 0xf; + asp.name = (asp.version == 1) ? "Asp" : "Cutoff"; + asp.hpa = ASP_INTERRUPT_ADDR; + + printk(KERN_INFO "%s version %d at 0x%lx found.\n", + asp.name, asp.version, dev->hpa); + + /* the IRQ ASP should use */ + ret = -EBUSY; + dev->irq = gsc_claim_irq(&gsc_irq, ASP_GSC_IRQ); + if (dev->irq < 0) { + printk(KERN_ERR "%s(): cannot get GSC irq\n", __FUNCTION__); + goto out; + } + + asp.eim = ((u32) gsc_irq.txn_addr) | gsc_irq.txn_data; + + ret = request_irq(gsc_irq.irq, gsc_asic_intr, 0, "asp", &asp); + if (ret < 0) + goto out; + + /* Program VIPER to interrupt on the ASP irq */ + gsc_writel((1 << (31 - ASP_GSC_IRQ)),VIPER_INT_WORD); + + /* Done init'ing, register this driver */ + ret = gsc_common_setup(dev, &asp); + if (ret) + goto out; + + gsc_fixup_irqs(dev, &asp, asp_choose_irq); + /* Mongoose is a sibling of Asp, not a child... */ + gsc_fixup_irqs(parisc_parent(dev), &asp, asp_choose_irq); + + /* initialize the chassis LEDs */ +#ifdef CONFIG_CHASSIS_LCD_LED + register_led_driver(DISPLAY_MODEL_OLD_ASP, LED_CMD_REG_NONE, + ASP_LED_ADDR); +#endif + + out: + return ret; +} + +static struct parisc_device_id asp_tbl[] = { + { HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00070 }, + { 0, } +}; + +struct parisc_driver asp_driver = { + .name = "Asp", + .id_table = asp_tbl, + .probe = asp_init_chip, +}; diff --git a/drivers/parisc/ccio-dma.c b/drivers/parisc/ccio-dma.c new file mode 100644 index 00000000000..0e98a9d9834 --- /dev/null +++ b/drivers/parisc/ccio-dma.c @@ -0,0 +1,1593 @@ +/* +** ccio-dma.c: +** DMA management routines for first generation cache-coherent machines. +** Program U2/Uturn in "Virtual Mode" and use the I/O MMU. +** +** (c) Copyright 2000 Grant Grundler +** (c) Copyright 2000 Ryan Bradetich +** (c) Copyright 2000 Hewlett-Packard Company +** +** 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. +** +** +** "Real Mode" operation refers to U2/Uturn chip operation. +** U2/Uturn were designed to perform coherency checks w/o using +** the I/O MMU - basically what x86 does. +** +** Philipp Rumpf has a "Real Mode" driver for PCX-W machines at: +** CVSROOT=:pserver:anonymous@198.186.203.37:/cvsroot/linux-parisc +** cvs -z3 co linux/arch/parisc/kernel/dma-rm.c +** +** I've rewritten his code to work under TPG's tree. See ccio-rm-dma.c. +** +** Drawbacks of using Real Mode are: +** o outbound DMA is slower - U2 won't prefetch data (GSC+ XQL signal). +** o Inbound DMA less efficient - U2 can't use DMA_FAST attribute. +** o Ability to do scatter/gather in HW is lost. +** o Doesn't work under PCX-U/U+ machines since they didn't follow +** the coherency design originally worked out. Only PCX-W does. +*/ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/pci.h> +#include <linux/reboot.h> + +#include <asm/byteorder.h> +#include <asm/cache.h> /* for L1_CACHE_BYTES */ +#include <asm/uaccess.h> +#include <asm/page.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/hardware.h> /* for register_module() */ +#include <asm/parisc-device.h> + +/* +** Choose "ccio" since that's what HP-UX calls it. +** Make it easier for folks to migrate from one to the other :^) +*/ +#define MODULE_NAME "ccio" + +#undef DEBUG_CCIO_RES +#undef DEBUG_CCIO_RUN +#undef DEBUG_CCIO_INIT +#undef DEBUG_CCIO_RUN_SG + +#ifdef CONFIG_PROC_FS +/* + * CCIO_SEARCH_TIME can help measure how fast the bitmap search is. + * impacts performance though - ditch it if you don't use it. + */ +#define CCIO_SEARCH_TIME +#undef CCIO_MAP_STATS +#else +#undef CCIO_SEARCH_TIME +#undef CCIO_MAP_STATS +#endif + +#include <linux/proc_fs.h> +#include <asm/runway.h> /* for proc_runway_root */ + +#ifdef DEBUG_CCIO_INIT +#define DBG_INIT(x...) printk(x) +#else +#define DBG_INIT(x...) +#endif + +#ifdef DEBUG_CCIO_RUN +#define DBG_RUN(x...) printk(x) +#else +#define DBG_RUN(x...) +#endif + +#ifdef DEBUG_CCIO_RES +#define DBG_RES(x...) printk(x) +#else +#define DBG_RES(x...) +#endif + +#ifdef DEBUG_CCIO_RUN_SG +#define DBG_RUN_SG(x...) printk(x) +#else +#define DBG_RUN_SG(x...) +#endif + +#define CCIO_INLINE /* inline */ +#define WRITE_U32(value, addr) gsc_writel(value, (u32 *)(addr)) +#define READ_U32(addr) gsc_readl((u32 *)(addr)) + +#define U2_IOA_RUNWAY 0x580 +#define U2_BC_GSC 0x501 +#define UTURN_IOA_RUNWAY 0x581 +#define UTURN_BC_GSC 0x502 + +#define IOA_NORMAL_MODE 0x00020080 /* IO_CONTROL to turn on CCIO */ +#define CMD_TLB_DIRECT_WRITE 35 /* IO_COMMAND for I/O TLB Writes */ +#define CMD_TLB_PURGE 33 /* IO_COMMAND to Purge I/O TLB entry */ + +struct ioa_registers { + /* Runway Supervisory Set */ + volatile int32_t unused1[12]; + volatile uint32_t io_command; /* Offset 12 */ + volatile uint32_t io_status; /* Offset 13 */ + volatile uint32_t io_control; /* Offset 14 */ + volatile int32_t unused2[1]; + + /* Runway Auxiliary Register Set */ + volatile uint32_t io_err_resp; /* Offset 0 */ + volatile uint32_t io_err_info; /* Offset 1 */ + volatile uint32_t io_err_req; /* Offset 2 */ + volatile uint32_t io_err_resp_hi; /* Offset 3 */ + volatile uint32_t io_tlb_entry_m; /* Offset 4 */ + volatile uint32_t io_tlb_entry_l; /* Offset 5 */ + volatile uint32_t unused3[1]; + volatile uint32_t io_pdir_base; /* Offset 7 */ + volatile uint32_t io_io_low_hv; /* Offset 8 */ + volatile uint32_t io_io_high_hv; /* Offset 9 */ + volatile uint32_t unused4[1]; + volatile uint32_t io_chain_id_mask; /* Offset 11 */ + volatile uint32_t unused5[2]; + volatile uint32_t io_io_low; /* Offset 14 */ + volatile uint32_t io_io_high; /* Offset 15 */ +}; + +/* +** IOA Registers +** ------------- +** +** Runway IO_CONTROL Register (+0x38) +** +** The Runway IO_CONTROL register controls the forwarding of transactions. +** +** | 0 ... 13 | 14 15 | 16 ... 21 | 22 | 23 24 | 25 ... 31 | +** | HV | TLB | reserved | HV | mode | reserved | +** +** o mode field indicates the address translation of transactions +** forwarded from Runway to GSC+: +** Mode Name Value Definition +** Off (default) 0 Opaque to matching addresses. +** Include 1 Transparent for matching addresses. +** Peek 3 Map matching addresses. +** +** + "Off" mode: Runway transactions which match the I/O range +** specified by the IO_IO_LOW/IO_IO_HIGH registers will be ignored. +** + "Include" mode: all addresses within the I/O range specified +** by the IO_IO_LOW and IO_IO_HIGH registers are transparently +** forwarded. This is the I/O Adapter's normal operating mode. +** + "Peek" mode: used during system configuration to initialize the +** GSC+ bus. Runway Write_Shorts in the address range specified by +** IO_IO_LOW and IO_IO_HIGH are forwarded through the I/O Adapter +** *AND* the GSC+ address is remapped to the Broadcast Physical +** Address space by setting the 14 high order address bits of the +** 32 bit GSC+ address to ones. +** +** o TLB field affects transactions which are forwarded from GSC+ to Runway. +** "Real" mode is the poweron default. +** +** TLB Mode Value Description +** Real 0 No TLB translation. Address is directly mapped and the +** virtual address is composed of selected physical bits. +** Error 1 Software fills the TLB manually. +** Normal 2 IOA fetches IO TLB misses from IO PDIR (in host memory). +** +** +** IO_IO_LOW_HV +0x60 (HV dependent) +** IO_IO_HIGH_HV +0x64 (HV dependent) +** IO_IO_LOW +0x78 (Architected register) +** IO_IO_HIGH +0x7c (Architected register) +** +** IO_IO_LOW and IO_IO_HIGH set the lower and upper bounds of the +** I/O Adapter address space, respectively. +** +** 0 ... 7 | 8 ... 15 | 16 ... 31 | +** 11111111 | 11111111 | address | +** +** Each LOW/HIGH pair describes a disjoint address space region. +** (2 per GSC+ port). Each incoming Runway transaction address is compared +** with both sets of LOW/HIGH registers. If the address is in the range +** greater than or equal to IO_IO_LOW and less than IO_IO_HIGH the transaction +** for forwarded to the respective GSC+ bus. +** Specify IO_IO_LOW equal to or greater than IO_IO_HIGH to avoid specifying +** an address space region. +** +** In order for a Runway address to reside within GSC+ extended address space: +** Runway Address [0:7] must identically compare to 8'b11111111 +** Runway Address [8:11] must be equal to IO_IO_LOW(_HV)[16:19] +** Runway Address [12:23] must be greater than or equal to +** IO_IO_LOW(_HV)[20:31] and less than IO_IO_HIGH(_HV)[20:31]. +** Runway Address [24:39] is not used in the comparison. +** +** When the Runway transaction is forwarded to GSC+, the GSC+ address is +** as follows: +** GSC+ Address[0:3] 4'b1111 +** GSC+ Address[4:29] Runway Address[12:37] +** GSC+ Address[30:31] 2'b00 +** +** All 4 Low/High registers must be initialized (by PDC) once the lower bus +** is interrogated and address space is defined. The operating system will +** modify the architectural IO_IO_LOW and IO_IO_HIGH registers following +** the PDC initialization. However, the hardware version dependent IO_IO_LOW +** and IO_IO_HIGH registers should not be subsequently altered by the OS. +** +** Writes to both sets of registers will take effect immediately, bypassing +** the queues, which ensures that subsequent Runway transactions are checked +** against the updated bounds values. However reads are queued, introducing +** the possibility of a read being bypassed by a subsequent write to the same +** register. This sequence can be avoided by having software wait for read +** returns before issuing subsequent writes. +*/ + +struct ioc { + struct ioa_registers *ioc_hpa; /* I/O MMU base address */ + u8 *res_map; /* resource map, bit == pdir entry */ + u64 *pdir_base; /* physical base address */ + u32 pdir_size; /* bytes, function of IOV Space size */ + u32 res_hint; /* next available IOVP - + circular search */ + u32 res_size; /* size of resource map in bytes */ + spinlock_t res_lock; + +#ifdef CCIO_SEARCH_TIME +#define CCIO_SEARCH_SAMPLE 0x100 + unsigned long avg_search[CCIO_SEARCH_SAMPLE]; + unsigned long avg_idx; /* current index into avg_search */ +#endif +#ifdef CCIO_MAP_STATS + unsigned long used_pages; + unsigned long msingle_calls; + unsigned long msingle_pages; + unsigned long msg_calls; + unsigned long msg_pages; + unsigned long usingle_calls; + unsigned long usingle_pages; + unsigned long usg_calls; + unsigned long usg_pages; +#endif + unsigned short cujo20_bug; + + /* STUFF We don't need in performance path */ + u32 chainid_shift; /* specify bit location of chain_id */ + struct ioc *next; /* Linked list of discovered iocs */ + const char *name; /* device name from firmware */ + unsigned int hw_path; /* the hardware path this ioc is associatd with */ + struct pci_dev *fake_pci_dev; /* the fake pci_dev for non-pci devs */ + struct resource mmio_region[2]; /* The "routed" MMIO regions */ +}; + +static struct ioc *ioc_list; +static int ioc_count; + +/************************************************************** +* +* I/O Pdir Resource Management +* +* Bits set in the resource map are in use. +* Each bit can represent a number of pages. +* LSbs represent lower addresses (IOVA's). +* +* This was was copied from sba_iommu.c. Don't try to unify +* the two resource managers unless a way to have different +* allocation policies is also adjusted. We'd like to avoid +* I/O TLB thrashing by having resource allocation policy +* match the I/O TLB replacement policy. +* +***************************************************************/ +#define IOVP_SIZE PAGE_SIZE +#define IOVP_SHIFT PAGE_SHIFT +#define IOVP_MASK PAGE_MASK + +/* Convert from IOVP to IOVA and vice versa. */ +#define CCIO_IOVA(iovp,offset) ((iovp) | (offset)) +#define CCIO_IOVP(iova) ((iova) & IOVP_MASK) + +#define PDIR_INDEX(iovp) ((iovp)>>IOVP_SHIFT) +#define MKIOVP(pdir_idx) ((long)(pdir_idx) << IOVP_SHIFT) +#define MKIOVA(iovp,offset) (dma_addr_t)((long)iovp | (long)offset) +#define ROUNDUP(x,y) ((x + ((y)-1)) & ~((y)-1)) + +/* +** Don't worry about the 150% average search length on a miss. +** If the search wraps around, and passes the res_hint, it will +** cause the kernel to panic anyhow. +*/ +#define CCIO_SEARCH_LOOP(ioc, res_idx, mask, size) \ + for(; res_ptr < res_end; ++res_ptr) { \ + if(0 == (*res_ptr & mask)) { \ + *res_ptr |= mask; \ + res_idx = (unsigned int)((unsigned long)res_ptr - (unsigned long)ioc->res_map); \ + ioc->res_hint = res_idx + (size >> 3); \ + goto resource_found; \ + } \ + } + +#define CCIO_FIND_FREE_MAPPING(ioa, res_idx, mask, size) \ + u##size *res_ptr = (u##size *)&((ioc)->res_map[ioa->res_hint & ~((size >> 3) - 1)]); \ + u##size *res_end = (u##size *)&(ioc)->res_map[ioa->res_size]; \ + CCIO_SEARCH_LOOP(ioc, res_idx, mask, size); \ + res_ptr = (u##size *)&(ioc)->res_map[0]; \ + CCIO_SEARCH_LOOP(ioa, res_idx, mask, size); + +/* +** Find available bit in this ioa's resource map. +** Use a "circular" search: +** o Most IOVA's are "temporary" - avg search time should be small. +** o keep a history of what happened for debugging +** o KISS. +** +** Perf optimizations: +** o search for log2(size) bits at a time. +** o search for available resource bits using byte/word/whatever. +** o use different search for "large" (eg > 4 pages) or "very large" +** (eg > 16 pages) mappings. +*/ + +/** + * ccio_alloc_range - Allocate pages in the ioc's resource map. + * @ioc: The I/O Controller. + * @pages_needed: The requested number of pages to be mapped into the + * I/O Pdir... + * + * This function searches the resource map of the ioc to locate a range + * of available pages for the requested size. + */ +static int +ccio_alloc_range(struct ioc *ioc, size_t size) +{ + unsigned int pages_needed = size >> IOVP_SHIFT; + unsigned int res_idx; +#ifdef CCIO_SEARCH_TIME + unsigned long cr_start = mfctl(16); +#endif + + BUG_ON(pages_needed == 0); + BUG_ON((pages_needed * IOVP_SIZE) > DMA_CHUNK_SIZE); + + DBG_RES("%s() size: %d pages_needed %d\n", + __FUNCTION__, size, pages_needed); + + /* + ** "seek and ye shall find"...praying never hurts either... + ** ggg sacrifices another 710 to the computer gods. + */ + + if (pages_needed <= 8) { + /* + * LAN traffic will not thrash the TLB IFF the same NIC + * uses 8 adjacent pages to map seperate payload data. + * ie the same byte in the resource bit map. + */ +#if 0 + /* FIXME: bit search should shift it's way through + * an unsigned long - not byte at a time. As it is now, + * we effectively allocate this byte to this mapping. + */ + unsigned long mask = ~(~0UL >> pages_needed); + CCIO_FIND_FREE_MAPPING(ioc, res_idx, mask, 8); +#else + CCIO_FIND_FREE_MAPPING(ioc, res_idx, 0xff, 8); +#endif + } else if (pages_needed <= 16) { + CCIO_FIND_FREE_MAPPING(ioc, res_idx, 0xffff, 16); + } else if (pages_needed <= 32) { + CCIO_FIND_FREE_MAPPING(ioc, res_idx, ~(unsigned int)0, 32); +#ifdef __LP64__ + } else if (pages_needed <= 64) { + CCIO_FIND_FREE_MAPPING(ioc, res_idx, ~0UL, 64); +#endif + } else { + panic("%s: %s() Too many pages to map. pages_needed: %u\n", + __FILE__, __FUNCTION__, pages_needed); + } + + panic("%s: %s() I/O MMU is out of mapping resources.\n", __FILE__, + __FUNCTION__); + +resource_found: + + DBG_RES("%s() res_idx %d res_hint: %d\n", + __FUNCTION__, res_idx, ioc->res_hint); + +#ifdef CCIO_SEARCH_TIME + { + unsigned long cr_end = mfctl(16); + unsigned long tmp = cr_end - cr_start; + /* check for roll over */ + cr_start = (cr_end < cr_start) ? -(tmp) : (tmp); + } + ioc->avg_search[ioc->avg_idx++] = cr_start; + ioc->avg_idx &= CCIO_SEARCH_SAMPLE - 1; +#endif +#ifdef CCIO_MAP_STATS + ioc->used_pages += pages_needed; +#endif + /* + ** return the bit address. + */ + return res_idx << 3; +} + +#define CCIO_FREE_MAPPINGS(ioc, res_idx, mask, size) \ + u##size *res_ptr = (u##size *)&((ioc)->res_map[res_idx]); \ + BUG_ON((*res_ptr & mask) != mask); \ + *res_ptr &= ~(mask); + +/** + * ccio_free_range - Free pages from the ioc's resource map. + * @ioc: The I/O Controller. + * @iova: The I/O Virtual Address. + * @pages_mapped: The requested number of pages to be freed from the + * I/O Pdir. + * + * This function frees the resouces allocated for the iova. + */ +static void +ccio_free_range(struct ioc *ioc, dma_addr_t iova, unsigned long pages_mapped) +{ + unsigned long iovp = CCIO_IOVP(iova); + unsigned int res_idx = PDIR_INDEX(iovp) >> 3; + + BUG_ON(pages_mapped == 0); + BUG_ON((pages_mapped * IOVP_SIZE) > DMA_CHUNK_SIZE); + BUG_ON(pages_mapped > BITS_PER_LONG); + + DBG_RES("%s(): res_idx: %d pages_mapped %d\n", + __FUNCTION__, res_idx, pages_mapped); + +#ifdef CCIO_MAP_STATS + ioc->used_pages -= pages_mapped; +#endif + + if(pages_mapped <= 8) { +#if 0 + /* see matching comments in alloc_range */ + unsigned long mask = ~(~0UL >> pages_mapped); + CCIO_FREE_MAPPINGS(ioc, res_idx, mask, 8); +#else + CCIO_FREE_MAPPINGS(ioc, res_idx, 0xff, 8); +#endif + } else if(pages_mapped <= 16) { + CCIO_FREE_MAPPINGS(ioc, res_idx, 0xffff, 16); + } else if(pages_mapped <= 32) { + CCIO_FREE_MAPPINGS(ioc, res_idx, ~(unsigned int)0, 32); +#ifdef __LP64__ + } else if(pages_mapped <= 64) { + CCIO_FREE_MAPPINGS(ioc, res_idx, ~0UL, 64); +#endif + } else { + panic("%s:%s() Too many pages to unmap.\n", __FILE__, + __FUNCTION__); + } +} + +/**************************************************************** +** +** CCIO dma_ops support routines +** +*****************************************************************/ + +typedef unsigned long space_t; +#define KERNEL_SPACE 0 + +/* +** DMA "Page Type" and Hints +** o if SAFE_DMA isn't set, mapping is for FAST_DMA. SAFE_DMA should be +** set for subcacheline DMA transfers since we don't want to damage the +** other part of a cacheline. +** o SAFE_DMA must be set for "memory" allocated via pci_alloc_consistent(). +** This bit tells U2 to do R/M/W for partial cachelines. "Streaming" +** data can avoid this if the mapping covers full cache lines. +** o STOP_MOST is needed for atomicity across cachelines. +** Apperently only "some EISA devices" need this. +** Using CONFIG_ISA is hack. Only the IOA with EISA under it needs +** to use this hint iff the EISA devices needs this feature. +** According to the U2 ERS, STOP_MOST enabled pages hurt performance. +** o PREFETCH should *not* be set for cases like Multiple PCI devices +** behind GSCtoPCI (dino) bus converter. Only one cacheline per GSC +** device can be fetched and multiply DMA streams will thrash the +** prefetch buffer and burn memory bandwidth. See 6.7.3 "Prefetch Rules +** and Invalidation of Prefetch Entries". +** +** FIXME: the default hints need to be per GSC device - not global. +** +** HP-UX dorks: linux device driver programming model is totally different +** than HP-UX's. HP-UX always sets HINT_PREFETCH since it's drivers +** do special things to work on non-coherent platforms...linux has to +** be much more careful with this. +*/ +#define IOPDIR_VALID 0x01UL +#define HINT_SAFE_DMA 0x02UL /* used for pci_alloc_consistent() pages */ +#ifdef CONFIG_EISA +#define HINT_STOP_MOST 0x04UL /* LSL support */ +#else +#define HINT_STOP_MOST 0x00UL /* only needed for "some EISA devices" */ +#endif +#define HINT_UDPATE_ENB 0x08UL /* not used/supported by U2 */ +#define HINT_PREFETCH 0x10UL /* for outbound pages which are not SAFE */ + + +/* +** Use direction (ie PCI_DMA_TODEVICE) to pick hint. +** ccio_alloc_consistent() depends on this to get SAFE_DMA +** when it passes in BIDIRECTIONAL flag. +*/ +static u32 hint_lookup[] = { + [PCI_DMA_BIDIRECTIONAL] = HINT_STOP_MOST | HINT_SAFE_DMA | IOPDIR_VALID, + [PCI_DMA_TODEVICE] = HINT_STOP_MOST | HINT_PREFETCH | IOPDIR_VALID, + [PCI_DMA_FROMDEVICE] = HINT_STOP_MOST | IOPDIR_VALID, +}; + +/** + * ccio_io_pdir_entry - Initialize an I/O Pdir. + * @pdir_ptr: A pointer into I/O Pdir. + * @sid: The Space Identifier. + * @vba: The virtual address. + * @hints: The DMA Hint. + * + * Given a virtual address (vba, arg2) and space id, (sid, arg1), + * load the I/O PDIR entry pointed to by pdir_ptr (arg0). Each IO Pdir + * entry consists of 8 bytes as shown below (MSB == bit 0): + * + * + * WORD 0: + * +------+----------------+-----------------------------------------------+ + * | Phys | Virtual Index | Phys | + * | 0:3 | 0:11 | 4:19 | + * |4 bits| 12 bits | 16 bits | + * +------+----------------+-----------------------------------------------+ + * WORD 1: + * +-----------------------+-----------------------------------------------+ + * | Phys | Rsvd | Prefetch |Update |Rsvd |Lock |Safe |Valid | + * | 20:39 | | Enable |Enable | |Enable|DMA | | + * | 20 bits | 5 bits | 1 bit |1 bit |2 bits|1 bit |1 bit |1 bit | + * +-----------------------+-----------------------------------------------+ + * + * The virtual index field is filled with the results of the LCI + * (Load Coherence Index) instruction. The 8 bits used for the virtual + * index are bits 12:19 of the value returned by LCI. + */ +void CCIO_INLINE +ccio_io_pdir_entry(u64 *pdir_ptr, space_t sid, unsigned long vba, + unsigned long hints) +{ + register unsigned long pa; + register unsigned long ci; /* coherent index */ + + /* We currently only support kernel addresses */ + BUG_ON(sid != KERNEL_SPACE); + + mtsp(sid,1); + + /* + ** WORD 1 - low order word + ** "hints" parm includes the VALID bit! + ** "dep" clobbers the physical address offset bits as well. + */ + pa = virt_to_phys(vba); + asm volatile("depw %1,31,12,%0" : "+r" (pa) : "r" (hints)); + ((u32 *)pdir_ptr)[1] = (u32) pa; + + /* + ** WORD 0 - high order word + */ + +#ifdef __LP64__ + /* + ** get bits 12:15 of physical address + ** shift bits 16:31 of physical address + ** and deposit them + */ + asm volatile ("extrd,u %1,15,4,%0" : "=r" (ci) : "r" (pa)); + asm volatile ("extrd,u %1,31,16,%0" : "+r" (pa) : "r" (pa)); + asm volatile ("depd %1,35,4,%0" : "+r" (pa) : "r" (ci)); +#else + pa = 0; +#endif + /* + ** get CPU coherency index bits + ** Grab virtual index [0:11] + ** Deposit virt_idx bits into I/O PDIR word + */ + asm volatile ("lci 0(%%sr1, %1), %0" : "=r" (ci) : "r" (vba)); + asm volatile ("extru %1,19,12,%0" : "+r" (ci) : "r" (ci)); + asm volatile ("depw %1,15,12,%0" : "+r" (pa) : "r" (ci)); + + ((u32 *)pdir_ptr)[0] = (u32) pa; + + + /* FIXME: PCX_W platforms don't need FDC/SYNC. (eg C360) + ** PCX-U/U+ do. (eg C200/C240) + ** PCX-T'? Don't know. (eg C110 or similar K-class) + ** + ** See PDC_MODEL/option 0/SW_CAP word for "Non-coherent IO-PDIR bit". + ** Hopefully we can patch (NOP) these out at boot time somehow. + ** + ** "Since PCX-U employs an offset hash that is incompatible with + ** the real mode coherence index generation of U2, the PDIR entry + ** must be flushed to memory to retain coherence." + */ + asm volatile("fdc 0(%0)" : : "r" (pdir_ptr)); + asm volatile("sync"); +} + +/** + * ccio_clear_io_tlb - Remove stale entries from the I/O TLB. + * @ioc: The I/O Controller. + * @iovp: The I/O Virtual Page. + * @byte_cnt: The requested number of bytes to be freed from the I/O Pdir. + * + * Purge invalid I/O PDIR entries from the I/O TLB. + * + * FIXME: Can we change the byte_cnt to pages_mapped? + */ +static CCIO_INLINE void +ccio_clear_io_tlb(struct ioc *ioc, dma_addr_t iovp, size_t byte_cnt) +{ + u32 chain_size = 1 << ioc->chainid_shift; + + iovp &= IOVP_MASK; /* clear offset bits, just want pagenum */ + byte_cnt += chain_size; + + while(byte_cnt > chain_size) { + WRITE_U32(CMD_TLB_PURGE | iovp, &ioc->ioc_hpa->io_command); + iovp += chain_size; + byte_cnt -= chain_size; + } +} + +/** + * ccio_mark_invalid - Mark the I/O Pdir entries invalid. + * @ioc: The I/O Controller. + * @iova: The I/O Virtual Address. + * @byte_cnt: The requested number of bytes to be freed from the I/O Pdir. + * + * Mark the I/O Pdir entries invalid and blow away the corresponding I/O + * TLB entries. + * + * FIXME: at some threshhold it might be "cheaper" to just blow + * away the entire I/O TLB instead of individual entries. + * + * FIXME: Uturn has 256 TLB entries. We don't need to purge every + * PDIR entry - just once for each possible TLB entry. + * (We do need to maker I/O PDIR entries invalid regardless). + * + * FIXME: Can we change byte_cnt to pages_mapped? + */ +static CCIO_INLINE void +ccio_mark_invalid(struct ioc *ioc, dma_addr_t iova, size_t byte_cnt) +{ + u32 iovp = (u32)CCIO_IOVP(iova); + size_t saved_byte_cnt; + + /* round up to nearest page size */ + saved_byte_cnt = byte_cnt = ROUNDUP(byte_cnt, IOVP_SIZE); + + while(byte_cnt > 0) { + /* invalidate one page at a time */ + unsigned int idx = PDIR_INDEX(iovp); + char *pdir_ptr = (char *) &(ioc->pdir_base[idx]); + + BUG_ON(idx >= (ioc->pdir_size / sizeof(u64))); + pdir_ptr[7] = 0; /* clear only VALID bit */ + /* + ** FIXME: PCX_W platforms don't need FDC/SYNC. (eg C360) + ** PCX-U/U+ do. (eg C200/C240) + ** See PDC_MODEL/option 0/SW_CAP for "Non-coherent IO-PDIR bit". + ** + ** Hopefully someone figures out how to patch (NOP) the + ** FDC/SYNC out at boot time. + */ + asm volatile("fdc 0(%0)" : : "r" (pdir_ptr[7])); + + iovp += IOVP_SIZE; + byte_cnt -= IOVP_SIZE; + } + + asm volatile("sync"); + ccio_clear_io_tlb(ioc, CCIO_IOVP(iova), saved_byte_cnt); +} + +/**************************************************************** +** +** CCIO dma_ops +** +*****************************************************************/ + +/** + * ccio_dma_supported - Verify the IOMMU supports the DMA address range. + * @dev: The PCI device. + * @mask: A bit mask describing the DMA address range of the device. + * + * This function implements the pci_dma_supported function. + */ +static int +ccio_dma_supported(struct device *dev, u64 mask) +{ + if(dev == NULL) { + printk(KERN_ERR MODULE_NAME ": EISA/ISA/et al not supported\n"); + BUG(); + return 0; + } + + /* only support 32-bit devices (ie PCI/GSC) */ + return (int)(mask == 0xffffffffUL); +} + +/** + * ccio_map_single - Map an address range into the IOMMU. + * @dev: The PCI device. + * @addr: The start address of the DMA region. + * @size: The length of the DMA region. + * @direction: The direction of the DMA transaction (to/from device). + * + * This function implements the pci_map_single function. + */ +static dma_addr_t +ccio_map_single(struct device *dev, void *addr, size_t size, + enum dma_data_direction direction) +{ + int idx; + struct ioc *ioc; + unsigned long flags; + dma_addr_t iovp; + dma_addr_t offset; + u64 *pdir_start; + unsigned long hint = hint_lookup[(int)direction]; + + BUG_ON(!dev); + ioc = GET_IOC(dev); + + BUG_ON(size <= 0); + + /* save offset bits */ + offset = ((unsigned long) addr) & ~IOVP_MASK; + + /* round up to nearest IOVP_SIZE */ + size = ROUNDUP(size + offset, IOVP_SIZE); + spin_lock_irqsave(&ioc->res_lock, flags); + +#ifdef CCIO_MAP_STATS + ioc->msingle_calls++; + ioc->msingle_pages += size >> IOVP_SHIFT; +#endif + + idx = ccio_alloc_range(ioc, size); + iovp = (dma_addr_t)MKIOVP(idx); + + pdir_start = &(ioc->pdir_base[idx]); + + DBG_RUN("%s() 0x%p -> 0x%lx size: %0x%x\n", + __FUNCTION__, addr, (long)iovp | offset, size); + + /* If not cacheline aligned, force SAFE_DMA on the whole mess */ + if((size % L1_CACHE_BYTES) || ((unsigned long)addr % L1_CACHE_BYTES)) + hint |= HINT_SAFE_DMA; + + while(size > 0) { + ccio_io_pdir_entry(pdir_start, KERNEL_SPACE, (unsigned long)addr, hint); + + DBG_RUN(" pdir %p %08x%08x\n", + pdir_start, + (u32) (((u32 *) pdir_start)[0]), + (u32) (((u32 *) pdir_start)[1])); + ++pdir_start; + addr += IOVP_SIZE; + size -= IOVP_SIZE; + } + + spin_unlock_irqrestore(&ioc->res_lock, flags); + + /* form complete address */ + return CCIO_IOVA(iovp, offset); +} + +/** + * ccio_unmap_single - Unmap an address range from the IOMMU. + * @dev: The PCI device. + * @addr: The start address of the DMA region. + * @size: The length of the DMA region. + * @direction: The direction of the DMA transaction (to/from device). + * + * This function implements the pci_unmap_single function. + */ +static void +ccio_unmap_single(struct device *dev, dma_addr_t iova, size_t size, + enum dma_data_direction direction) +{ + struct ioc *ioc; + unsigned long flags; + dma_addr_t offset = iova & ~IOVP_MASK; + + BUG_ON(!dev); + ioc = GET_IOC(dev); + + DBG_RUN("%s() iovp 0x%lx/%x\n", + __FUNCTION__, (long)iova, size); + + iova ^= offset; /* clear offset bits */ + size += offset; + size = ROUNDUP(size, IOVP_SIZE); + + spin_lock_irqsave(&ioc->res_lock, flags); + +#ifdef CCIO_MAP_STATS + ioc->usingle_calls++; + ioc->usingle_pages += size >> IOVP_SHIFT; +#endif + + ccio_mark_invalid(ioc, iova, size); + ccio_free_range(ioc, iova, (size >> IOVP_SHIFT)); + spin_unlock_irqrestore(&ioc->res_lock, flags); +} + +/** + * ccio_alloc_consistent - Allocate a consistent DMA mapping. + * @dev: The PCI device. + * @size: The length of the DMA region. + * @dma_handle: The DMA address handed back to the device (not the cpu). + * + * This function implements the pci_alloc_consistent function. + */ +static void * +ccio_alloc_consistent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag) +{ + void *ret; +#if 0 +/* GRANT Need to establish hierarchy for non-PCI devs as well +** and then provide matching gsc_map_xxx() functions for them as well. +*/ + if(!hwdev) { + /* only support PCI */ + *dma_handle = 0; + return 0; + } +#endif + ret = (void *) __get_free_pages(flag, get_order(size)); + + if (ret) { + memset(ret, 0, size); + *dma_handle = ccio_map_single(dev, ret, size, PCI_DMA_BIDIRECTIONAL); + } + + return ret; +} + +/** + * ccio_free_consistent - Free a consistent DMA mapping. + * @dev: The PCI device. + * @size: The length of the DMA region. + * @cpu_addr: The cpu address returned from the ccio_alloc_consistent. + * @dma_handle: The device address returned from the ccio_alloc_consistent. + * + * This function implements the pci_free_consistent function. + */ +static void +ccio_free_consistent(struct device *dev, size_t size, void *cpu_addr, + dma_addr_t dma_handle) +{ + ccio_unmap_single(dev, dma_handle, size, 0); + free_pages((unsigned long)cpu_addr, get_order(size)); +} + +/* +** Since 0 is a valid pdir_base index value, can't use that +** to determine if a value is valid or not. Use a flag to indicate +** the SG list entry contains a valid pdir index. +*/ +#define PIDE_FLAG 0x80000000UL + +#ifdef CCIO_MAP_STATS +#define IOMMU_MAP_STATS +#endif +#include "iommu-helpers.h" + +/** + * ccio_map_sg - Map the scatter/gather list into the IOMMU. + * @dev: The PCI device. + * @sglist: The scatter/gather list to be mapped in the IOMMU. + * @nents: The number of entries in the scatter/gather list. + * @direction: The direction of the DMA transaction (to/from device). + * + * This function implements the pci_map_sg function. + */ +static int +ccio_map_sg(struct device *dev, struct scatterlist *sglist, int nents, + enum dma_data_direction direction) +{ + struct ioc *ioc; + int coalesced, filled = 0; + unsigned long flags; + unsigned long hint = hint_lookup[(int)direction]; + unsigned long prev_len = 0, current_len = 0; + int i; + + BUG_ON(!dev); + ioc = GET_IOC(dev); + + DBG_RUN_SG("%s() START %d entries\n", __FUNCTION__, nents); + + /* Fast path single entry scatterlists. */ + if (nents == 1) { + sg_dma_address(sglist) = ccio_map_single(dev, + (void *)sg_virt_addr(sglist), sglist->length, + direction); + sg_dma_len(sglist) = sglist->length; + return 1; + } + + for(i = 0; i < nents; i++) + prev_len += sglist[i].length; + + spin_lock_irqsave(&ioc->res_lock, flags); + +#ifdef CCIO_MAP_STATS + ioc->msg_calls++; +#endif + + /* + ** First coalesce the chunks and allocate I/O pdir space + ** + ** If this is one DMA stream, we can properly map using the + ** correct virtual address associated with each DMA page. + ** w/o this association, we wouldn't have coherent DMA! + ** Access to the virtual address is what forces a two pass algorithm. + */ + coalesced = iommu_coalesce_chunks(ioc, sglist, nents, ccio_alloc_range); + + /* + ** Program the I/O Pdir + ** + ** map the virtual addresses to the I/O Pdir + ** o dma_address will contain the pdir index + ** o dma_len will contain the number of bytes to map + ** o page/offset contain the virtual address. + */ + filled = iommu_fill_pdir(ioc, sglist, nents, hint, ccio_io_pdir_entry); + + spin_unlock_irqrestore(&ioc->res_lock, flags); + + BUG_ON(coalesced != filled); + + DBG_RUN_SG("%s() DONE %d mappings\n", __FUNCTION__, filled); + + for (i = 0; i < filled; i++) + current_len += sg_dma_len(sglist + i); + + BUG_ON(current_len != prev_len); + + return filled; +} + +/** + * ccio_unmap_sg - Unmap the scatter/gather list from the IOMMU. + * @dev: The PCI device. + * @sglist: The scatter/gather list to be unmapped from the IOMMU. + * @nents: The number of entries in the scatter/gather list. + * @direction: The direction of the DMA transaction (to/from device). + * + * This function implements the pci_unmap_sg function. + */ +static void +ccio_unmap_sg(struct device *dev, struct scatterlist *sglist, int nents, + enum dma_data_direction direction) +{ + struct ioc *ioc; + + BUG_ON(!dev); + ioc = GET_IOC(dev); + + DBG_RUN_SG("%s() START %d entries, %08lx,%x\n", + __FUNCTION__, nents, sg_virt_addr(sglist), sglist->length); + +#ifdef CCIO_MAP_STATS + ioc->usg_calls++; +#endif + + while(sg_dma_len(sglist) && nents--) { + +#ifdef CCIO_MAP_STATS + ioc->usg_pages += sg_dma_len(sglist) >> PAGE_SHIFT; +#endif + ccio_unmap_single(dev, sg_dma_address(sglist), + sg_dma_len(sglist), direction); + ++sglist; + } + + DBG_RUN_SG("%s() DONE (nents %d)\n", __FUNCTION__, nents); +} + +static struct hppa_dma_ops ccio_ops = { + .dma_supported = ccio_dma_supported, + .alloc_consistent = ccio_alloc_consistent, + .alloc_noncoherent = ccio_alloc_consistent, + .free_consistent = ccio_free_consistent, + .map_single = ccio_map_single, + .unmap_single = ccio_unmap_single, + .map_sg = ccio_map_sg, + .unmap_sg = ccio_unmap_sg, + .dma_sync_single_for_cpu = NULL, /* NOP for U2/Uturn */ + .dma_sync_single_for_device = NULL, /* NOP for U2/Uturn */ + .dma_sync_sg_for_cpu = NULL, /* ditto */ + .dma_sync_sg_for_device = NULL, /* ditto */ +}; + +#ifdef CONFIG_PROC_FS +static int proc_append(char *src, int len, char **dst, off_t *offset, int *max) +{ + if (len < *offset) { + *offset -= len; + return 0; + } + if (*offset > 0) { + src += *offset; + len -= *offset; + *offset = 0; + } + if (len > *max) { + len = *max; + } + memcpy(*dst, src, len); + *dst += len; + *max -= len; + return (*max == 0); +} + +static int ccio_proc_info(char *buf, char **start, off_t offset, int count, + int *eof, void *data) +{ + int max = count; + char tmp[80]; /* width of an ANSI-standard terminal */ + struct ioc *ioc = ioc_list; + + while (ioc != NULL) { + unsigned int total_pages = ioc->res_size << 3; + unsigned long avg = 0, min, max; + int j, len; + + len = sprintf(tmp, "%s\n", ioc->name); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; + + len = sprintf(tmp, "Cujo 2.0 bug : %s\n", + (ioc->cujo20_bug ? "yes" : "no")); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; + + len = sprintf(tmp, "IO PDIR size : %d bytes (%d entries)\n", + total_pages * 8, total_pages); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; +#ifdef CCIO_MAP_STATS + len = sprintf(tmp, "IO PDIR entries : %ld free %ld used (%d%%)\n", + total_pages - ioc->used_pages, ioc->used_pages, + (int)(ioc->used_pages * 100 / total_pages)); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; +#endif + len = sprintf(tmp, "Resource bitmap : %d bytes (%d pages)\n", + ioc->res_size, total_pages); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; +#ifdef CCIO_SEARCH_TIME + min = max = ioc->avg_search[0]; + for(j = 0; j < CCIO_SEARCH_SAMPLE; ++j) { + avg += ioc->avg_search[j]; + if(ioc->avg_search[j] > max) + max = ioc->avg_search[j]; + if(ioc->avg_search[j] < min) + min = ioc->avg_search[j]; + } + avg /= CCIO_SEARCH_SAMPLE; + len = sprintf(tmp, " Bitmap search : %ld/%ld/%ld (min/avg/max CPU Cycles)\n", + min, avg, max); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; +#endif +#ifdef CCIO_MAP_STATS + len = sprintf(tmp, "pci_map_single(): %8ld calls %8ld pages (avg %d/1000)\n", + ioc->msingle_calls, ioc->msingle_pages, + (int)((ioc->msingle_pages * 1000)/ioc->msingle_calls)); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; + + + /* KLUGE - unmap_sg calls unmap_single for each mapped page */ + min = ioc->usingle_calls - ioc->usg_calls; + max = ioc->usingle_pages - ioc->usg_pages; + len = sprintf(tmp, "pci_unmap_single: %8ld calls %8ld pages (avg %d/1000)\n", + min, max, (int)((max * 1000)/min)); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; + + len = sprintf(tmp, "pci_map_sg() : %8ld calls %8ld pages (avg %d/1000)\n", + ioc->msg_calls, ioc->msg_pages, + (int)((ioc->msg_pages * 1000)/ioc->msg_calls)); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; + len = sprintf(tmp, "pci_unmap_sg() : %8ld calls %8ld pages (avg %d/1000)\n\n\n", + ioc->usg_calls, ioc->usg_pages, + (int)((ioc->usg_pages * 1000)/ioc->usg_calls)); + if (proc_append(tmp, len, &buf, &offset, &count)) + break; +#endif /* CCIO_MAP_STATS */ + ioc = ioc->next; + } + + if (count == 0) { + *eof = 1; + } + return (max - count); +} + +static int ccio_resource_map(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + struct ioc *ioc = ioc_list; + + buf[0] = '\0'; + while (ioc != NULL) { + u32 *res_ptr = (u32 *)ioc->res_map; + int j; + + for (j = 0; j < (ioc->res_size / sizeof(u32)); j++) { + if ((j & 7) == 0) + strcat(buf,"\n "); + sprintf(buf, "%s %08x", buf, *res_ptr); + res_ptr++; + } + strcat(buf, "\n\n"); + ioc = ioc->next; + break; /* XXX - remove me */ + } + + return strlen(buf); +} +#endif + +/** + * ccio_find_ioc - Find the ioc in the ioc_list + * @hw_path: The hardware path of the ioc. + * + * This function searches the ioc_list for an ioc that matches + * the provide hardware path. + */ +static struct ioc * ccio_find_ioc(int hw_path) +{ + int i; + struct ioc *ioc; + + ioc = ioc_list; + for (i = 0; i < ioc_count; i++) { + if (ioc->hw_path == hw_path) + return ioc; + + ioc = ioc->next; + } + + return NULL; +} + +/** + * ccio_get_iommu - Find the iommu which controls this device + * @dev: The parisc device. + * + * This function searches through the registered IOMMU's and returns + * the appropriate IOMMU for the device based on its hardware path. + */ +void * ccio_get_iommu(const struct parisc_device *dev) +{ + dev = find_pa_parent_type(dev, HPHW_IOA); + if (!dev) + return NULL; + + return ccio_find_ioc(dev->hw_path); +} + +#define CUJO_20_STEP 0x10000000 /* inc upper nibble */ + +/* Cujo 2.0 has a bug which will silently corrupt data being transferred + * to/from certain pages. To avoid this happening, we mark these pages + * as `used', and ensure that nothing will try to allocate from them. + */ +void ccio_cujo20_fixup(struct parisc_device *cujo, u32 iovp) +{ + unsigned int idx; + struct parisc_device *dev = parisc_parent(cujo); + struct ioc *ioc = ccio_get_iommu(dev); + u8 *res_ptr; + + ioc->cujo20_bug = 1; + res_ptr = ioc->res_map; + idx = PDIR_INDEX(iovp) >> 3; + + while (idx < ioc->res_size) { + res_ptr[idx] |= 0xff; + idx += PDIR_INDEX(CUJO_20_STEP) >> 3; + } +} + +#if 0 +/* GRANT - is this needed for U2 or not? */ + +/* +** Get the size of the I/O TLB for this I/O MMU. +** +** If spa_shift is non-zero (ie probably U2), +** then calculate the I/O TLB size using spa_shift. +** +** Otherwise we are supposed to get the IODC entry point ENTRY TLB +** and execute it. However, both U2 and Uturn firmware supplies spa_shift. +** I think only Java (K/D/R-class too?) systems don't do this. +*/ +static int +ccio_get_iotlb_size(struct parisc_device *dev) +{ + if (dev->spa_shift == 0) { + panic("%s() : Can't determine I/O TLB size.\n", __FUNCTION__); + } + return (1 << dev->spa_shift); +} +#else + +/* Uturn supports 256 TLB entries */ +#define CCIO_CHAINID_SHIFT 8 +#define CCIO_CHAINID_MASK 0xff +#endif /* 0 */ + +/* We *can't* support JAVA (T600). Venture there at your own risk. */ +static struct parisc_device_id ccio_tbl[] = { + { HPHW_IOA, HVERSION_REV_ANY_ID, U2_IOA_RUNWAY, 0xb }, /* U2 */ + { HPHW_IOA, HVERSION_REV_ANY_ID, UTURN_IOA_RUNWAY, 0xb }, /* UTurn */ + { 0, } +}; + +static int ccio_probe(struct parisc_device *dev); + +static struct parisc_driver ccio_driver = { + .name = "U2:Uturn", + .id_table = ccio_tbl, + .probe = ccio_probe, +}; + +/** + * ccio_ioc_init - Initalize the I/O Controller + * @ioc: The I/O Controller. + * + * Initalize the I/O Controller which includes setting up the + * I/O Page Directory, the resource map, and initalizing the + * U2/Uturn chip into virtual mode. + */ +static void +ccio_ioc_init(struct ioc *ioc) +{ + int i; + unsigned int iov_order; + u32 iova_space_size; + + /* + ** Determine IOVA Space size from memory size. + ** + ** Ideally, PCI drivers would register the maximum number + ** of DMA they can have outstanding for each device they + ** own. Next best thing would be to guess how much DMA + ** can be outstanding based on PCI Class/sub-class. Both + ** methods still require some "extra" to support PCI + ** Hot-Plug/Removal of PCI cards. (aka PCI OLARD). + */ + + iova_space_size = (u32) (num_physpages / count_parisc_driver(&ccio_driver)); + + /* limit IOVA space size to 1MB-1GB */ + + if (iova_space_size < (1 << (20 - PAGE_SHIFT))) { + iova_space_size = 1 << (20 - PAGE_SHIFT); +#ifdef __LP64__ + } else if (iova_space_size > (1 << (30 - PAGE_SHIFT))) { + iova_space_size = 1 << (30 - PAGE_SHIFT); +#endif + } + + /* + ** iova space must be log2() in size. + ** thus, pdir/res_map will also be log2(). + */ + + /* We could use larger page sizes in order to *decrease* the number + ** of mappings needed. (ie 8k pages means 1/2 the mappings). + ** + ** Note: Grant Grunder says "Using 8k I/O pages isn't trivial either + ** since the pages must also be physically contiguous - typically + ** this is the case under linux." + */ + + iov_order = get_order(iova_space_size << PAGE_SHIFT); + + /* iova_space_size is now bytes, not pages */ + iova_space_size = 1 << (iov_order + PAGE_SHIFT); + + ioc->pdir_size = (iova_space_size / IOVP_SIZE) * sizeof(u64); + + BUG_ON(ioc->pdir_size >= 4 * 1024 * 1024); /* max pdir size < 4MB */ + + /* Verify it's a power of two */ + BUG_ON((1 << get_order(ioc->pdir_size)) != (ioc->pdir_size >> PAGE_SHIFT)); + + DBG_INIT("%s() hpa 0x%lx mem %luMB IOV %dMB (%d bits)\n", + __FUNCTION__, + ioc->ioc_hpa, + (unsigned long) num_physpages >> (20 - PAGE_SHIFT), + iova_space_size>>20, + iov_order + PAGE_SHIFT); + + ioc->pdir_base = (u64 *)__get_free_pages(GFP_KERNEL, + get_order(ioc->pdir_size)); + if(NULL == ioc->pdir_base) { + panic("%s:%s() could not allocate I/O Page Table\n", __FILE__, + __FUNCTION__); + } + memset(ioc->pdir_base, 0, ioc->pdir_size); + + BUG_ON((((unsigned long)ioc->pdir_base) & PAGE_MASK) != (unsigned long)ioc->pdir_base); + DBG_INIT(" base %p", ioc->pdir_base); + + /* resource map size dictated by pdir_size */ + ioc->res_size = (ioc->pdir_size / sizeof(u64)) >> 3; + DBG_INIT("%s() res_size 0x%x\n", __FUNCTION__, ioc->res_size); + + ioc->res_map = (u8 *)__get_free_pages(GFP_KERNEL, + get_order(ioc->res_size)); + if(NULL == ioc->res_map) { + panic("%s:%s() could not allocate resource map\n", __FILE__, + __FUNCTION__); + } + memset(ioc->res_map, 0, ioc->res_size); + + /* Initialize the res_hint to 16 */ + ioc->res_hint = 16; + + /* Initialize the spinlock */ + spin_lock_init(&ioc->res_lock); + + /* + ** Chainid is the upper most bits of an IOVP used to determine + ** which TLB entry an IOVP will use. + */ + ioc->chainid_shift = get_order(iova_space_size) + PAGE_SHIFT - CCIO_CHAINID_SHIFT; + DBG_INIT(" chainid_shift 0x%x\n", ioc->chainid_shift); + + /* + ** Initialize IOA hardware + */ + WRITE_U32(CCIO_CHAINID_MASK << ioc->chainid_shift, + &ioc->ioc_hpa->io_chain_id_mask); + + WRITE_U32(virt_to_phys(ioc->pdir_base), + &ioc->ioc_hpa->io_pdir_base); + + /* + ** Go to "Virtual Mode" + */ + WRITE_U32(IOA_NORMAL_MODE, &ioc->ioc_hpa->io_control); + + /* + ** Initialize all I/O TLB entries to 0 (Valid bit off). + */ + WRITE_U32(0, &ioc->ioc_hpa->io_tlb_entry_m); + WRITE_U32(0, &ioc->ioc_hpa->io_tlb_entry_l); + + for(i = 1 << CCIO_CHAINID_SHIFT; i ; i--) { + WRITE_U32((CMD_TLB_DIRECT_WRITE | (i << ioc->chainid_shift)), + &ioc->ioc_hpa->io_command); + } +} + +static void +ccio_init_resource(struct resource *res, char *name, unsigned long ioaddr) +{ + int result; + + res->parent = NULL; + res->flags = IORESOURCE_MEM; + res->start = (unsigned long)(signed) __raw_readl(ioaddr) << 16; + res->end = (unsigned long)(signed) (__raw_readl(ioaddr + 4) << 16) - 1; + res->name = name; + if (res->end + 1 == res->start) + return; + result = request_resource(&iomem_resource, res); + if (result < 0) { + printk(KERN_ERR "%s: failed to claim CCIO bus address space (%08lx,%08lx)\n", + __FILE__, res->start, res->end); + } +} + +static void __init ccio_init_resources(struct ioc *ioc) +{ + struct resource *res = ioc->mmio_region; + char *name = kmalloc(14, GFP_KERNEL); + + sprintf(name, "GSC Bus [%d/]", ioc->hw_path); + + ccio_init_resource(res, name, (unsigned long)&ioc->ioc_hpa->io_io_low); + ccio_init_resource(res + 1, name, + (unsigned long)&ioc->ioc_hpa->io_io_low_hv); +} + +static int new_ioc_area(struct resource *res, unsigned long size, + unsigned long min, unsigned long max, unsigned long align) +{ + if (max <= min) + return -EBUSY; + + res->start = (max - size + 1) &~ (align - 1); + res->end = res->start + size; + if (!request_resource(&iomem_resource, res)) + return 0; + + return new_ioc_area(res, size, min, max - size, align); +} + +static int expand_ioc_area(struct resource *res, unsigned long size, + unsigned long min, unsigned long max, unsigned long align) +{ + unsigned long start, len; + + if (!res->parent) + return new_ioc_area(res, size, min, max, align); + + start = (res->start - size) &~ (align - 1); + len = res->end - start + 1; + if (start >= min) { + if (!adjust_resource(res, start, len)) + return 0; + } + + start = res->start; + len = ((size + res->end + align) &~ (align - 1)) - start; + if (start + len <= max) { + if (!adjust_resource(res, start, len)) + return 0; + } + + return -EBUSY; +} + +/* + * Dino calls this function. Beware that we may get called on systems + * which have no IOC (725, B180, C160L, etc) but do have a Dino. + * So it's legal to find no parent IOC. + * + * Some other issues: one of the resources in the ioc may be unassigned. + */ +int ccio_allocate_resource(const struct parisc_device *dev, + struct resource *res, unsigned long size, + unsigned long min, unsigned long max, unsigned long align) +{ + struct resource *parent = &iomem_resource; + struct ioc *ioc = ccio_get_iommu(dev); + if (!ioc) + goto out; + + parent = ioc->mmio_region; + if (parent->parent && + !allocate_resource(parent, res, size, min, max, align, NULL, NULL)) + return 0; + + if ((parent + 1)->parent && + !allocate_resource(parent + 1, res, size, min, max, align, + NULL, NULL)) + return 0; + + if (!expand_ioc_area(parent, size, min, max, align)) { + __raw_writel(((parent->start)>>16) | 0xffff0000, + (unsigned long)&(ioc->ioc_hpa->io_io_low)); + __raw_writel(((parent->end)>>16) | 0xffff0000, + (unsigned long)&(ioc->ioc_hpa->io_io_high)); + } else if (!expand_ioc_area(parent + 1, size, min, max, align)) { + parent++; + __raw_writel(((parent->start)>>16) | 0xffff0000, + (unsigned long)&(ioc->ioc_hpa->io_io_low_hv)); + __raw_writel(((parent->end)>>16) | 0xffff0000, + (unsigned long)&(ioc->ioc_hpa->io_io_high_hv)); + } else { + return -EBUSY; + } + + out: + return allocate_resource(parent, res, size, min, max, align, NULL,NULL); +} + +int ccio_request_resource(const struct parisc_device *dev, + struct resource *res) +{ + struct resource *parent; + struct ioc *ioc = ccio_get_iommu(dev); + + if (!ioc) { + parent = &iomem_resource; + } else if ((ioc->mmio_region->start <= res->start) && + (res->end <= ioc->mmio_region->end)) { + parent = ioc->mmio_region; + } else if (((ioc->mmio_region + 1)->start <= res->start) && + (res->end <= (ioc->mmio_region + 1)->end)) { + parent = ioc->mmio_region + 1; + } else { + return -EBUSY; + } + + return request_resource(parent, res); +} + +/** + * ccio_probe - Determine if ccio should claim this device. + * @dev: The device which has been found + * + * Determine if ccio should claim this chip (return 0) or not (return 1). + * If so, initialize the chip and tell other partners in crime they + * have work to do. + */ +static int ccio_probe(struct parisc_device *dev) +{ + int i; + struct ioc *ioc, **ioc_p = &ioc_list; + + ioc = kmalloc(sizeof(struct ioc), GFP_KERNEL); + if (ioc == NULL) { + printk(KERN_ERR MODULE_NAME ": memory allocation failure\n"); + return 1; + } + memset(ioc, 0, sizeof(struct ioc)); + + ioc->name = dev->id.hversion == U2_IOA_RUNWAY ? "U2" : "UTurn"; + + printk(KERN_INFO "Found %s at 0x%lx\n", ioc->name, dev->hpa); + + for (i = 0; i < ioc_count; i++) { + ioc_p = &(*ioc_p)->next; + } + *ioc_p = ioc; + + ioc->hw_path = dev->hw_path; + ioc->ioc_hpa = (struct ioa_registers *)dev->hpa; + ccio_ioc_init(ioc); + ccio_init_resources(ioc); + hppa_dma_ops = &ccio_ops; + dev->dev.platform_data = kmalloc(sizeof(struct pci_hba_data), GFP_KERNEL); + + /* if this fails, no I/O cards will work, so may as well bug */ + BUG_ON(dev->dev.platform_data == NULL); + HBA_DATA(dev->dev.platform_data)->iommu = ioc; + + + if (ioc_count == 0) { + /* FIXME: Create separate entries for each ioc */ + create_proc_read_entry(MODULE_NAME, S_IRWXU, proc_runway_root, + ccio_proc_info, NULL); + create_proc_read_entry(MODULE_NAME"-bitmap", S_IRWXU, + proc_runway_root, ccio_resource_map, NULL); + } + + ioc_count++; + + parisc_vmerge_boundary = IOVP_SIZE; + parisc_vmerge_max_size = BITS_PER_LONG * IOVP_SIZE; + parisc_has_iommu(); + return 0; +} + +/** + * ccio_init - ccio initalization procedure. + * + * Register this driver. + */ +void __init ccio_init(void) +{ + register_parisc_driver(&ccio_driver); +} + diff --git a/drivers/parisc/ccio-rm-dma.c b/drivers/parisc/ccio-rm-dma.c new file mode 100644 index 00000000000..57e6385976e --- /dev/null +++ b/drivers/parisc/ccio-rm-dma.c @@ -0,0 +1,201 @@ +/* + * ccio-rm-dma.c: + * DMA management routines for first generation cache-coherent machines. + * "Real Mode" operation refers to U2/Uturn chip operation. The chip + * can perform coherency checks w/o using the I/O MMU. That's all we + * need until support for more than 4GB phys mem is needed. + * + * This is the trivial case - basically what x86 does. + * + * Drawbacks of using Real Mode are: + * o outbound DMA is slower since one isn't using the prefetching + * U2 can do for outbound DMA. + * o Ability to do scatter/gather in HW is also lost. + * o only known to work with PCX-W processor. (eg C360) + * (PCX-U/U+ are not coherent with U2 in real mode.) + * + * + * 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. + * + * + * Original version/author: + * CVSROOT=:pserver:anonymous@198.186.203.37:/cvsroot/linux-parisc + * cvs -z3 co linux/arch/parisc/kernel/dma-rm.c + * + * (C) Copyright 2000 Philipp Rumpf <prumpf@tux.org> + * + * + * Adopted for The Puffin Group's parisc-linux port by Grant Grundler. + * (C) Copyright 2000 Grant Grundler <grundler@puffin.external.hp.com> + * + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/pci.h> + +#include <asm/uaccess.h> + +#include <asm/io.h> +#include <asm/hardware.h> +#include <asm/page.h> + +/* Only chose "ccio" since that's what HP-UX calls it.... +** Make it easier for folks to migrate from one to the other :^) +*/ +#define MODULE_NAME "ccio" + +#define U2_IOA_RUNWAY 0x580 +#define U2_BC_GSC 0x501 +#define UTURN_IOA_RUNWAY 0x581 +#define UTURN_BC_GSC 0x502 + +#define IS_U2(id) ( \ + (((id)->hw_type == HPHW_IOA) && ((id)->hversion == U2_IOA_RUNWAY)) || \ + (((id)->hw_type == HPHW_BCPORT) && ((id)->hversion == U2_BC_GSC)) \ +) + +#define IS_UTURN(id) ( \ + (((id)->hw_type == HPHW_IOA) && ((id)->hversion == UTURN_IOA_RUNWAY)) || \ + (((id)->hw_type == HPHW_BCPORT) && ((id)->hversion == UTURN_BC_GSC)) \ +) + +static int ccio_dma_supported( struct pci_dev *dev, u64 mask) +{ + if (dev == NULL) { + printk(KERN_ERR MODULE_NAME ": EISA/ISA/et al not supported\n"); + BUG(); + return(0); + } + + /* only support 32-bit devices (ie PCI/GSC) */ + return((int) (mask >= 0xffffffffUL)); +} + + +static void *ccio_alloc_consistent(struct pci_dev *dev, size_t size, + dma_addr_t *handle) +{ + void *ret; + + ret = (void *)__get_free_pages(GFP_ATOMIC, get_order(size)); + + if (ret != NULL) { + memset(ret, 0, size); + *handle = virt_to_phys(ret); + } + return ret; +} + +static void ccio_free_consistent(struct pci_dev *dev, size_t size, + void *vaddr, dma_addr_t handle) +{ + free_pages((unsigned long)vaddr, get_order(size)); +} + +static dma_addr_t ccio_map_single(struct pci_dev *dev, void *ptr, size_t size, + int direction) +{ + return virt_to_phys(ptr); +} + +static void ccio_unmap_single(struct pci_dev *dev, dma_addr_t dma_addr, + size_t size, int direction) +{ + /* Nothing to do */ +} + + +static int ccio_map_sg(struct pci_dev *dev, struct scatterlist *sglist, int nents, int direction) +{ + int tmp = nents; + + /* KISS: map each buffer separately. */ + while (nents) { + sg_dma_address(sglist) = ccio_map_single(dev, sglist->address, sglist->length, direction); + sg_dma_len(sglist) = sglist->length; + nents--; + sglist++; + } + + return tmp; +} + + +static void ccio_unmap_sg(struct pci_dev *dev, struct scatterlist *sglist, int nents, int direction) +{ +#if 0 + while (nents) { + ccio_unmap_single(dev, sg_dma_address(sglist), sg_dma_len(sglist), direction); + nents--; + sglist++; + } + return; +#else + /* Do nothing (copied from current ccio_unmap_single() :^) */ +#endif +} + + +static struct pci_dma_ops ccio_ops = { + ccio_dma_supported, + ccio_alloc_consistent, + ccio_free_consistent, + ccio_map_single, + ccio_unmap_single, + ccio_map_sg, + ccio_unmap_sg, + NULL, /* dma_sync_single_for_cpu : NOP for U2 */ + NULL, /* dma_sync_single_for_device : NOP for U2 */ + NULL, /* dma_sync_sg_for_cpu : ditto */ + NULL, /* dma_sync_sg_for_device : ditto */ +}; + + +/* +** Determine if u2 should claim this chip (return 0) or not (return 1). +** If so, initialize the chip and tell other partners in crime they +** have work to do. +*/ +static int +ccio_probe(struct parisc_device *dev) +{ + printk(KERN_INFO "%s found %s at 0x%lx\n", MODULE_NAME, + dev->id.hversion == U2_BC_GSC ? "U2" : "UTurn", + dev->hpa); + +/* +** FIXME - should check U2 registers to verify it's really running +** in "Real Mode". +*/ + +#if 0 +/* will need this for "Virtual Mode" operation */ + ccio_hw_init(ccio_dev); + ccio_common_init(ccio_dev); +#endif + hppa_dma_ops = &ccio_ops; + return 0; +} + +static struct parisc_device_id ccio_tbl[] = { + { HPHW_BCPORT, HVERSION_REV_ANY_ID, U2_BC_GSC, 0xc }, + { HPHW_BCPORT, HVERSION_REV_ANY_ID, UTURN_BC_GSC, 0xc }, + { 0, } +}; + +static struct parisc_driver ccio_driver = { + .name = "U2/Uturn", + .id_table = ccio_tbl, + .probe = ccio_probe, +}; + +void __init ccio_init(void) +{ + register_parisc_driver(&ccio_driver); +} diff --git a/drivers/parisc/dino.c b/drivers/parisc/dino.c new file mode 100644 index 00000000000..b0d2a73d1d4 --- /dev/null +++ b/drivers/parisc/dino.c @@ -0,0 +1,1044 @@ +/* +** DINO manager +** +** (c) Copyright 1999 Red Hat Software +** (c) Copyright 1999 SuSE GmbH +** (c) Copyright 1999,2000 Hewlett-Packard Company +** (c) Copyright 2000 Grant Grundler +** +** 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 module provides access to Dino PCI bus (config/IOport spaces) +** and helps manage Dino IRQ lines. +** +** Dino interrupt handling is a bit complicated. +** Dino always writes to the broadcast EIR via irr0 for now. +** (BIG WARNING: using broadcast EIR is a really bad thing for SMP!) +** Only one processor interrupt is used for the 11 IRQ line +** inputs to dino. +** +** The different between Built-in Dino and Card-Mode +** dino is in chip initialization and pci device initialization. +** +** Linux drivers can only use Card-Mode Dino if pci devices I/O port +** BARs are configured and used by the driver. Programming MMIO address +** requires substantial knowledge of available Host I/O address ranges +** is currently not supported. Port/Config accessor functions are the +** same. "BIOS" differences are handled within the existing routines. +*/ + +/* Changes : +** 2001-06-14 : Clement Moyroud (moyroudc@esiee.fr) +** - added support for the integrated RS232. +*/ + +/* +** TODO: create a virtual address for each Dino HPA. +** GSC code might be able to do this since IODC data tells us +** how many pages are used. PCI subsystem could (must?) do this +** for PCI drivers devices which implement/use MMIO registers. +*/ + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/interrupt.h> /* for struct irqaction */ +#include <linux/spinlock.h> /* for spinlock_t and prototypes */ + +#include <asm/pdc.h> +#include <asm/page.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/hardware.h> + +#include "gsc.h" + +#undef DINO_DEBUG + +#ifdef DINO_DEBUG +#define DBG(x...) printk(x) +#else +#define DBG(x...) +#endif + +/* +** Config accessor functions only pass in the 8-bit bus number +** and not the 8-bit "PCI Segment" number. Each Dino will be +** assigned a PCI bus number based on "when" it's discovered. +** +** The "secondary" bus number is set to this before calling +** pci_scan_bus(). If any PPB's are present, the scan will +** discover them and update the "secondary" and "subordinate" +** fields in Dino's pci_bus structure. +** +** Changes in the configuration *will* result in a different +** bus number for each dino. +*/ + +#define is_card_dino(id) ((id)->hw_type == HPHW_A_DMA) + +#define DINO_IAR0 0x004 +#define DINO_IODC_ADDR 0x008 +#define DINO_IODC_DATA_0 0x008 +#define DINO_IODC_DATA_1 0x008 +#define DINO_IRR0 0x00C +#define DINO_IAR1 0x010 +#define DINO_IRR1 0x014 +#define DINO_IMR 0x018 +#define DINO_IPR 0x01C +#define DINO_TOC_ADDR 0x020 +#define DINO_ICR 0x024 +#define DINO_ILR 0x028 +#define DINO_IO_COMMAND 0x030 +#define DINO_IO_STATUS 0x034 +#define DINO_IO_CONTROL 0x038 +#define DINO_IO_GSC_ERR_RESP 0x040 +#define DINO_IO_ERR_INFO 0x044 +#define DINO_IO_PCI_ERR_RESP 0x048 +#define DINO_IO_FBB_EN 0x05c +#define DINO_IO_ADDR_EN 0x060 +#define DINO_PCI_ADDR 0x064 +#define DINO_CONFIG_DATA 0x068 +#define DINO_IO_DATA 0x06c +#define DINO_MEM_DATA 0x070 /* Dino 3.x only */ +#define DINO_GSC2X_CONFIG 0x7b4 +#define DINO_GMASK 0x800 +#define DINO_PAMR 0x804 +#define DINO_PAPR 0x808 +#define DINO_DAMODE 0x80c +#define DINO_PCICMD 0x810 +#define DINO_PCISTS 0x814 +#define DINO_MLTIM 0x81c +#define DINO_BRDG_FEAT 0x820 +#define DINO_PCIROR 0x824 +#define DINO_PCIWOR 0x828 +#define DINO_TLTIM 0x830 + +#define DINO_IRQS 11 /* bits 0-10 are architected */ +#define DINO_IRR_MASK 0x5ff /* only 10 bits are implemented */ + +#define DINO_MASK_IRQ(x) (1<<(x)) + +#define PCIINTA 0x001 +#define PCIINTB 0x002 +#define PCIINTC 0x004 +#define PCIINTD 0x008 +#define PCIINTE 0x010 +#define PCIINTF 0x020 +#define GSCEXTINT 0x040 +/* #define xxx 0x080 - bit 7 is "default" */ +/* #define xxx 0x100 - bit 8 not used */ +/* #define xxx 0x200 - bit 9 not used */ +#define RS232INT 0x400 + +struct dino_device +{ + struct pci_hba_data hba; /* 'C' inheritance - must be first */ + spinlock_t dinosaur_pen; + unsigned long txn_addr; /* EIR addr to generate interrupt */ + u32 txn_data; /* EIR data assign to each dino */ + u32 imr; /* IRQ's which are enabled */ + int global_irq[12]; /* map IMR bit to global irq */ +#ifdef DINO_DEBUG + unsigned int dino_irr0; /* save most recent IRQ line stat */ +#endif +}; + +/* Looks nice and keeps the compiler happy */ +#define DINO_DEV(d) ((struct dino_device *) d) + + +/* + * Dino Configuration Space Accessor Functions + */ + +#define DINO_CFG_TOK(bus,dfn,pos) ((u32) ((bus)<<16 | (dfn)<<8 | (pos))) + +/* + * keep the current highest bus count to assist in allocating busses. This + * tries to keep a global bus count total so that when we discover an + * entirely new bus, it can be given a unique bus number. + */ +static int dino_current_bus = 0; + +static int dino_cfg_read(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 *val) +{ + struct dino_device *d = DINO_DEV(parisc_walk_tree(bus->bridge)); + u32 local_bus = (bus->parent == NULL) ? 0 : bus->secondary; + u32 v = DINO_CFG_TOK(local_bus, devfn, where & ~3); + void __iomem *base_addr = d->hba.base_addr; + unsigned long flags; + + spin_lock_irqsave(&d->dinosaur_pen, flags); + + /* tell HW which CFG address */ + __raw_writel(v, base_addr + DINO_PCI_ADDR); + + /* generate cfg read cycle */ + if (size == 1) { + *val = readb(base_addr + DINO_CONFIG_DATA + (where & 3)); + } else if (size == 2) { + *val = readw(base_addr + DINO_CONFIG_DATA + (where & 2)); + } else if (size == 4) { + *val = readl(base_addr + DINO_CONFIG_DATA); + } + + spin_unlock_irqrestore(&d->dinosaur_pen, flags); + return 0; +} + +/* + * Dino address stepping "feature": + * When address stepping, Dino attempts to drive the bus one cycle too soon + * even though the type of cycle (config vs. MMIO) might be different. + * The read of Ven/Prod ID is harmless and avoids Dino's address stepping. + */ +static int dino_cfg_write(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 val) +{ + struct dino_device *d = DINO_DEV(parisc_walk_tree(bus->bridge)); + u32 local_bus = (bus->parent == NULL) ? 0 : bus->secondary; + u32 v = DINO_CFG_TOK(local_bus, devfn, where & ~3); + void __iomem *base_addr = d->hba.base_addr; + unsigned long flags; + + spin_lock_irqsave(&d->dinosaur_pen, flags); + + /* avoid address stepping feature */ + __raw_writel(v & 0xffffff00, base_addr + DINO_PCI_ADDR); + __raw_readl(base_addr + DINO_CONFIG_DATA); + + /* tell HW which CFG address */ + __raw_writel(v, base_addr + DINO_PCI_ADDR); + /* generate cfg read cycle */ + if (size == 1) { + writeb(val, base_addr + DINO_CONFIG_DATA + (where & 3)); + } else if (size == 2) { + writew(val, base_addr + DINO_CONFIG_DATA + (where & 2)); + } else if (size == 4) { + writel(val, base_addr + DINO_CONFIG_DATA); + } + + spin_unlock_irqrestore(&d->dinosaur_pen, flags); + return 0; +} + +static struct pci_ops dino_cfg_ops = { + .read = dino_cfg_read, + .write = dino_cfg_write, +}; + + +/* + * Dino "I/O Port" Space Accessor Functions + * + * Many PCI devices don't require use of I/O port space (eg Tulip, + * NCR720) since they export the same registers to both MMIO and + * I/O port space. Performance is going to stink if drivers use + * I/O port instead of MMIO. + */ + +#define DINO_PORT_IN(type, size, mask) \ +static u##size dino_in##size (struct pci_hba_data *d, u16 addr) \ +{ \ + u##size v; \ + unsigned long flags; \ + spin_lock_irqsave(&(DINO_DEV(d)->dinosaur_pen), flags); \ + /* tell HW which IO Port address */ \ + __raw_writel((u32) addr, d->base_addr + DINO_PCI_ADDR); \ + /* generate I/O PORT read cycle */ \ + v = read##type(d->base_addr+DINO_IO_DATA+(addr&mask)); \ + spin_unlock_irqrestore(&(DINO_DEV(d)->dinosaur_pen), flags); \ + return v; \ +} + +DINO_PORT_IN(b, 8, 3) +DINO_PORT_IN(w, 16, 2) +DINO_PORT_IN(l, 32, 0) + +#define DINO_PORT_OUT(type, size, mask) \ +static void dino_out##size (struct pci_hba_data *d, u16 addr, u##size val) \ +{ \ + unsigned long flags; \ + spin_lock_irqsave(&(DINO_DEV(d)->dinosaur_pen), flags); \ + /* tell HW which IO port address */ \ + __raw_writel((u32) addr, d->base_addr + DINO_PCI_ADDR); \ + /* generate cfg write cycle */ \ + write##type(val, d->base_addr+DINO_IO_DATA+(addr&mask)); \ + spin_unlock_irqrestore(&(DINO_DEV(d)->dinosaur_pen), flags); \ +} + +DINO_PORT_OUT(b, 8, 3) +DINO_PORT_OUT(w, 16, 2) +DINO_PORT_OUT(l, 32, 0) + +struct pci_port_ops dino_port_ops = { + .inb = dino_in8, + .inw = dino_in16, + .inl = dino_in32, + .outb = dino_out8, + .outw = dino_out16, + .outl = dino_out32 +}; + +static void dino_disable_irq(unsigned int irq) +{ + struct dino_device *dino_dev = irq_desc[irq].handler_data; + int local_irq = gsc_find_local_irq(irq, dino_dev->global_irq, irq); + + DBG(KERN_WARNING "%s(0x%p, %d)\n", __FUNCTION__, irq_dev, irq); + + /* Clear the matching bit in the IMR register */ + dino_dev->imr &= ~(DINO_MASK_IRQ(local_irq)); + __raw_writel(dino_dev->imr, dino_dev->hba.base_addr+DINO_IMR); +} + +static void dino_enable_irq(unsigned int irq) +{ + struct dino_device *dino_dev = irq_desc[irq].handler_data; + int local_irq = gsc_find_local_irq(irq, dino_dev->global_irq, irq); + u32 tmp; + + DBG(KERN_WARNING "%s(0x%p, %d)\n", __FUNCTION__, irq_dev, irq); + + /* + ** clear pending IRQ bits + ** + ** This does NOT change ILR state! + ** See comment below for ILR usage. + */ + __raw_readl(dino_dev->hba.base_addr+DINO_IPR); + + /* set the matching bit in the IMR register */ + dino_dev->imr |= DINO_MASK_IRQ(local_irq); /* used in dino_isr() */ + __raw_writel( dino_dev->imr, dino_dev->hba.base_addr+DINO_IMR); + + /* Emulate "Level Triggered" Interrupt + ** Basically, a driver is blowing it if the IRQ line is asserted + ** while the IRQ is disabled. But tulip.c seems to do that.... + ** Give 'em a kluge award and a nice round of applause! + ** + ** The gsc_write will generate an interrupt which invokes dino_isr(). + ** dino_isr() will read IPR and find nothing. But then catch this + ** when it also checks ILR. + */ + tmp = __raw_readl(dino_dev->hba.base_addr+DINO_ILR); + if (tmp & DINO_MASK_IRQ(local_irq)) { + DBG(KERN_WARNING "%s(): IRQ asserted! (ILR 0x%x)\n", + __FUNCTION__, tmp); + gsc_writel(dino_dev->txn_data, dino_dev->txn_addr); + } +} + +static unsigned int dino_startup_irq(unsigned int irq) +{ + dino_enable_irq(irq); + return 0; +} + +static struct hw_interrupt_type dino_interrupt_type = { + .typename = "GSC-PCI", + .startup = dino_startup_irq, + .shutdown = dino_disable_irq, + .enable = dino_enable_irq, + .disable = dino_disable_irq, + .ack = no_ack_irq, + .end = no_end_irq, +}; + + +/* + * Handle a Processor interrupt generated by Dino. + * + * ilr_loop counter is a kluge to prevent a "stuck" IRQ line from + * wedging the CPU. Could be removed or made optional at some point. + */ +static irqreturn_t +dino_isr(int irq, void *intr_dev, struct pt_regs *regs) +{ + struct dino_device *dino_dev = intr_dev; + u32 mask; + int ilr_loop = 100; + + /* read and acknowledge pending interrupts */ +#ifdef DINO_DEBUG + dino_dev->dino_irr0 = +#endif + mask = __raw_readl(dino_dev->hba.base_addr+DINO_IRR0) & DINO_IRR_MASK; + + if (mask == 0) + return IRQ_NONE; + +ilr_again: + do { + int local_irq = __ffs(mask); + int irq = dino_dev->global_irq[local_irq]; + DBG(KERN_DEBUG "%s(%d, %p) mask 0x%x\n", + __FUNCTION__, irq, intr_dev, mask); + __do_IRQ(irq, regs); + mask &= ~(1 << local_irq); + } while (mask); + + /* Support for level triggered IRQ lines. + ** + ** Dropping this support would make this routine *much* faster. + ** But since PCI requires level triggered IRQ line to share lines... + ** device drivers may assume lines are level triggered (and not + ** edge triggered like EISA/ISA can be). + */ + mask = __raw_readl(dino_dev->hba.base_addr+DINO_ILR) & dino_dev->imr; + if (mask) { + if (--ilr_loop > 0) + goto ilr_again; + printk(KERN_ERR "Dino 0x%p: stuck interrupt %d\n", + dino_dev->hba.base_addr, mask); + return IRQ_NONE; + } + return IRQ_HANDLED; +} + +static void dino_assign_irq(struct dino_device *dino, int local_irq, int *irqp) +{ + int irq = gsc_assign_irq(&dino_interrupt_type, dino); + if (irq == NO_IRQ) + return; + + *irqp = irq; + dino->global_irq[local_irq] = irq; +} + +static void dino_choose_irq(struct parisc_device *dev, void *ctrl) +{ + int irq; + struct dino_device *dino = ctrl; + + switch (dev->id.sversion) { + case 0x00084: irq = 8; break; /* PS/2 */ + case 0x0008c: irq = 10; break; /* RS232 */ + case 0x00096: irq = 8; break; /* PS/2 */ + default: return; /* Unknown */ + } + + dino_assign_irq(dino, irq, &dev->irq); +} + +static void __init +dino_bios_init(void) +{ + DBG("dino_bios_init\n"); +} + +/* + * dino_card_setup - Set up the memory space for a Dino in card mode. + * @bus: the bus under this dino + * + * Claim an 8MB chunk of unused IO space and call the generic PCI routines + * to set up the addresses of the devices on this bus. + */ +#define _8MB 0x00800000UL +static void __init +dino_card_setup(struct pci_bus *bus, void __iomem *base_addr) +{ + int i; + struct dino_device *dino_dev = DINO_DEV(parisc_walk_tree(bus->bridge)); + struct resource *res; + char name[128]; + int size; + + res = &dino_dev->hba.lmmio_space; + res->flags = IORESOURCE_MEM; + size = scnprintf(name, sizeof(name), "Dino LMMIO (%s)", + bus->bridge->bus_id); + res->name = kmalloc(size+1, GFP_KERNEL); + if(res->name) + strcpy((char *)res->name, name); + else + res->name = dino_dev->hba.lmmio_space.name; + + + if (ccio_allocate_resource(dino_dev->hba.dev, res, _8MB, + F_EXTEND(0xf0000000UL) | _8MB, + F_EXTEND(0xffffffffUL) &~ _8MB, _8MB) < 0) { + struct list_head *ln, *tmp_ln; + + printk(KERN_ERR "Dino: cannot attach bus %s\n", + bus->bridge->bus_id); + /* kill the bus, we can't do anything with it */ + list_for_each_safe(ln, tmp_ln, &bus->devices) { + struct pci_dev *dev = pci_dev_b(ln); + + list_del(&dev->global_list); + list_del(&dev->bus_list); + } + + return; + } + bus->resource[1] = res; + bus->resource[0] = &(dino_dev->hba.io_space); + + /* Now tell dino what range it has */ + for (i = 1; i < 31; i++) { + if (res->start == F_EXTEND(0xf0000000UL | (i * _8MB))) + break; + } + DBG("DINO GSC WRITE i=%d, start=%lx, dino addr = %lx\n", + i, res->start, base_addr + DINO_IO_ADDR_EN); + __raw_writel(1 << i, base_addr + DINO_IO_ADDR_EN); +} + +static void __init +dino_card_fixup(struct pci_dev *dev) +{ + u32 irq_pin; + + /* + ** REVISIT: card-mode PCI-PCI expansion chassis do exist. + ** Not sure they were ever productized. + ** Die here since we'll die later in dino_inb() anyway. + */ + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { + panic("Card-Mode Dino: PCI-PCI Bridge not supported\n"); + } + + /* + ** Set Latency Timer to 0xff (not a shared bus) + ** Set CACHELINE_SIZE. + */ + dino_cfg_write(dev->bus, dev->devfn, + PCI_CACHE_LINE_SIZE, 2, 0xff00 | L1_CACHE_BYTES/4); + + /* + ** Program INT_LINE for card-mode devices. + ** The cards are hardwired according to this algorithm. + ** And it doesn't matter if PPB's are present or not since + ** the IRQ lines bypass the PPB. + ** + ** "-1" converts INTA-D (1-4) to PCIINTA-D (0-3) range. + ** The additional "-1" adjusts for skewing the IRQ<->slot. + */ + dino_cfg_read(dev->bus, dev->devfn, PCI_INTERRUPT_PIN, 1, &irq_pin); + dev->irq = (irq_pin + PCI_SLOT(dev->devfn) - 1) % 4 ; + + /* Shouldn't really need to do this but it's in case someone tries + ** to bypass PCI services and look at the card themselves. + */ + dino_cfg_write(dev->bus, dev->devfn, PCI_INTERRUPT_LINE, 1, dev->irq); +} + +/* The alignment contraints for PCI bridges under dino */ +#define DINO_BRIDGE_ALIGN 0x100000 + + +static void __init +dino_fixup_bus(struct pci_bus *bus) +{ + struct list_head *ln; + struct pci_dev *dev; + struct dino_device *dino_dev = DINO_DEV(parisc_walk_tree(bus->bridge)); + int port_base = HBA_PORT_BASE(dino_dev->hba.hba_num); + + DBG(KERN_WARNING "%s(0x%p) bus %d platform_data 0x%p\n", + __FUNCTION__, bus, bus->secondary, + bus->bridge->platform_data); + + /* Firmware doesn't set up card-mode dino, so we have to */ + if (is_card_dino(&dino_dev->hba.dev->id)) { + dino_card_setup(bus, dino_dev->hba.base_addr); + } else if(bus->parent == NULL) { + /* must have a dino above it, reparent the resources + * into the dino window */ + int i; + struct resource *res = &dino_dev->hba.lmmio_space; + + bus->resource[0] = &(dino_dev->hba.io_space); + for(i = 0; i < DINO_MAX_LMMIO_RESOURCES; i++) { + if(res[i].flags == 0) + break; + bus->resource[i+1] = &res[i]; + } + + } else if(bus->self) { + int i; + + pci_read_bridge_bases(bus); + + + for(i = PCI_BRIDGE_RESOURCES; i < PCI_NUM_RESOURCES; i++) { + if((bus->self->resource[i].flags & + (IORESOURCE_IO | IORESOURCE_MEM)) == 0) + continue; + + if(bus->self->resource[i].flags & IORESOURCE_MEM) { + /* There's a quirk to alignment of + * bridge memory resources: the start + * is the alignment and start-end is + * the size. However, firmware will + * have assigned start and end, so we + * need to take this into account */ + bus->self->resource[i].end = bus->self->resource[i].end - bus->self->resource[i].start + DINO_BRIDGE_ALIGN; + bus->self->resource[i].start = DINO_BRIDGE_ALIGN; + + } + + DBG("DEBUG %s assigning %d [0x%lx,0x%lx]\n", + bus->self->dev.bus_id, i, + bus->self->resource[i].start, + bus->self->resource[i].end); + pci_assign_resource(bus->self, i); + DBG("DEBUG %s after assign %d [0x%lx,0x%lx]\n", + bus->self->dev.bus_id, i, + bus->self->resource[i].start, + bus->self->resource[i].end); + } + } + + + list_for_each(ln, &bus->devices) { + int i; + + dev = pci_dev_b(ln); + if (is_card_dino(&dino_dev->hba.dev->id)) + dino_card_fixup(dev); + + /* + ** P2PB's only have 2 BARs, no IRQs. + ** I'd like to just ignore them for now. + */ + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) + continue; + + /* Adjust the I/O Port space addresses */ + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *res = &dev->resource[i]; + if (res->flags & IORESOURCE_IO) { + res->start |= port_base; + res->end |= port_base; + } +#ifdef __LP64__ + /* Sign Extend MMIO addresses */ + else if (res->flags & IORESOURCE_MEM) { + res->start |= F_EXTEND(0UL); + res->end |= F_EXTEND(0UL); + } +#endif + } + /* null out the ROM resource if there is one (we don't + * care about an expansion rom on parisc, since it + * usually contains (x86) bios code) */ + dev->resource[PCI_ROM_RESOURCE].flags = 0; + + if(dev->irq == 255) { + +#define DINO_FIX_UNASSIGNED_INTERRUPTS +#ifdef DINO_FIX_UNASSIGNED_INTERRUPTS + + /* This code tries to assign an unassigned + * interrupt. Leave it disabled unless you + * *really* know what you're doing since the + * pin<->interrupt line mapping varies by bus + * and machine */ + + u32 irq_pin; + + dino_cfg_read(dev->bus, dev->devfn, + PCI_INTERRUPT_PIN, 1, &irq_pin); + irq_pin = (irq_pin + PCI_SLOT(dev->devfn) - 1) % 4 ; + printk(KERN_WARNING "Device %s has undefined IRQ, " + "setting to %d\n", pci_name(dev), irq_pin); + dino_cfg_write(dev->bus, dev->devfn, + PCI_INTERRUPT_LINE, 1, irq_pin); + dino_assign_irq(dino_dev, irq_pin, &dev->irq); +#else + dev->irq = 65535; + printk(KERN_WARNING "Device %s has unassigned IRQ\n", pci_name(dev)); +#endif + } else { + + /* Adjust INT_LINE for that busses region */ + dino_assign_irq(dino_dev, dev->irq, &dev->irq); + } + } +} + + +struct pci_bios_ops dino_bios_ops = { + .init = dino_bios_init, + .fixup_bus = dino_fixup_bus +}; + + +/* + * Initialise a DINO controller chip + */ +static void __init +dino_card_init(struct dino_device *dino_dev) +{ + u32 brdg_feat = 0x00784e05; + + __raw_writel(0x00000000, dino_dev->hba.base_addr+DINO_GMASK); + __raw_writel(0x00000001, dino_dev->hba.base_addr+DINO_IO_FBB_EN); + __raw_writel(0x00000000, dino_dev->hba.base_addr+DINO_ICR); + +#if 1 +/* REVISIT - should be a runtime check (eg if (CPU_IS_PCX_L) ...) */ + /* + ** PCX-L processors don't support XQL like Dino wants it. + ** PCX-L2 ignore XQL signal and it doesn't matter. + */ + brdg_feat &= ~0x4; /* UXQL */ +#endif + __raw_writel( brdg_feat, dino_dev->hba.base_addr+DINO_BRDG_FEAT); + + /* + ** Don't enable address decoding until we know which I/O range + ** currently is available from the host. Only affects MMIO + ** and not I/O port space. + */ + __raw_writel(0x00000000, dino_dev->hba.base_addr+DINO_IO_ADDR_EN); + + __raw_writel(0x00000000, dino_dev->hba.base_addr+DINO_DAMODE); + __raw_writel(0x00222222, dino_dev->hba.base_addr+DINO_PCIROR); + __raw_writel(0x00222222, dino_dev->hba.base_addr+DINO_PCIWOR); + + __raw_writel(0x00000040, dino_dev->hba.base_addr+DINO_MLTIM); + __raw_writel(0x00000080, dino_dev->hba.base_addr+DINO_IO_CONTROL); + __raw_writel(0x0000008c, dino_dev->hba.base_addr+DINO_TLTIM); + + /* Disable PAMR before writing PAPR */ + __raw_writel(0x0000007e, dino_dev->hba.base_addr+DINO_PAMR); + __raw_writel(0x0000007f, dino_dev->hba.base_addr+DINO_PAPR); + __raw_writel(0x00000000, dino_dev->hba.base_addr+DINO_PAMR); + + /* + ** Dino ERS encourages enabling FBB (0x6f). + ** We can't until we know *all* devices below us can support it. + ** (Something in device configuration header tells us). + */ + __raw_writel(0x0000004f, dino_dev->hba.base_addr+DINO_PCICMD); + + /* Somewhere, the PCI spec says give devices 1 second + ** to recover from the #RESET being de-asserted. + ** Experience shows most devices only need 10ms. + ** This short-cut speeds up booting significantly. + */ + mdelay(pci_post_reset_delay); +} + +static int __init +dino_bridge_init(struct dino_device *dino_dev, const char *name) +{ + unsigned long io_addr; + int result, i, count=0; + struct resource *res, *prevres = NULL; + /* + * Decoding IO_ADDR_EN only works for Built-in Dino + * since PDC has already initialized this. + */ + + io_addr = __raw_readl(dino_dev->hba.base_addr + DINO_IO_ADDR_EN); + if (io_addr == 0) { + printk(KERN_WARNING "%s: No PCI devices enabled.\n", name); + return -ENODEV; + } + + res = &dino_dev->hba.lmmio_space; + for (i = 0; i < 32; i++) { + unsigned long start, end; + + if((io_addr & (1 << i)) == 0) + continue; + + start = (unsigned long)(signed int)(0xf0000000 | (i << 23)); + end = start + 8 * 1024 * 1024 - 1; + + DBG("DINO RANGE %d is at 0x%lx-0x%lx\n", count, + start, end); + + if(prevres && prevres->end + 1 == start) { + prevres->end = end; + } else { + if(count >= DINO_MAX_LMMIO_RESOURCES) { + printk(KERN_ERR "%s is out of resource windows for range %d (0x%lx-0x%lx)\n", name, count, start, end); + break; + } + prevres = res; + res->start = start; + res->end = end; + res->flags = IORESOURCE_MEM; + res->name = kmalloc(64, GFP_KERNEL); + if(res->name) + snprintf((char *)res->name, 64, "%s LMMIO %d", + name, count); + res++; + count++; + } + } + + res = &dino_dev->hba.lmmio_space; + + for(i = 0; i < DINO_MAX_LMMIO_RESOURCES; i++) { + if(res[i].flags == 0) + break; + + result = ccio_request_resource(dino_dev->hba.dev, &res[i]); + if (result < 0) { + printk(KERN_ERR "%s: failed to claim PCI Bus address space %d (0x%lx-0x%lx)!\n", name, i, res[i].start, res[i].end); + return result; + } + } + return 0; +} + +static int __init dino_common_init(struct parisc_device *dev, + struct dino_device *dino_dev, const char *name) +{ + int status; + u32 eim; + struct gsc_irq gsc_irq; + struct resource *res; + + pcibios_register_hba(&dino_dev->hba); + + pci_bios = &dino_bios_ops; /* used by pci_scan_bus() */ + pci_port = &dino_port_ops; + + /* + ** Note: SMP systems can make use of IRR1/IAR1 registers + ** But it won't buy much performance except in very + ** specific applications/configurations. Note Dino + ** still only has 11 IRQ input lines - just map some of them + ** to a different processor. + */ + dev->irq = gsc_alloc_irq(&gsc_irq); + dino_dev->txn_addr = gsc_irq.txn_addr; + dino_dev->txn_data = gsc_irq.txn_data; + eim = ((u32) gsc_irq.txn_addr) | gsc_irq.txn_data; + + /* + ** Dino needs a PA "IRQ" to get a processor's attention. + ** arch/parisc/kernel/irq.c returns an EIRR bit. + */ + if (dev->irq < 0) { + printk(KERN_WARNING "%s: gsc_alloc_irq() failed\n", name); + return 1; + } + + status = request_irq(dev->irq, dino_isr, 0, name, dino_dev); + if (status) { + printk(KERN_WARNING "%s: request_irq() failed with %d\n", + name, status); + return 1; + } + + /* Support the serial port which is sometimes attached on built-in + * Dino / Cujo chips. + */ + + gsc_fixup_irqs(dev, dino_dev, dino_choose_irq); + + /* + ** This enables DINO to generate interrupts when it sees + ** any of its inputs *change*. Just asserting an IRQ + ** before it's enabled (ie unmasked) isn't good enough. + */ + __raw_writel(eim, dino_dev->hba.base_addr+DINO_IAR0); + + /* + ** Some platforms don't clear Dino's IRR0 register at boot time. + ** Reading will clear it now. + */ + __raw_readl(dino_dev->hba.base_addr+DINO_IRR0); + + /* allocate I/O Port resource region */ + res = &dino_dev->hba.io_space; + if (dev->id.hversion == 0x680 || is_card_dino(&dev->id)) { + res->name = "Dino I/O Port"; + } else { + res->name = "Cujo I/O Port"; + } + res->start = HBA_PORT_BASE(dino_dev->hba.hba_num); + res->end = res->start + (HBA_PORT_SPACE_SIZE - 1); + res->flags = IORESOURCE_IO; /* do not mark it busy ! */ + if (request_resource(&ioport_resource, res) < 0) { + printk(KERN_ERR "%s: request I/O Port region failed " + "0x%lx/%lx (hpa 0x%p)\n", + name, res->start, res->end, dino_dev->hba.base_addr); + return 1; + } + + return 0; +} + +#define CUJO_RAVEN_ADDR F_EXTEND(0xf1000000UL) +#define CUJO_FIREHAWK_ADDR F_EXTEND(0xf1604000UL) +#define CUJO_RAVEN_BADPAGE 0x01003000UL +#define CUJO_FIREHAWK_BADPAGE 0x01607000UL + +static const char *dino_vers[] = { + "2.0", + "2.1", + "3.0", + "3.1" +}; + +static const char *cujo_vers[] = { + "1.0", + "2.0" +}; + +void ccio_cujo20_fixup(struct parisc_device *dev, u32 iovp); + +/* +** Determine if dino should claim this chip (return 0) or not (return 1). +** If so, initialize the chip appropriately (card-mode vs bridge mode). +** Much of the initialization is common though. +*/ +static int __init +dino_driver_callback(struct parisc_device *dev) +{ + struct dino_device *dino_dev; // Dino specific control struct + const char *version = "unknown"; + char *name; + int is_cujo = 0; + struct pci_bus *bus; + + name = "Dino"; + if (is_card_dino(&dev->id)) { + version = "3.x (card mode)"; + } else { + if(dev->id.hversion == 0x680) { + if (dev->id.hversion_rev < 4) { + version = dino_vers[dev->id.hversion_rev]; + } + } else { + name = "Cujo"; + is_cujo = 1; + if (dev->id.hversion_rev < 2) { + version = cujo_vers[dev->id.hversion_rev]; + } + } + } + + printk("%s version %s found at 0x%lx\n", name, version, dev->hpa); + + if (!request_mem_region(dev->hpa, PAGE_SIZE, name)) { + printk(KERN_ERR "DINO: Hey! Someone took my MMIO space (0x%ld)!\n", + dev->hpa); + return 1; + } + + /* Check for bugs */ + if (is_cujo && dev->id.hversion_rev == 1) { +#ifdef CONFIG_IOMMU_CCIO + printk(KERN_WARNING "Enabling Cujo 2.0 bug workaround\n"); + if (dev->hpa == (unsigned long)CUJO_RAVEN_ADDR) { + ccio_cujo20_fixup(dev, CUJO_RAVEN_BADPAGE); + } else if (dev->hpa == (unsigned long)CUJO_FIREHAWK_ADDR) { + ccio_cujo20_fixup(dev, CUJO_FIREHAWK_BADPAGE); + } else { + printk("Don't recognise Cujo at address 0x%lx, not enabling workaround\n", dev->hpa); + } +#endif + } else if (!is_cujo && !is_card_dino(&dev->id) && + dev->id.hversion_rev < 3) { + printk(KERN_WARNING +"The GSCtoPCI (Dino hrev %d) bus converter found may exhibit\n" +"data corruption. See Service Note Numbers: A4190A-01, A4191A-01.\n" +"Systems shipped after Aug 20, 1997 will not exhibit this problem.\n" +"Models affected: C180, C160, C160L, B160L, and B132L workstations.\n\n", + dev->id.hversion_rev); +/* REVISIT: why are C200/C240 listed in the README table but not +** "Models affected"? Could be an omission in the original literature. +*/ + } + + dino_dev = kmalloc(sizeof(struct dino_device), GFP_KERNEL); + if (!dino_dev) { + printk("dino_init_chip - couldn't alloc dino_device\n"); + return 1; + } + + memset(dino_dev, 0, sizeof(struct dino_device)); + + dino_dev->hba.dev = dev; + dino_dev->hba.base_addr = ioremap(dev->hpa, 4096); /* faster access */ + dino_dev->hba.lmmio_space_offset = 0; /* CPU addrs == bus addrs */ + spin_lock_init(&dino_dev->dinosaur_pen); + dino_dev->hba.iommu = ccio_get_iommu(dev); + + if (is_card_dino(&dev->id)) { + dino_card_init(dino_dev); + } else { + dino_bridge_init(dino_dev, name); + } + + if (dino_common_init(dev, dino_dev, name)) + return 1; + + dev->dev.platform_data = dino_dev; + + /* + ** It's not used to avoid chicken/egg problems + ** with configuration accessor functions. + */ + bus = pci_scan_bus_parented(&dev->dev, dino_current_bus, + &dino_cfg_ops, NULL); + if(bus) { + /* This code *depends* on scanning being single threaded + * if it isn't, this global bus number count will fail + */ + dino_current_bus = bus->subordinate + 1; + pci_bus_assign_resources(bus); + } else { + printk(KERN_ERR "ERROR: failed to scan PCI bus on %s (probably duplicate bus number %d)\n", dev->dev.bus_id, dino_current_bus); + /* increment the bus number in case of duplicates */ + dino_current_bus++; + } + dino_dev->hba.hba_bus = bus; + return 0; +} + +/* + * Normally, we would just test sversion. But the Elroy PCI adapter has + * the same sversion as Dino, so we have to check hversion as well. + * Unfortunately, the J2240 PDC reports the wrong hversion for the first + * Dino, so we have to test for Dino, Cujo and Dino-in-a-J2240. + * For card-mode Dino, most machines report an sversion of 9D. But 715 + * and 725 firmware misreport it as 0x08080 for no adequately explained + * reason. + */ +static struct parisc_device_id dino_tbl[] = { + { HPHW_A_DMA, HVERSION_REV_ANY_ID, 0x004, 0x0009D },/* Card-mode Dino */ + { HPHW_A_DMA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x08080 }, /* XXX */ + { HPHW_BRIDGE, HVERSION_REV_ANY_ID, 0x680, 0xa }, /* Bridge-mode Dino */ + { HPHW_BRIDGE, HVERSION_REV_ANY_ID, 0x682, 0xa }, /* Bridge-mode Cujo */ + { HPHW_BRIDGE, HVERSION_REV_ANY_ID, 0x05d, 0xa }, /* Dino in a J2240 */ + { 0, } +}; + +static struct parisc_driver dino_driver = { + .name = "Dino", + .id_table = dino_tbl, + .probe = dino_driver_callback, +}; + +/* + * One time initialization to let the world know Dino is here. + * This is the only routine which is NOT static. + * Must be called exactly once before pci_init(). + */ +int __init dino_init(void) +{ + register_parisc_driver(&dino_driver); + return 0; +} + diff --git a/drivers/parisc/eisa.c b/drivers/parisc/eisa.c new file mode 100644 index 00000000000..043d47aea75 --- /dev/null +++ b/drivers/parisc/eisa.c @@ -0,0 +1,464 @@ +/* + * eisa.c - provide support for EISA adapters in PA-RISC machines + * + * 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. + * + * Copyright (c) 2001 Matthew Wilcox for Hewlett Packard + * Copyright (c) 2001 Daniel Engstrom <5116@telia.com> + * + * There are two distinct EISA adapters. Mongoose is found in machines + * before the 712; then the Wax ASIC is used. To complicate matters, the + * Wax ASIC also includes a PS/2 and RS-232 controller, but those are + * dealt with elsewhere; this file is concerned only with the EISA portions + * of Wax. + * + * + * HINT: + * ----- + * To allow an ISA card to work properly in the EISA slot you need to + * set an edge trigger level. This may be done on the palo command line + * by adding the kernel parameter "eisa_irq_edge=n,n2,[...]]", with + * n and n2 as the irq levels you want to use. + * + * Example: "eisa_irq_edge=10,11" allows ISA cards to operate at + * irq levels 10 and 11. + */ + +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/eisa.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/hardware.h> +#include <asm/processor.h> +#include <asm/parisc-device.h> +#include <asm/delay.h> +#include <asm/eisa_bus.h> +#include <asm/eisa_eeprom.h> + +#if 0 +#define EISA_DBG(msg, arg... ) printk(KERN_DEBUG "eisa: " msg , ## arg ) +#else +#define EISA_DBG(msg, arg... ) +#endif + +#define SNAKES_EEPROM_BASE_ADDR 0xF0810400 +#define MIRAGE_EEPROM_BASE_ADDR 0xF00C0400 + +static DEFINE_SPINLOCK(eisa_irq_lock); + +void __iomem *eisa_eeprom_addr; + +/* We can only have one EISA adapter in the system because neither + * implementation can be flexed. + */ +static struct eisa_ba { + struct pci_hba_data hba; + unsigned long eeprom_addr; + struct eisa_root_device root; +} eisa_dev; + +/* Port ops */ + +static inline unsigned long eisa_permute(unsigned short port) +{ + if (port & 0x300) { + return 0xfc000000 | ((port & 0xfc00) >> 6) + | ((port & 0x3f8) << 9) | (port & 7); + } else { + return 0xfc000000 | port; + } +} + +unsigned char eisa_in8(unsigned short port) +{ + if (EISA_bus) + return gsc_readb(eisa_permute(port)); + return 0xff; +} + +unsigned short eisa_in16(unsigned short port) +{ + if (EISA_bus) + return le16_to_cpu(gsc_readw(eisa_permute(port))); + return 0xffff; +} + +unsigned int eisa_in32(unsigned short port) +{ + if (EISA_bus) + return le32_to_cpu(gsc_readl(eisa_permute(port))); + return 0xffffffff; +} + +void eisa_out8(unsigned char data, unsigned short port) +{ + if (EISA_bus) + gsc_writeb(data, eisa_permute(port)); +} + +void eisa_out16(unsigned short data, unsigned short port) +{ + if (EISA_bus) + gsc_writew(cpu_to_le16(data), eisa_permute(port)); +} + +void eisa_out32(unsigned int data, unsigned short port) +{ + if (EISA_bus) + gsc_writel(cpu_to_le32(data), eisa_permute(port)); +} + +#ifndef CONFIG_PCI +/* We call these directly without PCI. See asm/io.h. */ +EXPORT_SYMBOL(eisa_in8); +EXPORT_SYMBOL(eisa_in16); +EXPORT_SYMBOL(eisa_in32); +EXPORT_SYMBOL(eisa_out8); +EXPORT_SYMBOL(eisa_out16); +EXPORT_SYMBOL(eisa_out32); +#endif + +/* Interrupt handling */ + +/* cached interrupt mask registers */ +static int master_mask; +static int slave_mask; + +/* the trig level can be set with the + * eisa_irq_edge=n,n,n commandline parameter + * We should really read this from the EEPROM + * in the furure. + */ +/* irq 13,8,2,1,0 must be edge */ +static unsigned int eisa_irq_level; /* default to edge triggered */ + + +/* called by free irq */ +static void eisa_disable_irq(unsigned int irq) +{ + unsigned long flags; + + EISA_DBG("disable irq %d\n", irq); + /* just mask for now */ + spin_lock_irqsave(&eisa_irq_lock, flags); + if (irq & 8) { + slave_mask |= (1 << (irq&7)); + eisa_out8(slave_mask, 0xa1); + } else { + master_mask |= (1 << (irq&7)); + eisa_out8(master_mask, 0x21); + } + spin_unlock_irqrestore(&eisa_irq_lock, flags); + EISA_DBG("pic0 mask %02x\n", eisa_in8(0x21)); + EISA_DBG("pic1 mask %02x\n", eisa_in8(0xa1)); +} + +/* called by request irq */ +static void eisa_enable_irq(unsigned int irq) +{ + unsigned long flags; + EISA_DBG("enable irq %d\n", irq); + + spin_lock_irqsave(&eisa_irq_lock, flags); + if (irq & 8) { + slave_mask &= ~(1 << (irq&7)); + eisa_out8(slave_mask, 0xa1); + } else { + master_mask &= ~(1 << (irq&7)); + eisa_out8(master_mask, 0x21); + } + spin_unlock_irqrestore(&eisa_irq_lock, flags); + EISA_DBG("pic0 mask %02x\n", eisa_in8(0x21)); + EISA_DBG("pic1 mask %02x\n", eisa_in8(0xa1)); +} + +static unsigned int eisa_startup_irq(unsigned int irq) +{ + eisa_enable_irq(irq); + return 0; +} + +static struct hw_interrupt_type eisa_interrupt_type = { + .typename = "EISA", + .startup = eisa_startup_irq, + .shutdown = eisa_disable_irq, + .enable = eisa_enable_irq, + .disable = eisa_disable_irq, + .ack = no_ack_irq, + .end = no_end_irq, +}; + +static irqreturn_t eisa_irq(int wax_irq, void *intr_dev, struct pt_regs *regs) +{ + int irq = gsc_readb(0xfc01f000); /* EISA supports 16 irqs */ + unsigned long flags; + + spin_lock_irqsave(&eisa_irq_lock, flags); + /* read IRR command */ + eisa_out8(0x0a, 0x20); + eisa_out8(0x0a, 0xa0); + + EISA_DBG("irq IAR %02x 8259-1 irr %02x 8259-2 irr %02x\n", + irq, eisa_in8(0x20), eisa_in8(0xa0)); + + /* read ISR command */ + eisa_out8(0x0a, 0x20); + eisa_out8(0x0a, 0xa0); + EISA_DBG("irq 8259-1 isr %02x imr %02x 8259-2 isr %02x imr %02x\n", + eisa_in8(0x20), eisa_in8(0x21), eisa_in8(0xa0), eisa_in8(0xa1)); + + irq &= 0xf; + + /* mask irq and write eoi */ + if (irq & 8) { + slave_mask |= (1 << (irq&7)); + eisa_out8(slave_mask, 0xa1); + eisa_out8(0x60 | (irq&7),0xa0);/* 'Specific EOI' to slave */ + eisa_out8(0x62,0x20); /* 'Specific EOI' to master-IRQ2 */ + + } else { + master_mask |= (1 << (irq&7)); + eisa_out8(master_mask, 0x21); + eisa_out8(0x60|irq,0x20); /* 'Specific EOI' to master */ + } + spin_unlock_irqrestore(&eisa_irq_lock, flags); + + __do_IRQ(irq, regs); + + spin_lock_irqsave(&eisa_irq_lock, flags); + /* unmask */ + if (irq & 8) { + slave_mask &= ~(1 << (irq&7)); + eisa_out8(slave_mask, 0xa1); + } else { + master_mask &= ~(1 << (irq&7)); + eisa_out8(master_mask, 0x21); + } + spin_unlock_irqrestore(&eisa_irq_lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t dummy_irq2_handler(int _, void *dev, struct pt_regs *regs) +{ + printk(KERN_ALERT "eisa: uhh, irq2?\n"); + return IRQ_HANDLED; +} + +static struct irqaction irq2_action = { + .handler = dummy_irq2_handler, + .name = "cascade", +}; + +static void init_eisa_pic(void) +{ + unsigned long flags; + + spin_lock_irqsave(&eisa_irq_lock, flags); + + eisa_out8(0xff, 0x21); /* mask during init */ + eisa_out8(0xff, 0xa1); /* mask during init */ + + /* master pic */ + eisa_out8(0x11,0x20); /* ICW1 */ + eisa_out8(0x00,0x21); /* ICW2 */ + eisa_out8(0x04,0x21); /* ICW3 */ + eisa_out8(0x01,0x21); /* ICW4 */ + eisa_out8(0x40,0x20); /* OCW2 */ + + /* slave pic */ + eisa_out8(0x11,0xa0); /* ICW1 */ + eisa_out8(0x08,0xa1); /* ICW2 */ + eisa_out8(0x02,0xa1); /* ICW3 */ + eisa_out8(0x01,0xa1); /* ICW4 */ + eisa_out8(0x40,0xa0); /* OCW2 */ + + udelay(100); + + slave_mask = 0xff; + master_mask = 0xfb; + eisa_out8(slave_mask, 0xa1); /* OCW1 */ + eisa_out8(master_mask, 0x21); /* OCW1 */ + + /* setup trig level */ + EISA_DBG("EISA edge/level %04x\n", eisa_irq_level); + + eisa_out8(eisa_irq_level&0xff, 0x4d0); /* Set all irq's to edge */ + eisa_out8((eisa_irq_level >> 8) & 0xff, 0x4d1); + + EISA_DBG("pic0 mask %02x\n", eisa_in8(0x21)); + EISA_DBG("pic1 mask %02x\n", eisa_in8(0xa1)); + EISA_DBG("pic0 edge/level %02x\n", eisa_in8(0x4d0)); + EISA_DBG("pic1 edge/level %02x\n", eisa_in8(0x4d1)); + + spin_unlock_irqrestore(&eisa_irq_lock, flags); +} + +/* Device initialisation */ + +#define is_mongoose(dev) (dev->id.sversion == 0x00076) + +static int __devinit eisa_probe(struct parisc_device *dev) +{ + int i, result; + + char *name = is_mongoose(dev) ? "Mongoose" : "Wax"; + + printk(KERN_INFO "%s EISA Adapter found at 0x%08lx\n", + name, dev->hpa); + + eisa_dev.hba.dev = dev; + eisa_dev.hba.iommu = ccio_get_iommu(dev); + + eisa_dev.hba.lmmio_space.name = "EISA"; + eisa_dev.hba.lmmio_space.start = F_EXTEND(0xfc000000); + eisa_dev.hba.lmmio_space.end = F_EXTEND(0xffbfffff); + eisa_dev.hba.lmmio_space.flags = IORESOURCE_MEM; + result = ccio_request_resource(dev, &eisa_dev.hba.lmmio_space); + if (result < 0) { + printk(KERN_ERR "EISA: failed to claim EISA Bus address space!\n"); + return result; + } + eisa_dev.hba.io_space.name = "EISA"; + eisa_dev.hba.io_space.start = 0; + eisa_dev.hba.io_space.end = 0xffff; + eisa_dev.hba.lmmio_space.flags = IORESOURCE_IO; + result = request_resource(&ioport_resource, &eisa_dev.hba.io_space); + if (result < 0) { + printk(KERN_ERR "EISA: failed to claim EISA Bus port space!\n"); + return result; + } + pcibios_register_hba(&eisa_dev.hba); + + result = request_irq(dev->irq, eisa_irq, SA_SHIRQ, "EISA", &eisa_dev); + if (result) { + printk(KERN_ERR "EISA: request_irq failed!\n"); + return result; + } + + /* Reserve IRQ2 */ + irq_desc[2].action = &irq2_action; + + for (i = 0; i < 16; i++) { + irq_desc[i].handler = &eisa_interrupt_type; + } + + EISA_bus = 1; + + if (dev->num_addrs) { + /* newer firmware hand out the eeprom address */ + eisa_dev.eeprom_addr = dev->addr[0]; + } else { + /* old firmware, need to figure out the box */ + if (is_mongoose(dev)) { + eisa_dev.eeprom_addr = SNAKES_EEPROM_BASE_ADDR; + } else { + eisa_dev.eeprom_addr = MIRAGE_EEPROM_BASE_ADDR; + } + } + eisa_eeprom_addr = ioremap(eisa_dev.eeprom_addr, HPEE_MAX_LENGTH); + result = eisa_enumerator(eisa_dev.eeprom_addr, &eisa_dev.hba.io_space, + &eisa_dev.hba.lmmio_space); + init_eisa_pic(); + + if (result >= 0) { + /* FIXME : Don't enumerate the bus twice. */ + eisa_dev.root.dev = &dev->dev; + dev->dev.driver_data = &eisa_dev.root; + eisa_dev.root.bus_base_addr = 0; + eisa_dev.root.res = &eisa_dev.hba.io_space; + eisa_dev.root.slots = result; + eisa_dev.root.dma_mask = 0xffffffff; /* wild guess */ + if (eisa_root_register (&eisa_dev.root)) { + printk(KERN_ERR "EISA: Failed to register EISA root\n"); + return -1; + } + } + + return 0; +} + +static struct parisc_device_id eisa_tbl[] = { + { HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00076 }, /* Mongoose */ + { HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00090 }, /* Wax EISA */ + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, eisa_tbl); + +static struct parisc_driver eisa_driver = { + .name = "EISA Bus Adapter", + .id_table = eisa_tbl, + .probe = eisa_probe, +}; + +void __init eisa_init(void) +{ + register_parisc_driver(&eisa_driver); +} + + +static unsigned int eisa_irq_configured; +void eisa_make_irq_level(int num) +{ + if (eisa_irq_configured& (1<<num)) { + printk(KERN_WARNING + "IRQ %d polarity configured twice (last to level)\n", + num); + } + eisa_irq_level |= (1<<num); /* set the corresponding bit */ + eisa_irq_configured |= (1<<num); /* set the corresponding bit */ +} + +void eisa_make_irq_edge(int num) +{ + if (eisa_irq_configured& (1<<num)) { + printk(KERN_WARNING + "IRQ %d polarity configured twice (last to edge)\n", + num); + } + eisa_irq_level &= ~(1<<num); /* clear the corresponding bit */ + eisa_irq_configured |= (1<<num); /* set the corresponding bit */ +} + +static int __init eisa_irq_setup(char *str) +{ + char *cur = str; + int val; + + EISA_DBG("IRQ setup\n"); + while (cur != NULL) { + char *pe; + + val = (int) simple_strtoul(cur, &pe, 0); + if (val > 15 || val < 0) { + printk(KERN_ERR "eisa: EISA irq value are 0-15\n"); + continue; + } + if (val == 2) { + val = 9; + } + eisa_make_irq_edge(val); /* clear the corresponding bit */ + EISA_DBG("setting IRQ %d to edge-triggered mode\n", val); + + if ((cur = strchr(cur, ','))) { + cur++; + } else { + break; + } + } + return 1; +} + +__setup("eisa_irq_edge=", eisa_irq_setup); + diff --git a/drivers/parisc/eisa_eeprom.c b/drivers/parisc/eisa_eeprom.c new file mode 100644 index 00000000000..3a1b4826e5c --- /dev/null +++ b/drivers/parisc/eisa_eeprom.c @@ -0,0 +1,134 @@ +/* + * EISA "eeprom" support routines + * + * Copyright (C) 2001 Thomas Bogendoerfer <tsbogend at parisc-linux.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 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/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/eisa_eeprom.h> + +#define EISA_EEPROM_MINOR 241 + +static loff_t eisa_eeprom_llseek(struct file *file, loff_t offset, int origin ) +{ + switch (origin) { + case 0: + /* nothing to do */ + break; + case 1: + offset += file->f_pos; + break; + case 2: + offset += HPEE_MAX_LENGTH; + break; + } + return (offset >= 0 && offset < HPEE_MAX_LENGTH) ? (file->f_pos = offset) : -EINVAL; +} + +static ssize_t eisa_eeprom_read(struct file * file, + char *buf, size_t count, loff_t *ppos ) +{ + unsigned char *tmp; + ssize_t ret; + int i; + + if (*ppos >= HPEE_MAX_LENGTH) + return 0; + + count = *ppos + count < HPEE_MAX_LENGTH ? count : HPEE_MAX_LENGTH - *ppos; + tmp = kmalloc(count, GFP_KERNEL); + if (tmp) { + for (i = 0; i < count; i++) + tmp[i] = readb(eisa_eeprom_addr+(*ppos)++); + + if (copy_to_user (buf, tmp, count)) + ret = -EFAULT; + else + ret = count; + kfree (tmp); + } else + ret = -ENOMEM; + + return ret; +} + +static int eisa_eeprom_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, + unsigned long arg) +{ + return -ENOTTY; +} + +static int eisa_eeprom_open(struct inode *inode, struct file *file) +{ + if (file->f_mode & 2) + return -EINVAL; + + return 0; +} + +static int eisa_eeprom_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * The various file operations we support. + */ +static struct file_operations eisa_eeprom_fops = { + .owner = THIS_MODULE, + .llseek = eisa_eeprom_llseek, + .read = eisa_eeprom_read, + .ioctl = eisa_eeprom_ioctl, + .open = eisa_eeprom_open, + .release = eisa_eeprom_release, +}; + +static struct miscdevice eisa_eeprom_dev = { + EISA_EEPROM_MINOR, + "eisa_eeprom", + &eisa_eeprom_fops +}; + +static int __init eisa_eeprom_init(void) +{ + int retval; + + if (!eisa_eeprom_addr) + return -ENODEV; + + retval = misc_register(&eisa_eeprom_dev); + if (retval < 0) { + printk(KERN_ERR "EISA EEPROM: cannot register misc device.\n"); + return retval; + } + + printk(KERN_INFO "EISA EEPROM at 0x%p\n", eisa_eeprom_addr); + return 0; +} + +MODULE_LICENSE("GPL"); + +module_init(eisa_eeprom_init); diff --git a/drivers/parisc/eisa_enumerator.c b/drivers/parisc/eisa_enumerator.c new file mode 100644 index 00000000000..6d8aae003f6 --- /dev/null +++ b/drivers/parisc/eisa_enumerator.c @@ -0,0 +1,521 @@ +/* + * eisa_enumerator.c - provide support for EISA adapters in PA-RISC machines + * + * 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. + * + * Copyright (c) 2002 Daniel Engstrom <5116@telia.com> + * + */ + +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/byteorder.h> + +#include <asm/eisa_bus.h> +#include <asm/eisa_eeprom.h> + + +/* + * Todo: + * + * PORT init with MASK attr and other size than byte + * MEMORY with other decode than 20 bit + * CRC stuff + * FREEFORM stuff + */ + +#define EPI 0xc80 +#define NUM_SLOT 16 +#define SLOT2PORT(x) (x<<12) + + +/* macros to handle unaligned accesses and + * byte swapping. The data in the EEPROM is + * little-endian on the big-endian PAROSC */ +#define get_8(x) (*(u_int8_t*)(x)) + +static inline u_int16_t get_16(const unsigned char *x) +{ + return (x[1] << 8) | x[0]; +} + +static inline u_int32_t get_32(const unsigned char *x) +{ + return (x[3] << 24) | (x[2] << 16) | (x[1] << 8) | x[0]; +} + +static inline u_int32_t get_24(const unsigned char *x) +{ + return (x[2] << 24) | (x[1] << 16) | (x[0] << 8); +} + +static void print_eisa_id(char *s, u_int32_t id) +{ + char vendor[4]; + int rev; + int device; + + rev = id & 0xff; + id >>= 8; + device = id & 0xff; + id >>= 8; + vendor[3] = '\0'; + vendor[2] = '@' + (id & 0x1f); + id >>= 5; + vendor[1] = '@' + (id & 0x1f); + id >>= 5; + vendor[0] = '@' + (id & 0x1f); + id >>= 5; + + sprintf(s, "%s%02X%02X", vendor, device, rev); +} + +static int configure_memory(const unsigned char *buf, + struct resource *mem_parent, + char *name) +{ + int len; + u_int8_t c; + int i; + struct resource *res; + + len=0; + + for (i=0;i<HPEE_MEMORY_MAX_ENT;i++) { + c = get_8(buf+len); + + if (NULL != (res = kmalloc(sizeof(struct resource), GFP_KERNEL))) { + int result; + + res->name = name; + res->start = mem_parent->start + get_24(buf+len+2); + res->end = res->start + get_16(buf+len+5)*1024; + res->flags = IORESOURCE_MEM; + printk("memory %lx-%lx ", res->start, res->end); + result = request_resource(mem_parent, res); + if (result < 0) { + printk("\n" KERN_ERR "EISA Enumerator: failed to claim EISA Bus address space!\n"); + return result; + } + } + + len+=7; + + if (!(c & HPEE_MEMORY_MORE)) { + break; + } + } + + return len; +} + + +static int configure_irq(const unsigned char *buf) +{ + int len; + u_int8_t c; + int i; + + len=0; + + for (i=0;i<HPEE_IRQ_MAX_ENT;i++) { + c = get_8(buf+len); + + printk("IRQ %d ", c & HPEE_IRQ_CHANNEL_MASK); + if (c & HPEE_IRQ_TRIG_LEVEL) { + eisa_make_irq_level(c & HPEE_IRQ_CHANNEL_MASK); + } else { + eisa_make_irq_edge(c & HPEE_IRQ_CHANNEL_MASK); + } + + len+=2; + /* hpux seems to allow for + * two bytes of irq data but only defines one of + * them, I think */ + if (!(c & HPEE_IRQ_MORE)) { + break; + } + } + + return len; +} + + +static int configure_dma(const unsigned char *buf) +{ + int len; + u_int8_t c; + int i; + + len=0; + + for (i=0;i<HPEE_DMA_MAX_ENT;i++) { + c = get_8(buf+len); + printk("DMA %d ", c&HPEE_DMA_CHANNEL_MASK); + /* fixme: maybe initialize the dma channel withthe timing ? */ + len+=2; + if (!(c & HPEE_DMA_MORE)) { + break; + } + } + + return len; +} + +static int configure_port(const unsigned char *buf, struct resource *io_parent, + char *board) +{ + int len; + u_int8_t c; + int i; + struct resource *res; + int result; + + len=0; + + for (i=0;i<HPEE_PORT_MAX_ENT;i++) { + c = get_8(buf+len); + + if (NULL != (res = kmalloc(sizeof(struct resource), GFP_KERNEL))) { + res->name = board; + res->start = get_16(buf+len+1); + res->end = get_16(buf+len+1)+(c&HPEE_PORT_SIZE_MASK)+1; + res->flags = IORESOURCE_IO; + printk("ioports %lx-%lx ", res->start, res->end); + result = request_resource(io_parent, res); + if (result < 0) { + printk("\n" KERN_ERR "EISA Enumerator: failed to claim EISA Bus address space!\n"); + return result; + } + } + + len+=3; + if (!(c & HPEE_PORT_MORE)) { + break; + } + } + + return len; +} + + +/* byte 1 and 2 is the port number to write + * and at byte 3 the value to write starts. + * I assume that there are and- and or- masks + * here when HPEE_PORT_INIT_MASK is set but I have + * not yet encountered this. */ +static int configure_port_init(const unsigned char *buf) +{ + int len=0; + u_int8_t c; + + while (len<HPEE_PORT_INIT_MAX_LEN) { + int s=0; + c = get_8(buf+len); + + switch (c & HPEE_PORT_INIT_WIDTH_MASK) { + case HPEE_PORT_INIT_WIDTH_BYTE: + s=1; + if (c & HPEE_PORT_INIT_MASK) { + printk("\n" KERN_WARNING "port_init: unverified mask attribute\n"); + outb((inb(get_16(buf+len+1) & + get_8(buf+len+3)) | + get_8(buf+len+4)), get_16(buf+len+1)); + + } else { + outb(get_8(buf+len+3), get_16(buf+len+1)); + + } + break; + case HPEE_PORT_INIT_WIDTH_WORD: + s=2; + if (c & HPEE_PORT_INIT_MASK) { + printk(KERN_WARNING "port_init: unverified mask attribute\n"); + outw((inw(get_16(buf+len+1)) & + get_16(buf+len+3)) | + get_16(buf+len+5), + get_16(buf+len+1)); + } else { + outw(cpu_to_le16(get_16(buf+len+3)), get_16(buf+len+1)); + } + break; + case HPEE_PORT_INIT_WIDTH_DWORD: + s=4; + if (c & HPEE_PORT_INIT_MASK) { + printk("\n" KERN_WARNING "port_init: unverified mask attribute\n"); + outl((inl(get_16(buf+len+1) & + get_32(buf+len+3)) | + get_32(buf+len+7)), get_16(buf+len+1)); + } else { + outl(cpu_to_le32(get_32(buf+len+3)), get_16(buf+len+1)); + } + + break; + default: + printk("\n" KERN_ERR "Invalid port init word %02x\n", c); + return 0; + } + + if (c & HPEE_PORT_INIT_MASK) { + s*=2; + } + + len+=s+3; + if (!(c & HPEE_PORT_INIT_MORE)) { + break; + } + } + + return len; +} + +static int configure_choise(const unsigned char *buf, u_int8_t *info) +{ + int len; + + /* theis record contain the value of the functions + * configuration choises and an info byte which + * describes which other records to expect in this + * function */ + len = get_8(buf); + *info=get_8(buf+len+1); + + return len+2; +} + +static int configure_type_string(const unsigned char *buf) +{ + int len; + + /* just skip past the type field */ + len = get_8(buf); + if (len > 80) { + printk("\n" KERN_ERR "eisa_enumerator: type info field too long (%d, max is 80)\n", len); + } + + return 1+len; +} + +static int configure_function(const unsigned char *buf, int *more) +{ + /* the init field seems to be a two-byte field + * which is non-zero if there are an other function following + * I think it is the length of the function def + */ + *more = get_16(buf); + + return 2; +} + +static int parse_slot_config(int slot, + const unsigned char *buf, + struct eeprom_eisa_slot_info *es, + struct resource *io_parent, + struct resource *mem_parent) +{ + int res=0; + int function_len; + unsigned int pos=0; + unsigned int maxlen; + int num_func=0; + u_int8_t flags; + int p0; + + char *board; + int id_string_used=0; + + if (NULL == (board = kmalloc(8, GFP_KERNEL))) { + return -1; + } + print_eisa_id(board, es->eisa_slot_id); + printk(KERN_INFO "EISA slot %d: %s %s ", + slot, board, es->flags&HPEE_FLAG_BOARD_IS_ISA ? "ISA" : "EISA"); + + maxlen = es->config_data_length < HPEE_MAX_LENGTH ? + es->config_data_length : HPEE_MAX_LENGTH; + while ((pos < maxlen) && (num_func <= es->num_functions)) { + pos+=configure_function(buf+pos, &function_len); + + if (!function_len) { + break; + } + num_func++; + p0 = pos; + pos += configure_choise(buf+pos, &flags); + + if (flags & HPEE_FUNCTION_INFO_F_DISABLED) { + /* function disabled, skip silently */ + pos = p0 + function_len; + continue; + } + if (flags & HPEE_FUNCTION_INFO_CFG_FREE_FORM) { + /* I have no idea how to handle this */ + printk("function %d have free-form confgiuration, skipping ", + num_func); + pos = p0 + function_len; + continue; + } + + /* the ordering of the sections need + * more investigation. + * Currently I think that memory comaed before IRQ + * I assume the order is LSB to MSB in the + * info flags + * eg type, memory, irq, dma, port, HPEE_PORT_init + */ + + if (flags & HPEE_FUNCTION_INFO_HAVE_TYPE) { + pos += configure_type_string(buf+pos); + } + + if (flags & HPEE_FUNCTION_INFO_HAVE_MEMORY) { + id_string_used=1; + pos += configure_memory(buf+pos, mem_parent, board); + } + + if (flags & HPEE_FUNCTION_INFO_HAVE_IRQ) { + pos += configure_irq(buf+pos); + } + + if (flags & HPEE_FUNCTION_INFO_HAVE_DMA) { + pos += configure_dma(buf+pos); + } + + if (flags & HPEE_FUNCTION_INFO_HAVE_PORT) { + id_string_used=1; + pos += configure_port(buf+pos, io_parent, board); + } + + if (flags & HPEE_FUNCTION_INFO_HAVE_PORT_INIT) { + pos += configure_port_init(buf+pos); + } + + if (p0 + function_len < pos) { + printk("\n" KERN_ERR "eisa_enumerator: function %d length mis-match " + "got %d, expected %d\n", + num_func, pos-p0, function_len); + res=-1; + break; + } + pos = p0 + function_len; + } + printk("\n"); + if (!id_string_used) { + kfree(board); + } + + if (pos != es->config_data_length) { + printk(KERN_ERR "eisa_enumerator: config data length mis-match got %d, expected %d\n", + pos, es->config_data_length); + res=-1; + } + + if (num_func != es->num_functions) { + printk(KERN_ERR "eisa_enumerator: number of functions mis-match got %d, expected %d\n", + num_func, es->num_functions); + res=-2; + } + + return res; + +} + +static int init_slot(int slot, struct eeprom_eisa_slot_info *es) +{ + unsigned int id; + + char id_string[8]; + + if (!(es->slot_info&HPEE_SLOT_INFO_NO_READID)) { + /* try to read the id of the board in the slot */ + id = le32_to_cpu(inl(SLOT2PORT(slot)+EPI)); + + if (0xffffffff == id) { + /* Maybe we didn't expect a card to be here... */ + if (es->eisa_slot_id == 0xffffffff) + return -1; + + /* this board is not here or it does not + * support readid + */ + printk(KERN_ERR "EISA slot %d a configured board was not detected (", + slot); + + print_eisa_id(id_string, es->eisa_slot_id); + printk(" expected %s)\n", id_string); + + return -1; + + } + if (es->eisa_slot_id != id) { + print_eisa_id(id_string, id); + printk(KERN_ERR "EISA slot %d id mis-match: got %s", + slot, id_string); + + print_eisa_id(id_string, es->eisa_slot_id); + printk(" expected %s \n", id_string); + + return -1; + + } + } + + /* now: we need to enable the board if + * it supports enabling and run through + * the port init sction if present + * and finally record any interrupt polarity + */ + if (es->slot_features & HPEE_SLOT_FEATURES_ENABLE) { + /* enable board */ + outb(0x01| inb(SLOT2PORT(slot)+EPI+4), + SLOT2PORT(slot)+EPI+4); + } + + return 0; +} + + +int eisa_enumerator(unsigned long eeprom_addr, + struct resource *io_parent, struct resource *mem_parent) +{ + int i; + struct eeprom_header *eh; + static char eeprom_buf[HPEE_MAX_LENGTH]; + + for (i=0; i < HPEE_MAX_LENGTH; i++) { + eeprom_buf[i] = gsc_readb(eeprom_addr+i); + } + + printk(KERN_INFO "Enumerating EISA bus\n"); + + eh = (struct eeprom_header*)(eeprom_buf); + for (i=0;i<eh->num_slots;i++) { + struct eeprom_eisa_slot_info *es; + + es = (struct eeprom_eisa_slot_info*) + (&eeprom_buf[HPEE_SLOT_INFO(i)]); + + if (-1==init_slot(i+1, es)) { + continue; + } + + if (es->config_data_offset < HPEE_MAX_LENGTH) { + if (parse_slot_config(i+1, &eeprom_buf[es->config_data_offset], + es, io_parent, mem_parent)) { + return -1; + } + } else { + printk (KERN_WARNING "EISA EEPROM offset 0x%x out of range\n",es->config_data_offset); + return -1; + } + } + return eh->num_slots; +} + diff --git a/drivers/parisc/gsc.c b/drivers/parisc/gsc.c new file mode 100644 index 00000000000..af5e02526a1 --- /dev/null +++ b/drivers/parisc/gsc.c @@ -0,0 +1,245 @@ +/* + * Interrupt management for most GSC and related devices. + * + * (c) Copyright 1999 Alex deVries for The Puffin Group + * (c) Copyright 1999 Grant Grundler for Hewlett-Packard + * (c) Copyright 1999 Matthew Wilcox + * (c) Copyright 2000 Helge Deller + * (c) Copyright 2001 Matthew Wilcox for Hewlett-Packard + * + * 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/bitops.h> +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <asm/hardware.h> +#include <asm/io.h> + +#include "gsc.h" + +#undef DEBUG + +#ifdef DEBUG +#define DEBPRINTK printk +#else +#define DEBPRINTK(x,...) +#endif + +int gsc_alloc_irq(struct gsc_irq *i) +{ + int irq = txn_alloc_irq(GSC_EIM_WIDTH); + if (irq < 0) { + printk("cannot get irq\n"); + return irq; + } + + i->txn_addr = txn_alloc_addr(irq); + i->txn_data = txn_alloc_data(irq); + i->irq = irq; + + return irq; +} + +int gsc_claim_irq(struct gsc_irq *i, int irq) +{ + int c = irq; + + irq += CPU_IRQ_BASE; /* virtualize the IRQ first */ + + irq = txn_claim_irq(irq); + if (irq < 0) { + printk("cannot claim irq %d\n", c); + return irq; + } + + i->txn_addr = txn_alloc_addr(irq); + i->txn_data = txn_alloc_data(irq); + i->irq = irq; + + return irq; +} + +EXPORT_SYMBOL(gsc_alloc_irq); +EXPORT_SYMBOL(gsc_claim_irq); + +/* Common interrupt demultiplexer used by Asp, Lasi & Wax. */ +irqreturn_t gsc_asic_intr(int gsc_asic_irq, void *dev, struct pt_regs *regs) +{ + unsigned long irr; + struct gsc_asic *gsc_asic = dev; + + irr = gsc_readl(gsc_asic->hpa + OFFSET_IRR); + if (irr == 0) + return IRQ_NONE; + + DEBPRINTK("%s intr, mask=0x%x\n", gsc_asic->name, irr); + + do { + int local_irq = __ffs(irr); + unsigned int irq = gsc_asic->global_irq[local_irq]; + __do_IRQ(irq, regs); + irr &= ~(1 << local_irq); + } while (irr); + + return IRQ_HANDLED; +} + +int gsc_find_local_irq(unsigned int irq, int *global_irqs, int limit) +{ + int local_irq; + + for (local_irq = 0; local_irq < limit; local_irq++) { + if (global_irqs[local_irq] == irq) + return local_irq; + } + + return NO_IRQ; +} + +static void gsc_asic_disable_irq(unsigned int irq) +{ + struct gsc_asic *irq_dev = irq_desc[irq].handler_data; + int local_irq = gsc_find_local_irq(irq, irq_dev->global_irq, 32); + u32 imr; + + DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __FUNCTION__, irq, + irq_dev->name, imr); + + /* Disable the IRQ line by clearing the bit in the IMR */ + imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); + imr &= ~(1 << local_irq); + gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); +} + +static void gsc_asic_enable_irq(unsigned int irq) +{ + struct gsc_asic *irq_dev = irq_desc[irq].handler_data; + int local_irq = gsc_find_local_irq(irq, irq_dev->global_irq, 32); + u32 imr; + + DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __FUNCTION__, irq, + irq_dev->name, imr); + + /* Enable the IRQ line by setting the bit in the IMR */ + imr = gsc_readl(irq_dev->hpa + OFFSET_IMR); + imr |= 1 << local_irq; + gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); + /* + * FIXME: read IPR to make sure the IRQ isn't already pending. + * If so, we need to read IRR and manually call do_irq(). + */ +} + +static unsigned int gsc_asic_startup_irq(unsigned int irq) +{ + gsc_asic_enable_irq(irq); + return 0; +} + +static struct hw_interrupt_type gsc_asic_interrupt_type = { + .typename = "GSC-ASIC", + .startup = gsc_asic_startup_irq, + .shutdown = gsc_asic_disable_irq, + .enable = gsc_asic_enable_irq, + .disable = gsc_asic_disable_irq, + .ack = no_ack_irq, + .end = no_end_irq, +}; + +int gsc_assign_irq(struct hw_interrupt_type *type, void *data) +{ + static int irq = GSC_IRQ_BASE; + + if (irq > GSC_IRQ_MAX) + return NO_IRQ; + + irq_desc[irq].handler = type; + irq_desc[irq].handler_data = data; + return irq++; +} + +void gsc_asic_assign_irq(struct gsc_asic *asic, int local_irq, int *irqp) +{ + int irq = asic->global_irq[local_irq]; + + if (irq <= 0) { + irq = gsc_assign_irq(&gsc_asic_interrupt_type, asic); + if (irq == NO_IRQ) + return; + + asic->global_irq[local_irq] = irq; + } + *irqp = irq; +} + +void gsc_fixup_irqs(struct parisc_device *parent, void *ctrl, + void (*choose_irq)(struct parisc_device *, void *)) +{ + struct device *dev; + + list_for_each_entry(dev, &parent->dev.children, node) { + struct parisc_device *padev = to_parisc_device(dev); + + /* work-around for 715/64 and others which have parent + at path [5] and children at path [5/0/x] */ + if (padev->id.hw_type == HPHW_FAULTY) + return gsc_fixup_irqs(padev, ctrl, choose_irq); + choose_irq(padev, ctrl); + } +} + +int gsc_common_setup(struct parisc_device *parent, struct gsc_asic *gsc_asic) +{ + struct resource *res; + int i; + + gsc_asic->gsc = parent; + + /* Initialise local irq -> global irq mapping */ + for (i = 0; i < 32; i++) { + gsc_asic->global_irq[i] = NO_IRQ; + } + + /* allocate resource region */ + res = request_mem_region(gsc_asic->hpa, 0x100000, gsc_asic->name); + if (res) { + res->flags = IORESOURCE_MEM; /* do not mark it busy ! */ + } + +#if 0 + printk(KERN_WARNING "%s IRQ %d EIM 0x%x", gsc_asic->name, + parent->irq, gsc_asic->eim); + if (gsc_readl(gsc_asic->hpa + OFFSET_IMR)) + printk(" IMR is non-zero! (0x%x)", + gsc_readl(gsc_asic->hpa + OFFSET_IMR)); + printk("\n"); +#endif + + return 0; +} + +extern struct parisc_driver lasi_driver; +extern struct parisc_driver asp_driver; +extern struct parisc_driver wax_driver; + +void __init gsc_init(void) +{ +#ifdef CONFIG_GSC_LASI + register_parisc_driver(&lasi_driver); + register_parisc_driver(&asp_driver); +#endif +#ifdef CONFIG_GSC_WAX + register_parisc_driver(&wax_driver); +#endif +} diff --git a/drivers/parisc/gsc.h b/drivers/parisc/gsc.h new file mode 100644 index 00000000000..a3dc456709d --- /dev/null +++ b/drivers/parisc/gsc.h @@ -0,0 +1,47 @@ +/* + * drivers/parisc/gsc.h + * Declarations for functions in gsc.c + * Copyright (c) 2000-2002 Helge Deller, Matthew Wilcox + * + * Distributed under the terms of the GPL, version 2 + */ + +#include <linux/interrupt.h> +#include <asm/hardware.h> +#include <asm/parisc-device.h> + +#define OFFSET_IRR 0x0000 /* Interrupt request register */ +#define OFFSET_IMR 0x0004 /* Interrupt mask register */ +#define OFFSET_IPR 0x0008 /* Interrupt pending register */ +#define OFFSET_ICR 0x000C /* Interrupt control register */ +#define OFFSET_IAR 0x0010 /* Interrupt address register */ + +/* PA I/O Architected devices support at least 5 bits in the EIM register. */ +#define GSC_EIM_WIDTH 5 + +struct gsc_irq { + unsigned long txn_addr; /* IRQ "target" */ + int txn_data; /* HW "IRQ" */ + int irq; /* virtual IRQ */ +}; + +struct gsc_asic { + struct parisc_device *gsc; + unsigned long hpa; + char *name; + int version; + int type; + int eim; + int global_irq[32]; +}; + +int gsc_common_setup(struct parisc_device *parent, struct gsc_asic *gsc_asic); +int gsc_alloc_irq(struct gsc_irq *dev); /* dev needs an irq */ +int gsc_claim_irq(struct gsc_irq *dev, int irq); /* dev needs this irq */ +int gsc_assign_irq(struct hw_interrupt_type *type, void *data); +int gsc_find_local_irq(unsigned int irq, int *global_irq, int limit); +void gsc_fixup_irqs(struct parisc_device *parent, void *ctrl, + void (*choose)(struct parisc_device *child, void *ctrl)); +void gsc_asic_assign_irq(struct gsc_asic *asic, int local_irq, int *irqp); + +irqreturn_t gsc_asic_intr(int irq, void *dev, struct pt_regs *regs); diff --git a/drivers/parisc/hppb.c b/drivers/parisc/hppb.c new file mode 100644 index 00000000000..e869c602037 --- /dev/null +++ b/drivers/parisc/hppb.c @@ -0,0 +1,109 @@ +/* +** hppb.c: +** HP-PB bus driver for the NOVA and K-Class systems. +** +** (c) Copyright 2002 Ryan Bradetich +** (c) Copyright 2002 Hewlett-Packard Company +** +** 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 currently only supports the console (port 0) on the MUX. +** Additional work will be needed on this driver to enable the full +** functionality of the MUX. +** +*/ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/ioport.h> + +#include <asm/io.h> +#include <asm/hardware.h> +#include <asm/parisc-device.h> + +#include <linux/pci.h> + +struct hppb_card { + unsigned long hpa; + struct resource mmio_region; + struct hppb_card *next; +}; + +struct hppb_card hppb_card_head = { + .hpa = 0, + .next = NULL, +}; + +#define IO_IO_LOW offsetof(struct bc_module, io_io_low) +#define IO_IO_HIGH offsetof(struct bc_module, io_io_high) + +/** + * hppb_probe - Determine if the hppb driver should claim this device. + * @dev: The device which has been found + * + * Determine if hppb driver should claim this chip (return 0) or not + * (return 1). If so, initialize the chip and tell other partners in crime + * they have work to do. + */ +static int hppb_probe(struct parisc_device *dev) +{ + int status; + struct hppb_card *card = &hppb_card_head; + + while(card->next) { + card = card->next; + } + + if(card->hpa) { + card->next = kmalloc(sizeof(struct hppb_card), GFP_KERNEL); + if(!card->next) { + printk(KERN_ERR "HP-PB: Unable to allocate memory.\n"); + return 1; + } + memset(card->next, '\0', sizeof(struct hppb_card)); + card = card->next; + } + printk(KERN_INFO "Found GeckoBoa at 0x%lx\n", dev->hpa); + + card->hpa = dev->hpa; + card->mmio_region.name = "HP-PB Bus"; + card->mmio_region.flags = IORESOURCE_MEM; + + card->mmio_region.start = __raw_readl(dev->hpa + IO_IO_LOW); + card->mmio_region.end = __raw_readl(dev->hpa + IO_IO_HIGH) - 1; + + status = ccio_request_resource(dev, &card->mmio_region); + if(status < 0) { + printk(KERN_ERR "%s: failed to claim HP-PB bus space (%08lx, %08lx)\n", + __FILE__, card->mmio_region.start, card->mmio_region.end); + } + + return 0; +} + + +static struct parisc_device_id hppb_tbl[] = { + { HPHW_BCPORT, HVERSION_REV_ANY_ID, 0x500, 0xc }, + { 0, } +}; + +static struct parisc_driver hppb_driver = { + .name = "Gecko Boa", + .id_table = hppb_tbl, + .probe = hppb_probe, +}; + +/** + * hppb_init - HP-PB bus initalization procedure. + * + * Register this driver. + */ +void __init hppb_init(void) +{ + register_parisc_driver(&hppb_driver); +} diff --git a/drivers/parisc/iommu-helpers.h b/drivers/parisc/iommu-helpers.h new file mode 100644 index 00000000000..38d9e1aba1d --- /dev/null +++ b/drivers/parisc/iommu-helpers.h @@ -0,0 +1,171 @@ +/** + * iommu_fill_pdir - Insert coalesced scatter/gather chunks into the I/O Pdir. + * @ioc: The I/O Controller. + * @startsg: The scatter/gather list of coalesced chunks. + * @nents: The number of entries in the scatter/gather list. + * @hint: The DMA Hint. + * + * This function inserts the coalesced scatter/gather list chunks into the + * I/O Controller's I/O Pdir. + */ +static inline unsigned int +iommu_fill_pdir(struct ioc *ioc, struct scatterlist *startsg, int nents, + unsigned long hint, + void (*iommu_io_pdir_entry)(u64 *, space_t, unsigned long, + unsigned long)) +{ + struct scatterlist *dma_sg = startsg; /* pointer to current DMA */ + unsigned int n_mappings = 0; + unsigned long dma_offset = 0, dma_len = 0; + u64 *pdirp = NULL; + + /* Horrible hack. For efficiency's sake, dma_sg starts one + * entry below the true start (it is immediately incremented + * in the loop) */ + dma_sg--; + + while (nents-- > 0) { + unsigned long vaddr; + long size; + + DBG_RUN_SG(" %d : %08lx/%05x %08lx/%05x\n", nents, + (unsigned long)sg_dma_address(startsg), cnt, + sg_virt_addr(startsg), startsg->length + ); + + + /* + ** Look for the start of a new DMA stream + */ + + if (sg_dma_address(startsg) & PIDE_FLAG) { + u32 pide = sg_dma_address(startsg) & ~PIDE_FLAG; + + BUG_ON(pdirp && (dma_len != sg_dma_len(dma_sg))); + + dma_sg++; + + dma_len = sg_dma_len(startsg); + sg_dma_len(startsg) = 0; + dma_offset = (unsigned long) pide & ~IOVP_MASK; + n_mappings++; +#if defined(ZX1_SUPPORT) + /* Pluto IOMMU IO Virt Address is not zero based */ + sg_dma_address(dma_sg) = pide | ioc->ibase; +#else + /* SBA, ccio, and dino are zero based. + * Trying to save a few CPU cycles for most users. + */ + sg_dma_address(dma_sg) = pide; +#endif + pdirp = &(ioc->pdir_base[pide >> IOVP_SHIFT]); + prefetchw(pdirp); + } + + BUG_ON(pdirp == NULL); + + vaddr = sg_virt_addr(startsg); + sg_dma_len(dma_sg) += startsg->length; + size = startsg->length + dma_offset; + dma_offset = 0; +#ifdef IOMMU_MAP_STATS + ioc->msg_pages += startsg->length >> IOVP_SHIFT; +#endif + do { + iommu_io_pdir_entry(pdirp, KERNEL_SPACE, + vaddr, hint); + vaddr += IOVP_SIZE; + size -= IOVP_SIZE; + pdirp++; + } while(unlikely(size > 0)); + startsg++; + } + return(n_mappings); +} + + +/* +** First pass is to walk the SG list and determine where the breaks are +** in the DMA stream. Allocates PDIR entries but does not fill them. +** Returns the number of DMA chunks. +** +** Doing the fill separate from the coalescing/allocation keeps the +** code simpler. Future enhancement could make one pass through +** the sglist do both. +*/ + +static inline unsigned int +iommu_coalesce_chunks(struct ioc *ioc, struct scatterlist *startsg, int nents, + int (*iommu_alloc_range)(struct ioc *, size_t)) +{ + struct scatterlist *contig_sg; /* contig chunk head */ + unsigned long dma_offset, dma_len; /* start/len of DMA stream */ + unsigned int n_mappings = 0; + + while (nents > 0) { + + /* + ** Prepare for first/next DMA stream + */ + contig_sg = startsg; + dma_len = startsg->length; + dma_offset = sg_virt_addr(startsg) & ~IOVP_MASK; + + /* PARANOID: clear entries */ + sg_dma_address(startsg) = 0; + sg_dma_len(startsg) = 0; + + /* + ** This loop terminates one iteration "early" since + ** it's always looking one "ahead". + */ + while(--nents > 0) { + unsigned long prevstartsg_end, startsg_end; + + prevstartsg_end = sg_virt_addr(startsg) + + startsg->length; + + startsg++; + startsg_end = sg_virt_addr(startsg) + + startsg->length; + + /* PARANOID: clear entries */ + sg_dma_address(startsg) = 0; + sg_dma_len(startsg) = 0; + + /* + ** First make sure current dma stream won't + ** exceed DMA_CHUNK_SIZE if we coalesce the + ** next entry. + */ + if(unlikely(ROUNDUP(dma_len + dma_offset + startsg->length, + IOVP_SIZE) > DMA_CHUNK_SIZE)) + break; + + /* + ** Next see if we can append the next chunk (i.e. + ** it must end on one page and begin on another + */ + if (unlikely(((prevstartsg_end | sg_virt_addr(startsg)) & ~PAGE_MASK) != 0)) + break; + + dma_len += startsg->length; + } + + /* + ** End of DMA Stream + ** Terminate last VCONTIG block. + ** Allocate space for DMA stream. + */ + sg_dma_len(contig_sg) = dma_len; + dma_len = ROUNDUP(dma_len + dma_offset, IOVP_SIZE); + sg_dma_address(contig_sg) = + PIDE_FLAG + | (iommu_alloc_range(ioc, dma_len) << IOVP_SHIFT) + | dma_offset; + n_mappings++; + } + + return n_mappings; +} + diff --git a/drivers/parisc/iosapic.c b/drivers/parisc/iosapic.c new file mode 100644 index 00000000000..91df0bf181d --- /dev/null +++ b/drivers/parisc/iosapic.c @@ -0,0 +1,921 @@ +/* +** I/O Sapic Driver - PCI interrupt line support +** +** (c) Copyright 1999 Grant Grundler +** (c) Copyright 1999 Hewlett-Packard Company +** +** 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. +** +** The I/O sapic driver manages the Interrupt Redirection Table which is +** the control logic to convert PCI line based interrupts into a Message +** Signaled Interrupt (aka Transaction Based Interrupt, TBI). +** +** Acronyms +** -------- +** HPA Hard Physical Address (aka MMIO address) +** IRQ Interrupt ReQuest. Implies Line based interrupt. +** IRT Interrupt Routing Table (provided by PAT firmware) +** IRdT Interrupt Redirection Table. IRQ line to TXN ADDR/DATA +** table which is implemented in I/O SAPIC. +** ISR Interrupt Service Routine. aka Interrupt handler. +** MSI Message Signaled Interrupt. PCI 2.2 functionality. +** aka Transaction Based Interrupt (or TBI). +** PA Precision Architecture. HP's RISC architecture. +** RISC Reduced Instruction Set Computer. +** +** +** What's a Message Signalled Interrupt? +** ------------------------------------- +** MSI is a write transaction which targets a processor and is similar +** to a processor write to memory or MMIO. MSIs can be generated by I/O +** devices as well as processors and require *architecture* to work. +** +** PA only supports MSI. So I/O subsystems must either natively generate +** MSIs (e.g. GSC or HP-PB) or convert line based interrupts into MSIs +** (e.g. PCI and EISA). IA64 supports MSIs via a "local SAPIC" which +** acts on behalf of a processor. +** +** MSI allows any I/O device to interrupt any processor. This makes +** load balancing of the interrupt processing possible on an SMP platform. +** Interrupts are also ordered WRT to DMA data. It's possible on I/O +** coherent systems to completely eliminate PIO reads from the interrupt +** path. The device and driver must be designed and implemented to +** guarantee all DMA has been issued (issues about atomicity here) +** before the MSI is issued. I/O status can then safely be read from +** DMA'd data by the ISR. +** +** +** PA Firmware +** ----------- +** PA-RISC platforms have two fundementally different types of firmware. +** For PCI devices, "Legacy" PDC initializes the "INTERRUPT_LINE" register +** and BARs similar to a traditional PC BIOS. +** The newer "PAT" firmware supports PDC calls which return tables. +** PAT firmware only initializes PCI Console and Boot interface. +** With these tables, the OS can progam all other PCI devices. +** +** One such PAT PDC call returns the "Interrupt Routing Table" (IRT). +** The IRT maps each PCI slot's INTA-D "output" line to an I/O SAPIC +** input line. If the IRT is not available, this driver assumes +** INTERRUPT_LINE register has been programmed by firmware. The latter +** case also means online addition of PCI cards can NOT be supported +** even if HW support is present. +** +** All platforms with PAT firmware to date (Oct 1999) use one Interrupt +** Routing Table for the entire platform. +** +** Where's the iosapic? +** -------------------- +** I/O sapic is part of the "Core Electronics Complex". And on HP platforms +** it's integrated as part of the PCI bus adapter, "lba". So no bus walk +** will discover I/O Sapic. I/O Sapic driver learns about each device +** when lba driver advertises the presence of the I/O sapic by calling +** iosapic_register(). +** +** +** IRQ handling notes +** ------------------ +** The IO-SAPIC can indicate to the CPU which interrupt was asserted. +** So, unlike the GSC-ASIC and Dino, we allocate one CPU interrupt per +** IO-SAPIC interrupt and call the device driver's handler directly. +** The IO-SAPIC driver hijacks the CPU interrupt handler so it can +** issue the End Of Interrupt command to the IO-SAPIC. +** +** Overview of exported iosapic functions +** -------------------------------------- +** (caveat: code isn't finished yet - this is just the plan) +** +** iosapic_init: +** o initialize globals (lock, etc) +** o try to read IRT. Presence of IRT determines if this is +** a PAT platform or not. +** +** iosapic_register(): +** o create iosapic_info instance data structure +** o allocate vector_info array for this iosapic +** o initialize vector_info - read corresponding IRdT? +** +** iosapic_xlate_pin: (only called by fixup_irq for PAT platform) +** o intr_pin = read cfg (INTERRUPT_PIN); +** o if (device under PCI-PCI bridge) +** translate slot/pin +** +** iosapic_fixup_irq: +** o if PAT platform (IRT present) +** intr_pin = iosapic_xlate_pin(isi,pcidev): +** intr_line = find IRT entry(isi, PCI_SLOT(pcidev), intr_pin) +** save IRT entry into vector_info later +** write cfg INTERRUPT_LINE (with intr_line)? +** else +** intr_line = pcidev->irq +** IRT pointer = NULL +** endif +** o locate vector_info (needs: isi, intr_line) +** o allocate processor "irq" and get txn_addr/data +** o request_irq(processor_irq, iosapic_interrupt, vector_info,...) +** +** iosapic_enable_irq: +** o clear any pending IRQ on that line +** o enable IRdT - call enable_irq(vector[line]->processor_irq) +** o write EOI in case line is already asserted. +** +** iosapic_disable_irq: +** o disable IRdT - call disable_irq(vector[line]->processor_irq) +*/ + + +/* FIXME: determine which include files are really needed */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> + +#include <asm/byteorder.h> /* get in-line asm for swab */ +#include <asm/pdc.h> +#include <asm/pdcpat.h> +#include <asm/page.h> +#include <asm/system.h> +#include <asm/io.h> /* read/write functions */ +#ifdef CONFIG_SUPERIO +#include <asm/superio.h> +#endif + +#include <asm/iosapic.h> +#include "./iosapic_private.h" + +#define MODULE_NAME "iosapic" + +/* "local" compile flags */ +#undef PCI_BRIDGE_FUNCS +#undef DEBUG_IOSAPIC +#undef DEBUG_IOSAPIC_IRT + + +#ifdef DEBUG_IOSAPIC +#define DBG(x...) printk(x) +#else /* DEBUG_IOSAPIC */ +#define DBG(x...) +#endif /* DEBUG_IOSAPIC */ + +#ifdef DEBUG_IOSAPIC_IRT +#define DBG_IRT(x...) printk(x) +#else +#define DBG_IRT(x...) +#endif + +#ifdef CONFIG_64BIT +#define COMPARE_IRTE_ADDR(irte, hpa) ((irte)->dest_iosapic_addr == (hpa)) +#else +#define COMPARE_IRTE_ADDR(irte, hpa) \ + ((irte)->dest_iosapic_addr == ((hpa) | 0xffffffff00000000ULL)) +#endif + +#define IOSAPIC_REG_SELECT 0x00 +#define IOSAPIC_REG_WINDOW 0x10 +#define IOSAPIC_REG_EOI 0x40 + +#define IOSAPIC_REG_VERSION 0x1 + +#define IOSAPIC_IRDT_ENTRY(idx) (0x10+(idx)*2) +#define IOSAPIC_IRDT_ENTRY_HI(idx) (0x11+(idx)*2) + +static inline unsigned int iosapic_read(void __iomem *iosapic, unsigned int reg) +{ + writel(reg, iosapic + IOSAPIC_REG_SELECT); + return readl(iosapic + IOSAPIC_REG_WINDOW); +} + +static inline void iosapic_write(void __iomem *iosapic, unsigned int reg, u32 val) +{ + writel(reg, iosapic + IOSAPIC_REG_SELECT); + writel(val, iosapic + IOSAPIC_REG_WINDOW); +} + +#define IOSAPIC_VERSION_MASK 0x000000ff +#define IOSAPIC_VERSION(ver) ((int) (ver & IOSAPIC_VERSION_MASK)) + +#define IOSAPIC_MAX_ENTRY_MASK 0x00ff0000 +#define IOSAPIC_MAX_ENTRY_SHIFT 0x10 +#define IOSAPIC_IRDT_MAX_ENTRY(ver) \ + (int) (((ver) & IOSAPIC_MAX_ENTRY_MASK) >> IOSAPIC_MAX_ENTRY_SHIFT) + +/* bits in the "low" I/O Sapic IRdT entry */ +#define IOSAPIC_IRDT_ENABLE 0x10000 +#define IOSAPIC_IRDT_PO_LOW 0x02000 +#define IOSAPIC_IRDT_LEVEL_TRIG 0x08000 +#define IOSAPIC_IRDT_MODE_LPRI 0x00100 + +/* bits in the "high" I/O Sapic IRdT entry */ +#define IOSAPIC_IRDT_ID_EID_SHIFT 0x10 + + +static spinlock_t iosapic_lock = SPIN_LOCK_UNLOCKED; + +static inline void iosapic_eoi(void __iomem *addr, unsigned int data) +{ + __raw_writel(data, addr); +} + +/* +** REVISIT: future platforms may have more than one IRT. +** If so, the following three fields form a structure which +** then be linked into a list. Names are chosen to make searching +** for them easy - not necessarily accurate (eg "cell"). +** +** Alternative: iosapic_info could point to the IRT it's in. +** iosapic_register() could search a list of IRT's. +*/ +static struct irt_entry *irt_cell; +static size_t irt_num_entry; + +static struct irt_entry *iosapic_alloc_irt(int num_entries) +{ + unsigned long a; + + /* The IRT needs to be 8-byte aligned for the PDC call. + * Normally kmalloc would guarantee larger alignment, but + * if CONFIG_DEBUG_SLAB is enabled, then we can get only + * 4-byte alignment on 32-bit kernels + */ + a = (unsigned long)kmalloc(sizeof(struct irt_entry) * num_entries + 8, GFP_KERNEL); + a = (a + 7) & ~7; + return (struct irt_entry *)a; +} + +/** + * iosapic_load_irt - Fill in the interrupt routing table + * @cell_num: The cell number of the CPU we're currently executing on + * @irt: The address to place the new IRT at + * @return The number of entries found + * + * The "Get PCI INT Routing Table Size" option returns the number of + * entries in the PCI interrupt routing table for the cell specified + * in the cell_number argument. The cell number must be for a cell + * within the caller's protection domain. + * + * The "Get PCI INT Routing Table" option returns, for the cell + * specified in the cell_number argument, the PCI interrupt routing + * table in the caller allocated memory pointed to by mem_addr. + * We assume the IRT only contains entries for I/O SAPIC and + * calculate the size based on the size of I/O sapic entries. + * + * The PCI interrupt routing table entry format is derived from the + * IA64 SAL Specification 2.4. The PCI interrupt routing table defines + * the routing of PCI interrupt signals between the PCI device output + * "pins" and the IO SAPICs' input "lines" (including core I/O PCI + * devices). This table does NOT include information for devices/slots + * behind PCI to PCI bridges. See PCI to PCI Bridge Architecture Spec. + * for the architected method of routing of IRQ's behind PPB's. + */ + + +static int __init +iosapic_load_irt(unsigned long cell_num, struct irt_entry **irt) +{ + long status; /* PDC return value status */ + struct irt_entry *table; /* start of interrupt routing tbl */ + unsigned long num_entries = 0UL; + + BUG_ON(!irt); + + if (is_pdc_pat()) { + /* Use pat pdc routine to get interrupt routing table size */ + DBG("calling get_irt_size (cell %ld)\n", cell_num); + status = pdc_pat_get_irt_size(&num_entries, cell_num); + DBG("get_irt_size: %ld\n", status); + + BUG_ON(status != PDC_OK); + BUG_ON(num_entries == 0); + + /* + ** allocate memory for interrupt routing table + ** This interface isn't really right. We are assuming + ** the contents of the table are exclusively + ** for I/O sapic devices. + */ + table = iosapic_alloc_irt(num_entries); + if (table == NULL) { + printk(KERN_WARNING MODULE_NAME ": read_irt : can " + "not alloc mem for IRT\n"); + return 0; + } + + /* get PCI INT routing table */ + status = pdc_pat_get_irt(table, cell_num); + DBG("pdc_pat_get_irt: %ld\n", status); + WARN_ON(status != PDC_OK); + } else { + /* + ** C3000/J5000 (and similar) platforms with Sprockets PDC + ** will return exactly one IRT for all iosapics. + ** So if we have one, don't need to get it again. + */ + if (irt_cell) + return 0; + + /* Should be using the Elroy's HPA, but it's ignored anyway */ + status = pdc_pci_irt_size(&num_entries, 0); + DBG("pdc_pci_irt_size: %ld\n", status); + + if (status != PDC_OK) { + /* Not a "legacy" system with I/O SAPIC either */ + return 0; + } + + BUG_ON(num_entries == 0); + + table = iosapic_alloc_irt(num_entries); + if (!table) { + printk(KERN_WARNING MODULE_NAME ": read_irt : can " + "not alloc mem for IRT\n"); + return 0; + } + + /* HPA ignored by this call too. */ + status = pdc_pci_irt(num_entries, 0, table); + BUG_ON(status != PDC_OK); + } + + /* return interrupt table address */ + *irt = table; + +#ifdef DEBUG_IOSAPIC_IRT +{ + struct irt_entry *p = table; + int i; + + printk(MODULE_NAME " Interrupt Routing Table (cell %ld)\n", cell_num); + printk(MODULE_NAME " start = 0x%p num_entries %ld entry_size %d\n", + table, + num_entries, + (int) sizeof(struct irt_entry)); + + for (i = 0 ; i < num_entries ; i++, p++) { + printk(MODULE_NAME " %02x %02x %02x %02x %02x %02x %02x %02x %08x%08x\n", + p->entry_type, p->entry_length, p->interrupt_type, + p->polarity_trigger, p->src_bus_irq_devno, p->src_bus_id, + p->src_seg_id, p->dest_iosapic_intin, + ((u32 *) p)[2], + ((u32 *) p)[3] + ); + } +} +#endif /* DEBUG_IOSAPIC_IRT */ + + return num_entries; +} + + + +void __init iosapic_init(void) +{ + unsigned long cell = 0; + + DBG("iosapic_init()\n"); + +#ifdef __LP64__ + if (is_pdc_pat()) { + int status; + struct pdc_pat_cell_num cell_info; + + status = pdc_pat_cell_get_number(&cell_info); + if (status == PDC_OK) { + cell = cell_info.cell_num; + } + } +#endif + + /* get interrupt routing table for this cell */ + irt_num_entry = iosapic_load_irt(cell, &irt_cell); + if (irt_num_entry == 0) + irt_cell = NULL; /* old PDC w/o iosapic */ +} + + +/* +** Return the IRT entry in case we need to look something else up. +*/ +static struct irt_entry * +irt_find_irqline(struct iosapic_info *isi, u8 slot, u8 intr_pin) +{ + struct irt_entry *i = irt_cell; + int cnt; /* track how many entries we've looked at */ + u8 irq_devno = (slot << IRT_DEV_SHIFT) | (intr_pin-1); + + DBG_IRT("irt_find_irqline() SLOT %d pin %d\n", slot, intr_pin); + + for (cnt=0; cnt < irt_num_entry; cnt++, i++) { + + /* + ** Validate: entry_type, entry_length, interrupt_type + ** + ** Difference between validate vs compare is the former + ** should print debug info and is not expected to "fail" + ** on current platforms. + */ + if (i->entry_type != IRT_IOSAPIC_TYPE) { + DBG_IRT(KERN_WARNING MODULE_NAME ":find_irqline(0x%p): skipping entry %d type %d\n", i, cnt, i->entry_type); + continue; + } + + if (i->entry_length != IRT_IOSAPIC_LENGTH) { + DBG_IRT(KERN_WARNING MODULE_NAME ":find_irqline(0x%p): skipping entry %d length %d\n", i, cnt, i->entry_length); + continue; + } + + if (i->interrupt_type != IRT_VECTORED_INTR) { + DBG_IRT(KERN_WARNING MODULE_NAME ":find_irqline(0x%p): skipping entry %d interrupt_type %d\n", i, cnt, i->interrupt_type); + continue; + } + + if (!COMPARE_IRTE_ADDR(i, isi->isi_hpa)) + continue; + + if ((i->src_bus_irq_devno & IRT_IRQ_DEVNO_MASK) != irq_devno) + continue; + + /* + ** Ignore: src_bus_id and rc_seg_id correlate with + ** iosapic_info->isi_hpa on HP platforms. + ** If needed, pass in "PFA" (aka config space addr) + ** instead of slot. + */ + + /* Found it! */ + return i; + } + + printk(KERN_WARNING MODULE_NAME ": 0x%lx : no IRT entry for slot %d, pin %d\n", + isi->isi_hpa, slot, intr_pin); + return NULL; +} + + +/* +** xlate_pin() supports the skewing of IRQ lines done by subsidiary bridges. +** Legacy PDC already does this translation for us and stores it in INTR_LINE. +** +** PAT PDC needs to basically do what legacy PDC does: +** o read PIN +** o adjust PIN in case device is "behind" a PPB +** (eg 4-port 100BT and SCSI/LAN "Combo Card") +** o convert slot/pin to I/O SAPIC input line. +** +** HP platforms only support: +** o one level of skewing for any number of PPBs +** o only support PCI-PCI Bridges. +*/ +static struct irt_entry * +iosapic_xlate_pin(struct iosapic_info *isi, struct pci_dev *pcidev) +{ + u8 intr_pin, intr_slot; + + pci_read_config_byte(pcidev, PCI_INTERRUPT_PIN, &intr_pin); + + DBG_IRT("iosapic_xlate_pin(%s) SLOT %d pin %d\n", + pcidev->slot_name, PCI_SLOT(pcidev->devfn), intr_pin); + + if (intr_pin == 0) { + /* The device does NOT support/use IRQ lines. */ + return NULL; + } + + /* Check if pcidev behind a PPB */ + if (NULL != pcidev->bus->self) { + /* Convert pcidev INTR_PIN into something we + ** can lookup in the IRT. + */ +#ifdef PCI_BRIDGE_FUNCS + /* + ** Proposal #1: + ** + ** call implementation specific translation function + ** This is architecturally "cleaner". HP-UX doesn't + ** support other secondary bus types (eg. E/ISA) directly. + ** May be needed for other processor (eg IA64) architectures + ** or by some ambitous soul who wants to watch TV. + */ + if (pci_bridge_funcs->xlate_intr_line) { + intr_pin = pci_bridge_funcs->xlate_intr_line(pcidev); + } +#else /* PCI_BRIDGE_FUNCS */ + struct pci_bus *p = pcidev->bus; + /* + ** Proposal #2: + ** The "pin" is skewed ((pin + dev - 1) % 4). + ** + ** This isn't very clean since I/O SAPIC must assume: + ** - all platforms only have PCI busses. + ** - only PCI-PCI bridge (eg not PCI-EISA, PCI-PCMCIA) + ** - IRQ routing is only skewed once regardless of + ** the number of PPB's between iosapic and device. + ** (Bit3 expansion chassis follows this rule) + ** + ** Advantage is it's really easy to implement. + */ + intr_pin = ((intr_pin-1)+PCI_SLOT(pcidev->devfn)) % 4; + intr_pin++; /* convert back to INTA-D (1-4) */ +#endif /* PCI_BRIDGE_FUNCS */ + + /* + ** Locate the host slot the PPB nearest the Host bus + ** adapter. + */ + while (NULL != p->parent->self) + p = p->parent; + + intr_slot = PCI_SLOT(p->self->devfn); + } else { + intr_slot = PCI_SLOT(pcidev->devfn); + } + DBG_IRT("iosapic_xlate_pin: bus %d slot %d pin %d\n", + pcidev->bus->secondary, intr_slot, intr_pin); + + return irt_find_irqline(isi, intr_slot, intr_pin); +} + +static void iosapic_rd_irt_entry(struct vector_info *vi , u32 *dp0, u32 *dp1) +{ + struct iosapic_info *isp = vi->iosapic; + u8 idx = vi->irqline; + + *dp0 = iosapic_read(isp->addr, IOSAPIC_IRDT_ENTRY(idx)); + *dp1 = iosapic_read(isp->addr, IOSAPIC_IRDT_ENTRY_HI(idx)); +} + + +static void iosapic_wr_irt_entry(struct vector_info *vi, u32 dp0, u32 dp1) +{ + struct iosapic_info *isp = vi->iosapic; + + DBG_IRT("iosapic_wr_irt_entry(): irq %d hpa %lx 0x%x 0x%x\n", + vi->irqline, isp->isi_hpa, dp0, dp1); + + iosapic_write(isp->addr, IOSAPIC_IRDT_ENTRY(vi->irqline), dp0); + + /* Read the window register to flush the writes down to HW */ + dp0 = readl(isp->addr+IOSAPIC_REG_WINDOW); + + iosapic_write(isp->addr, IOSAPIC_IRDT_ENTRY_HI(vi->irqline), dp1); + + /* Read the window register to flush the writes down to HW */ + dp1 = readl(isp->addr+IOSAPIC_REG_WINDOW); +} + +/* +** set_irt prepares the data (dp0, dp1) according to the vector_info +** and target cpu (id_eid). dp0/dp1 are then used to program I/O SAPIC +** IRdT for the given "vector" (aka IRQ line). +*/ +static void +iosapic_set_irt_data( struct vector_info *vi, u32 *dp0, u32 *dp1) +{ + u32 mode = 0; + struct irt_entry *p = vi->irte; + + if ((p->polarity_trigger & IRT_PO_MASK) == IRT_ACTIVE_LO) + mode |= IOSAPIC_IRDT_PO_LOW; + + if (((p->polarity_trigger >> IRT_EL_SHIFT) & IRT_EL_MASK) == IRT_LEVEL_TRIG) + mode |= IOSAPIC_IRDT_LEVEL_TRIG; + + /* + ** IA64 REVISIT + ** PA doesn't support EXTINT or LPRIO bits. + */ + + *dp0 = mode | (u32) vi->txn_data; + + /* + ** Extracting id_eid isn't a real clean way of getting it. + ** But the encoding is the same for both PA and IA64 platforms. + */ + if (is_pdc_pat()) { + /* + ** PAT PDC just hands it to us "right". + ** txn_addr comes from cpu_data[x].txn_addr. + */ + *dp1 = (u32) (vi->txn_addr); + } else { + /* + ** eg if base_addr == 0xfffa0000), + ** we want to get 0xa0ff0000. + ** + ** eid 0x0ff00000 -> 0x00ff0000 + ** id 0x000ff000 -> 0xff000000 + */ + *dp1 = (((u32)vi->txn_addr & 0x0ff00000) >> 4) | + (((u32)vi->txn_addr & 0x000ff000) << 12); + } + DBG_IRT("iosapic_set_irt_data(): 0x%x 0x%x\n", *dp0, *dp1); +} + + +static struct vector_info *iosapic_get_vector(unsigned int irq) +{ + return irq_desc[irq].handler_data; +} + +static void iosapic_disable_irq(unsigned int irq) +{ + unsigned long flags; + struct vector_info *vi = iosapic_get_vector(irq); + u32 d0, d1; + + spin_lock_irqsave(&iosapic_lock, flags); + iosapic_rd_irt_entry(vi, &d0, &d1); + d0 |= IOSAPIC_IRDT_ENABLE; + iosapic_wr_irt_entry(vi, d0, d1); + spin_unlock_irqrestore(&iosapic_lock, flags); +} + +static void iosapic_enable_irq(unsigned int irq) +{ + struct vector_info *vi = iosapic_get_vector(irq); + u32 d0, d1; + + /* data is initialized by fixup_irq */ + WARN_ON(vi->txn_irq == 0); + + iosapic_set_irt_data(vi, &d0, &d1); + iosapic_wr_irt_entry(vi, d0, d1); + +#ifdef DEBUG_IOSAPIC_IRT +{ + u32 *t = (u32 *) ((ulong) vi->eoi_addr & ~0xffUL); + printk("iosapic_enable_irq(): regs %p", vi->eoi_addr); + for ( ; t < vi->eoi_addr; t++) + printk(" %x", readl(t)); + printk("\n"); +} + +printk("iosapic_enable_irq(): sel "); +{ + struct iosapic_info *isp = vi->iosapic; + + for (d0=0x10; d0<0x1e; d0++) { + d1 = iosapic_read(isp->addr, d0); + printk(" %x", d1); + } +} +printk("\n"); +#endif + + /* + * Issuing I/O SAPIC an EOI causes an interrupt IFF IRQ line is + * asserted. IRQ generally should not be asserted when a driver + * enables their IRQ. It can lead to "interesting" race conditions + * in the driver initialization sequence. + */ + DBG(KERN_DEBUG "enable_irq(%d): eoi(%p, 0x%x)\n", irq, + vi->eoi_addr, vi->eoi_data); + iosapic_eoi(vi->eoi_addr, vi->eoi_data); +} + +/* + * PARISC only supports PCI devices below I/O SAPIC. + * PCI only supports level triggered in order to share IRQ lines. + * ergo I/O SAPIC must always issue EOI on parisc. + * + * i386/ia64 support ISA devices and have to deal with + * edge-triggered interrupts too. + */ +static void iosapic_end_irq(unsigned int irq) +{ + struct vector_info *vi = iosapic_get_vector(irq); + DBG(KERN_DEBUG "end_irq(%d): eoi(%p, 0x%x)\n", irq, + vi->eoi_addr, vi->eoi_data); + iosapic_eoi(vi->eoi_addr, vi->eoi_data); +} + +static unsigned int iosapic_startup_irq(unsigned int irq) +{ + iosapic_enable_irq(irq); + return 0; +} + +static struct hw_interrupt_type iosapic_interrupt_type = { + .typename = "IO-SAPIC-level", + .startup = iosapic_startup_irq, + .shutdown = iosapic_disable_irq, + .enable = iosapic_enable_irq, + .disable = iosapic_disable_irq, + .ack = no_ack_irq, + .end = iosapic_end_irq, +// .set_affinity = iosapic_set_affinity_irq, +}; + +int iosapic_fixup_irq(void *isi_obj, struct pci_dev *pcidev) +{ + struct iosapic_info *isi = isi_obj; + struct irt_entry *irte = NULL; /* only used if PAT PDC */ + struct vector_info *vi; + int isi_line; /* line used by device */ + + if (!isi) { + printk(KERN_WARNING MODULE_NAME ": hpa not registered for %s\n", + pci_name(pcidev)); + return -1; + } + +#ifdef CONFIG_SUPERIO + /* + * HACK ALERT! (non-compliant PCI device support) + * + * All SuckyIO interrupts are routed through the PIC's on function 1. + * But SuckyIO OHCI USB controller gets an IRT entry anyway because + * it advertises INT D for INT_PIN. Use that IRT entry to get the + * SuckyIO interrupt routing for PICs on function 1 (*BLEECCHH*). + */ + if (is_superio_device(pcidev)) { + /* We must call superio_fixup_irq() to register the pdev */ + pcidev->irq = superio_fixup_irq(pcidev); + + /* Don't return if need to program the IOSAPIC's IRT... */ + if (PCI_FUNC(pcidev->devfn) != SUPERIO_USB_FN) + return pcidev->irq; + } +#endif /* CONFIG_SUPERIO */ + + /* lookup IRT entry for isi/slot/pin set */ + irte = iosapic_xlate_pin(isi, pcidev); + if (!irte) { + printk("iosapic: no IRTE for %s (IRQ not connected?)\n", + pci_name(pcidev)); + return -1; + } + DBG_IRT("iosapic_fixup_irq(): irte %p %x %x %x %x %x %x %x %x\n", + irte, + irte->entry_type, + irte->entry_length, + irte->polarity_trigger, + irte->src_bus_irq_devno, + irte->src_bus_id, + irte->src_seg_id, + irte->dest_iosapic_intin, + (u32) irte->dest_iosapic_addr); + isi_line = irte->dest_iosapic_intin; + + /* get vector info for this input line */ + vi = isi->isi_vector + isi_line; + DBG_IRT("iosapic_fixup_irq: line %d vi 0x%p\n", isi_line, vi); + + /* If this IRQ line has already been setup, skip it */ + if (vi->irte) + goto out; + + vi->irte = irte; + + /* + * Allocate processor IRQ + * + * XXX/FIXME The txn_alloc_irq() code and related code should be + * moved to enable_irq(). That way we only allocate processor IRQ + * bits for devices that actually have drivers claiming them. + * Right now we assign an IRQ to every PCI device present, + * regardless of whether it's used or not. + */ + vi->txn_irq = txn_alloc_irq(8); + + if (vi->txn_irq < 0) + panic("I/O sapic: couldn't get TXN IRQ\n"); + + /* enable_irq() will use txn_* to program IRdT */ + vi->txn_addr = txn_alloc_addr(vi->txn_irq); + vi->txn_data = txn_alloc_data(vi->txn_irq); + + vi->eoi_addr = isi->addr + IOSAPIC_REG_EOI; + vi->eoi_data = cpu_to_le32(vi->txn_data); + + cpu_claim_irq(vi->txn_irq, &iosapic_interrupt_type, vi); + + out: + pcidev->irq = vi->txn_irq; + + DBG_IRT("iosapic_fixup_irq() %d:%d %x %x line %d irq %d\n", + PCI_SLOT(pcidev->devfn), PCI_FUNC(pcidev->devfn), + pcidev->vendor, pcidev->device, isi_line, pcidev->irq); + + return pcidev->irq; +} + + +/* +** squirrel away the I/O Sapic Version +*/ +static unsigned int +iosapic_rd_version(struct iosapic_info *isi) +{ + return iosapic_read(isi->addr, IOSAPIC_REG_VERSION); +} + + +/* +** iosapic_register() is called by "drivers" with an integrated I/O SAPIC. +** Caller must be certain they have an I/O SAPIC and know its MMIO address. +** +** o allocate iosapic_info and add it to the list +** o read iosapic version and squirrel that away +** o read size of IRdT. +** o allocate and initialize isi_vector[] +** o allocate irq region +*/ +void *iosapic_register(unsigned long hpa) +{ + struct iosapic_info *isi = NULL; + struct irt_entry *irte = irt_cell; + struct vector_info *vip; + int cnt; /* track how many entries we've looked at */ + + /* + * Astro based platforms can only support PCI OLARD if they implement + * PAT PDC. Legacy PDC omits LBAs with no PCI devices from the IRT. + * Search the IRT and ignore iosapic's which aren't in the IRT. + */ + for (cnt=0; cnt < irt_num_entry; cnt++, irte++) { + WARN_ON(IRT_IOSAPIC_TYPE != irte->entry_type); + if (COMPARE_IRTE_ADDR(irte, hpa)) + break; + } + + if (cnt >= irt_num_entry) { + DBG("iosapic_register() ignoring 0x%lx (NOT FOUND)\n", hpa); + return NULL; + } + + isi = (struct iosapic_info *)kmalloc(sizeof(struct iosapic_info), GFP_KERNEL); + if (!isi) { + BUG(); + return NULL; + } + + memset(isi, 0, sizeof(struct iosapic_info)); + + isi->addr = ioremap(hpa, 4096); + isi->isi_hpa = hpa; + isi->isi_version = iosapic_rd_version(isi); + isi->isi_num_vectors = IOSAPIC_IRDT_MAX_ENTRY(isi->isi_version) + 1; + + vip = isi->isi_vector = (struct vector_info *) + kmalloc(sizeof(struct vector_info) * isi->isi_num_vectors, GFP_KERNEL); + if (vip == NULL) { + kfree(isi); + return NULL; + } + + memset(vip, 0, sizeof(struct vector_info) * isi->isi_num_vectors); + + for (cnt=0; cnt < isi->isi_num_vectors; cnt++, vip++) { + vip->irqline = (unsigned char) cnt; + vip->iosapic = isi; + } + return isi; +} + + +#ifdef DEBUG_IOSAPIC + +static void +iosapic_prt_irt(void *irt, long num_entry) +{ + unsigned int i, *irp = (unsigned int *) irt; + + + printk(KERN_DEBUG MODULE_NAME ": Interrupt Routing Table (%lx entries)\n", num_entry); + + for (i=0; i<num_entry; i++, irp += 4) { + printk(KERN_DEBUG "%p : %2d %.8x %.8x %.8x %.8x\n", + irp, i, irp[0], irp[1], irp[2], irp[3]); + } +} + + +static void +iosapic_prt_vi(struct vector_info *vi) +{ + printk(KERN_DEBUG MODULE_NAME ": vector_info[%d] is at %p\n", vi->irqline, vi); + printk(KERN_DEBUG "\t\tstatus: %.4x\n", vi->status); + printk(KERN_DEBUG "\t\ttxn_irq: %d\n", vi->txn_irq); + printk(KERN_DEBUG "\t\ttxn_addr: %lx\n", vi->txn_addr); + printk(KERN_DEBUG "\t\ttxn_data: %lx\n", vi->txn_data); + printk(KERN_DEBUG "\t\teoi_addr: %p\n", vi->eoi_addr); + printk(KERN_DEBUG "\t\teoi_data: %x\n", vi->eoi_data); +} + + +static void +iosapic_prt_isi(struct iosapic_info *isi) +{ + printk(KERN_DEBUG MODULE_NAME ": io_sapic_info at %p\n", isi); + printk(KERN_DEBUG "\t\tisi_hpa: %lx\n", isi->isi_hpa); + printk(KERN_DEBUG "\t\tisi_status: %x\n", isi->isi_status); + printk(KERN_DEBUG "\t\tisi_version: %x\n", isi->isi_version); + printk(KERN_DEBUG "\t\tisi_vector: %p\n", isi->isi_vector); +} +#endif /* DEBUG_IOSAPIC */ diff --git a/drivers/parisc/iosapic_private.h b/drivers/parisc/iosapic_private.h new file mode 100644 index 00000000000..41e7ec2a44a --- /dev/null +++ b/drivers/parisc/iosapic_private.h @@ -0,0 +1,188 @@ +/* + * Private structs/constants for PARISC IOSAPIC support + * + * Copyright (C) 2000 Hewlett Packard (Grant Grundler) + * Copyright (C) 2000,2003 Grant Grundler (grundler at parisc-linux.org) + * Copyright (C) 2002 Matthew Wilcox (willy at parisc-linux.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 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 + */ + +/* +** This file is private to iosapic driver. +** If stuff needs to be used by another driver, move it to a common file. +** +** WARNING: fields most data structures here are ordered to make sure +** they pack nicely for 64-bit compilation. (ie sizeof(long) == 8) +*/ + + +/* +** Interrupt Routing Stuff +** ----------------------- +** The interrupt routing table consists of entries derived from +** MP Specification Draft 1.5. There is one interrupt routing +** table per cell. N- and L-class consist of a single cell. +*/ +struct irt_entry { + + /* Entry Type 139 identifies an I/O SAPIC interrupt entry */ + u8 entry_type; + + /* Entry Length 16 indicates entry is 16 bytes long */ + u8 entry_length; + + /* + ** Interrupt Type of 0 indicates a vectored interrupt, + ** all other values are reserved + */ + u8 interrupt_type; + + /* + ** PO and EL + ** Polarity of SAPIC I/O input signals: + ** 00 = Reserved + ** 01 = Active high + ** 10 = Reserved + ** 11 = Active low + ** Trigger mode of SAPIC I/O input signals: + ** 00 = Reserved + ** 01 = Edge-triggered + ** 10 = Reserved + ** 11 = Level-triggered + */ + u8 polarity_trigger; + + /* + ** IRQ and DEVNO + ** irq identifies PCI interrupt signal where + ** 0x0 corresponds to INT_A#, + ** 0x1 corresponds to INT_B#, + ** 0x2 corresponds to INT_C# + ** 0x3 corresponds to INT_D# + ** PCI device number where interrupt originates + */ + u8 src_bus_irq_devno; + + /* Source Bus ID identifies the bus where interrupt signal comes from */ + u8 src_bus_id; + + /* + ** Segment ID is unique across a protection domain and + ** identifies a segment of PCI buses (reserved in + ** MP Specification Draft 1.5) + */ + u8 src_seg_id; + + /* + ** Destination I/O SAPIC INTIN# identifies the INTIN n pin + ** to which the signal is connected + */ + u8 dest_iosapic_intin; + + /* + ** Destination I/O SAPIC Address identifies the I/O SAPIC + ** to which the signal is connected + */ + u64 dest_iosapic_addr; +}; + +#define IRT_IOSAPIC_TYPE 139 +#define IRT_IOSAPIC_LENGTH 16 + +#define IRT_VECTORED_INTR 0 + +#define IRT_PO_MASK 0x3 +#define IRT_ACTIVE_HI 1 +#define IRT_ACTIVE_LO 3 + +#define IRT_EL_MASK 0x3 +#define IRT_EL_SHIFT 2 +#define IRT_EDGE_TRIG 1 +#define IRT_LEVEL_TRIG 3 + +#define IRT_IRQ_MASK 0x3 +#define IRT_DEV_MASK 0x1f +#define IRT_DEV_SHIFT 2 + +#define IRT_IRQ_DEVNO_MASK ((IRT_DEV_MASK << IRT_DEV_SHIFT) | IRT_IRQ_MASK) + +#ifdef SUPPORT_MULTI_CELL +struct iosapic_irt { + struct iosapic_irt *irt_next; /* next routing table */ + struct irt_entry *irt_base; /* intr routing table address */ + size_t irte_count; /* number of entries in the table */ + size_t irte_size; /* size (bytes) of each entry */ +}; +#endif + +struct vector_info { + struct iosapic_info *iosapic; /* I/O SAPIC this vector is on */ + struct irt_entry *irte; /* IRT entry */ + u32 *eoi_addr; /* precalculate EOI reg address */ + u32 eoi_data; /* IA64: ? PA: swapped txn_data */ + int txn_irq; /* virtual IRQ number for processor */ + ulong txn_addr; /* IA64: id_eid PA: partial HPA */ + u32 txn_data; /* CPU interrupt bit */ + u8 status; /* status/flags */ + u8 irqline; /* INTINn(IRQ) */ +}; + + +struct iosapic_info { + struct iosapic_info * isi_next; /* list of I/O SAPIC */ + void __iomem * addr; /* remapped address */ + unsigned long isi_hpa; /* physical base address */ + struct vector_info * isi_vector; /* IRdT (IRQ line) array */ + int isi_num_vectors; /* size of IRdT array */ + int isi_status; /* status/flags */ + unsigned int isi_version; /* DEBUG: data fr version reg */ +}; + + + +#ifdef __IA64__ +/* +** PA risc does NOT have any local sapics. IA64 does. +** PIB (Processor Interrupt Block) is handled by Astro or Dew (Stretch CEC). +** +** PA: Get id_eid from IRT and hardcode PIB to 0xfeeNNNN0 +** Emulate the data on PAT platforms. +*/ +struct local_sapic_info { + struct local_sapic_info *lsi_next; /* point to next CPU info */ + int *lsi_cpu_id; /* point to logical CPU id */ + unsigned long *lsi_id_eid; /* point to IA-64 CPU id */ + int *lsi_status; /* point to CPU status */ + void *lsi_private; /* point to special info */ +}; + +/* +** "root" data structure which ties everything together. +** Should always be able to start with sapic_root and locate +** the desired information. +*/ +struct sapic_info { + struct sapic_info *si_next; /* info is per cell */ + int si_cellid; /* cell id */ + unsigned int si_status; /* status */ + char *si_pib_base; /* intr blk base address */ + local_sapic_info_t *si_local_info; + io_sapic_info_t *si_io_info; + extint_info_t *si_extint_info;/* External Intr info */ +}; +#endif + diff --git a/drivers/parisc/lasi.c b/drivers/parisc/lasi.c new file mode 100644 index 00000000000..73185505339 --- /dev/null +++ b/drivers/parisc/lasi.c @@ -0,0 +1,240 @@ +/* + * LASI Device Driver + * + * (c) Copyright 1999 Red Hat Software + * Portions (c) Copyright 1999 The Puffin Group Inc. + * Portions (c) Copyright 1999 Hewlett-Packard + * + * 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. + * + * by Alan Cox <alan@redhat.com> and + * Alex deVries <alex@onefishtwo.ca> + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <asm/io.h> +#include <asm/hardware.h> +#include <asm/led.h> + +#include "gsc.h" + + +#define LASI_VER 0xC008 /* LASI Version */ + +#define LASI_IO_CONF 0x7FFFE /* LASI primary configuration register */ +#define LASI_IO_CONF2 0x7FFFF /* LASI secondary configuration register */ + +static void lasi_choose_irq(struct parisc_device *dev, void *ctrl) +{ + int irq; + + switch (dev->id.sversion) { + case 0x74: irq = 7; break; /* Centronics */ + case 0x7B: irq = 13; break; /* Audio */ + case 0x81: irq = 14; break; /* Lasi itself */ + case 0x82: irq = 9; break; /* SCSI */ + case 0x83: irq = 20; break; /* Floppy */ + case 0x84: irq = 26; break; /* PS/2 Keyboard */ + case 0x87: irq = 18; break; /* ISDN */ + case 0x8A: irq = 8; break; /* LAN */ + case 0x8C: irq = 5; break; /* RS232 */ + case 0x8D: irq = (dev->hw_path == 13) ? 16 : 17; break; + /* Telephone */ + default: return; /* unknown */ + } + + gsc_asic_assign_irq(ctrl, irq, &dev->irq); +} + +static void __init +lasi_init_irq(struct gsc_asic *this_lasi) +{ + unsigned long lasi_base = this_lasi->hpa; + + /* Stop LASI barking for a bit */ + gsc_writel(0x00000000, lasi_base+OFFSET_IMR); + + /* clear pending interrupts */ + gsc_readl(lasi_base+OFFSET_IRR); + + /* We're not really convinced we want to reset the onboard + * devices. Firmware does it for us... + */ + + /* Resets */ + /* gsc_writel(0xFFFFFFFF, lasi_base+0x2000);*/ /* Parallel */ + if(pdc_add_valid(lasi_base+0x4004) == PDC_OK) + gsc_writel(0xFFFFFFFF, lasi_base+0x4004); /* Audio */ + /* gsc_writel(0xFFFFFFFF, lasi_base+0x5000);*/ /* Serial */ + /* gsc_writel(0xFFFFFFFF, lasi_base+0x6000);*/ /* SCSI */ + gsc_writel(0xFFFFFFFF, lasi_base+0x7000); /* LAN */ + gsc_writel(0xFFFFFFFF, lasi_base+0x8000); /* Keyboard */ + gsc_writel(0xFFFFFFFF, lasi_base+0xA000); /* FDC */ + + /* Ok we hit it on the head with a hammer, our Dog is now + ** comatose and muzzled. Devices will now unmask LASI + ** interrupts as they are registered as irq's in the LASI range. + */ + /* XXX: I thought it was `awks that got `it on the `ead with an + * `ammer. -- willy + */ +} + + +/* + ** lasi_led_init() + ** + ** lasi_led_init() initializes the LED controller on the LASI. + ** + ** Since Mirage and Electra machines use a different LED + ** address register, we need to check for these machines + ** explicitly. + */ + +#ifndef CONFIG_CHASSIS_LCD_LED + +#define lasi_led_init(x) /* nothing */ + +#else + +void __init lasi_led_init(unsigned long lasi_hpa) +{ + unsigned long datareg; + + switch (CPU_HVERSION) { + /* Gecko machines have only one single LED, which can be permanently + turned on by writing a zero into the power control register. */ + case 0x600: /* Gecko (712/60) */ + case 0x601: /* Gecko (712/80) */ + case 0x602: /* Gecko (712/100) */ + case 0x603: /* Anole 64 (743/64) */ + case 0x604: /* Anole 100 (743/100) */ + case 0x605: /* Gecko (712/120) */ + datareg = lasi_hpa + 0x0000C000; + gsc_writeb(0, datareg); + return; /* no need to register the LED interrupt-function */ + + /* Mirage and Electra machines need special offsets */ + case 0x60A: /* Mirage Jr (715/64) */ + case 0x60B: /* Mirage 100 */ + case 0x60C: /* Mirage 100+ */ + case 0x60D: /* Electra 100 */ + case 0x60E: /* Electra 120 */ + datareg = lasi_hpa - 0x00020000; + break; + + default: + datareg = lasi_hpa + 0x0000C000; + break; + } + + register_led_driver(DISPLAY_MODEL_LASI, LED_CMD_REG_NONE, datareg); +} +#endif + +/* + * lasi_power_off + * + * Function for lasi to turn off the power. This is accomplished by setting a + * 1 to PWR_ON_L in the Power Control Register + * + */ + +static unsigned long lasi_power_off_hpa; + +static void lasi_power_off(void) +{ + unsigned long datareg; + + /* calculate addr of the Power Control Register */ + datareg = lasi_power_off_hpa + 0x0000C000; + + /* Power down the machine */ + gsc_writel(0x02, datareg); +} + +int __init +lasi_init_chip(struct parisc_device *dev) +{ + struct gsc_asic *lasi; + struct gsc_irq gsc_irq; + int ret; + + lasi = kmalloc(sizeof(*lasi), GFP_KERNEL); + if (!lasi) + return -ENOMEM; + + lasi->name = "Lasi"; + lasi->hpa = dev->hpa; + + /* Check the 4-bit (yes, only 4) version register */ + lasi->version = gsc_readl(lasi->hpa + LASI_VER) & 0xf; + printk(KERN_INFO "%s version %d at 0x%lx found.\n", + lasi->name, lasi->version, lasi->hpa); + + /* initialize the chassis LEDs really early */ + lasi_led_init(lasi->hpa); + + /* Stop LASI barking for a bit */ + lasi_init_irq(lasi); + + /* the IRQ lasi should use */ + dev->irq = gsc_alloc_irq(&gsc_irq); + if (dev->irq < 0) { + printk(KERN_ERR "%s(): cannot get GSC irq\n", + __FUNCTION__); + kfree(lasi); + return -EBUSY; + } + + lasi->eim = ((u32) gsc_irq.txn_addr) | gsc_irq.txn_data; + + ret = request_irq(gsc_irq.irq, gsc_asic_intr, 0, "lasi", lasi); + if (ret < 0) { + kfree(lasi); + return ret; + } + + /* enable IRQ's for devices below LASI */ + gsc_writel(lasi->eim, lasi->hpa + OFFSET_IAR); + + /* Done init'ing, register this driver */ + ret = gsc_common_setup(dev, lasi); + if (ret) { + kfree(lasi); + return ret; + } + + gsc_fixup_irqs(dev, lasi, lasi_choose_irq); + + /* initialize the power off function */ + /* FIXME: Record the LASI HPA for the power off function. This should + * ensure that only the first LASI (the one controlling the power off) + * should set the HPA here */ + lasi_power_off_hpa = lasi->hpa; + pm_power_off = lasi_power_off; + + return ret; +} + +static struct parisc_device_id lasi_tbl[] = { + { HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00081 }, + { 0, } +}; + +struct parisc_driver lasi_driver = { + .name = "Lasi", + .id_table = lasi_tbl, + .probe = lasi_init_chip, +}; diff --git a/drivers/parisc/lba_pci.c b/drivers/parisc/lba_pci.c new file mode 100644 index 00000000000..dc838804c0d --- /dev/null +++ b/drivers/parisc/lba_pci.c @@ -0,0 +1,1649 @@ +/* +** +** PCI Lower Bus Adapter (LBA) manager +** +** (c) Copyright 1999,2000 Grant Grundler +** (c) Copyright 1999,2000 Hewlett-Packard Company +** +** 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 module primarily provides access to PCI bus (config/IOport +** spaces) on platforms with an SBA/LBA chipset. A/B/C/J/L/N-class +** with 4 digit model numbers - eg C3000 (and A400...sigh). +** +** LBA driver isn't as simple as the Dino driver because: +** (a) this chip has substantial bug fixes between revisions +** (Only one Dino bug has a software workaround :^( ) +** (b) has more options which we don't (yet) support (DMA hints, OLARD) +** (c) IRQ support lives in the I/O SAPIC driver (not with PCI driver) +** (d) play nicely with both PAT and "Legacy" PA-RISC firmware (PDC). +** (dino only deals with "Legacy" PDC) +** +** LBA driver passes the I/O SAPIC HPA to the I/O SAPIC driver. +** (I/O SAPIC is integratd in the LBA chip). +** +** FIXME: Add support to SBA and LBA drivers for DMA hint sets +** FIXME: Add support for PCI card hot-plug (OLARD). +*/ + +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/init.h> /* for __init and __devinit */ +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> + +#include <asm/byteorder.h> +#include <asm/pdc.h> +#include <asm/pdcpat.h> +#include <asm/page.h> +#include <asm/system.h> + +#include <asm/hardware.h> /* for register_parisc_driver() stuff */ +#include <asm/parisc-device.h> +#include <asm/iosapic.h> /* for iosapic_register() */ +#include <asm/io.h> /* read/write stuff */ + +#undef DEBUG_LBA /* general stuff */ +#undef DEBUG_LBA_PORT /* debug I/O Port access */ +#undef DEBUG_LBA_CFG /* debug Config Space Access (ie PCI Bus walk) */ +#undef DEBUG_LBA_PAT /* debug PCI Resource Mgt code - PDC PAT only */ + +#undef FBB_SUPPORT /* Fast Back-Back xfers - NOT READY YET */ + + +#ifdef DEBUG_LBA +#define DBG(x...) printk(x) +#else +#define DBG(x...) +#endif + +#ifdef DEBUG_LBA_PORT +#define DBG_PORT(x...) printk(x) +#else +#define DBG_PORT(x...) +#endif + +#ifdef DEBUG_LBA_CFG +#define DBG_CFG(x...) printk(x) +#else +#define DBG_CFG(x...) +#endif + +#ifdef DEBUG_LBA_PAT +#define DBG_PAT(x...) printk(x) +#else +#define DBG_PAT(x...) +#endif + + +/* +** Config accessor functions only pass in the 8-bit bus number and not +** the 8-bit "PCI Segment" number. Each LBA will be assigned a PCI bus +** number based on what firmware wrote into the scratch register. +** +** The "secondary" bus number is set to this before calling +** pci_register_ops(). If any PPB's are present, the scan will +** discover them and update the "secondary" and "subordinate" +** fields in the pci_bus structure. +** +** Changes in the configuration *may* result in a different +** bus number for each LBA depending on what firmware does. +*/ + +#define MODULE_NAME "LBA" + +#define LBA_FUNC_ID 0x0000 /* function id */ +#define LBA_FCLASS 0x0008 /* function class, bist, header, rev... */ +#define LBA_CAPABLE 0x0030 /* capabilities register */ + +#define LBA_PCI_CFG_ADDR 0x0040 /* poke CFG address here */ +#define LBA_PCI_CFG_DATA 0x0048 /* read or write data here */ + +#define LBA_PMC_MTLT 0x0050 /* Firmware sets this - read only. */ +#define LBA_FW_SCRATCH 0x0058 /* Firmware writes the PCI bus number here. */ +#define LBA_ERROR_ADDR 0x0070 /* On error, address gets logged here */ + +#define LBA_ARB_MASK 0x0080 /* bit 0 enable arbitration. PAT/PDC enables */ +#define LBA_ARB_PRI 0x0088 /* firmware sets this. */ +#define LBA_ARB_MODE 0x0090 /* firmware sets this. */ +#define LBA_ARB_MTLT 0x0098 /* firmware sets this. */ + +#define LBA_MOD_ID 0x0100 /* Module ID. PDC_PAT_CELL reports 4 */ + +#define LBA_STAT_CTL 0x0108 /* Status & Control */ +#define LBA_BUS_RESET 0x01 /* Deassert PCI Bus Reset Signal */ +#define CLEAR_ERRLOG 0x10 /* "Clear Error Log" cmd */ +#define CLEAR_ERRLOG_ENABLE 0x20 /* "Clear Error Log" Enable */ +#define HF_ENABLE 0x40 /* enable HF mode (default is -1 mode) */ + +#define LBA_LMMIO_BASE 0x0200 /* < 4GB I/O address range */ +#define LBA_LMMIO_MASK 0x0208 + +#define LBA_GMMIO_BASE 0x0210 /* > 4GB I/O address range */ +#define LBA_GMMIO_MASK 0x0218 + +#define LBA_WLMMIO_BASE 0x0220 /* All < 4GB ranges under the same *SBA* */ +#define LBA_WLMMIO_MASK 0x0228 + +#define LBA_WGMMIO_BASE 0x0230 /* All > 4GB ranges under the same *SBA* */ +#define LBA_WGMMIO_MASK 0x0238 + +#define LBA_IOS_BASE 0x0240 /* I/O port space for this LBA */ +#define LBA_IOS_MASK 0x0248 + +#define LBA_ELMMIO_BASE 0x0250 /* Extra LMMIO range */ +#define LBA_ELMMIO_MASK 0x0258 + +#define LBA_EIOS_BASE 0x0260 /* Extra I/O port space */ +#define LBA_EIOS_MASK 0x0268 + +#define LBA_GLOBAL_MASK 0x0270 /* Mercury only: Global Address Mask */ +#define LBA_DMA_CTL 0x0278 /* firmware sets this */ + +#define LBA_IBASE 0x0300 /* SBA DMA support */ +#define LBA_IMASK 0x0308 + +/* FIXME: ignore DMA Hint stuff until we can measure performance */ +#define LBA_HINT_CFG 0x0310 +#define LBA_HINT_BASE 0x0380 /* 14 registers at every 8 bytes. */ + +#define LBA_BUS_MODE 0x0620 + +/* ERROR regs are needed for config cycle kluges */ +#define LBA_ERROR_CONFIG 0x0680 +#define LBA_SMART_MODE 0x20 +#define LBA_ERROR_STATUS 0x0688 +#define LBA_ROPE_CTL 0x06A0 + +#define LBA_IOSAPIC_BASE 0x800 /* Offset of IRQ logic */ + +/* non-postable I/O port space, densely packed */ +#define LBA_PORT_BASE (PCI_F_EXTEND | 0xfee00000UL) +static void __iomem *astro_iop_base; + +#define ELROY_HVERS 0x782 +#define MERCURY_HVERS 0x783 +#define QUICKSILVER_HVERS 0x784 + +static inline int IS_ELROY(struct parisc_device *d) +{ + return (d->id.hversion == ELROY_HVERS); +} + +static inline int IS_MERCURY(struct parisc_device *d) +{ + return (d->id.hversion == MERCURY_HVERS); +} + +static inline int IS_QUICKSILVER(struct parisc_device *d) +{ + return (d->id.hversion == QUICKSILVER_HVERS); +} + + +/* +** lba_device: Per instance Elroy data structure +*/ +struct lba_device { + struct pci_hba_data hba; + + spinlock_t lba_lock; + void *iosapic_obj; + +#ifdef CONFIG_64BIT + void __iomem * iop_base; /* PA_VIEW - for IO port accessor funcs */ +#endif + + int flags; /* state/functionality enabled */ + int hw_rev; /* HW revision of chip */ +}; + + +static u32 lba_t32; + +/* lba flags */ +#define LBA_FLAG_SKIP_PROBE 0x10 + +#define LBA_SKIP_PROBE(d) ((d)->flags & LBA_FLAG_SKIP_PROBE) + + +/* Looks nice and keeps the compiler happy */ +#define LBA_DEV(d) ((struct lba_device *) (d)) + + +/* +** Only allow 8 subsidiary busses per LBA +** Problem is the PCI bus numbering is globally shared. +*/ +#define LBA_MAX_NUM_BUSES 8 + +/************************************ + * LBA register read and write support + * + * BE WARNED: register writes are posted. + * (ie follow writes which must reach HW with a read) + */ +#define READ_U8(addr) __raw_readb(addr) +#define READ_U16(addr) __raw_readw(addr) +#define READ_U32(addr) __raw_readl(addr) +#define WRITE_U8(value, addr) __raw_writeb(value, addr) +#define WRITE_U16(value, addr) __raw_writew(value, addr) +#define WRITE_U32(value, addr) __raw_writel(value, addr) + +#define READ_REG8(addr) readb(addr) +#define READ_REG16(addr) readw(addr) +#define READ_REG32(addr) readl(addr) +#define READ_REG64(addr) readq(addr) +#define WRITE_REG8(value, addr) writeb(value, addr) +#define WRITE_REG16(value, addr) writew(value, addr) +#define WRITE_REG32(value, addr) writel(value, addr) + + +#define LBA_CFG_TOK(bus,dfn) ((u32) ((bus)<<16 | (dfn)<<8)) +#define LBA_CFG_BUS(tok) ((u8) ((tok)>>16)) +#define LBA_CFG_DEV(tok) ((u8) ((tok)>>11) & 0x1f) +#define LBA_CFG_FUNC(tok) ((u8) ((tok)>>8 ) & 0x7) + + +/* +** Extract LBA (Rope) number from HPA +** REVISIT: 16 ropes for Stretch/Ike? +*/ +#define ROPES_PER_IOC 8 +#define LBA_NUM(x) ((((unsigned long) x) >> 13) & (ROPES_PER_IOC-1)) + + +static void +lba_dump_res(struct resource *r, int d) +{ + int i; + + if (NULL == r) + return; + + printk(KERN_DEBUG "(%p)", r->parent); + for (i = d; i ; --i) printk(" "); + printk(KERN_DEBUG "%p [%lx,%lx]/%lx\n", r, r->start, r->end, r->flags); + lba_dump_res(r->child, d+2); + lba_dump_res(r->sibling, d); +} + + +/* +** LBA rev 2.0, 2.1, 2.2, and 3.0 bus walks require a complex +** workaround for cfg cycles: +** -- preserve LBA state +** -- prevent any DMA from occurring +** -- turn on smart mode +** -- probe with config writes before doing config reads +** -- check ERROR_STATUS +** -- clear ERROR_STATUS +** -- restore LBA state +** +** The workaround is only used for device discovery. +*/ + +static int lba_device_present(u8 bus, u8 dfn, struct lba_device *d) +{ + u8 first_bus = d->hba.hba_bus->secondary; + u8 last_sub_bus = d->hba.hba_bus->subordinate; + + if ((bus < first_bus) || + (bus > last_sub_bus) || + ((bus - first_bus) >= LBA_MAX_NUM_BUSES)) { + return 0; + } + + return 1; +} + + + +#define LBA_CFG_SETUP(d, tok) { \ + /* Save contents of error config register. */ \ + error_config = READ_REG32(d->hba.base_addr + LBA_ERROR_CONFIG); \ +\ + /* Save contents of status control register. */ \ + status_control = READ_REG32(d->hba.base_addr + LBA_STAT_CTL); \ +\ + /* For LBA rev 2.0, 2.1, 2.2, and 3.0, we must disable DMA \ + ** arbitration for full bus walks. \ + */ \ + /* Save contents of arb mask register. */ \ + arb_mask = READ_REG32(d->hba.base_addr + LBA_ARB_MASK); \ +\ + /* \ + * Turn off all device arbitration bits (i.e. everything \ + * except arbitration enable bit). \ + */ \ + WRITE_REG32(0x1, d->hba.base_addr + LBA_ARB_MASK); \ +\ + /* \ + * Set the smart mode bit so that master aborts don't cause \ + * LBA to go into PCI fatal mode (required). \ + */ \ + WRITE_REG32(error_config | LBA_SMART_MODE, d->hba.base_addr + LBA_ERROR_CONFIG); \ +} + + +#define LBA_CFG_PROBE(d, tok) { \ + /* \ + * Setup Vendor ID write and read back the address register \ + * to make sure that LBA is the bus master. \ + */ \ + WRITE_REG32(tok | PCI_VENDOR_ID, (d)->hba.base_addr + LBA_PCI_CFG_ADDR);\ + /* \ + * Read address register to ensure that LBA is the bus master, \ + * which implies that DMA traffic has stopped when DMA arb is off. \ + */ \ + lba_t32 = READ_REG32((d)->hba.base_addr + LBA_PCI_CFG_ADDR); \ + /* \ + * Generate a cfg write cycle (will have no affect on \ + * Vendor ID register since read-only). \ + */ \ + WRITE_REG32(~0, (d)->hba.base_addr + LBA_PCI_CFG_DATA); \ + /* \ + * Make sure write has completed before proceeding further, \ + * i.e. before setting clear enable. \ + */ \ + lba_t32 = READ_REG32((d)->hba.base_addr + LBA_PCI_CFG_ADDR); \ +} + + +/* + * HPREVISIT: + * -- Can't tell if config cycle got the error. + * + * OV bit is broken until rev 4.0, so can't use OV bit and + * LBA_ERROR_LOG_ADDR to tell if error belongs to config cycle. + * + * As of rev 4.0, no longer need the error check. + * + * -- Even if we could tell, we still want to return -1 + * for **ANY** error (not just master abort). + * + * -- Only clear non-fatal errors (we don't want to bring + * LBA out of pci-fatal mode). + * + * Actually, there is still a race in which + * we could be clearing a fatal error. We will + * live with this during our initial bus walk + * until rev 4.0 (no driver activity during + * initial bus walk). The initial bus walk + * has race conditions concerning the use of + * smart mode as well. + */ + +#define LBA_MASTER_ABORT_ERROR 0xc +#define LBA_FATAL_ERROR 0x10 + +#define LBA_CFG_MASTER_ABORT_CHECK(d, base, tok, error) { \ + u32 error_status = 0; \ + /* \ + * Set clear enable (CE) bit. Unset by HW when new \ + * errors are logged -- LBA HW ERS section 14.3.3). \ + */ \ + WRITE_REG32(status_control | CLEAR_ERRLOG_ENABLE, base + LBA_STAT_CTL); \ + error_status = READ_REG32(base + LBA_ERROR_STATUS); \ + if ((error_status & 0x1f) != 0) { \ + /* \ + * Fail the config read request. \ + */ \ + error = 1; \ + if ((error_status & LBA_FATAL_ERROR) == 0) { \ + /* \ + * Clear error status (if fatal bit not set) by setting \ + * clear error log bit (CL). \ + */ \ + WRITE_REG32(status_control | CLEAR_ERRLOG, base + LBA_STAT_CTL); \ + } \ + } \ +} + +#define LBA_CFG_TR4_ADDR_SETUP(d, addr) \ + WRITE_REG32(((addr) & ~3), (d)->hba.base_addr + LBA_PCI_CFG_ADDR); + +#define LBA_CFG_ADDR_SETUP(d, addr) { \ + WRITE_REG32(((addr) & ~3), (d)->hba.base_addr + LBA_PCI_CFG_ADDR); \ + /* \ + * Read address register to ensure that LBA is the bus master, \ + * which implies that DMA traffic has stopped when DMA arb is off. \ + */ \ + lba_t32 = READ_REG32((d)->hba.base_addr + LBA_PCI_CFG_ADDR); \ +} + + +#define LBA_CFG_RESTORE(d, base) { \ + /* \ + * Restore status control register (turn off clear enable). \ + */ \ + WRITE_REG32(status_control, base + LBA_STAT_CTL); \ + /* \ + * Restore error config register (turn off smart mode). \ + */ \ + WRITE_REG32(error_config, base + LBA_ERROR_CONFIG); \ + /* \ + * Restore arb mask register (reenables DMA arbitration). \ + */ \ + WRITE_REG32(arb_mask, base + LBA_ARB_MASK); \ +} + + + +static unsigned int +lba_rd_cfg(struct lba_device *d, u32 tok, u8 reg, u32 size) +{ + u32 data = ~0U; + int error = 0; + u32 arb_mask = 0; /* used by LBA_CFG_SETUP/RESTORE */ + u32 error_config = 0; /* used by LBA_CFG_SETUP/RESTORE */ + u32 status_control = 0; /* used by LBA_CFG_SETUP/RESTORE */ + + LBA_CFG_SETUP(d, tok); + LBA_CFG_PROBE(d, tok); + LBA_CFG_MASTER_ABORT_CHECK(d, d->hba.base_addr, tok, error); + if (!error) { + void __iomem *data_reg = d->hba.base_addr + LBA_PCI_CFG_DATA; + + LBA_CFG_ADDR_SETUP(d, tok | reg); + switch (size) { + case 1: data = (u32) READ_REG8(data_reg + (reg & 3)); break; + case 2: data = (u32) READ_REG16(data_reg+ (reg & 2)); break; + case 4: data = READ_REG32(data_reg); break; + } + } + LBA_CFG_RESTORE(d, d->hba.base_addr); + return(data); +} + + +static int elroy_cfg_read(struct pci_bus *bus, unsigned int devfn, int pos, int size, u32 *data) +{ + struct lba_device *d = LBA_DEV(parisc_walk_tree(bus->bridge)); + u32 local_bus = (bus->parent == NULL) ? 0 : bus->secondary; + u32 tok = LBA_CFG_TOK(local_bus, devfn); + void __iomem *data_reg = d->hba.base_addr + LBA_PCI_CFG_DATA; + + if ((pos > 255) || (devfn > 255)) + return -EINVAL; + +/* FIXME: B2K/C3600 workaround is always use old method... */ + /* if (!LBA_SKIP_PROBE(d)) */ { + /* original - Generate config cycle on broken elroy + with risk we will miss PCI bus errors. */ + *data = lba_rd_cfg(d, tok, pos, size); + DBG_CFG("%s(%x+%2x) -> 0x%x (a)\n", __FUNCTION__, tok, pos, *data); + return 0; + } + + if (LBA_SKIP_PROBE(d) && !lba_device_present(bus->secondary, devfn, d)) { + DBG_CFG("%s(%x+%2x) -> -1 (b)\n", __FUNCTION__, tok, pos); + /* either don't want to look or know device isn't present. */ + *data = ~0U; + return(0); + } + + /* Basic Algorithm + ** Should only get here on fully working LBA rev. + ** This is how simple the code should have been. + */ + LBA_CFG_ADDR_SETUP(d, tok | pos); + switch(size) { + case 1: *data = READ_REG8 (data_reg + (pos & 3)); break; + case 2: *data = READ_REG16(data_reg + (pos & 2)); break; + case 4: *data = READ_REG32(data_reg); break; + } + DBG_CFG("%s(%x+%2x) -> 0x%x (c)\n", __FUNCTION__, tok, pos, *data); + return 0; +} + + +static void +lba_wr_cfg(struct lba_device *d, u32 tok, u8 reg, u32 data, u32 size) +{ + int error = 0; + u32 arb_mask = 0; + u32 error_config = 0; + u32 status_control = 0; + void __iomem *data_reg = d->hba.base_addr + LBA_PCI_CFG_DATA; + + LBA_CFG_SETUP(d, tok); + LBA_CFG_ADDR_SETUP(d, tok | reg); + switch (size) { + case 1: WRITE_REG8 (data, data_reg + (reg & 3)); break; + case 2: WRITE_REG16(data, data_reg + (reg & 2)); break; + case 4: WRITE_REG32(data, data_reg); break; + } + LBA_CFG_MASTER_ABORT_CHECK(d, d->hba.base_addr, tok, error); + LBA_CFG_RESTORE(d, d->hba.base_addr); +} + + +/* + * LBA 4.0 config write code implements non-postable semantics + * by doing a read of CONFIG ADDR after the write. + */ + +static int elroy_cfg_write(struct pci_bus *bus, unsigned int devfn, int pos, int size, u32 data) +{ + struct lba_device *d = LBA_DEV(parisc_walk_tree(bus->bridge)); + u32 local_bus = (bus->parent == NULL) ? 0 : bus->secondary; + u32 tok = LBA_CFG_TOK(local_bus,devfn); + + if ((pos > 255) || (devfn > 255)) + return -EINVAL; + + if (!LBA_SKIP_PROBE(d)) { + /* Original Workaround */ + lba_wr_cfg(d, tok, pos, (u32) data, size); + DBG_CFG("%s(%x+%2x) = 0x%x (a)\n", __FUNCTION__, tok, pos,data); + return 0; + } + + if (LBA_SKIP_PROBE(d) && (!lba_device_present(bus->secondary, devfn, d))) { + DBG_CFG("%s(%x+%2x) = 0x%x (b)\n", __FUNCTION__, tok, pos,data); + return 1; /* New Workaround */ + } + + DBG_CFG("%s(%x+%2x) = 0x%x (c)\n", __FUNCTION__, tok, pos, data); + + /* Basic Algorithm */ + LBA_CFG_ADDR_SETUP(d, tok | pos); + switch(size) { + case 1: WRITE_REG8 (data, d->hba.base_addr + LBA_PCI_CFG_DATA + (pos & 3)); + break; + case 2: WRITE_REG16(data, d->hba.base_addr + LBA_PCI_CFG_DATA + (pos & 2)); + break; + case 4: WRITE_REG32(data, d->hba.base_addr + LBA_PCI_CFG_DATA); + break; + } + /* flush posted write */ + lba_t32 = READ_REG32(d->hba.base_addr + LBA_PCI_CFG_ADDR); + return 0; +} + + +static struct pci_ops elroy_cfg_ops = { + .read = elroy_cfg_read, + .write = elroy_cfg_write, +}; + +/* + * The mercury_cfg_ops are slightly misnamed; they're also used for Elroy + * TR4.0 as no additional bugs were found in this areea between Elroy and + * Mercury + */ + +static int mercury_cfg_read(struct pci_bus *bus, unsigned int devfn, int pos, int size, u32 *data) +{ + struct lba_device *d = LBA_DEV(parisc_walk_tree(bus->bridge)); + u32 local_bus = (bus->parent == NULL) ? 0 : bus->secondary; + u32 tok = LBA_CFG_TOK(local_bus, devfn); + void __iomem *data_reg = d->hba.base_addr + LBA_PCI_CFG_DATA; + + if ((pos > 255) || (devfn > 255)) + return -EINVAL; + + LBA_CFG_TR4_ADDR_SETUP(d, tok | pos); + switch(size) { + case 1: + *data = READ_REG8(data_reg + (pos & 3)); + break; + case 2: + *data = READ_REG16(data_reg + (pos & 2)); + break; + case 4: + *data = READ_REG32(data_reg); break; + break; + } + + DBG_CFG("mercury_cfg_read(%x+%2x) -> 0x%x\n", tok, pos, *data); + return 0; +} + +/* + * LBA 4.0 config write code implements non-postable semantics + * by doing a read of CONFIG ADDR after the write. + */ + +static int mercury_cfg_write(struct pci_bus *bus, unsigned int devfn, int pos, int size, u32 data) +{ + struct lba_device *d = LBA_DEV(parisc_walk_tree(bus->bridge)); + void __iomem *data_reg = d->hba.base_addr + LBA_PCI_CFG_DATA; + u32 local_bus = (bus->parent == NULL) ? 0 : bus->secondary; + u32 tok = LBA_CFG_TOK(local_bus,devfn); + + if ((pos > 255) || (devfn > 255)) + return -EINVAL; + + DBG_CFG("%s(%x+%2x) <- 0x%x (c)\n", __FUNCTION__, tok, pos, data); + + LBA_CFG_TR4_ADDR_SETUP(d, tok | pos); + switch(size) { + case 1: + WRITE_REG8 (data, data_reg + (pos & 3)); + break; + case 2: + WRITE_REG16(data, data_reg + (pos & 2)); + break; + case 4: + WRITE_REG32(data, data_reg); + break; + } + + /* flush posted write */ + lba_t32 = READ_U32(d->hba.base_addr + LBA_PCI_CFG_ADDR); + return 0; +} + +static struct pci_ops mercury_cfg_ops = { + .read = mercury_cfg_read, + .write = mercury_cfg_write, +}; + + +static void +lba_bios_init(void) +{ + DBG(MODULE_NAME ": lba_bios_init\n"); +} + + +#ifdef CONFIG_64BIT + +/* +** Determine if a device is already configured. +** If so, reserve it resources. +** +** Read PCI cfg command register and see if I/O or MMIO is enabled. +** PAT has to enable the devices it's using. +** +** Note: resources are fixed up before we try to claim them. +*/ +static void +lba_claim_dev_resources(struct pci_dev *dev) +{ + u16 cmd; + int i, srch_flags; + + (void) pci_read_config_word(dev, PCI_COMMAND, &cmd); + + srch_flags = (cmd & PCI_COMMAND_IO) ? IORESOURCE_IO : 0; + if (cmd & PCI_COMMAND_MEMORY) + srch_flags |= IORESOURCE_MEM; + + if (!srch_flags) + return; + + for (i = 0; i <= PCI_ROM_RESOURCE; i++) { + if (dev->resource[i].flags & srch_flags) { + pci_claim_resource(dev, i); + DBG(" claimed %s %d [%lx,%lx]/%lx\n", + pci_name(dev), i, + dev->resource[i].start, + dev->resource[i].end, + dev->resource[i].flags + ); + } + } +} +#else +#define lba_claim_dev_resources(dev) +#endif + + +/* +** The algorithm is generic code. +** But it needs to access local data structures to get the IRQ base. +** Could make this a "pci_fixup_irq(bus, region)" but not sure +** it's worth it. +** +** Called by do_pci_scan_bus() immediately after each PCI bus is walked. +** Resources aren't allocated until recursive buswalk below HBA is completed. +*/ +static void +lba_fixup_bus(struct pci_bus *bus) +{ + struct list_head *ln; +#ifdef FBB_SUPPORT + u16 status; +#endif + struct lba_device *ldev = LBA_DEV(parisc_walk_tree(bus->bridge)); + int lba_portbase = HBA_PORT_BASE(ldev->hba.hba_num); + + DBG("lba_fixup_bus(0x%p) bus %d platform_data 0x%p\n", + bus, bus->secondary, bus->bridge->platform_data); + + /* + ** Properly Setup MMIO resources for this bus. + ** pci_alloc_primary_bus() mangles this. + */ + if (bus->self) { + /* PCI-PCI Bridge */ + pci_read_bridge_bases(bus); + } else { + /* Host-PCI Bridge */ + int err, i; + + DBG("lba_fixup_bus() %s [%lx/%lx]/%lx\n", + ldev->hba.io_space.name, + ldev->hba.io_space.start, ldev->hba.io_space.end, + ldev->hba.io_space.flags); + DBG("lba_fixup_bus() %s [%lx/%lx]/%lx\n", + ldev->hba.lmmio_space.name, + ldev->hba.lmmio_space.start, ldev->hba.lmmio_space.end, + ldev->hba.lmmio_space.flags); + + err = request_resource(&ioport_resource, &(ldev->hba.io_space)); + if (err < 0) { + lba_dump_res(&ioport_resource, 2); + BUG(); + } + + if (ldev->hba.elmmio_space.start) { + err = request_resource(&iomem_resource, + &(ldev->hba.elmmio_space)); + if (err < 0) { + + printk("FAILED: lba_fixup_bus() request for " + "elmmio_space [%lx/%lx]\n", + ldev->hba.elmmio_space.start, + ldev->hba.elmmio_space.end); + + /* lba_dump_res(&iomem_resource, 2); */ + /* BUG(); */ + } + } + + err = request_resource(&iomem_resource, &(ldev->hba.lmmio_space)); + if (err < 0) { + /* FIXME overlaps with elmmio will fail here. + * Need to prune (or disable) the distributed range. + * + * BEWARE: conflicts with this lmmio range may be + * elmmio range which is pointing down another rope. + */ + + printk("FAILED: lba_fixup_bus() request for " + "lmmio_space [%lx/%lx]\n", + ldev->hba.lmmio_space.start, + ldev->hba.lmmio_space.end); + /* lba_dump_res(&iomem_resource, 2); */ + } + +#ifdef CONFIG_64BIT + /* GMMIO is distributed range. Every LBA/Rope gets part it. */ + if (ldev->hba.gmmio_space.flags) { + err = request_resource(&iomem_resource, &(ldev->hba.gmmio_space)); + if (err < 0) { + printk("FAILED: lba_fixup_bus() request for " + "gmmio_space [%lx/%lx]\n", + ldev->hba.gmmio_space.start, + ldev->hba.gmmio_space.end); + lba_dump_res(&iomem_resource, 2); + BUG(); + } + } +#endif + + /* advertize Host bridge resources to PCI bus */ + bus->resource[0] = &(ldev->hba.io_space); + bus->resource[1] = &(ldev->hba.lmmio_space); + i=2; + if (ldev->hba.elmmio_space.start) + bus->resource[i++] = &(ldev->hba.elmmio_space); + if (ldev->hba.gmmio_space.start) + bus->resource[i++] = &(ldev->hba.gmmio_space); + + } + + list_for_each(ln, &bus->devices) { + int i; + struct pci_dev *dev = pci_dev_b(ln); + + DBG("lba_fixup_bus() %s\n", pci_name(dev)); + + /* Virtualize Device/Bridge Resources. */ + for (i = 0; i < PCI_BRIDGE_RESOURCES; i++) { + struct resource *res = &dev->resource[i]; + + /* If resource not allocated - skip it */ + if (!res->start) + continue; + + if (res->flags & IORESOURCE_IO) { + DBG("lba_fixup_bus() I/O Ports [%lx/%lx] -> ", + res->start, res->end); + res->start |= lba_portbase; + res->end |= lba_portbase; + DBG("[%lx/%lx]\n", res->start, res->end); + } else if (res->flags & IORESOURCE_MEM) { + /* + ** Convert PCI (IO_VIEW) addresses to + ** processor (PA_VIEW) addresses + */ + DBG("lba_fixup_bus() MMIO [%lx/%lx] -> ", + res->start, res->end); + res->start = PCI_HOST_ADDR(HBA_DATA(ldev), res->start); + res->end = PCI_HOST_ADDR(HBA_DATA(ldev), res->end); + DBG("[%lx/%lx]\n", res->start, res->end); + } else { + DBG("lba_fixup_bus() WTF? 0x%lx [%lx/%lx] XXX", + res->flags, res->start, res->end); + } + } + +#ifdef FBB_SUPPORT + /* + ** If one device does not support FBB transfers, + ** No one on the bus can be allowed to use them. + */ + (void) pci_read_config_word(dev, PCI_STATUS, &status); + bus->bridge_ctl &= ~(status & PCI_STATUS_FAST_BACK); +#endif + + if (is_pdc_pat()) { + /* Claim resources for PDC's devices */ + lba_claim_dev_resources(dev); + } + + /* + ** P2PB's have no IRQs. ignore them. + */ + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) + continue; + + /* Adjust INTERRUPT_LINE for this dev */ + iosapic_fixup_irq(ldev->iosapic_obj, dev); + } + +#ifdef FBB_SUPPORT +/* FIXME/REVISIT - finish figuring out to set FBB on both +** pci_setup_bridge() clobbers PCI_BRIDGE_CONTROL. +** Can't fixup here anyway....garr... +*/ + if (fbb_enable) { + if (bus->self) { + u8 control; + /* enable on PPB */ + (void) pci_read_config_byte(bus->self, PCI_BRIDGE_CONTROL, &control); + (void) pci_write_config_byte(bus->self, PCI_BRIDGE_CONTROL, control | PCI_STATUS_FAST_BACK); + + } else { + /* enable on LBA */ + } + fbb_enable = PCI_COMMAND_FAST_BACK; + } + + /* Lastly enable FBB/PERR/SERR on all devices too */ + list_for_each(ln, &bus->devices) { + (void) pci_read_config_word(dev, PCI_COMMAND, &status); + status |= PCI_COMMAND_PARITY | PCI_COMMAND_SERR | fbb_enable; + (void) pci_write_config_word(dev, PCI_COMMAND, status); + } +#endif +} + + +struct pci_bios_ops lba_bios_ops = { + .init = lba_bios_init, + .fixup_bus = lba_fixup_bus, +}; + + + + +/******************************************************* +** +** LBA Sprockets "I/O Port" Space Accessor Functions +** +** This set of accessor functions is intended for use with +** "legacy firmware" (ie Sprockets on Allegro/Forte boxes). +** +** Many PCI devices don't require use of I/O port space (eg Tulip, +** NCR720) since they export the same registers to both MMIO and +** I/O port space. In general I/O port space is slower than +** MMIO since drivers are designed so PIO writes can be posted. +** +********************************************************/ + +#define LBA_PORT_IN(size, mask) \ +static u##size lba_astro_in##size (struct pci_hba_data *d, u16 addr) \ +{ \ + u##size t; \ + t = READ_REG##size(astro_iop_base + addr); \ + DBG_PORT(" 0x%x\n", t); \ + return (t); \ +} + +LBA_PORT_IN( 8, 3) +LBA_PORT_IN(16, 2) +LBA_PORT_IN(32, 0) + + + +/* +** BUG X4107: Ordering broken - DMA RD return can bypass PIO WR +** +** Fixed in Elroy 2.2. The READ_U32(..., LBA_FUNC_ID) below is +** guarantee non-postable completion semantics - not avoid X4107. +** The READ_U32 only guarantees the write data gets to elroy but +** out to the PCI bus. We can't read stuff from I/O port space +** since we don't know what has side-effects. Attempting to read +** from configuration space would be suicidal given the number of +** bugs in that elroy functionality. +** +** Description: +** DMA read results can improperly pass PIO writes (X4107). The +** result of this bug is that if a processor modifies a location in +** memory after having issued PIO writes, the PIO writes are not +** guaranteed to be completed before a PCI device is allowed to see +** the modified data in a DMA read. +** +** Note that IKE bug X3719 in TR1 IKEs will result in the same +** symptom. +** +** Workaround: +** The workaround for this bug is to always follow a PIO write with +** a PIO read to the same bus before starting DMA on that PCI bus. +** +*/ +#define LBA_PORT_OUT(size, mask) \ +static void lba_astro_out##size (struct pci_hba_data *d, u16 addr, u##size val) \ +{ \ + DBG_PORT("%s(0x%p, 0x%x, 0x%x)\n", __FUNCTION__, d, addr, val); \ + WRITE_REG##size(val, astro_iop_base + addr); \ + if (LBA_DEV(d)->hw_rev < 3) \ + lba_t32 = READ_U32(d->base_addr + LBA_FUNC_ID); \ +} + +LBA_PORT_OUT( 8, 3) +LBA_PORT_OUT(16, 2) +LBA_PORT_OUT(32, 0) + + +static struct pci_port_ops lba_astro_port_ops = { + .inb = lba_astro_in8, + .inw = lba_astro_in16, + .inl = lba_astro_in32, + .outb = lba_astro_out8, + .outw = lba_astro_out16, + .outl = lba_astro_out32 +}; + + +#ifdef CONFIG_64BIT +#define PIOP_TO_GMMIO(lba, addr) \ + ((lba)->iop_base + (((addr)&0xFFFC)<<10) + ((addr)&3)) + +/******************************************************* +** +** LBA PAT "I/O Port" Space Accessor Functions +** +** This set of accessor functions is intended for use with +** "PAT PDC" firmware (ie Prelude/Rhapsody/Piranha boxes). +** +** This uses the PIOP space located in the first 64MB of GMMIO. +** Each rope gets a full 64*KB* (ie 4 bytes per page) this way. +** bits 1:0 stay the same. bits 15:2 become 25:12. +** Then add the base and we can generate an I/O Port cycle. +********************************************************/ +#undef LBA_PORT_IN +#define LBA_PORT_IN(size, mask) \ +static u##size lba_pat_in##size (struct pci_hba_data *l, u16 addr) \ +{ \ + u##size t; \ + DBG_PORT("%s(0x%p, 0x%x) ->", __FUNCTION__, l, addr); \ + t = READ_REG##size(PIOP_TO_GMMIO(LBA_DEV(l), addr)); \ + DBG_PORT(" 0x%x\n", t); \ + return (t); \ +} + +LBA_PORT_IN( 8, 3) +LBA_PORT_IN(16, 2) +LBA_PORT_IN(32, 0) + + +#undef LBA_PORT_OUT +#define LBA_PORT_OUT(size, mask) \ +static void lba_pat_out##size (struct pci_hba_data *l, u16 addr, u##size val) \ +{ \ + void *where = (void *) PIOP_TO_GMMIO(LBA_DEV(l), addr); \ + DBG_PORT("%s(0x%p, 0x%x, 0x%x)\n", __FUNCTION__, l, addr, val); \ + WRITE_REG##size(val, where); \ + /* flush the I/O down to the elroy at least */ \ + lba_t32 = READ_U32(l->base_addr + LBA_FUNC_ID); \ +} + +LBA_PORT_OUT( 8, 3) +LBA_PORT_OUT(16, 2) +LBA_PORT_OUT(32, 0) + + +static struct pci_port_ops lba_pat_port_ops = { + .inb = lba_pat_in8, + .inw = lba_pat_in16, + .inl = lba_pat_in32, + .outb = lba_pat_out8, + .outw = lba_pat_out16, + .outl = lba_pat_out32 +}; + + + +/* +** make range information from PDC available to PCI subsystem. +** We make the PDC call here in order to get the PCI bus range +** numbers. The rest will get forwarded in pcibios_fixup_bus(). +** We don't have a struct pci_bus assigned to us yet. +*/ +static void +lba_pat_resources(struct parisc_device *pa_dev, struct lba_device *lba_dev) +{ + unsigned long bytecnt; + pdc_pat_cell_mod_maddr_block_t pa_pdc_cell; /* PA_VIEW */ + pdc_pat_cell_mod_maddr_block_t io_pdc_cell; /* IO_VIEW */ + long io_count; + long status; /* PDC return status */ + long pa_count; + int i; + + /* return cell module (IO view) */ + status = pdc_pat_cell_module(&bytecnt, pa_dev->pcell_loc, pa_dev->mod_index, + PA_VIEW, & pa_pdc_cell); + pa_count = pa_pdc_cell.mod[1]; + + status |= pdc_pat_cell_module(&bytecnt, pa_dev->pcell_loc, pa_dev->mod_index, + IO_VIEW, &io_pdc_cell); + io_count = io_pdc_cell.mod[1]; + + /* We've already done this once for device discovery...*/ + if (status != PDC_OK) { + panic("pdc_pat_cell_module() call failed for LBA!\n"); + } + + if (PAT_GET_ENTITY(pa_pdc_cell.mod_info) != PAT_ENTITY_LBA) { + panic("pdc_pat_cell_module() entity returned != PAT_ENTITY_LBA!\n"); + } + + /* + ** Inspect the resources PAT tells us about + */ + for (i = 0; i < pa_count; i++) { + struct { + unsigned long type; + unsigned long start; + unsigned long end; /* aka finish */ + } *p, *io; + struct resource *r; + + p = (void *) &(pa_pdc_cell.mod[2+i*3]); + io = (void *) &(io_pdc_cell.mod[2+i*3]); + + /* Convert the PAT range data to PCI "struct resource" */ + switch(p->type & 0xff) { + case PAT_PBNUM: + lba_dev->hba.bus_num.start = p->start; + lba_dev->hba.bus_num.end = p->end; + break; + + case PAT_LMMIO: + /* used to fix up pre-initialized MEM BARs */ + if (!lba_dev->hba.lmmio_space.start) { + sprintf(lba_dev->hba.lmmio_name, + "PCI%02lx LMMIO", + lba_dev->hba.bus_num.start); + lba_dev->hba.lmmio_space_offset = p->start - + io->start; + r = &lba_dev->hba.lmmio_space; + r->name = lba_dev->hba.lmmio_name; + } else if (!lba_dev->hba.elmmio_space.start) { + sprintf(lba_dev->hba.elmmio_name, + "PCI%02lx ELMMIO", + lba_dev->hba.bus_num.start); + r = &lba_dev->hba.elmmio_space; + r->name = lba_dev->hba.elmmio_name; + } else { + printk(KERN_WARNING MODULE_NAME + " only supports 2 LMMIO resources!\n"); + break; + } + + r->start = p->start; + r->end = p->end; + r->flags = IORESOURCE_MEM; + r->parent = r->sibling = r->child = NULL; + break; + + case PAT_GMMIO: + /* MMIO space > 4GB phys addr; for 64-bit BAR */ + sprintf(lba_dev->hba.gmmio_name, "PCI%02lx GMMIO", + lba_dev->hba.bus_num.start); + r = &lba_dev->hba.gmmio_space; + r->name = lba_dev->hba.gmmio_name; + r->start = p->start; + r->end = p->end; + r->flags = IORESOURCE_MEM; + r->parent = r->sibling = r->child = NULL; + break; + + case PAT_NPIOP: + printk(KERN_WARNING MODULE_NAME + " range[%d] : ignoring NPIOP (0x%lx)\n", + i, p->start); + break; + + case PAT_PIOP: + /* + ** Postable I/O port space is per PCI host adapter. + ** base of 64MB PIOP region + */ + lba_dev->iop_base = ioremap(p->start, 64 * 1024 * 1024); + + sprintf(lba_dev->hba.io_name, "PCI%02lx Ports", + lba_dev->hba.bus_num.start); + r = &lba_dev->hba.io_space; + r->name = lba_dev->hba.io_name; + r->start = HBA_PORT_BASE(lba_dev->hba.hba_num); + r->end = r->start + HBA_PORT_SPACE_SIZE - 1; + r->flags = IORESOURCE_IO; + r->parent = r->sibling = r->child = NULL; + break; + + default: + printk(KERN_WARNING MODULE_NAME + " range[%d] : unknown pat range type (0x%lx)\n", + i, p->type & 0xff); + break; + } + } +} +#else +/* keep compiler from complaining about missing declarations */ +#define lba_pat_port_ops lba_astro_port_ops +#define lba_pat_resources(pa_dev, lba_dev) +#endif /* CONFIG_64BIT */ + + +extern void sba_distributed_lmmio(struct parisc_device *, struct resource *); +extern void sba_directed_lmmio(struct parisc_device *, struct resource *); + + +static void +lba_legacy_resources(struct parisc_device *pa_dev, struct lba_device *lba_dev) +{ + struct resource *r; + int lba_num; + + lba_dev->hba.lmmio_space_offset = PCI_F_EXTEND; + + /* + ** With "legacy" firmware, the lowest byte of FW_SCRATCH + ** represents bus->secondary and the second byte represents + ** bus->subsidiary (i.e. highest PPB programmed by firmware). + ** PCI bus walk *should* end up with the same result. + ** FIXME: But we don't have sanity checks in PCI or LBA. + */ + lba_num = READ_REG32(lba_dev->hba.base_addr + LBA_FW_SCRATCH); + r = &(lba_dev->hba.bus_num); + r->name = "LBA PCI Busses"; + r->start = lba_num & 0xff; + r->end = (lba_num>>8) & 0xff; + + /* Set up local PCI Bus resources - we don't need them for + ** Legacy boxes but it's nice to see in /proc/iomem. + */ + r = &(lba_dev->hba.lmmio_space); + sprintf(lba_dev->hba.lmmio_name, "PCI%02lx LMMIO", + lba_dev->hba.bus_num.start); + r->name = lba_dev->hba.lmmio_name; + +#if 1 + /* We want the CPU -> IO routing of addresses. + * The SBA BASE/MASK registers control CPU -> IO routing. + * Ask SBA what is routed to this rope/LBA. + */ + sba_distributed_lmmio(pa_dev, r); +#else + /* + * The LBA BASE/MASK registers control IO -> System routing. + * + * The following code works but doesn't get us what we want. + * Well, only because firmware (v5.0) on C3000 doesn't program + * the LBA BASE/MASE registers to be the exact inverse of + * the corresponding SBA registers. Other Astro/Pluto + * based platform firmware may do it right. + * + * Should someone want to mess with MSI, they may need to + * reprogram LBA BASE/MASK registers. Thus preserve the code + * below until MSI is known to work on C3000/A500/N4000/RP3440. + * + * Using the code below, /proc/iomem shows: + * ... + * f0000000-f0ffffff : PCI00 LMMIO + * f05d0000-f05d0000 : lcd_data + * f05d0008-f05d0008 : lcd_cmd + * f1000000-f1ffffff : PCI01 LMMIO + * f4000000-f4ffffff : PCI02 LMMIO + * f4000000-f4001fff : sym53c8xx + * f4002000-f4003fff : sym53c8xx + * f4004000-f40043ff : sym53c8xx + * f4005000-f40053ff : sym53c8xx + * f4007000-f4007fff : ohci_hcd + * f4008000-f40083ff : tulip + * f6000000-f6ffffff : PCI03 LMMIO + * f8000000-fbffffff : PCI00 ELMMIO + * fa100000-fa4fffff : stifb mmio + * fb000000-fb1fffff : stifb fb + * + * But everything listed under PCI02 actually lives under PCI00. + * This is clearly wrong. + * + * Asking SBA how things are routed tells the correct story: + * LMMIO_BASE/MASK/ROUTE f4000001 fc000000 00000000 + * DIR0_BASE/MASK/ROUTE fa000001 fe000000 00000006 + * DIR1_BASE/MASK/ROUTE f9000001 ff000000 00000004 + * DIR2_BASE/MASK/ROUTE f0000000 fc000000 00000000 + * DIR3_BASE/MASK/ROUTE f0000000 fc000000 00000000 + * + * Which looks like this in /proc/iomem: + * f4000000-f47fffff : PCI00 LMMIO + * f4000000-f4001fff : sym53c8xx + * ...[deteled core devices - same as above]... + * f4008000-f40083ff : tulip + * f4800000-f4ffffff : PCI01 LMMIO + * f6000000-f67fffff : PCI02 LMMIO + * f7000000-f77fffff : PCI03 LMMIO + * f9000000-f9ffffff : PCI02 ELMMIO + * fa000000-fbffffff : PCI03 ELMMIO + * fa100000-fa4fffff : stifb mmio + * fb000000-fb1fffff : stifb fb + * + * ie all Built-in core are under now correctly under PCI00. + * The "PCI02 ELMMIO" directed range is for: + * +-[02]---03.0 3Dfx Interactive, Inc. Voodoo 2 + * + * All is well now. + */ + r->start = READ_REG32(lba_dev->hba.base_addr + LBA_LMMIO_BASE); + if (r->start & 1) { + unsigned long rsize; + + r->flags = IORESOURCE_MEM; + /* mmio_mask also clears Enable bit */ + r->start &= mmio_mask; + r->start = PCI_HOST_ADDR(HBA_DATA(lba_dev), r->start); + rsize = ~ READ_REG32(lba_dev->hba.base_addr + LBA_LMMIO_MASK); + + /* + ** Each rope only gets part of the distributed range. + ** Adjust "window" for this rope. + */ + rsize /= ROPES_PER_IOC; + r->start += (rsize + 1) * LBA_NUM(pa_dev->hpa); + r->end = r->start + rsize; + } else { + r->end = r->start = 0; /* Not enabled. */ + } +#endif + + /* + ** "Directed" ranges are used when the "distributed range" isn't + ** sufficient for all devices below a given LBA. Typically devices + ** like graphics cards or X25 may need a directed range when the + ** bus has multiple slots (ie multiple devices) or the device + ** needs more than the typical 4 or 8MB a distributed range offers. + ** + ** The main reason for ignoring it now frigging complications. + ** Directed ranges may overlap (and have precedence) over + ** distributed ranges. Or a distributed range assigned to a unused + ** rope may be used by a directed range on a different rope. + ** Support for graphics devices may require fixing this + ** since they may be assigned a directed range which overlaps + ** an existing (but unused portion of) distributed range. + */ + r = &(lba_dev->hba.elmmio_space); + sprintf(lba_dev->hba.elmmio_name, "PCI%02lx ELMMIO", + lba_dev->hba.bus_num.start); + r->name = lba_dev->hba.elmmio_name; + +#if 1 + /* See comment which precedes call to sba_directed_lmmio() */ + sba_directed_lmmio(pa_dev, r); +#else + r->start = READ_REG32(lba_dev->hba.base_addr + LBA_ELMMIO_BASE); + + if (r->start & 1) { + unsigned long rsize; + r->flags = IORESOURCE_MEM; + /* mmio_mask also clears Enable bit */ + r->start &= mmio_mask; + r->start = PCI_HOST_ADDR(HBA_DATA(lba_dev), r->start); + rsize = READ_REG32(lba_dev->hba.base_addr + LBA_ELMMIO_MASK); + r->end = r->start + ~rsize; + } +#endif + + r = &(lba_dev->hba.io_space); + sprintf(lba_dev->hba.io_name, "PCI%02lx Ports", + lba_dev->hba.bus_num.start); + r->name = lba_dev->hba.io_name; + r->flags = IORESOURCE_IO; + r->start = READ_REG32(lba_dev->hba.base_addr + LBA_IOS_BASE) & ~1L; + r->end = r->start + (READ_REG32(lba_dev->hba.base_addr + LBA_IOS_MASK) ^ (HBA_PORT_SPACE_SIZE - 1)); + + /* Virtualize the I/O Port space ranges */ + lba_num = HBA_PORT_BASE(lba_dev->hba.hba_num); + r->start |= lba_num; + r->end |= lba_num; +} + + +/************************************************************************** +** +** LBA initialization code (HW and SW) +** +** o identify LBA chip itself +** o initialize LBA chip modes (HardFail) +** o FIXME: initialize DMA hints for reasonable defaults +** o enable configuration functions +** o call pci_register_ops() to discover devs (fixup/fixup_bus get invoked) +** +**************************************************************************/ + +static int __init +lba_hw_init(struct lba_device *d) +{ + u32 stat; + u32 bus_reset; /* PDC_PAT_BUG */ + +#if 0 + printk(KERN_DEBUG "LBA %lx STAT_CTL %Lx ERROR_CFG %Lx STATUS %Lx DMA_CTL %Lx\n", + d->hba.base_addr, + READ_REG64(d->hba.base_addr + LBA_STAT_CTL), + READ_REG64(d->hba.base_addr + LBA_ERROR_CONFIG), + READ_REG64(d->hba.base_addr + LBA_ERROR_STATUS), + READ_REG64(d->hba.base_addr + LBA_DMA_CTL) ); + printk(KERN_DEBUG " ARB mask %Lx pri %Lx mode %Lx mtlt %Lx\n", + READ_REG64(d->hba.base_addr + LBA_ARB_MASK), + READ_REG64(d->hba.base_addr + LBA_ARB_PRI), + READ_REG64(d->hba.base_addr + LBA_ARB_MODE), + READ_REG64(d->hba.base_addr + LBA_ARB_MTLT) ); + printk(KERN_DEBUG " HINT cfg 0x%Lx\n", + READ_REG64(d->hba.base_addr + LBA_HINT_CFG)); + printk(KERN_DEBUG " HINT reg "); + { int i; + for (i=LBA_HINT_BASE; i< (14*8 + LBA_HINT_BASE); i+=8) + printk(" %Lx", READ_REG64(d->hba.base_addr + i)); + } + printk("\n"); +#endif /* DEBUG_LBA_PAT */ + +#ifdef CONFIG_64BIT +/* + * FIXME add support for PDC_PAT_IO "Get slot status" - OLAR support + * Only N-Class and up can really make use of Get slot status. + * maybe L-class too but I've never played with it there. + */ +#endif + + /* PDC_PAT_BUG: exhibited in rev 40.48 on L2000 */ + bus_reset = READ_REG32(d->hba.base_addr + LBA_STAT_CTL + 4) & 1; + if (bus_reset) { + printk(KERN_DEBUG "NOTICE: PCI bus reset still asserted! (clearing)\n"); + } + + stat = READ_REG32(d->hba.base_addr + LBA_ERROR_CONFIG); + if (stat & LBA_SMART_MODE) { + printk(KERN_DEBUG "NOTICE: LBA in SMART mode! (cleared)\n"); + stat &= ~LBA_SMART_MODE; + WRITE_REG32(stat, d->hba.base_addr + LBA_ERROR_CONFIG); + } + + /* Set HF mode as the default (vs. -1 mode). */ + stat = READ_REG32(d->hba.base_addr + LBA_STAT_CTL); + WRITE_REG32(stat | HF_ENABLE, d->hba.base_addr + LBA_STAT_CTL); + + /* + ** Writing a zero to STAT_CTL.rf (bit 0) will clear reset signal + ** if it's not already set. If we just cleared the PCI Bus Reset + ** signal, wait a bit for the PCI devices to recover and setup. + */ + if (bus_reset) + mdelay(pci_post_reset_delay); + + if (0 == READ_REG32(d->hba.base_addr + LBA_ARB_MASK)) { + /* + ** PDC_PAT_BUG: PDC rev 40.48 on L2000. + ** B2000/C3600/J6000 also have this problem? + ** + ** Elroys with hot pluggable slots don't get configured + ** correctly if the slot is empty. ARB_MASK is set to 0 + ** and we can't master transactions on the bus if it's + ** not at least one. 0x3 enables elroy and first slot. + */ + printk(KERN_DEBUG "NOTICE: Enabling PCI Arbitration\n"); + WRITE_REG32(0x3, d->hba.base_addr + LBA_ARB_MASK); + } + + /* + ** FIXME: Hint registers are programmed with default hint + ** values by firmware. Hints should be sane even if we + ** can't reprogram them the way drivers want. + */ + return 0; +} + + + +/* +** Determine if lba should claim this chip (return 0) or not (return 1). +** If so, initialize the chip and tell other partners in crime they +** have work to do. +*/ +static int __init +lba_driver_probe(struct parisc_device *dev) +{ + struct lba_device *lba_dev; + struct pci_bus *lba_bus; + struct pci_ops *cfg_ops; + u32 func_class; + void *tmp_obj; + char *version; + void __iomem *addr = ioremap(dev->hpa, 4096); + + /* Read HW Rev First */ + func_class = READ_REG32(addr + LBA_FCLASS); + + if (IS_ELROY(dev)) { + func_class &= 0xf; + switch (func_class) { + case 0: version = "TR1.0"; break; + case 1: version = "TR2.0"; break; + case 2: version = "TR2.1"; break; + case 3: version = "TR2.2"; break; + case 4: version = "TR3.0"; break; + case 5: version = "TR4.0"; break; + default: version = "TR4+"; + } + + printk(KERN_INFO "%s version %s (0x%x) found at 0x%lx\n", + MODULE_NAME, version, func_class & 0xf, dev->hpa); + + if (func_class < 2) { + printk(KERN_WARNING "Can't support LBA older than " + "TR2.1 - continuing under adversity.\n"); + } + +#if 0 +/* Elroy TR4.0 should work with simple algorithm. + But it doesn't. Still missing something. *sigh* +*/ + if (func_class > 4) { + cfg_ops = &mercury_cfg_ops; + } else +#endif + { + cfg_ops = &elroy_cfg_ops; + } + + } else if (IS_MERCURY(dev) || IS_QUICKSILVER(dev)) { + func_class &= 0xff; + version = kmalloc(6, GFP_KERNEL); + sprintf(version,"TR%d.%d",(func_class >> 4),(func_class & 0xf)); + /* We could use one printk for both Elroy and Mercury, + * but for the mask for func_class. + */ + printk(KERN_INFO "%s version %s (0x%x) found at 0x%lx\n", + MODULE_NAME, version, func_class & 0xff, dev->hpa); + cfg_ops = &mercury_cfg_ops; + } else { + printk(KERN_ERR "Unknown LBA found at 0x%lx\n", dev->hpa); + return -ENODEV; + } + + /* + ** Tell I/O SAPIC driver we have a IRQ handler/region. + */ + tmp_obj = iosapic_register(dev->hpa + LBA_IOSAPIC_BASE); + + /* NOTE: PCI devices (e.g. 103c:1005 graphics card) which don't + ** have an IRT entry will get NULL back from iosapic code. + */ + + lba_dev = kmalloc(sizeof(struct lba_device), GFP_KERNEL); + if (!lba_dev) { + printk(KERN_ERR "lba_init_chip - couldn't alloc lba_device\n"); + return(1); + } + + memset(lba_dev, 0, sizeof(struct lba_device)); + + + /* ---------- First : initialize data we already have --------- */ + + lba_dev->hw_rev = func_class; + lba_dev->hba.base_addr = addr; + lba_dev->hba.dev = dev; + lba_dev->iosapic_obj = tmp_obj; /* save interrupt handle */ + lba_dev->hba.iommu = sba_get_iommu(dev); /* get iommu data */ + + /* ------------ Second : initialize common stuff ---------- */ + pci_bios = &lba_bios_ops; + pcibios_register_hba(HBA_DATA(lba_dev)); + spin_lock_init(&lba_dev->lba_lock); + + if (lba_hw_init(lba_dev)) + return(1); + + /* ---------- Third : setup I/O Port and MMIO resources --------- */ + + if (is_pdc_pat()) { + /* PDC PAT firmware uses PIOP region of GMMIO space. */ + pci_port = &lba_pat_port_ops; + /* Go ask PDC PAT what resources this LBA has */ + lba_pat_resources(dev, lba_dev); + } else { + if (!astro_iop_base) { + /* Sprockets PDC uses NPIOP region */ + astro_iop_base = ioremap(LBA_PORT_BASE, 64 * 1024); + pci_port = &lba_astro_port_ops; + } + + /* Poke the chip a bit for /proc output */ + lba_legacy_resources(dev, lba_dev); + } + + /* + ** Tell PCI support another PCI bus was found. + ** Walks PCI bus for us too. + */ + dev->dev.platform_data = lba_dev; + lba_bus = lba_dev->hba.hba_bus = + pci_scan_bus_parented(&dev->dev, lba_dev->hba.bus_num.start, + cfg_ops, NULL); + + /* This is in lieu of calling pci_assign_unassigned_resources() */ + if (is_pdc_pat()) { + /* assign resources to un-initialized devices */ + + DBG_PAT("LBA pci_bus_size_bridges()\n"); + pci_bus_size_bridges(lba_bus); + + DBG_PAT("LBA pci_bus_assign_resources()\n"); + pci_bus_assign_resources(lba_bus); + +#ifdef DEBUG_LBA_PAT + DBG_PAT("\nLBA PIOP resource tree\n"); + lba_dump_res(&lba_dev->hba.io_space, 2); + DBG_PAT("\nLBA LMMIO resource tree\n"); + lba_dump_res(&lba_dev->hba.lmmio_space, 2); +#endif + } + pci_enable_bridges(lba_bus); + + + /* + ** Once PCI register ops has walked the bus, access to config + ** space is restricted. Avoids master aborts on config cycles. + ** Early LBA revs go fatal on *any* master abort. + */ + if (cfg_ops == &elroy_cfg_ops) { + lba_dev->flags |= LBA_FLAG_SKIP_PROBE; + } + + /* Whew! Finally done! Tell services we got this one covered. */ + return 0; +} + +static struct parisc_device_id lba_tbl[] = { + { HPHW_BRIDGE, HVERSION_REV_ANY_ID, ELROY_HVERS, 0xa }, + { HPHW_BRIDGE, HVERSION_REV_ANY_ID, MERCURY_HVERS, 0xa }, + { HPHW_BRIDGE, HVERSION_REV_ANY_ID, QUICKSILVER_HVERS, 0xa }, + { 0, } +}; + +static struct parisc_driver lba_driver = { + .name = MODULE_NAME, + .id_table = lba_tbl, + .probe = lba_driver_probe, +}; + +/* +** One time initialization to let the world know the LBA was found. +** Must be called exactly once before pci_init(). +*/ +void __init lba_init(void) +{ + register_parisc_driver(&lba_driver); +} + +/* +** Initialize the IBASE/IMASK registers for LBA (Elroy). +** Only called from sba_iommu.c in order to route ranges (MMIO vs DMA). +** sba_iommu is responsible for locking (none needed at init time). +*/ +void lba_set_iregs(struct parisc_device *lba, u32 ibase, u32 imask) +{ + void __iomem * base_addr = ioremap(lba->hpa, 4096); + + imask <<= 2; /* adjust for hints - 2 more bits */ + + /* Make sure we aren't trying to set bits that aren't writeable. */ + WARN_ON((ibase & 0x001fffff) != 0); + WARN_ON((imask & 0x001fffff) != 0); + + DBG("%s() ibase 0x%x imask 0x%x\n", __FUNCTION__, ibase, imask); + WRITE_REG32( imask, base_addr + LBA_IMASK); + WRITE_REG32( ibase, base_addr + LBA_IBASE); + iounmap(base_addr); +} + diff --git a/drivers/parisc/led.c b/drivers/parisc/led.c new file mode 100644 index 00000000000..e90fb72a696 --- /dev/null +++ b/drivers/parisc/led.c @@ -0,0 +1,760 @@ +/* + * Chassis LCD/LED driver for HP-PARISC workstations + * + * (c) Copyright 2000 Red Hat Software + * (c) Copyright 2000 Helge Deller <hdeller@redhat.com> + * (c) Copyright 2001-2004 Helge Deller <deller@gmx.de> + * (c) Copyright 2001 Randolph Chung <tausq@debian.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. + * + * TODO: + * - speed-up calculations with inlined assembler + * - interface to write to second row of LCD from /proc (if technically possible) + * + * Changes: + * - Audit copy_from_user in led_proc_write. + * Daniele Bellucci <bellucda@tiscali.it> + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/stddef.h> /* for offsetof() */ +#include <linux/init.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/utsname.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/inetdevice.h> +#include <linux/in.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/reboot.h> +#include <linux/proc_fs.h> +#include <linux/ctype.h> +#include <linux/blkdev.h> +#include <asm/io.h> +#include <asm/processor.h> +#include <asm/hardware.h> +#include <asm/param.h> /* HZ */ +#include <asm/led.h> +#include <asm/pdc.h> +#include <asm/uaccess.h> + +/* The control of the LEDs and LCDs on PARISC-machines have to be done + completely in software. The necessary calculations are done in a tasklet + which is scheduled at every timer interrupt and since the calculations + may consume relatively much CPU-time some of the calculations can be + turned off with the following variables (controlled via procfs) */ + +static int led_type = -1; +static int led_heartbeat = 1; +static int led_diskio = 1; +static int led_lanrxtx = 1; +static char lcd_text[32]; +static char lcd_text_default[32]; + +#if 0 +#define DPRINTK(x) printk x +#else +#define DPRINTK(x) +#endif + + +struct lcd_block { + unsigned char command; /* stores the command byte */ + unsigned char on; /* value for turning LED on */ + unsigned char off; /* value for turning LED off */ +}; + +/* Structure returned by PDC_RETURN_CHASSIS_INFO */ +/* NOTE: we use unsigned long:16 two times, since the following member + lcd_cmd_reg_addr needs to be 64bit aligned on 64bit PA2.0-machines */ +struct pdc_chassis_lcd_info_ret_block { + unsigned long model:16; /* DISPLAY_MODEL_XXXX */ + unsigned long lcd_width:16; /* width of the LCD in chars (DISPLAY_MODEL_LCD only) */ + unsigned long lcd_cmd_reg_addr; /* ptr to LCD cmd-register & data ptr for LED */ + unsigned long lcd_data_reg_addr; /* ptr to LCD data-register (LCD only) */ + unsigned int min_cmd_delay; /* delay in uS after cmd-write (LCD only) */ + unsigned char reset_cmd1; /* command #1 for writing LCD string (LCD only) */ + unsigned char reset_cmd2; /* command #2 for writing LCD string (LCD only) */ + unsigned char act_enable; /* 0 = no activity (LCD only) */ + struct lcd_block heartbeat; + struct lcd_block disk_io; + struct lcd_block lan_rcv; + struct lcd_block lan_tx; + char _pad; +}; + + +/* LCD_CMD and LCD_DATA for KittyHawk machines */ +#define KITTYHAWK_LCD_CMD F_EXTEND(0xf0190000UL) /* 64bit-ready */ +#define KITTYHAWK_LCD_DATA (KITTYHAWK_LCD_CMD+1) + +/* lcd_info is pre-initialized to the values needed to program KittyHawk LCD's + * HP seems to have used Sharp/Hitachi HD44780 LCDs most of the time. */ +static struct pdc_chassis_lcd_info_ret_block +lcd_info __attribute__((aligned(8))) = +{ + .model = DISPLAY_MODEL_LCD, + .lcd_width = 16, + .lcd_cmd_reg_addr = KITTYHAWK_LCD_CMD, + .lcd_data_reg_addr = KITTYHAWK_LCD_DATA, + .min_cmd_delay = 40, + .reset_cmd1 = 0x80, + .reset_cmd2 = 0xc0, +}; + + +/* direct access to some of the lcd_info variables */ +#define LCD_CMD_REG lcd_info.lcd_cmd_reg_addr +#define LCD_DATA_REG lcd_info.lcd_data_reg_addr +#define LED_DATA_REG lcd_info.lcd_cmd_reg_addr /* LASI & ASP only */ + + +/* ptr to LCD/LED-specific function */ +static void (*led_func_ptr) (unsigned char); + +#define LED_HASLCD 1 +#define LED_NOLCD 0 +#ifdef CONFIG_PROC_FS +static int led_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + char *out = page; + int len; + + switch ((long)data) + { + case LED_NOLCD: + out += sprintf(out, "Heartbeat: %d\n", led_heartbeat); + out += sprintf(out, "Disk IO: %d\n", led_diskio); + out += sprintf(out, "LAN Rx/Tx: %d\n", led_lanrxtx); + break; + case LED_HASLCD: + out += sprintf(out, "%s\n", lcd_text); + break; + default: + *eof = 1; + return 0; + } + + len = out - page - off; + if (len < count) { + *eof = 1; + if (len <= 0) return 0; + } else { + len = count; + } + *start = page + off; + return len; +} + +static int led_proc_write(struct file *file, const char *buf, + unsigned long count, void *data) +{ + char *cur, lbuf[count + 1]; + int d; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + memset(lbuf, 0, count + 1); + + if (copy_from_user(lbuf, buf, count)) + return -EFAULT; + + cur = lbuf; + + /* skip initial spaces */ + while (*cur && isspace(*cur)) + { + cur++; + } + + switch ((long)data) + { + case LED_NOLCD: + d = *cur++ - '0'; + if (d != 0 && d != 1) goto parse_error; + led_heartbeat = d; + + if (*cur++ != ' ') goto parse_error; + + d = *cur++ - '0'; + if (d != 0 && d != 1) goto parse_error; + led_diskio = d; + + if (*cur++ != ' ') goto parse_error; + + d = *cur++ - '0'; + if (d != 0 && d != 1) goto parse_error; + led_lanrxtx = d; + + break; + case LED_HASLCD: + if (*cur && cur[strlen(cur)-1] == '\n') + cur[strlen(cur)-1] = 0; + if (*cur == 0) + cur = lcd_text_default; + lcd_print(cur); + break; + default: + return 0; + } + + return count; + +parse_error: + if ((long)data == LED_NOLCD) + printk(KERN_CRIT "Parse error: expect \"n n n\" (n == 0 or 1) for heartbeat,\ndisk io and lan tx/rx indicators\n"); + return -EINVAL; +} + +static int __init led_create_procfs(void) +{ + struct proc_dir_entry *proc_pdc_root = NULL; + struct proc_dir_entry *ent; + + if (led_type == -1) return -1; + + proc_pdc_root = proc_mkdir("pdc", 0); + if (!proc_pdc_root) return -1; + proc_pdc_root->owner = THIS_MODULE; + ent = create_proc_entry("led", S_IFREG|S_IRUGO|S_IWUSR, proc_pdc_root); + if (!ent) return -1; + ent->nlink = 1; + ent->data = (void *)LED_NOLCD; /* LED */ + ent->read_proc = led_proc_read; + ent->write_proc = led_proc_write; + ent->owner = THIS_MODULE; + + if (led_type == LED_HASLCD) + { + ent = create_proc_entry("lcd", S_IFREG|S_IRUGO|S_IWUSR, proc_pdc_root); + if (!ent) return -1; + ent->nlink = 1; + ent->data = (void *)LED_HASLCD; /* LCD */ + ent->read_proc = led_proc_read; + ent->write_proc = led_proc_write; + ent->owner = THIS_MODULE; + } + + return 0; +} +#endif + +/* + ** + ** led_ASP_driver() + ** + */ +#define LED_DATA 0x01 /* data to shift (0:on 1:off) */ +#define LED_STROBE 0x02 /* strobe to clock data */ +static void led_ASP_driver(unsigned char leds) +{ + int i; + + leds = ~leds; + for (i = 0; i < 8; i++) { + unsigned char value; + value = (leds & 0x80) >> 7; + gsc_writeb( value, LED_DATA_REG ); + gsc_writeb( value | LED_STROBE, LED_DATA_REG ); + leds <<= 1; + } +} + + +/* + ** + ** led_LASI_driver() + ** + */ +static void led_LASI_driver(unsigned char leds) +{ + leds = ~leds; + gsc_writeb( leds, LED_DATA_REG ); +} + + +/* + ** + ** led_LCD_driver() + ** + ** The logic of the LCD driver is, that we write at every scheduled call + ** only to one of LCD_CMD_REG _or_ LCD_DATA_REG - registers. + ** That way we don't need to let this tasklet busywait for min_cmd_delay + ** milliseconds. + ** + ** TODO: check the value of "min_cmd_delay" against the value of HZ. + ** + */ +static void led_LCD_driver(unsigned char leds) +{ + static int last_index; /* 0:heartbeat, 1:disk, 2:lan_in, 3:lan_out */ + static int last_was_cmd;/* 0: CMD was written last, 1: DATA was last */ + struct lcd_block *block_ptr; + int value; + + switch (last_index) { + case 0: block_ptr = &lcd_info.heartbeat; + value = leds & LED_HEARTBEAT; + break; + case 1: block_ptr = &lcd_info.disk_io; + value = leds & LED_DISK_IO; + break; + case 2: block_ptr = &lcd_info.lan_rcv; + value = leds & LED_LAN_RCV; + break; + case 3: block_ptr = &lcd_info.lan_tx; + value = leds & LED_LAN_TX; + break; + default: /* should never happen: */ + return; + } + + if (last_was_cmd) { + /* write the value to the LCD data port */ + gsc_writeb( value ? block_ptr->on : block_ptr->off, LCD_DATA_REG ); + } else { + /* write the command-byte to the LCD command register */ + gsc_writeb( block_ptr->command, LCD_CMD_REG ); + } + + /* now update the vars for the next interrupt iteration */ + if (++last_was_cmd == 2) { /* switch between cmd & data */ + last_was_cmd = 0; + if (++last_index == 4) + last_index = 0; /* switch back to heartbeat index */ + } +} + + +/* + ** + ** led_get_net_activity() + ** + ** calculate if there was TX- or RX-troughput on the network interfaces + ** (analog to dev_get_info() from net/core/dev.c) + ** + */ +static __inline__ int led_get_net_activity(void) +{ +#ifndef CONFIG_NET + return 0; +#else + static unsigned long rx_total_last, tx_total_last; + unsigned long rx_total, tx_total; + struct net_device *dev; + int retval; + + rx_total = tx_total = 0; + + /* we are running as tasklet, so locking dev_base + * for reading should be OK */ + read_lock(&dev_base_lock); + for (dev = dev_base; dev; dev = dev->next) { + struct net_device_stats *stats; + struct in_device *in_dev = __in_dev_get(dev); + if (!in_dev || !in_dev->ifa_list) + continue; + if (LOOPBACK(in_dev->ifa_list->ifa_local)) + continue; + if (!dev->get_stats) + continue; + stats = dev->get_stats(dev); + rx_total += stats->rx_packets; + tx_total += stats->tx_packets; + } + read_unlock(&dev_base_lock); + + retval = 0; + + if (rx_total != rx_total_last) { + rx_total_last = rx_total; + retval |= LED_LAN_RCV; + } + + if (tx_total != tx_total_last) { + tx_total_last = tx_total; + retval |= LED_LAN_TX; + } + + return retval; +#endif +} + + +/* + ** + ** led_get_diskio_activity() + ** + ** calculate if there was disk-io in the system + ** + */ +static __inline__ int led_get_diskio_activity(void) +{ + static unsigned long last_pgpgin, last_pgpgout; + struct page_state pgstat; + int changed; + + get_full_page_state(&pgstat); /* get no of sectors in & out */ + + /* Just use a very simple calculation here. Do not care about overflow, + since we only want to know if there was activity or not. */ + changed = (pgstat.pgpgin != last_pgpgin) || (pgstat.pgpgout != last_pgpgout); + last_pgpgin = pgstat.pgpgin; + last_pgpgout = pgstat.pgpgout; + + return (changed ? LED_DISK_IO : 0); +} + + + +/* + ** led_tasklet_func() + ** + ** is scheduled at every timer interrupt from time.c and + ** updates the chassis LCD/LED + + TODO: + - display load average (older machines like 715/64 have 4 "free" LED's for that) + - optimizations + */ + +#define HEARTBEAT_LEN (HZ*6/100) +#define HEARTBEAT_2ND_RANGE_START (HZ*22/100) +#define HEARTBEAT_2ND_RANGE_END (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN) + +#define NORMALIZED_COUNT(count) (count/(HZ/100)) + +static void led_tasklet_func(unsigned long unused) +{ + static unsigned char lastleds; + unsigned char currentleds; /* stores current value of the LEDs */ + static unsigned long count; /* static incremented value, not wrapped */ + static unsigned long count_HZ; /* counter in range 0..HZ */ + + /* exit if not initialized */ + if (!led_func_ptr) + return; + + /* increment the local counters */ + ++count; + if (++count_HZ == HZ) + count_HZ = 0; + + currentleds = lastleds; + + if (led_heartbeat) + { + /* flash heartbeat-LED like a real heart (2 x short then a long delay) */ + if (count_HZ<HEARTBEAT_LEN || + (count_HZ>=HEARTBEAT_2ND_RANGE_START && count_HZ<HEARTBEAT_2ND_RANGE_END)) + currentleds |= LED_HEARTBEAT; + else + currentleds &= ~LED_HEARTBEAT; + } + + /* look for network activity and flash LEDs respectively */ + if (led_lanrxtx && ((NORMALIZED_COUNT(count)+(8/2)) & 7) == 0) + { + currentleds &= ~(LED_LAN_RCV | LED_LAN_TX); + currentleds |= led_get_net_activity(); + } + + /* avoid to calculate diskio-stats at same irq as netio-stats */ + if (led_diskio && (NORMALIZED_COUNT(count) & 7) == 0) + { + currentleds &= ~LED_DISK_IO; + currentleds |= led_get_diskio_activity(); + } + + /* blink all LEDs twice a second if we got an Oops (HPMC) */ + if (oops_in_progress) { + currentleds = (count_HZ<=(HZ/2)) ? 0 : 0xff; + } + + /* update the LCD/LEDs */ + if (currentleds != lastleds) { + led_func_ptr(currentleds); + lastleds = currentleds; + } +} + +/* main led tasklet struct (scheduled from time.c) */ +DECLARE_TASKLET_DISABLED(led_tasklet, led_tasklet_func, 0); + + +/* + ** led_halt() + ** + ** called by the reboot notifier chain at shutdown and stops all + ** LED/LCD activities. + ** + */ + +static int led_halt(struct notifier_block *, unsigned long, void *); + +static struct notifier_block led_notifier = { + .notifier_call = led_halt, +}; + +static int led_halt(struct notifier_block *nb, unsigned long event, void *buf) +{ + char *txt; + + switch (event) { + case SYS_RESTART: txt = "SYSTEM RESTART"; + break; + case SYS_HALT: txt = "SYSTEM HALT"; + break; + case SYS_POWER_OFF: txt = "SYSTEM POWER OFF"; + break; + default: return NOTIFY_DONE; + } + + /* completely stop the LED/LCD tasklet */ + tasklet_disable(&led_tasklet); + + if (lcd_info.model == DISPLAY_MODEL_LCD) + lcd_print(txt); + else + if (led_func_ptr) + led_func_ptr(0xff); /* turn all LEDs ON */ + + unregister_reboot_notifier(&led_notifier); + return NOTIFY_OK; +} + +/* + ** register_led_driver() + ** + ** registers an external LED or LCD for usage by this driver. + ** currently only LCD-, LASI- and ASP-style LCD/LED's are supported. + ** + */ + +int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg) +{ + static int initialized; + + if (initialized || !data_reg) + return 1; + + lcd_info.model = model; /* store the values */ + LCD_CMD_REG = (cmd_reg == LED_CMD_REG_NONE) ? 0 : cmd_reg; + + switch (lcd_info.model) { + case DISPLAY_MODEL_LCD: + LCD_DATA_REG = data_reg; + printk(KERN_INFO "LCD display at %lx,%lx registered\n", + LCD_CMD_REG , LCD_DATA_REG); + led_func_ptr = led_LCD_driver; + lcd_print( lcd_text_default ); + led_type = LED_HASLCD; + break; + + case DISPLAY_MODEL_LASI: + LED_DATA_REG = data_reg; + led_func_ptr = led_LASI_driver; + printk(KERN_INFO "LED display at %lx registered\n", LED_DATA_REG); + led_type = LED_NOLCD; + break; + + case DISPLAY_MODEL_OLD_ASP: + LED_DATA_REG = data_reg; + led_func_ptr = led_ASP_driver; + printk(KERN_INFO "LED (ASP-style) display at %lx registered\n", + LED_DATA_REG); + led_type = LED_NOLCD; + break; + + default: + printk(KERN_ERR "%s: Wrong LCD/LED model %d !\n", + __FUNCTION__, lcd_info.model); + return 1; + } + + /* mark the LCD/LED driver now as initialized and + * register to the reboot notifier chain */ + initialized++; + register_reboot_notifier(&led_notifier); + + /* start the led tasklet for the first time */ + tasklet_enable(&led_tasklet); + + return 0; +} + +/* + ** register_led_regions() + ** + ** register_led_regions() registers the LCD/LED regions for /procfs. + ** At bootup - where the initialisation of the LCD/LED normally happens - + ** not all internal structures of request_region() are properly set up, + ** so that we delay the led-registration until after busdevices_init() + ** has been executed. + ** + */ + +void __init register_led_regions(void) +{ + switch (lcd_info.model) { + case DISPLAY_MODEL_LCD: + request_mem_region((unsigned long)LCD_CMD_REG, 1, "lcd_cmd"); + request_mem_region((unsigned long)LCD_DATA_REG, 1, "lcd_data"); + break; + case DISPLAY_MODEL_LASI: + case DISPLAY_MODEL_OLD_ASP: + request_mem_region((unsigned long)LED_DATA_REG, 1, "led_data"); + break; + } +} + + +/* + ** + ** lcd_print() + ** + ** Displays the given string on the LCD-Display of newer machines. + ** lcd_print() disables the timer-based led tasklet during its + ** execution and enables it afterwards again. + ** + */ +int lcd_print( char *str ) +{ + int i; + + if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD) + return 0; + + /* temporarily disable the led tasklet */ + tasklet_disable(&led_tasklet); + + /* copy display string to buffer for procfs */ + strlcpy(lcd_text, str, sizeof(lcd_text)); + + /* Set LCD Cursor to 1st character */ + gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG); + udelay(lcd_info.min_cmd_delay); + + /* Print the string */ + for (i=0; i < lcd_info.lcd_width; i++) { + if (str && *str) + gsc_writeb(*str++, LCD_DATA_REG); + else + gsc_writeb(' ', LCD_DATA_REG); + udelay(lcd_info.min_cmd_delay); + } + + /* re-enable the led tasklet */ + tasklet_enable(&led_tasklet); + + return lcd_info.lcd_width; +} + +/* + ** led_init() + ** + ** led_init() is called very early in the bootup-process from setup.c + ** and asks the PDC for an usable chassis LCD or LED. + ** If the PDC doesn't return any info, then the LED + ** is detected by lasi.c or asp.c and registered with the + ** above functions lasi_led_init() or asp_led_init(). + ** KittyHawk machines have often a buggy PDC, so that + ** we explicitly check for those machines here. + */ + +int __init led_init(void) +{ + struct pdc_chassis_info chassis_info; + int ret; + + snprintf(lcd_text_default, sizeof(lcd_text_default), + "Linux %s", system_utsname.release); + + /* Work around the buggy PDC of KittyHawk-machines */ + switch (CPU_HVERSION) { + case 0x580: /* KittyHawk DC2-100 (K100) */ + case 0x581: /* KittyHawk DC3-120 (K210) */ + case 0x582: /* KittyHawk DC3 100 (K400) */ + case 0x583: /* KittyHawk DC3 120 (K410) */ + case 0x58B: /* KittyHawk DC2 100 (K200) */ + printk(KERN_INFO "%s: KittyHawk-Machine (hversion 0x%x) found, " + "LED detection skipped.\n", __FILE__, CPU_HVERSION); + goto found; /* use the preinitialized values of lcd_info */ + } + + /* initialize the struct, so that we can check for valid return values */ + lcd_info.model = DISPLAY_MODEL_NONE; + chassis_info.actcnt = chassis_info.maxcnt = 0; + + ret = pdc_chassis_info(&chassis_info, &lcd_info, sizeof(lcd_info)); + if (ret == PDC_OK) { + DPRINTK((KERN_INFO "%s: chassis info: model=%d (%s), " + "lcd_width=%d, cmd_delay=%u,\n" + "%s: sizecnt=%d, actcnt=%ld, maxcnt=%ld\n", + __FILE__, lcd_info.model, + (lcd_info.model==DISPLAY_MODEL_LCD) ? "LCD" : + (lcd_info.model==DISPLAY_MODEL_LASI) ? "LED" : "unknown", + lcd_info.lcd_width, lcd_info.min_cmd_delay, + __FILE__, sizeof(lcd_info), + chassis_info.actcnt, chassis_info.maxcnt)); + DPRINTK((KERN_INFO "%s: cmd=%p, data=%p, reset1=%x, reset2=%x, act_enable=%d\n", + __FILE__, lcd_info.lcd_cmd_reg_addr, + lcd_info.lcd_data_reg_addr, lcd_info.reset_cmd1, + lcd_info.reset_cmd2, lcd_info.act_enable )); + + /* check the results. Some machines have a buggy PDC */ + if (chassis_info.actcnt <= 0 || chassis_info.actcnt != chassis_info.maxcnt) + goto not_found; + + switch (lcd_info.model) { + case DISPLAY_MODEL_LCD: /* LCD display */ + if (chassis_info.actcnt < + offsetof(struct pdc_chassis_lcd_info_ret_block, _pad)-1) + goto not_found; + if (!lcd_info.act_enable) { + DPRINTK((KERN_INFO "PDC prohibited usage of the LCD.\n")); + goto not_found; + } + break; + + case DISPLAY_MODEL_NONE: /* no LED or LCD available */ + printk(KERN_INFO "PDC reported no LCD or LED.\n"); + goto not_found; + + case DISPLAY_MODEL_LASI: /* Lasi style 8 bit LED display */ + if (chassis_info.actcnt != 8 && chassis_info.actcnt != 32) + goto not_found; + break; + + default: + printk(KERN_WARNING "PDC reported unknown LCD/LED model %d\n", + lcd_info.model); + goto not_found; + } /* switch() */ + +found: + /* register the LCD/LED driver */ + register_led_driver(lcd_info.model, LCD_CMD_REG, LCD_DATA_REG); + return 0; + + } else { /* if() */ + DPRINTK((KERN_INFO "pdc_chassis_info call failed with retval = %d\n", ret)); + } + +not_found: + lcd_info.model = DISPLAY_MODEL_NONE; + return 1; +} + +#ifdef CONFIG_PROC_FS +module_init(led_create_procfs) +#endif diff --git a/drivers/parisc/pdc_stable.c b/drivers/parisc/pdc_stable.c new file mode 100644 index 00000000000..67c8f3b4484 --- /dev/null +++ b/drivers/parisc/pdc_stable.c @@ -0,0 +1,735 @@ +/* + * Interfaces to retrieve and set PDC Stable options (firmware) + * + * Copyright (C) 2005 Thibaut VARENE <varenet@parisc-linux.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 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 + * + * + * DEV NOTE: the PDC Procedures reference states that: + * "A minimum of 96 bytes of Stable Storage is required. Providing more than + * 96 bytes of Stable Storage is optional [...]. Failure to provide the + * optional locations from 96 to 192 results in the loss of certain + * functionality during boot." + * + * Since locations between 96 and 192 are the various paths, most (if not + * all) PA-RISC machines should have them. Anyway, for safety reasons, the + * following code can deal with only 96 bytes of Stable Storage, and all + * sizes between 96 and 192 bytes (provided they are multiple of struct + * device_path size, eg: 128, 160 and 192) to provide full information. + * The code makes no use of data above 192 bytes. One last word: there's one + * path we can always count on: the primary path. + */ + +#undef PDCS_DEBUG +#ifdef PDCS_DEBUG +#define DPRINTK(fmt, args...) printk(KERN_DEBUG fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> /* for capable() */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/sysfs.h> +#include <linux/kobject.h> +#include <linux/device.h> +#include <linux/errno.h> + +#include <asm/pdc.h> +#include <asm/page.h> +#include <asm/uaccess.h> +#include <asm/hardware.h> + +#define PDCS_VERSION "0.09" + +#define PDCS_ADDR_PPRI 0x00 +#define PDCS_ADDR_OSID 0x40 +#define PDCS_ADDR_FSIZ 0x5C +#define PDCS_ADDR_PCON 0x60 +#define PDCS_ADDR_PALT 0x80 +#define PDCS_ADDR_PKBD 0xA0 + +MODULE_AUTHOR("Thibaut VARENE <varenet@parisc-linux.org>"); +MODULE_DESCRIPTION("sysfs interface to HP PDC Stable Storage data"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(PDCS_VERSION); + +static unsigned long pdcs_size = 0; + +/* This struct defines what we need to deal with a parisc pdc path entry */ +struct pdcspath_entry { + short ready; /* entry record is valid if != 0 */ + unsigned long addr; /* entry address in stable storage */ + char *name; /* entry name */ + struct device_path devpath; /* device path in parisc representation */ + struct device *dev; /* corresponding device */ + struct kobject kobj; +}; + +struct pdcspath_attribute { + struct attribute attr; + ssize_t (*show)(struct pdcspath_entry *entry, char *buf); + ssize_t (*store)(struct pdcspath_entry *entry, const char *buf, size_t count); +}; + +#define PDCSPATH_ENTRY(_addr, _name) \ +struct pdcspath_entry pdcspath_entry_##_name = { \ + .ready = 0, \ + .addr = _addr, \ + .name = __stringify(_name), \ +}; + +#define PDCS_ATTR(_name, _mode, _show, _store) \ +struct subsys_attribute pdcs_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE}, \ + .show = _show, \ + .store = _store, \ +}; + +#define PATHS_ATTR(_name, _mode, _show, _store) \ +struct pdcspath_attribute paths_attr_##_name = { \ + .attr = {.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE}, \ + .show = _show, \ + .store = _store, \ +}; + +#define to_pdcspath_attribute(_attr) container_of(_attr, struct pdcspath_attribute, attr) +#define to_pdcspath_entry(obj) container_of(obj, struct pdcspath_entry, kobj) + +/** + * pdcspath_fetch - This function populates the path entry structs. + * @entry: A pointer to an allocated pdcspath_entry. + * + * The general idea is that you don't read from the Stable Storage every time + * you access the files provided by the facilites. We store a copy of the + * content of the stable storage WRT various paths in these structs. We read + * these structs when reading the files, and we will write to these structs when + * writing to the files, and only then write them back to the Stable Storage. + */ +static int +pdcspath_fetch(struct pdcspath_entry *entry) +{ + struct device_path *devpath; + + if (!entry) + return -EINVAL; + + devpath = &entry->devpath; + + DPRINTK("%s: fetch: 0x%p, 0x%p, addr: 0x%lx\n", __func__, + entry, devpath, entry->addr); + + /* addr, devpath and count must be word aligned */ + if (pdc_stable_read(entry->addr, devpath, sizeof(*devpath)) != PDC_OK) + return -EIO; + + /* Find the matching device. + NOTE: hardware_path overlays with device_path, so the nice cast can + be used */ + entry->dev = hwpath_to_device((struct hardware_path *)devpath); + + entry->ready = 1; + + DPRINTK("%s: device: 0x%p\n", __func__, entry->dev); + + return 0; +} + +/** + * pdcspath_store - This function writes a path to stable storage. + * @entry: A pointer to an allocated pdcspath_entry. + * + * It can be used in two ways: either by passing it a preset devpath struct + * containing an already computed hardware path, or by passing it a device + * pointer, from which it'll find out the corresponding hardware path. + * For now we do not handle the case where there's an error in writing to the + * Stable Storage area, so you'd better not mess up the data :P + */ +static int +pdcspath_store(struct pdcspath_entry *entry) +{ + struct device_path *devpath; + + if (!entry) + return -EINVAL; + + devpath = &entry->devpath; + + /* We expect the caller to set the ready flag to 0 if the hardware + path struct provided is invalid, so that we know we have to fill it. + First case, we don't have a preset hwpath... */ + if (!entry->ready) { + /* ...but we have a device, map it */ + if (entry->dev) + device_to_hwpath(entry->dev, (struct hardware_path *)devpath); + else + return -EINVAL; + } + /* else, we expect the provided hwpath to be valid. */ + + DPRINTK("%s: store: 0x%p, 0x%p, addr: 0x%lx\n", __func__, + entry, devpath, entry->addr); + + /* addr, devpath and count must be word aligned */ + if (pdc_stable_write(entry->addr, devpath, sizeof(*devpath)) != PDC_OK) { + printk(KERN_ERR "%s: an error occured when writing to PDC.\n" + "It is likely that the Stable Storage data has been corrupted.\n" + "Please check it carefully upon next reboot.\n", __func__); + return -EIO; + } + + entry->ready = 1; + + DPRINTK("%s: device: 0x%p\n", __func__, entry->dev); + + return 0; +} + +/** + * pdcspath_hwpath_read - This function handles hardware path pretty printing. + * @entry: An allocated and populated pdscpath_entry struct. + * @buf: The output buffer to write to. + * + * We will call this function to format the output of the hwpath attribute file. + */ +static ssize_t +pdcspath_hwpath_read(struct pdcspath_entry *entry, char *buf) +{ + char *out = buf; + struct device_path *devpath; + unsigned short i; + + if (!entry || !buf) + return -EINVAL; + + devpath = &entry->devpath; + + if (!entry->ready) + return -ENODATA; + + for (i = 0; i < 6; i++) { + if (devpath->bc[i] >= 128) + continue; + out += sprintf(out, "%u/", (unsigned char)devpath->bc[i]); + } + out += sprintf(out, "%u\n", (unsigned char)devpath->mod); + + return out - buf; +} + +/** + * pdcspath_hwpath_write - This function handles hardware path modifying. + * @entry: An allocated and populated pdscpath_entry struct. + * @buf: The input buffer to read from. + * @count: The number of bytes to be read. + * + * We will call this function to change the current hardware path. + * Hardware paths are to be given '/'-delimited, without brackets. + * We take care to make sure that the provided path actually maps to an existing + * device, BUT nothing would prevent some foolish user to set the path to some + * PCI bridge or even a CPU... + * A better work around would be to make sure we are at the end of a device tree + * for instance, but it would be IMHO beyond the simple scope of that driver. + * The aim is to provide a facility. Data correctness is left to userland. + */ +static ssize_t +pdcspath_hwpath_write(struct pdcspath_entry *entry, const char *buf, size_t count) +{ + struct hardware_path hwpath; + unsigned short i; + char in[count+1], *temp; + struct device *dev; + + if (!entry || !buf || !count) + return -EINVAL; + + /* We'll use a local copy of buf */ + memset(in, 0, count+1); + strncpy(in, buf, count); + + /* Let's clean up the target. 0xff is a blank pattern */ + memset(&hwpath, 0xff, sizeof(hwpath)); + + /* First, pick the mod field (the last one of the input string) */ + if (!(temp = strrchr(in, '/'))) + return -EINVAL; + + hwpath.mod = simple_strtoul(temp+1, NULL, 10); + in[temp-in] = '\0'; /* truncate the remaining string. just precaution */ + DPRINTK("%s: mod: %d\n", __func__, hwpath.mod); + + /* Then, loop for each delimiter, making sure we don't have too many. + we write the bc fields in a down-top way. No matter what, we stop + before writing the last field. If there are too many fields anyway, + then the user is a moron and it'll be caught up later when we'll + check the consistency of the given hwpath. */ + for (i=5; ((temp = strrchr(in, '/'))) && (temp-in > 0) && (likely(i)); i--) { + hwpath.bc[i] = simple_strtoul(temp+1, NULL, 10); + in[temp-in] = '\0'; + DPRINTK("%s: bc[%d]: %d\n", __func__, i, hwpath.bc[i]); + } + + /* Store the final field */ + hwpath.bc[i] = simple_strtoul(in, NULL, 10); + DPRINTK("%s: bc[%d]: %d\n", __func__, i, hwpath.bc[i]); + + /* Now we check that the user isn't trying to lure us */ + if (!(dev = hwpath_to_device((struct hardware_path *)&hwpath))) { + printk(KERN_WARNING "%s: attempt to set invalid \"%s\" " + "hardware path: %s\n", __func__, entry->name, buf); + return -EINVAL; + } + + /* So far so good, let's get in deep */ + entry->ready = 0; + entry->dev = dev; + + /* Now, dive in. Write back to the hardware */ + WARN_ON(pdcspath_store(entry)); /* this warn should *NEVER* happen */ + + /* Update the symlink to the real device */ + sysfs_remove_link(&entry->kobj, "device"); + sysfs_create_link(&entry->kobj, &entry->dev->kobj, "device"); + + printk(KERN_INFO "PDC Stable Storage: changed \"%s\" path to \"%s\"\n", + entry->name, buf); + + return count; +} + +/** + * pdcspath_layer_read - Extended layer (eg. SCSI ids) pretty printing. + * @entry: An allocated and populated pdscpath_entry struct. + * @buf: The output buffer to write to. + * + * We will call this function to format the output of the layer attribute file. + */ +static ssize_t +pdcspath_layer_read(struct pdcspath_entry *entry, char *buf) +{ + char *out = buf; + struct device_path *devpath; + unsigned short i; + + if (!entry || !buf) + return -EINVAL; + + devpath = &entry->devpath; + + if (!entry->ready) + return -ENODATA; + + for (i = 0; devpath->layers[i] && (likely(i < 6)); i++) + out += sprintf(out, "%u ", devpath->layers[i]); + + out += sprintf(out, "\n"); + + return out - buf; +} + +/** + * pdcspath_layer_write - This function handles extended layer modifying. + * @entry: An allocated and populated pdscpath_entry struct. + * @buf: The input buffer to read from. + * @count: The number of bytes to be read. + * + * We will call this function to change the current layer value. + * Layers are to be given '.'-delimited, without brackets. + * XXX beware we are far less checky WRT input data provided than for hwpath. + * Potential harm can be done, since there's no way to check the validity of + * the layer fields. + */ +static ssize_t +pdcspath_layer_write(struct pdcspath_entry *entry, const char *buf, size_t count) +{ + unsigned int layers[6]; /* device-specific info (ctlr#, unit#, ...) */ + unsigned short i; + char in[count+1], *temp; + + if (!entry || !buf || !count) + return -EINVAL; + + /* We'll use a local copy of buf */ + memset(in, 0, count+1); + strncpy(in, buf, count); + + /* Let's clean up the target. 0 is a blank pattern */ + memset(&layers, 0, sizeof(layers)); + + /* First, pick the first layer */ + if (unlikely(!isdigit(*in))) + return -EINVAL; + layers[0] = simple_strtoul(in, NULL, 10); + DPRINTK("%s: layer[0]: %d\n", __func__, layers[0]); + + temp = in; + for (i=1; ((temp = strchr(temp, '.'))) && (likely(i<6)); i++) { + if (unlikely(!isdigit(*(++temp)))) + return -EINVAL; + layers[i] = simple_strtoul(temp, NULL, 10); + DPRINTK("%s: layer[%d]: %d\n", __func__, i, layers[i]); + } + + /* So far so good, let's get in deep */ + + /* First, overwrite the current layers with the new ones, not touching + the hardware path. */ + memcpy(&entry->devpath.layers, &layers, sizeof(layers)); + + /* Now, dive in. Write back to the hardware */ + WARN_ON(pdcspath_store(entry)); /* this warn should *NEVER* happen */ + + printk(KERN_INFO "PDC Stable Storage: changed \"%s\" layers to \"%s\"\n", + entry->name, buf); + + return count; +} + +/** + * pdcspath_attr_show - Generic read function call wrapper. + * @kobj: The kobject to get info from. + * @attr: The attribute looked upon. + * @buf: The output buffer. + */ +static ssize_t +pdcspath_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct pdcspath_entry *entry = to_pdcspath_entry(kobj); + struct pdcspath_attribute *pdcs_attr = to_pdcspath_attribute(attr); + ssize_t ret = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (pdcs_attr->show) + ret = pdcs_attr->show(entry, buf); + + return ret; +} + +/** + * pdcspath_attr_store - Generic write function call wrapper. + * @kobj: The kobject to write info to. + * @attr: The attribute to be modified. + * @buf: The input buffer. + * @count: The size of the buffer. + */ +static ssize_t +pdcspath_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct pdcspath_entry *entry = to_pdcspath_entry(kobj); + struct pdcspath_attribute *pdcs_attr = to_pdcspath_attribute(attr); + ssize_t ret = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (pdcs_attr->store) + ret = pdcs_attr->store(entry, buf, count); + + return ret; +} + +static struct sysfs_ops pdcspath_attr_ops = { + .show = pdcspath_attr_show, + .store = pdcspath_attr_store, +}; + +/* These are the two attributes of any PDC path. */ +static PATHS_ATTR(hwpath, 0600, pdcspath_hwpath_read, pdcspath_hwpath_write); +static PATHS_ATTR(layer, 0600, pdcspath_layer_read, pdcspath_layer_write); + +static struct attribute *paths_subsys_attrs[] = { + &paths_attr_hwpath.attr, + &paths_attr_layer.attr, + NULL, +}; + +/* Specific kobject type for our PDC paths */ +static struct kobj_type ktype_pdcspath = { + .sysfs_ops = &pdcspath_attr_ops, + .default_attrs = paths_subsys_attrs, +}; + +/* We hard define the 4 types of path we expect to find */ +static PDCSPATH_ENTRY(PDCS_ADDR_PPRI, primary); +static PDCSPATH_ENTRY(PDCS_ADDR_PCON, console); +static PDCSPATH_ENTRY(PDCS_ADDR_PALT, alternative); +static PDCSPATH_ENTRY(PDCS_ADDR_PKBD, keyboard); + +/* An array containing all PDC paths we will deal with */ +static struct pdcspath_entry *pdcspath_entries[] = { + &pdcspath_entry_primary, + &pdcspath_entry_alternative, + &pdcspath_entry_console, + &pdcspath_entry_keyboard, + NULL, +}; + +/** + * pdcs_info_read - Pretty printing of the remaining useful data. + * @entry: An allocated and populated subsytem struct. We don't use it tho. + * @buf: The output buffer to write to. + * + * We will call this function to format the output of the 'info' attribute file. + * Please refer to PDC Procedures documentation, section PDC_STABLE to get a + * better insight of what we're doing here. + */ +static ssize_t +pdcs_info_read(struct subsystem *entry, char *buf) +{ + char *out = buf; + __u32 result; + struct device_path devpath; + char *tmpstr = NULL; + + if (!entry || !buf) + return -EINVAL; + + /* show the size of the stable storage */ + out += sprintf(out, "Stable Storage size: %ld bytes\n", pdcs_size); + + /* deal with flags */ + if (pdc_stable_read(PDCS_ADDR_PPRI, &devpath, sizeof(devpath)) != PDC_OK) + return -EIO; + + out += sprintf(out, "Autoboot: %s\n", (devpath.flags & PF_AUTOBOOT) ? "On" : "Off"); + out += sprintf(out, "Autosearch: %s\n", (devpath.flags & PF_AUTOSEARCH) ? "On" : "Off"); + out += sprintf(out, "Timer: %u s\n", (devpath.flags & PF_TIMER) ? (1 << (devpath.flags & PF_TIMER)) : 0); + + /* get OSID */ + if (pdc_stable_read(PDCS_ADDR_OSID, &result, sizeof(result)) != PDC_OK) + return -EIO; + + /* the actual result is 16 bits away */ + switch (result >> 16) { + case 0x0000: tmpstr = "No OS-dependent data"; break; + case 0x0001: tmpstr = "HP-UX dependent data"; break; + case 0x0002: tmpstr = "MPE-iX dependent data"; break; + case 0x0003: tmpstr = "OSF dependent data"; break; + case 0x0004: tmpstr = "HP-RT dependent data"; break; + case 0x0005: tmpstr = "Novell Netware dependent data"; break; + default: tmpstr = "Unknown"; break; + } + out += sprintf(out, "OS ID: %s (0x%.4x)\n", tmpstr, (result >> 16)); + + /* get fast-size */ + if (pdc_stable_read(PDCS_ADDR_FSIZ, &result, sizeof(result)) != PDC_OK) + return -EIO; + + out += sprintf(out, "Memory tested: "); + if ((result & 0x0F) < 0x0E) + out += sprintf(out, "%.3f MB", 0.256*(1<<(result & 0x0F))); + else + out += sprintf(out, "All"); + out += sprintf(out, "\n"); + + return out - buf; +} + +/** + * pdcs_info_write - This function handles boot flag modifying. + * @entry: An allocated and populated subsytem struct. We don't use it tho. + * @buf: The input buffer to read from. + * @count: The number of bytes to be read. + * + * We will call this function to change the current boot flags. + * We expect a precise syntax: + * \"n n\" (n == 0 or 1) to toggle respectively AutoBoot and AutoSearch + * + * As of now there is no incentive on my side to provide more "knobs" to that + * interface, since modifying the rest of the data is pretty meaningless when + * the machine is running and for the expected use of that facility, such as + * PALO setting up the boot disk when installing a Linux distribution... + */ +static ssize_t +pdcs_info_write(struct subsystem *entry, const char *buf, size_t count) +{ + struct pdcspath_entry *pathentry; + unsigned char flags; + char in[count+1], *temp; + char c; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (!entry || !buf || !count) + return -EINVAL; + + /* We'll use a local copy of buf */ + memset(in, 0, count+1); + strncpy(in, buf, count); + + /* Current flags are stored in primary boot path entry */ + pathentry = &pdcspath_entry_primary; + + /* Be nice to the existing flag record */ + flags = pathentry->devpath.flags; + + DPRINTK("%s: flags before: 0x%X\n", __func__, flags); + + temp = in; + + while (*temp && isspace(*temp)) + temp++; + + c = *temp++ - '0'; + if ((c != 0) && (c != 1)) + goto parse_error; + if (c == 0) + flags &= ~PF_AUTOBOOT; + else + flags |= PF_AUTOBOOT; + + if (*temp++ != ' ') + goto parse_error; + + c = *temp++ - '0'; + if ((c != 0) && (c != 1)) + goto parse_error; + if (c == 0) + flags &= ~PF_AUTOSEARCH; + else + flags |= PF_AUTOSEARCH; + + DPRINTK("%s: flags after: 0x%X\n", __func__, flags); + + /* So far so good, let's get in deep */ + + /* Change the path entry flags first */ + pathentry->devpath.flags = flags; + + /* Now, dive in. Write back to the hardware */ + WARN_ON(pdcspath_store(pathentry)); /* this warn should *NEVER* happen */ + + printk(KERN_INFO "PDC Stable Storage: changed flags to \"%s\"\n", buf); + + return count; + +parse_error: + printk(KERN_WARNING "%s: Parse error: expect \"n n\" (n == 0 or 1) for AB and AS\n", __func__); + return -EINVAL; +} + +/* The last attribute (the 'root' one actually) with all remaining data. */ +static PDCS_ATTR(info, 0600, pdcs_info_read, pdcs_info_write); + +static struct subsys_attribute *pdcs_subsys_attrs[] = { + &pdcs_attr_info, + NULL, /* maybe more in the future? */ +}; + +static decl_subsys(paths, &ktype_pdcspath, NULL); +static decl_subsys(pdc, NULL, NULL); + +/** + * pdcs_register_pathentries - Prepares path entries kobjects for sysfs usage. + * + * It creates kobjects corresponding to each path entry with nice sysfs + * links to the real device. This is where the magic takes place: when + * registering the subsystem attributes during module init, each kobject hereby + * created will show in the sysfs tree as a folder containing files as defined + * by path_subsys_attr[]. + */ +static inline int __init +pdcs_register_pathentries(void) +{ + unsigned short i; + struct pdcspath_entry *entry; + + for (i = 0; (entry = pdcspath_entries[i]); i++) { + if (pdcspath_fetch(entry) < 0) + continue; + + kobject_set_name(&entry->kobj, "%s", entry->name); + kobj_set_kset_s(entry, paths_subsys); + kobject_register(&entry->kobj); + + if (!entry->dev) + continue; + + /* Add a nice symlink to the real device */ + sysfs_create_link(&entry->kobj, &entry->dev->kobj, "device"); + } + + return 0; +} + +/** + * pdcs_unregister_pathentries - Routine called when unregistering the module. + */ +static inline void __exit +pdcs_unregister_pathentries(void) +{ + unsigned short i; + struct pdcspath_entry *entry; + + for (i = 0; (entry = pdcspath_entries[i]); i++) + if (entry->ready) + kobject_unregister(&entry->kobj); +} + +/* + * For now we register the pdc subsystem with the firmware subsystem + * and the paths subsystem with the pdc subsystem + */ +static int __init +pdc_stable_init(void) +{ + struct subsys_attribute *attr; + int i, rc = 0, error = 0; + + /* find the size of the stable storage */ + if (pdc_stable_get_size(&pdcs_size) != PDC_OK) + return -ENODEV; + + printk(KERN_INFO "PDC Stable Storage facility v%s\n", PDCS_VERSION); + + /* For now we'll register the pdc subsys within this driver */ + if ((rc = firmware_register(&pdc_subsys))) + return rc; + + /* Don't forget the info entry */ + for (i = 0; (attr = pdcs_subsys_attrs[i]) && !error; i++) + if (attr->show) + error = subsys_create_file(&pdc_subsys, attr); + + /* register the paths subsys as a subsystem of pdc subsys */ + kset_set_kset_s(&paths_subsys, pdc_subsys); + subsystem_register(&paths_subsys); + + /* now we create all "files" for the paths subsys */ + pdcs_register_pathentries(); + + return 0; +} + +static void __exit +pdc_stable_exit(void) +{ + pdcs_unregister_pathentries(); + subsystem_unregister(&paths_subsys); + + firmware_unregister(&pdc_subsys); +} + + +module_init(pdc_stable_init); +module_exit(pdc_stable_exit); diff --git a/drivers/parisc/power.c b/drivers/parisc/power.c new file mode 100644 index 00000000000..ff75e9296df --- /dev/null +++ b/drivers/parisc/power.c @@ -0,0 +1,278 @@ +/* + * linux/arch/parisc/kernel/power.c + * HP PARISC soft power switch support driver + * + * Copyright (c) 2001-2002 Helge Deller <deller@gmx.de> + * All rights reserved. + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * + * + * HINT: + * Support of the soft power switch button may be enabled or disabled at + * runtime through the "/proc/sys/kernel/power" procfs entry. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> + +#include <asm/pdc.h> +#include <asm/io.h> +#include <asm/led.h> +#include <asm/uaccess.h> + + +#ifdef DEBUG +# define DPRINTK(x...) printk(x) +#else +# define DPRINTK(x...) +#endif + + +/* filename in /proc which can be used to enable/disable the power switch */ +#define SYSCTL_FILENAME "sys/kernel/power" + + +#define DIAG_CODE(code) (0x14000000 + ((code)<<5)) + +/* this will go to processor.h or any other place... */ +/* taken from PCXL ERS page 82 */ +#define MFCPU_X(rDiagReg, t_ch, t_th, code) \ + (DIAG_CODE(code) + ((rDiagReg)<<21) + ((t_ch)<<16) + ((t_th)<<0) ) + +#define MTCPU(dr, gr) MFCPU_X(dr, gr, 0, 0x12) /* move value of gr to dr[dr] */ +#define MFCPU_C(dr, gr) MFCPU_X(dr, gr, 0, 0x30) /* for dr0 and dr8 only ! */ +#define MFCPU_T(dr, gr) MFCPU_X(dr, 0, gr, 0xa0) /* all dr except dr0 and dr8 */ + +#define __getDIAG(dr) ( { \ + register unsigned long __res asm("r28");\ + __asm__ __volatile__ ( \ + ".word %1\n nop\n" : "=&r" (__res) : "i" (MFCPU_T(dr,28)) \ + ); \ + __res; \ +} ) + + +static void deferred_poweroff(void *dummy) +{ + extern int cad_pid; /* from kernel/sys.c */ + if (kill_proc(cad_pid, SIGINT, 1)) { + /* just in case killing init process failed */ + machine_power_off(); + } +} + +/* + * This function gets called from interrupt context. + * As it's called within an interrupt, it wouldn't sync if we don't + * use schedule_work(). + */ + +static DECLARE_WORK(poweroff_work, deferred_poweroff, NULL); + +static void poweroff(void) +{ + static int powering_off; + + if (powering_off) + return; + + powering_off++; + schedule_work(&poweroff_work); +} + + +/* local time-counter for shutdown */ +static int shutdown_timer; + +/* check, give feedback and start shutdown after one second */ +static void process_shutdown(void) +{ + if (shutdown_timer == 0) + DPRINTK(KERN_INFO "Shutdown requested...\n"); + + shutdown_timer++; + + /* wait until the button was pressed for 1 second */ + if (shutdown_timer == HZ) { +#if defined (DEBUG) || defined(CONFIG_CHASSIS_LCD_LED) + static char msg[] = "Shutting down..."; +#endif + DPRINTK(KERN_INFO "%s\n", msg); + lcd_print(msg); + poweroff(); + } +} + + +/* main power switch tasklet struct (scheduled from time.c) */ +DECLARE_TASKLET_DISABLED(power_tasklet, NULL, 0); + +/* soft power switch enabled/disabled */ +int pwrsw_enabled = 1; + +/* + * On gecko style machines (e.g. 712/xx and 715/xx) + * the power switch status is stored in Bit 0 ("the highest bit") + * of CPU diagnose register 25. + * + */ +static void gecko_tasklet_func(unsigned long unused) +{ + if (!pwrsw_enabled) + return; + + if (__getDIAG(25) & 0x80000000) { + /* power switch button not pressed or released again */ + /* Warning: Some machines do never reset this DIAG flag! */ + shutdown_timer = 0; + } else { + process_shutdown(); + } +} + + + +/* + * Check the power switch status which is read from the + * real I/O location at soft_power_reg. + * Bit 31 ("the lowest bit) is the status of the power switch. + */ + +static void polling_tasklet_func(unsigned long soft_power_reg) +{ + unsigned long current_status; + + if (!pwrsw_enabled) + return; + + current_status = gsc_readl(soft_power_reg); + if (current_status & 0x1) { + /* power switch button not pressed */ + shutdown_timer = 0; + } else { + process_shutdown(); + } +} + + +/* + * powerfail interruption handler (irq IRQ_FROM_REGION(CPU_IRQ_REGION)+2) + */ +#if 0 +static void powerfail_interrupt(int code, void *x, struct pt_regs *regs) +{ + printk(KERN_CRIT "POWERFAIL INTERRUPTION !\n"); + poweroff(); +} +#endif + + + + +/* parisc_panic_event() is called by the panic handler. + * As soon as a panic occurs, our tasklets above will not be + * executed any longer. This function then re-enables the + * soft-power switch and allows the user to switch off the system + */ +static int parisc_panic_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + /* re-enable the soft-power switch */ + pdc_soft_power_button(0); + return NOTIFY_DONE; +} + +static struct notifier_block parisc_panic_block = { + .notifier_call = parisc_panic_event, + .priority = INT_MAX, +}; + + +static int __init power_init(void) +{ + unsigned long ret; + unsigned long soft_power_reg = 0; + +#if 0 + request_irq( IRQ_FROM_REGION(CPU_IRQ_REGION)+2, &powerfail_interrupt, + 0, "powerfail", NULL); +#endif + + /* enable the soft power switch if possible */ + ret = pdc_soft_power_info(&soft_power_reg); + if (ret == PDC_OK) + ret = pdc_soft_power_button(1); + if (ret != PDC_OK) + soft_power_reg = -1UL; + + switch (soft_power_reg) { + case 0: printk(KERN_INFO "Gecko-style soft power switch enabled.\n"); + power_tasklet.func = gecko_tasklet_func; + break; + + case -1UL: printk(KERN_INFO "Soft power switch support not available.\n"); + return -ENODEV; + + default: printk(KERN_INFO "Soft power switch enabled, polling @ 0x%08lx.\n", + soft_power_reg); + power_tasklet.data = soft_power_reg; + power_tasklet.func = polling_tasklet_func; + } + + /* Register a call for panic conditions. */ + notifier_chain_register(&panic_notifier_list, &parisc_panic_block); + + tasklet_enable(&power_tasklet); + + return 0; +} + +static void __exit power_exit(void) +{ + if (!power_tasklet.func) + return; + + tasklet_disable(&power_tasklet); + notifier_chain_unregister(&panic_notifier_list, &parisc_panic_block); + power_tasklet.func = NULL; + pdc_soft_power_button(0); +} + +module_init(power_init); +module_exit(power_exit); + + +MODULE_AUTHOR("Helge Deller"); +MODULE_DESCRIPTION("Soft power switch driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/parisc/sba_iommu.c b/drivers/parisc/sba_iommu.c new file mode 100644 index 00000000000..82ea68b55df --- /dev/null +++ b/drivers/parisc/sba_iommu.c @@ -0,0 +1,2165 @@ +/* +** System Bus Adapter (SBA) I/O MMU manager +** +** (c) Copyright 2000-2004 Grant Grundler <grundler @ parisc-linux x org> +** (c) Copyright 2004 Naresh Kumar Inna <knaresh at india x hp x com> +** (c) Copyright 2000-2004 Hewlett-Packard Company +** +** Portions (c) 1999 Dave S. Miller (from sparc64 I/O MMU code) +** +** 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 module initializes the IOC (I/O Controller) found on B1000/C3000/ +** J5000/J7000/N-class/L-class machines and their successors. +** +** FIXME: add DMA hint support programming in both sba and lba modules. +*/ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/init.h> + +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/pci.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/dma.h> /* for DMA_CHUNK_SIZE */ + +#include <asm/hardware.h> /* for register_parisc_driver() stuff */ + +#include <linux/proc_fs.h> +#include <asm/runway.h> /* for proc_runway_root */ +#include <asm/pdc.h> /* for PDC_MODEL_* */ +#include <asm/pdcpat.h> /* for is_pdc_pat() */ +#include <asm/parisc-device.h> + + +/* declared in arch/parisc/kernel/setup.c */ +extern struct proc_dir_entry * proc_mckinley_root; + +#define MODULE_NAME "SBA" + +#ifdef CONFIG_PROC_FS +/* depends on proc fs support. But costs CPU performance */ +#undef SBA_COLLECT_STATS +#endif + +/* +** The number of debug flags is a clue - this code is fragile. +** Don't even think about messing with it unless you have +** plenty of 710's to sacrifice to the computer gods. :^) +*/ +#undef DEBUG_SBA_INIT +#undef DEBUG_SBA_RUN +#undef DEBUG_SBA_RUN_SG +#undef DEBUG_SBA_RESOURCE +#undef ASSERT_PDIR_SANITY +#undef DEBUG_LARGE_SG_ENTRIES +#undef DEBUG_DMB_TRAP + +#ifdef DEBUG_SBA_INIT +#define DBG_INIT(x...) printk(x) +#else +#define DBG_INIT(x...) +#endif + +#ifdef DEBUG_SBA_RUN +#define DBG_RUN(x...) printk(x) +#else +#define DBG_RUN(x...) +#endif + +#ifdef DEBUG_SBA_RUN_SG +#define DBG_RUN_SG(x...) printk(x) +#else +#define DBG_RUN_SG(x...) +#endif + + +#ifdef DEBUG_SBA_RESOURCE +#define DBG_RES(x...) printk(x) +#else +#define DBG_RES(x...) +#endif + +#if defined(__LP64__) && !defined(CONFIG_PDC_NARROW) +/* "low end" PA8800 machines use ZX1 chipset */ +#define ZX1_SUPPORT +#endif + +#define SBA_INLINE __inline__ + + +/* +** The number of pdir entries to "free" before issueing +** a read to PCOM register to flush out PCOM writes. +** Interacts with allocation granularity (ie 4 or 8 entries +** allocated and free'd/purged at a time might make this +** less interesting). +*/ +#define DELAYED_RESOURCE_CNT 16 + +#define DEFAULT_DMA_HINT_REG 0 + +#define ASTRO_RUNWAY_PORT 0x582 +#define IKE_MERCED_PORT 0x803 +#define REO_MERCED_PORT 0x804 +#define REOG_MERCED_PORT 0x805 +#define PLUTO_MCKINLEY_PORT 0x880 + +#define SBA_FUNC_ID 0x0000 /* function id */ +#define SBA_FCLASS 0x0008 /* function class, bist, header, rev... */ + +#define IS_ASTRO(id) ((id)->hversion == ASTRO_RUNWAY_PORT) +#define IS_IKE(id) ((id)->hversion == IKE_MERCED_PORT) +#define IS_PLUTO(id) ((id)->hversion == PLUTO_MCKINLEY_PORT) + +#define SBA_FUNC_SIZE 4096 /* SBA configuration function reg set */ + +#define ASTRO_IOC_OFFSET (32 * SBA_FUNC_SIZE) +#define PLUTO_IOC_OFFSET (1 * SBA_FUNC_SIZE) +/* Ike's IOC's occupy functions 2 and 3 */ +#define IKE_IOC_OFFSET(p) ((p+2) * SBA_FUNC_SIZE) + +#define IOC_CTRL 0x8 /* IOC_CTRL offset */ +#define IOC_CTRL_TC (1 << 0) /* TOC Enable */ +#define IOC_CTRL_CE (1 << 1) /* Coalesce Enable */ +#define IOC_CTRL_DE (1 << 2) /* Dillon Enable */ +#define IOC_CTRL_RM (1 << 8) /* Real Mode */ +#define IOC_CTRL_NC (1 << 9) /* Non Coherent Mode */ +#define IOC_CTRL_D4 (1 << 11) /* Disable 4-byte coalescing */ +#define IOC_CTRL_DD (1 << 13) /* Disable distr. LMMIO range coalescing */ + +#define MAX_IOC 2 /* per Ike. Pluto/Astro only have 1. */ + +#define ROPES_PER_IOC 8 /* per Ike half or Pluto/Astro */ + + +/* +** Offsets into MBIB (Function 0 on Ike and hopefully Astro) +** Firmware programs this stuff. Don't touch it. +*/ +#define LMMIO_DIRECT0_BASE 0x300 +#define LMMIO_DIRECT0_MASK 0x308 +#define LMMIO_DIRECT0_ROUTE 0x310 + +#define LMMIO_DIST_BASE 0x360 +#define LMMIO_DIST_MASK 0x368 +#define LMMIO_DIST_ROUTE 0x370 + +#define IOS_DIST_BASE 0x390 +#define IOS_DIST_MASK 0x398 +#define IOS_DIST_ROUTE 0x3A0 + +#define IOS_DIRECT_BASE 0x3C0 +#define IOS_DIRECT_MASK 0x3C8 +#define IOS_DIRECT_ROUTE 0x3D0 + +/* +** Offsets into I/O TLB (Function 2 and 3 on Ike) +*/ +#define ROPE0_CTL 0x200 /* "regbus pci0" */ +#define ROPE1_CTL 0x208 +#define ROPE2_CTL 0x210 +#define ROPE3_CTL 0x218 +#define ROPE4_CTL 0x220 +#define ROPE5_CTL 0x228 +#define ROPE6_CTL 0x230 +#define ROPE7_CTL 0x238 + +#define HF_ENABLE 0x40 + + +#define IOC_IBASE 0x300 /* IO TLB */ +#define IOC_IMASK 0x308 +#define IOC_PCOM 0x310 +#define IOC_TCNFG 0x318 +#define IOC_PDIR_BASE 0x320 + +/* AGP GART driver looks for this */ +#define SBA_IOMMU_COOKIE 0x0000badbadc0ffeeUL + + +/* +** IOC supports 4/8/16/64KB page sizes (see TCNFG register) +** It's safer (avoid memory corruption) to keep DMA page mappings +** equivalently sized to VM PAGE_SIZE. +** +** We really can't avoid generating a new mapping for each +** page since the Virtual Coherence Index has to be generated +** and updated for each page. +** +** PAGE_SIZE could be greater than IOVP_SIZE. But not the inverse. +*/ +#define IOVP_SIZE PAGE_SIZE +#define IOVP_SHIFT PAGE_SHIFT +#define IOVP_MASK PAGE_MASK + +#define SBA_PERF_CFG 0x708 /* Performance Counter stuff */ +#define SBA_PERF_MASK1 0x718 +#define SBA_PERF_MASK2 0x730 + + +/* +** Offsets into PCI Performance Counters (functions 12 and 13) +** Controlled by PERF registers in function 2 & 3 respectively. +*/ +#define SBA_PERF_CNT1 0x200 +#define SBA_PERF_CNT2 0x208 +#define SBA_PERF_CNT3 0x210 + + +struct ioc { + void __iomem *ioc_hpa; /* I/O MMU base address */ + char *res_map; /* resource map, bit == pdir entry */ + u64 *pdir_base; /* physical base address */ + unsigned long ibase; /* pdir IOV Space base - shared w/lba_pci */ + unsigned long imask; /* pdir IOV Space mask - shared w/lba_pci */ +#ifdef ZX1_SUPPORT + unsigned long iovp_mask; /* help convert IOVA to IOVP */ +#endif + unsigned long *res_hint; /* next avail IOVP - circular search */ + spinlock_t res_lock; + unsigned int res_bitshift; /* from the LEFT! */ + unsigned int res_size; /* size of resource map in bytes */ +#if SBA_HINT_SUPPORT +/* FIXME : DMA HINTs not used */ + unsigned long hint_mask_pdir; /* bits used for DMA hints */ + unsigned int hint_shift_pdir; +#endif +#if DELAYED_RESOURCE_CNT > 0 + int saved_cnt; + struct sba_dma_pair { + dma_addr_t iova; + size_t size; + } saved[DELAYED_RESOURCE_CNT]; +#endif + +#ifdef SBA_COLLECT_STATS +#define SBA_SEARCH_SAMPLE 0x100 + unsigned long avg_search[SBA_SEARCH_SAMPLE]; + unsigned long avg_idx; /* current index into avg_search */ + unsigned long used_pages; + unsigned long msingle_calls; + unsigned long msingle_pages; + unsigned long msg_calls; + unsigned long msg_pages; + unsigned long usingle_calls; + unsigned long usingle_pages; + unsigned long usg_calls; + unsigned long usg_pages; +#endif + + /* STUFF We don't need in performance path */ + unsigned int pdir_size; /* in bytes, determined by IOV Space size */ +}; + +struct sba_device { + struct sba_device *next; /* list of SBA's in system */ + struct parisc_device *dev; /* dev found in bus walk */ + struct parisc_device_id *iodc; /* data about dev from firmware */ + const char *name; + void __iomem *sba_hpa; /* base address */ + spinlock_t sba_lock; + unsigned int flags; /* state/functionality enabled */ + unsigned int hw_rev; /* HW revision of chip */ + + struct resource chip_resv; /* MMIO reserved for chip */ + struct resource iommu_resv; /* MMIO reserved for iommu */ + + unsigned int num_ioc; /* number of on-board IOC's */ + struct ioc ioc[MAX_IOC]; +}; + + +static struct sba_device *sba_list; + +static unsigned long ioc_needs_fdc = 0; + +/* global count of IOMMUs in the system */ +static unsigned int global_ioc_cnt = 0; + +/* PA8700 (Piranha 2.2) bug workaround */ +static unsigned long piranha_bad_128k = 0; + +/* Looks nice and keeps the compiler happy */ +#define SBA_DEV(d) ((struct sba_device *) (d)) + +#if SBA_AGP_SUPPORT +static int reserve_sba_gart = 1; +#endif + +#define ROUNDUP(x,y) ((x + ((y)-1)) & ~((y)-1)) + + +/************************************ +** SBA register read and write support +** +** BE WARNED: register writes are posted. +** (ie follow writes which must reach HW with a read) +** +** Superdome (in particular, REO) allows only 64-bit CSR accesses. +*/ +#define READ_REG32(addr) le32_to_cpu(__raw_readl(addr)) +#define READ_REG64(addr) le64_to_cpu(__raw_readq(addr)) +#define WRITE_REG32(val, addr) __raw_writel(cpu_to_le32(val), addr) +#define WRITE_REG64(val, addr) __raw_writeq(cpu_to_le64(val), addr) + +#ifdef __LP64__ +#define READ_REG(addr) READ_REG64(addr) +#define WRITE_REG(value, addr) WRITE_REG64(value, addr) +#else +#define READ_REG(addr) READ_REG32(addr) +#define WRITE_REG(value, addr) WRITE_REG32(value, addr) +#endif + +#ifdef DEBUG_SBA_INIT + +/* NOTE: When __LP64__ isn't defined, READ_REG64() is two 32-bit reads */ + +/** + * sba_dump_ranges - debugging only - print ranges assigned to this IOA + * @hpa: base address of the sba + * + * Print the MMIO and IO Port address ranges forwarded by an Astro/Ike/RIO + * IO Adapter (aka Bus Converter). + */ +static void +sba_dump_ranges(void __iomem *hpa) +{ + DBG_INIT("SBA at 0x%p\n", hpa); + DBG_INIT("IOS_DIST_BASE : %Lx\n", READ_REG64(hpa+IOS_DIST_BASE)); + DBG_INIT("IOS_DIST_MASK : %Lx\n", READ_REG64(hpa+IOS_DIST_MASK)); + DBG_INIT("IOS_DIST_ROUTE : %Lx\n", READ_REG64(hpa+IOS_DIST_ROUTE)); + DBG_INIT("\n"); + DBG_INIT("IOS_DIRECT_BASE : %Lx\n", READ_REG64(hpa+IOS_DIRECT_BASE)); + DBG_INIT("IOS_DIRECT_MASK : %Lx\n", READ_REG64(hpa+IOS_DIRECT_MASK)); + DBG_INIT("IOS_DIRECT_ROUTE: %Lx\n", READ_REG64(hpa+IOS_DIRECT_ROUTE)); +} + +/** + * sba_dump_tlb - debugging only - print IOMMU operating parameters + * @hpa: base address of the IOMMU + * + * Print the size/location of the IO MMU PDIR. + */ +static void sba_dump_tlb(void __iomem *hpa) +{ + DBG_INIT("IO TLB at 0x%p\n", hpa); + DBG_INIT("IOC_IBASE : 0x%Lx\n", READ_REG64(hpa+IOC_IBASE)); + DBG_INIT("IOC_IMASK : 0x%Lx\n", READ_REG64(hpa+IOC_IMASK)); + DBG_INIT("IOC_TCNFG : 0x%Lx\n", READ_REG64(hpa+IOC_TCNFG)); + DBG_INIT("IOC_PDIR_BASE: 0x%Lx\n", READ_REG64(hpa+IOC_PDIR_BASE)); + DBG_INIT("\n"); +} +#else +#define sba_dump_ranges(x) +#define sba_dump_tlb(x) +#endif + + +#ifdef ASSERT_PDIR_SANITY + +/** + * sba_dump_pdir_entry - debugging only - print one IOMMU PDIR entry + * @ioc: IO MMU structure which owns the pdir we are interested in. + * @msg: text to print ont the output line. + * @pide: pdir index. + * + * Print one entry of the IO MMU PDIR in human readable form. + */ +static void +sba_dump_pdir_entry(struct ioc *ioc, char *msg, uint pide) +{ + /* start printing from lowest pde in rval */ + u64 *ptr = &(ioc->pdir_base[pide & (~0U * BITS_PER_LONG)]); + unsigned long *rptr = (unsigned long *) &(ioc->res_map[(pide >>3) & ~(sizeof(unsigned long) - 1)]); + uint rcnt; + + printk(KERN_DEBUG "SBA: %s rp %p bit %d rval 0x%lx\n", + msg, + rptr, pide & (BITS_PER_LONG - 1), *rptr); + + rcnt = 0; + while (rcnt < BITS_PER_LONG) { + printk(KERN_DEBUG "%s %2d %p %016Lx\n", + (rcnt == (pide & (BITS_PER_LONG - 1))) + ? " -->" : " ", + rcnt, ptr, *ptr ); + rcnt++; + ptr++; + } + printk(KERN_DEBUG "%s", msg); +} + + +/** + * sba_check_pdir - debugging only - consistency checker + * @ioc: IO MMU structure which owns the pdir we are interested in. + * @msg: text to print ont the output line. + * + * Verify the resource map and pdir state is consistent + */ +static int +sba_check_pdir(struct ioc *ioc, char *msg) +{ + u32 *rptr_end = (u32 *) &(ioc->res_map[ioc->res_size]); + u32 *rptr = (u32 *) ioc->res_map; /* resource map ptr */ + u64 *pptr = ioc->pdir_base; /* pdir ptr */ + uint pide = 0; + + while (rptr < rptr_end) { + u32 rval = *rptr; + int rcnt = 32; /* number of bits we might check */ + + while (rcnt) { + /* Get last byte and highest bit from that */ + u32 pde = ((u32) (((char *)pptr)[7])) << 24; + if ((rval ^ pde) & 0x80000000) + { + /* + ** BUMMER! -- res_map != pdir -- + ** Dump rval and matching pdir entries + */ + sba_dump_pdir_entry(ioc, msg, pide); + return(1); + } + rcnt--; + rval <<= 1; /* try the next bit */ + pptr++; + pide++; + } + rptr++; /* look at next word of res_map */ + } + /* It'd be nice if we always got here :^) */ + return 0; +} + + +/** + * sba_dump_sg - debugging only - print Scatter-Gather list + * @ioc: IO MMU structure which owns the pdir we are interested in. + * @startsg: head of the SG list + * @nents: number of entries in SG list + * + * print the SG list so we can verify it's correct by hand. + */ +static void +sba_dump_sg( struct ioc *ioc, struct scatterlist *startsg, int nents) +{ + while (nents-- > 0) { + printk(KERN_DEBUG " %d : %08lx/%05x %p/%05x\n", + nents, + (unsigned long) sg_dma_address(startsg), + sg_dma_len(startsg), + sg_virt_addr(startsg), startsg->length); + startsg++; + } +} + +#endif /* ASSERT_PDIR_SANITY */ + + + + +/************************************************************** +* +* I/O Pdir Resource Management +* +* Bits set in the resource map are in use. +* Each bit can represent a number of pages. +* LSbs represent lower addresses (IOVA's). +* +***************************************************************/ +#define PAGES_PER_RANGE 1 /* could increase this to 4 or 8 if needed */ + +/* Convert from IOVP to IOVA and vice versa. */ + +#ifdef ZX1_SUPPORT +/* Pluto (aka ZX1) boxes need to set or clear the ibase bits appropriately */ +#define SBA_IOVA(ioc,iovp,offset,hint_reg) ((ioc->ibase) | (iovp) | (offset)) +#define SBA_IOVP(ioc,iova) ((iova) & (ioc)->iovp_mask) +#else +/* only support Astro and ancestors. Saves a few cycles in key places */ +#define SBA_IOVA(ioc,iovp,offset,hint_reg) ((iovp) | (offset)) +#define SBA_IOVP(ioc,iova) (iova) +#endif + +#define PDIR_INDEX(iovp) ((iovp)>>IOVP_SHIFT) + +#define RESMAP_MASK(n) (~0UL << (BITS_PER_LONG - (n))) +#define RESMAP_IDX_MASK (sizeof(unsigned long) - 1) + + +/** + * sba_search_bitmap - find free space in IO PDIR resource bitmap + * @ioc: IO MMU structure which owns the pdir we are interested in. + * @bits_wanted: number of entries we need. + * + * Find consecutive free bits in resource bitmap. + * Each bit represents one entry in the IO Pdir. + * Cool perf optimization: search for log2(size) bits at a time. + */ +static SBA_INLINE unsigned long +sba_search_bitmap(struct ioc *ioc, unsigned long bits_wanted) +{ + unsigned long *res_ptr = ioc->res_hint; + unsigned long *res_end = (unsigned long *) &(ioc->res_map[ioc->res_size]); + unsigned long pide = ~0UL; + + if (bits_wanted > (BITS_PER_LONG/2)) { + /* Search word at a time - no mask needed */ + for(; res_ptr < res_end; ++res_ptr) { + if (*res_ptr == 0) { + *res_ptr = RESMAP_MASK(bits_wanted); + pide = ((unsigned long)res_ptr - (unsigned long)ioc->res_map); + pide <<= 3; /* convert to bit address */ + break; + } + } + /* point to the next word on next pass */ + res_ptr++; + ioc->res_bitshift = 0; + } else { + /* + ** Search the resource bit map on well-aligned values. + ** "o" is the alignment. + ** We need the alignment to invalidate I/O TLB using + ** SBA HW features in the unmap path. + */ + unsigned long o = 1 << get_order(bits_wanted << PAGE_SHIFT); + uint bitshiftcnt = ROUNDUP(ioc->res_bitshift, o); + unsigned long mask; + + if (bitshiftcnt >= BITS_PER_LONG) { + bitshiftcnt = 0; + res_ptr++; + } + mask = RESMAP_MASK(bits_wanted) >> bitshiftcnt; + + DBG_RES("%s() o %ld %p", __FUNCTION__, o, res_ptr); + while(res_ptr < res_end) + { + DBG_RES(" %p %lx %lx\n", res_ptr, mask, *res_ptr); + WARN_ON(mask == 0); + if(((*res_ptr) & mask) == 0) { + *res_ptr |= mask; /* mark resources busy! */ + pide = ((unsigned long)res_ptr - (unsigned long)ioc->res_map); + pide <<= 3; /* convert to bit address */ + pide += bitshiftcnt; + break; + } + mask >>= o; + bitshiftcnt += o; + if (mask == 0) { + mask = RESMAP_MASK(bits_wanted); + bitshiftcnt=0; + res_ptr++; + } + } + /* look in the same word on the next pass */ + ioc->res_bitshift = bitshiftcnt + bits_wanted; + } + + /* wrapped ? */ + if (res_end <= res_ptr) { + ioc->res_hint = (unsigned long *) ioc->res_map; + ioc->res_bitshift = 0; + } else { + ioc->res_hint = res_ptr; + } + return (pide); +} + + +/** + * sba_alloc_range - find free bits and mark them in IO PDIR resource bitmap + * @ioc: IO MMU structure which owns the pdir we are interested in. + * @size: number of bytes to create a mapping for + * + * Given a size, find consecutive unmarked and then mark those bits in the + * resource bit map. + */ +static int +sba_alloc_range(struct ioc *ioc, size_t size) +{ + unsigned int pages_needed = size >> IOVP_SHIFT; +#ifdef SBA_COLLECT_STATS + unsigned long cr_start = mfctl(16); +#endif + unsigned long pide; + + pide = sba_search_bitmap(ioc, pages_needed); + if (pide >= (ioc->res_size << 3)) { + pide = sba_search_bitmap(ioc, pages_needed); + if (pide >= (ioc->res_size << 3)) + panic("%s: I/O MMU @ %p is out of mapping resources\n", + __FILE__, ioc->ioc_hpa); + } + +#ifdef ASSERT_PDIR_SANITY + /* verify the first enable bit is clear */ + if(0x00 != ((u8 *) ioc->pdir_base)[pide*sizeof(u64) + 7]) { + sba_dump_pdir_entry(ioc, "sba_search_bitmap() botched it?", pide); + } +#endif + + DBG_RES("%s(%x) %d -> %lx hint %x/%x\n", + __FUNCTION__, size, pages_needed, pide, + (uint) ((unsigned long) ioc->res_hint - (unsigned long) ioc->res_map), + ioc->res_bitshift ); + +#ifdef SBA_COLLECT_STATS + { + unsigned long cr_end = mfctl(16); + unsigned long tmp = cr_end - cr_start; + /* check for roll over */ + cr_start = (cr_end < cr_start) ? -(tmp) : (tmp); + } + ioc->avg_search[ioc->avg_idx++] = cr_start; + ioc->avg_idx &= SBA_SEARCH_SAMPLE - 1; + + ioc->used_pages += pages_needed; +#endif + + return (pide); +} + + +/** + * sba_free_range - unmark bits in IO PDIR resource bitmap + * @ioc: IO MMU structure which owns the pdir we are interested in. + * @iova: IO virtual address which was previously allocated. + * @size: number of bytes to create a mapping for + * + * clear bits in the ioc's resource map + */ +static SBA_INLINE void +sba_free_range(struct ioc *ioc, dma_addr_t iova, size_t size) +{ + unsigned long iovp = SBA_IOVP(ioc, iova); + unsigned int pide = PDIR_INDEX(iovp); + unsigned int ridx = pide >> 3; /* convert bit to byte address */ + unsigned long *res_ptr = (unsigned long *) &((ioc)->res_map[ridx & ~RESMAP_IDX_MASK]); + + int bits_not_wanted = size >> IOVP_SHIFT; + + /* 3-bits "bit" address plus 2 (or 3) bits for "byte" == bit in word */ + unsigned long m = RESMAP_MASK(bits_not_wanted) >> (pide & (BITS_PER_LONG - 1)); + + DBG_RES("%s( ,%x,%x) %x/%lx %x %p %lx\n", + __FUNCTION__, (uint) iova, size, + bits_not_wanted, m, pide, res_ptr, *res_ptr); + +#ifdef SBA_COLLECT_STATS + ioc->used_pages -= bits_not_wanted; +#endif + + *res_ptr &= ~m; +} + + +/************************************************************** +* +* "Dynamic DMA Mapping" support (aka "Coherent I/O") +* +***************************************************************/ + +#if SBA_HINT_SUPPORT +#define SBA_DMA_HINT(ioc, val) ((val) << (ioc)->hint_shift_pdir) +#endif + +typedef unsigned long space_t; +#define KERNEL_SPACE 0 + +/** + * sba_io_pdir_entry - fill in one IO PDIR entry + * @pdir_ptr: pointer to IO PDIR entry + * @sid: process Space ID - currently only support KERNEL_SPACE + * @vba: Virtual CPU address of buffer to map + * @hint: DMA hint set to use for this mapping + * + * SBA Mapping Routine + * + * Given a virtual address (vba, arg2) and space id, (sid, arg1) + * sba_io_pdir_entry() loads the I/O PDIR entry pointed to by + * pdir_ptr (arg0). + * Using the bass-ackwards HP bit numbering, Each IO Pdir entry + * for Astro/Ike looks like: + * + * + * 0 19 51 55 63 + * +-+---------------------+----------------------------------+----+--------+ + * |V| U | PPN[43:12] | U | VI | + * +-+---------------------+----------------------------------+----+--------+ + * + * Pluto is basically identical, supports fewer physical address bits: + * + * 0 23 51 55 63 + * +-+------------------------+-------------------------------+----+--------+ + * |V| U | PPN[39:12] | U | VI | + * +-+------------------------+-------------------------------+----+--------+ + * + * V == Valid Bit (Most Significant Bit is bit 0) + * U == Unused + * PPN == Physical Page Number + * VI == Virtual Index (aka Coherent Index) + * + * LPA instruction output is put into PPN field. + * LCI (Load Coherence Index) instruction provides the "VI" bits. + * + * We pre-swap the bytes since PCX-W is Big Endian and the + * IOMMU uses little endian for the pdir. + */ + +void SBA_INLINE +sba_io_pdir_entry(u64 *pdir_ptr, space_t sid, unsigned long vba, + unsigned long hint) +{ + u64 pa; /* physical address */ + register unsigned ci; /* coherent index */ + + pa = virt_to_phys(vba); + pa &= IOVP_MASK; + + mtsp(sid,1); + asm("lci 0(%%sr1, %1), %0" : "=r" (ci) : "r" (vba)); + pa |= (ci >> 12) & 0xff; /* move CI (8 bits) into lowest byte */ + + pa |= 0x8000000000000000ULL; /* set "valid" bit */ + *pdir_ptr = cpu_to_le64(pa); /* swap and store into I/O Pdir */ + + /* + * If the PDC_MODEL capabilities has Non-coherent IO-PDIR bit set + * (bit #61, big endian), we have to flush and sync every time + * IO-PDIR is changed in Ike/Astro. + */ + if (ioc_needs_fdc) { + asm volatile("fdc 0(%%sr1,%0)\n\tsync" : : "r" (pdir_ptr)); + } +} + + +/** + * sba_mark_invalid - invalidate one or more IO PDIR entries + * @ioc: IO MMU structure which owns the pdir we are interested in. + * @iova: IO Virtual Address mapped earlier + * @byte_cnt: number of bytes this mapping covers. + * + * Marking the IO PDIR entry(ies) as Invalid and invalidate + * corresponding IO TLB entry. The Ike PCOM (Purge Command Register) + * is to purge stale entries in the IO TLB when unmapping entries. + * + * The PCOM register supports purging of multiple pages, with a minium + * of 1 page and a maximum of 2GB. Hardware requires the address be + * aligned to the size of the range being purged. The size of the range + * must be a power of 2. The "Cool perf optimization" in the + * allocation routine helps keep that true. + */ +static SBA_INLINE void +sba_mark_invalid(struct ioc *ioc, dma_addr_t iova, size_t byte_cnt) +{ + u32 iovp = (u32) SBA_IOVP(ioc,iova); + + /* Even though this is a big-endian machine, the entries + ** in the iopdir are little endian. That's why we clear the byte + ** at +7 instead of at +0. + */ + int off = PDIR_INDEX(iovp)*sizeof(u64)+7; + +#ifdef ASSERT_PDIR_SANITY + /* Assert first pdir entry is set */ + if (0x80 != (((u8 *) ioc->pdir_base)[off])) { + sba_dump_pdir_entry(ioc,"sba_mark_invalid()", PDIR_INDEX(iovp)); + } +#endif + + if (byte_cnt <= IOVP_SIZE) + { + iovp |= IOVP_SHIFT; /* set "size" field for PCOM */ + + /* + ** clear I/O PDIR entry "valid" bit + ** Do NOT clear the rest - save it for debugging. + ** We should only clear bits that have previously + ** been enabled. + */ + ((u8 *)(ioc->pdir_base))[off] = 0; + } else { + u32 t = get_order(byte_cnt) + PAGE_SHIFT; + + iovp |= t; + do { + /* clear I/O Pdir entry "valid" bit first */ + ((u8 *)(ioc->pdir_base))[off] = 0; + off += sizeof(u64); + byte_cnt -= IOVP_SIZE; + } while (byte_cnt > 0); + } + + WRITE_REG( SBA_IOVA(ioc, iovp, 0, 0), ioc->ioc_hpa+IOC_PCOM); +} + +/** + * sba_dma_supported - PCI driver can query DMA support + * @dev: instance of PCI owned by the driver that's asking + * @mask: number of address bits this PCI device can handle + * + * See Documentation/DMA-mapping.txt + */ +static int sba_dma_supported( struct device *dev, u64 mask) +{ + struct ioc *ioc; + if (dev == NULL) { + printk(KERN_ERR MODULE_NAME ": EISA/ISA/et al not supported\n"); + BUG(); + return(0); + } + + ioc = GET_IOC(dev); + + /* check if mask is > than the largest IO Virt Address */ + + return((int) (mask >= (ioc->ibase + + (ioc->pdir_size / sizeof(u64) * IOVP_SIZE) ))); +} + + +/** + * sba_map_single - map one buffer and return IOVA for DMA + * @dev: instance of PCI owned by the driver that's asking. + * @addr: driver buffer to map. + * @size: number of bytes to map in driver buffer. + * @direction: R/W or both. + * + * See Documentation/DMA-mapping.txt + */ +static dma_addr_t +sba_map_single(struct device *dev, void *addr, size_t size, + enum dma_data_direction direction) +{ + struct ioc *ioc; + unsigned long flags; + dma_addr_t iovp; + dma_addr_t offset; + u64 *pdir_start; + int pide; + + ioc = GET_IOC(dev); + + /* save offset bits */ + offset = ((dma_addr_t) (long) addr) & ~IOVP_MASK; + + /* round up to nearest IOVP_SIZE */ + size = (size + offset + ~IOVP_MASK) & IOVP_MASK; + + spin_lock_irqsave(&ioc->res_lock, flags); +#ifdef ASSERT_PDIR_SANITY + sba_check_pdir(ioc,"Check before sba_map_single()"); +#endif + +#ifdef SBA_COLLECT_STATS + ioc->msingle_calls++; + ioc->msingle_pages += size >> IOVP_SHIFT; +#endif + pide = sba_alloc_range(ioc, size); + iovp = (dma_addr_t) pide << IOVP_SHIFT; + + DBG_RUN("%s() 0x%p -> 0x%lx\n", + __FUNCTION__, addr, (long) iovp | offset); + + pdir_start = &(ioc->pdir_base[pide]); + + while (size > 0) { + sba_io_pdir_entry(pdir_start, KERNEL_SPACE, (unsigned long) addr, 0); + + DBG_RUN(" pdir 0x%p %02x%02x%02x%02x%02x%02x%02x%02x\n", + pdir_start, + (u8) (((u8 *) pdir_start)[7]), + (u8) (((u8 *) pdir_start)[6]), + (u8) (((u8 *) pdir_start)[5]), + (u8) (((u8 *) pdir_start)[4]), + (u8) (((u8 *) pdir_start)[3]), + (u8) (((u8 *) pdir_start)[2]), + (u8) (((u8 *) pdir_start)[1]), + (u8) (((u8 *) pdir_start)[0]) + ); + + addr += IOVP_SIZE; + size -= IOVP_SIZE; + pdir_start++; + } + /* form complete address */ +#ifdef ASSERT_PDIR_SANITY + sba_check_pdir(ioc,"Check after sba_map_single()"); +#endif + spin_unlock_irqrestore(&ioc->res_lock, flags); + return SBA_IOVA(ioc, iovp, offset, DEFAULT_DMA_HINT_REG); +} + + +/** + * sba_unmap_single - unmap one IOVA and free resources + * @dev: instance of PCI owned by the driver that's asking. + * @iova: IOVA of driver buffer previously mapped. + * @size: number of bytes mapped in driver buffer. + * @direction: R/W or both. + * + * See Documentation/DMA-mapping.txt + */ +static void +sba_unmap_single(struct device *dev, dma_addr_t iova, size_t size, + enum dma_data_direction direction) +{ + struct ioc *ioc; +#if DELAYED_RESOURCE_CNT > 0 + struct sba_dma_pair *d; +#endif + unsigned long flags; + dma_addr_t offset; + + DBG_RUN("%s() iovp 0x%lx/%x\n", __FUNCTION__, (long) iova, size); + + ioc = GET_IOC(dev); + offset = iova & ~IOVP_MASK; + iova ^= offset; /* clear offset bits */ + size += offset; + size = ROUNDUP(size, IOVP_SIZE); + + spin_lock_irqsave(&ioc->res_lock, flags); + +#ifdef SBA_COLLECT_STATS + ioc->usingle_calls++; + ioc->usingle_pages += size >> IOVP_SHIFT; +#endif + + sba_mark_invalid(ioc, iova, size); + +#if DELAYED_RESOURCE_CNT > 0 + /* Delaying when we re-use a IO Pdir entry reduces the number + * of MMIO reads needed to flush writes to the PCOM register. + */ + d = &(ioc->saved[ioc->saved_cnt]); + d->iova = iova; + d->size = size; + if (++(ioc->saved_cnt) >= DELAYED_RESOURCE_CNT) { + int cnt = ioc->saved_cnt; + while (cnt--) { + sba_free_range(ioc, d->iova, d->size); + d--; + } + ioc->saved_cnt = 0; + READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */ + } +#else /* DELAYED_RESOURCE_CNT == 0 */ + sba_free_range(ioc, iova, size); + READ_REG(ioc->ioc_hpa+IOC_PCOM); /* flush purges */ +#endif /* DELAYED_RESOURCE_CNT == 0 */ + spin_unlock_irqrestore(&ioc->res_lock, flags); + + /* XXX REVISIT for 2.5 Linux - need syncdma for zero-copy support. + ** For Astro based systems this isn't a big deal WRT performance. + ** As long as 2.4 kernels copyin/copyout data from/to userspace, + ** we don't need the syncdma. The issue here is I/O MMU cachelines + ** are *not* coherent in all cases. May be hwrev dependent. + ** Need to investigate more. + asm volatile("syncdma"); + */ +} + + +/** + * sba_alloc_consistent - allocate/map shared mem for DMA + * @hwdev: instance of PCI owned by the driver that's asking. + * @size: number of bytes mapped in driver buffer. + * @dma_handle: IOVA of new buffer. + * + * See Documentation/DMA-mapping.txt + */ +static void *sba_alloc_consistent(struct device *hwdev, size_t size, + dma_addr_t *dma_handle, int gfp) +{ + void *ret; + + if (!hwdev) { + /* only support PCI */ + *dma_handle = 0; + return 0; + } + + ret = (void *) __get_free_pages(gfp, get_order(size)); + + if (ret) { + memset(ret, 0, size); + *dma_handle = sba_map_single(hwdev, ret, size, 0); + } + + return ret; +} + + +/** + * sba_free_consistent - free/unmap shared mem for DMA + * @hwdev: instance of PCI owned by the driver that's asking. + * @size: number of bytes mapped in driver buffer. + * @vaddr: virtual address IOVA of "consistent" buffer. + * @dma_handler: IO virtual address of "consistent" buffer. + * + * See Documentation/DMA-mapping.txt + */ +static void +sba_free_consistent(struct device *hwdev, size_t size, void *vaddr, + dma_addr_t dma_handle) +{ + sba_unmap_single(hwdev, dma_handle, size, 0); + free_pages((unsigned long) vaddr, get_order(size)); +} + + +/* +** Since 0 is a valid pdir_base index value, can't use that +** to determine if a value is valid or not. Use a flag to indicate +** the SG list entry contains a valid pdir index. +*/ +#define PIDE_FLAG 0x80000000UL + +#ifdef SBA_COLLECT_STATS +#define IOMMU_MAP_STATS +#endif +#include "iommu-helpers.h" + +#ifdef DEBUG_LARGE_SG_ENTRIES +int dump_run_sg = 0; +#endif + + +/** + * sba_map_sg - map Scatter/Gather list + * @dev: instance of PCI owned by the driver that's asking. + * @sglist: array of buffer/length pairs + * @nents: number of entries in list + * @direction: R/W or both. + * + * See Documentation/DMA-mapping.txt + */ +static int +sba_map_sg(struct device *dev, struct scatterlist *sglist, int nents, + enum dma_data_direction direction) +{ + struct ioc *ioc; + int coalesced, filled = 0; + unsigned long flags; + + DBG_RUN_SG("%s() START %d entries\n", __FUNCTION__, nents); + + ioc = GET_IOC(dev); + + /* Fast path single entry scatterlists. */ + if (nents == 1) { + sg_dma_address(sglist) = sba_map_single(dev, + (void *)sg_virt_addr(sglist), + sglist->length, direction); + sg_dma_len(sglist) = sglist->length; + return 1; + } + + spin_lock_irqsave(&ioc->res_lock, flags); + +#ifdef ASSERT_PDIR_SANITY + if (sba_check_pdir(ioc,"Check before sba_map_sg()")) + { + sba_dump_sg(ioc, sglist, nents); + panic("Check before sba_map_sg()"); + } +#endif + +#ifdef SBA_COLLECT_STATS + ioc->msg_calls++; +#endif + + /* + ** First coalesce the chunks and allocate I/O pdir space + ** + ** If this is one DMA stream, we can properly map using the + ** correct virtual address associated with each DMA page. + ** w/o this association, we wouldn't have coherent DMA! + ** Access to the virtual address is what forces a two pass algorithm. + */ + coalesced = iommu_coalesce_chunks(ioc, sglist, nents, sba_alloc_range); + + /* + ** Program the I/O Pdir + ** + ** map the virtual addresses to the I/O Pdir + ** o dma_address will contain the pdir index + ** o dma_len will contain the number of bytes to map + ** o address contains the virtual address. + */ + filled = iommu_fill_pdir(ioc, sglist, nents, 0, sba_io_pdir_entry); + +#ifdef ASSERT_PDIR_SANITY + if (sba_check_pdir(ioc,"Check after sba_map_sg()")) + { + sba_dump_sg(ioc, sglist, nents); + panic("Check after sba_map_sg()\n"); + } +#endif + + spin_unlock_irqrestore(&ioc->res_lock, flags); + + DBG_RUN_SG("%s() DONE %d mappings\n", __FUNCTION__, filled); + + return filled; +} + + +/** + * sba_unmap_sg - unmap Scatter/Gather list + * @dev: instance of PCI owned by the driver that's asking. + * @sglist: array of buffer/length pairs + * @nents: number of entries in list + * @direction: R/W or both. + * + * See Documentation/DMA-mapping.txt + */ +static void +sba_unmap_sg(struct device *dev, struct scatterlist *sglist, int nents, + enum dma_data_direction direction) +{ + struct ioc *ioc; +#ifdef ASSERT_PDIR_SANITY + unsigned long flags; +#endif + + DBG_RUN_SG("%s() START %d entries, %p,%x\n", + __FUNCTION__, nents, sg_virt_addr(sglist), sglist->length); + + ioc = GET_IOC(dev); + +#ifdef SBA_COLLECT_STATS + ioc->usg_calls++; +#endif + +#ifdef ASSERT_PDIR_SANITY + spin_lock_irqsave(&ioc->res_lock, flags); + sba_check_pdir(ioc,"Check before sba_unmap_sg()"); + spin_unlock_irqrestore(&ioc->res_lock, flags); +#endif + + while (sg_dma_len(sglist) && nents--) { + + sba_unmap_single(dev, sg_dma_address(sglist), sg_dma_len(sglist), direction); +#ifdef SBA_COLLECT_STATS + ioc->usg_pages += ((sg_dma_address(sglist) & ~IOVP_MASK) + sg_dma_len(sglist) + IOVP_SIZE - 1) >> PAGE_SHIFT; + ioc->usingle_calls--; /* kluge since call is unmap_sg() */ +#endif + ++sglist; + } + + DBG_RUN_SG("%s() DONE (nents %d)\n", __FUNCTION__, nents); + +#ifdef ASSERT_PDIR_SANITY + spin_lock_irqsave(&ioc->res_lock, flags); + sba_check_pdir(ioc,"Check after sba_unmap_sg()"); + spin_unlock_irqrestore(&ioc->res_lock, flags); +#endif + +} + +static struct hppa_dma_ops sba_ops = { + .dma_supported = sba_dma_supported, + .alloc_consistent = sba_alloc_consistent, + .alloc_noncoherent = sba_alloc_consistent, + .free_consistent = sba_free_consistent, + .map_single = sba_map_single, + .unmap_single = sba_unmap_single, + .map_sg = sba_map_sg, + .unmap_sg = sba_unmap_sg, + .dma_sync_single_for_cpu = NULL, + .dma_sync_single_for_device = NULL, + .dma_sync_sg_for_cpu = NULL, + .dma_sync_sg_for_device = NULL, +}; + + +/************************************************************************** +** +** SBA PAT PDC support +** +** o call pdc_pat_cell_module() +** o store ranges in PCI "resource" structures +** +**************************************************************************/ + +static void +sba_get_pat_resources(struct sba_device *sba_dev) +{ +#if 0 +/* +** TODO/REVISIT/FIXME: support for directed ranges requires calls to +** PAT PDC to program the SBA/LBA directed range registers...this +** burden may fall on the LBA code since it directly supports the +** PCI subsystem. It's not clear yet. - ggg +*/ +PAT_MOD(mod)->mod_info.mod_pages = PAT_GET_MOD_PAGES(temp); + FIXME : ??? +PAT_MOD(mod)->mod_info.dvi = PAT_GET_DVI(temp); + Tells where the dvi bits are located in the address. +PAT_MOD(mod)->mod_info.ioc = PAT_GET_IOC(temp); + FIXME : ??? +#endif +} + + +/************************************************************** +* +* Initialization and claim +* +***************************************************************/ +#define PIRANHA_ADDR_MASK 0x00160000UL /* bit 17,18,20 */ +#define PIRANHA_ADDR_VAL 0x00060000UL /* bit 17,18 on */ +static void * +sba_alloc_pdir(unsigned int pdir_size) +{ + unsigned long pdir_base; + unsigned long pdir_order = get_order(pdir_size); + + pdir_base = __get_free_pages(GFP_KERNEL, pdir_order); + if (NULL == (void *) pdir_base) + panic("sba_ioc_init() could not allocate I/O Page Table\n"); + + /* If this is not PA8700 (PCX-W2) + ** OR newer than ver 2.2 + ** OR in a system that doesn't need VINDEX bits from SBA, + ** + ** then we aren't exposed to the HW bug. + */ + if ( ((boot_cpu_data.pdc.cpuid >> 5) & 0x7f) != 0x13 + || (boot_cpu_data.pdc.versions > 0x202) + || (boot_cpu_data.pdc.capabilities & 0x08L) ) + return (void *) pdir_base; + + /* + * PA8700 (PCX-W2, aka piranha) silent data corruption fix + * + * An interaction between PA8700 CPU (Ver 2.2 or older) and + * Ike/Astro can cause silent data corruption. This is only + * a problem if the I/O PDIR is located in memory such that + * (little-endian) bits 17 and 18 are on and bit 20 is off. + * + * Since the max IO Pdir size is 2MB, by cleverly allocating the + * right physical address, we can either avoid (IOPDIR <= 1MB) + * or minimize (2MB IO Pdir) the problem if we restrict the + * IO Pdir to a maximum size of 2MB-128K (1902K). + * + * Because we always allocate 2^N sized IO pdirs, either of the + * "bad" regions will be the last 128K if at all. That's easy + * to test for. + * + */ + if (pdir_order <= (19-12)) { + if (((virt_to_phys(pdir_base)+pdir_size-1) & PIRANHA_ADDR_MASK) == PIRANHA_ADDR_VAL) { + /* allocate a new one on 512k alignment */ + unsigned long new_pdir = __get_free_pages(GFP_KERNEL, (19-12)); + /* release original */ + free_pages(pdir_base, pdir_order); + + pdir_base = new_pdir; + + /* release excess */ + while (pdir_order < (19-12)) { + new_pdir += pdir_size; + free_pages(new_pdir, pdir_order); + pdir_order +=1; + pdir_size <<=1; + } + } + } else { + /* + ** 1MB or 2MB Pdir + ** Needs to be aligned on an "odd" 1MB boundary. + */ + unsigned long new_pdir = __get_free_pages(GFP_KERNEL, pdir_order+1); /* 2 or 4MB */ + + /* release original */ + free_pages( pdir_base, pdir_order); + + /* release first 1MB */ + free_pages(new_pdir, 20-12); + + pdir_base = new_pdir + 1024*1024; + + if (pdir_order > (20-12)) { + /* + ** 2MB Pdir. + ** + ** Flag tells init_bitmap() to mark bad 128k as used + ** and to reduce the size by 128k. + */ + piranha_bad_128k = 1; + + new_pdir += 3*1024*1024; + /* release last 1MB */ + free_pages(new_pdir, 20-12); + + /* release unusable 128KB */ + free_pages(new_pdir - 128*1024 , 17-12); + + pdir_size -= 128*1024; + } + } + + memset((void *) pdir_base, 0, pdir_size); + return (void *) pdir_base; +} + +/* setup Mercury or Elroy IBASE/IMASK registers. */ +static void setup_ibase_imask(struct parisc_device *sba, struct ioc *ioc, int ioc_num) +{ + /* lba_set_iregs() is in drivers/parisc/lba_pci.c */ + extern void lba_set_iregs(struct parisc_device *, u32, u32); + struct device *dev; + + list_for_each_entry(dev, &sba->dev.children, node) { + struct parisc_device *lba = to_parisc_device(dev); + int rope_num = (lba->hpa >> 13) & 0xf; + if (rope_num >> 3 == ioc_num) + lba_set_iregs(lba, ioc->ibase, ioc->imask); + } +} + +static void +sba_ioc_init_pluto(struct parisc_device *sba, struct ioc *ioc, int ioc_num) +{ + u32 iova_space_mask; + u32 iova_space_size; + int iov_order, tcnfg; +#if SBA_AGP_SUPPORT + int agp_found = 0; +#endif + /* + ** Firmware programs the base and size of a "safe IOVA space" + ** (one that doesn't overlap memory or LMMIO space) in the + ** IBASE and IMASK registers. + */ + ioc->ibase = READ_REG(ioc->ioc_hpa + IOC_IBASE); + iova_space_size = ~(READ_REG(ioc->ioc_hpa + IOC_IMASK) & 0xFFFFFFFFUL) + 1; + + if ((ioc->ibase < 0xfed00000UL) && ((ioc->ibase + iova_space_size) > 0xfee00000UL)) { + printk("WARNING: IOV space overlaps local config and interrupt message, truncating\n"); + iova_space_size /= 2; + } + + /* + ** iov_order is always based on a 1GB IOVA space since we want to + ** turn on the other half for AGP GART. + */ + iov_order = get_order(iova_space_size >> (IOVP_SHIFT - PAGE_SHIFT)); + ioc->pdir_size = (iova_space_size / IOVP_SIZE) * sizeof(u64); + + DBG_INIT("%s() hpa 0x%lx IOV %dMB (%d bits)\n", + __FUNCTION__, ioc->ioc_hpa, iova_space_size >> 20, + iov_order + PAGE_SHIFT); + + ioc->pdir_base = (void *) __get_free_pages(GFP_KERNEL, + get_order(ioc->pdir_size)); + if (!ioc->pdir_base) + panic("Couldn't allocate I/O Page Table\n"); + + memset(ioc->pdir_base, 0, ioc->pdir_size); + + DBG_INIT("%s() pdir %p size %x\n", + __FUNCTION__, ioc->pdir_base, ioc->pdir_size); + +#if SBA_HINT_SUPPORT + ioc->hint_shift_pdir = iov_order + PAGE_SHIFT; + ioc->hint_mask_pdir = ~(0x3 << (iov_order + PAGE_SHIFT)); + + DBG_INIT(" hint_shift_pdir %x hint_mask_pdir %lx\n", + ioc->hint_shift_pdir, ioc->hint_mask_pdir); +#endif + + WARN_ON((((unsigned long) ioc->pdir_base) & PAGE_MASK) != (unsigned long) ioc->pdir_base); + WRITE_REG(virt_to_phys(ioc->pdir_base), ioc->ioc_hpa + IOC_PDIR_BASE); + + /* build IMASK for IOC and Elroy */ + iova_space_mask = 0xffffffff; + iova_space_mask <<= (iov_order + PAGE_SHIFT); + ioc->imask = iova_space_mask; +#ifdef ZX1_SUPPORT + ioc->iovp_mask = ~(iova_space_mask + PAGE_SIZE - 1); +#endif + sba_dump_tlb(ioc->ioc_hpa); + + setup_ibase_imask(sba, ioc, ioc_num); + + WRITE_REG(ioc->imask, ioc->ioc_hpa + IOC_IMASK); + +#ifdef __LP64__ + /* + ** Setting the upper bits makes checking for bypass addresses + ** a little faster later on. + */ + ioc->imask |= 0xFFFFFFFF00000000UL; +#endif + + /* Set I/O PDIR Page size to system page size */ + switch (PAGE_SHIFT) { + case 12: tcnfg = 0; break; /* 4K */ + case 13: tcnfg = 1; break; /* 8K */ + case 14: tcnfg = 2; break; /* 16K */ + case 16: tcnfg = 3; break; /* 64K */ + default: + panic(__FILE__ "Unsupported system page size %d", + 1 << PAGE_SHIFT); + break; + } + WRITE_REG(tcnfg, ioc->ioc_hpa + IOC_TCNFG); + + /* + ** Program the IOC's ibase and enable IOVA translation + ** Bit zero == enable bit. + */ + WRITE_REG(ioc->ibase | 1, ioc->ioc_hpa + IOC_IBASE); + + /* + ** Clear I/O TLB of any possible entries. + ** (Yes. This is a bit paranoid...but so what) + */ + WRITE_REG(ioc->ibase | 31, ioc->ioc_hpa + IOC_PCOM); + +#if SBA_AGP_SUPPORT + /* + ** If an AGP device is present, only use half of the IOV space + ** for PCI DMA. Unfortunately we can't know ahead of time + ** whether GART support will actually be used, for now we + ** can just key on any AGP device found in the system. + ** We program the next pdir index after we stop w/ a key for + ** the GART code to handshake on. + */ + device=NULL; + for (lba = sba->child; lba; lba = lba->sibling) { + if (IS_QUICKSILVER(lba)) + break; + } + + if (lba) { + DBG_INIT("%s: Reserving half of IOVA space for AGP GART support\n", __FUNCTION__); + ioc->pdir_size /= 2; + ((u64 *)ioc->pdir_base)[PDIR_INDEX(iova_space_size/2)] = SBA_IOMMU_COOKIE; + } else { + DBG_INIT("%s: No GART needed - no AGP controller found\n", __FUNCTION__); + } +#endif /* 0 */ + +} + +static void +sba_ioc_init(struct parisc_device *sba, struct ioc *ioc, int ioc_num) +{ + u32 iova_space_size, iova_space_mask; + unsigned int pdir_size, iov_order; + + /* + ** Determine IOVA Space size from memory size. + ** + ** Ideally, PCI drivers would register the maximum number + ** of DMA they can have outstanding for each device they + ** own. Next best thing would be to guess how much DMA + ** can be outstanding based on PCI Class/sub-class. Both + ** methods still require some "extra" to support PCI + ** Hot-Plug/Removal of PCI cards. (aka PCI OLARD). + ** + ** While we have 32-bits "IOVA" space, top two 2 bits are used + ** for DMA hints - ergo only 30 bits max. + */ + + iova_space_size = (u32) (num_physpages/global_ioc_cnt); + + /* limit IOVA space size to 1MB-1GB */ + if (iova_space_size < (1 << (20 - PAGE_SHIFT))) { + iova_space_size = 1 << (20 - PAGE_SHIFT); + } +#ifdef __LP64__ + else if (iova_space_size > (1 << (30 - PAGE_SHIFT))) { + iova_space_size = 1 << (30 - PAGE_SHIFT); + } +#endif + + /* + ** iova space must be log2() in size. + ** thus, pdir/res_map will also be log2(). + ** PIRANHA BUG: Exception is when IO Pdir is 2MB (gets reduced) + */ + iov_order = get_order(iova_space_size << PAGE_SHIFT); + + /* iova_space_size is now bytes, not pages */ + iova_space_size = 1 << (iov_order + PAGE_SHIFT); + + ioc->pdir_size = pdir_size = (iova_space_size/IOVP_SIZE) * sizeof(u64); + + DBG_INIT("%s() hpa 0x%lx mem %ldMB IOV %dMB (%d bits)\n", + __FUNCTION__, + ioc->ioc_hpa, + (unsigned long) num_physpages >> (20 - PAGE_SHIFT), + iova_space_size>>20, + iov_order + PAGE_SHIFT); + + ioc->pdir_base = sba_alloc_pdir(pdir_size); + + DBG_INIT("%s() pdir %p size %x\n", + __FUNCTION__, ioc->pdir_base, pdir_size); + +#if SBA_HINT_SUPPORT + /* FIXME : DMA HINTs not used */ + ioc->hint_shift_pdir = iov_order + PAGE_SHIFT; + ioc->hint_mask_pdir = ~(0x3 << (iov_order + PAGE_SHIFT)); + + DBG_INIT(" hint_shift_pdir %x hint_mask_pdir %lx\n", + ioc->hint_shift_pdir, ioc->hint_mask_pdir); +#endif + + WRITE_REG64(virt_to_phys(ioc->pdir_base), ioc->ioc_hpa + IOC_PDIR_BASE); + + /* build IMASK for IOC and Elroy */ + iova_space_mask = 0xffffffff; + iova_space_mask <<= (iov_order + PAGE_SHIFT); + + /* + ** On C3000 w/512MB mem, HP-UX 10.20 reports: + ** ibase=0, imask=0xFE000000, size=0x2000000. + */ + ioc->ibase = 0; + ioc->imask = iova_space_mask; /* save it */ +#ifdef ZX1_SUPPORT + ioc->iovp_mask = ~(iova_space_mask + PAGE_SIZE - 1); +#endif + + DBG_INIT("%s() IOV base 0x%lx mask 0x%0lx\n", + __FUNCTION__, ioc->ibase, ioc->imask); + + /* + ** FIXME: Hint registers are programmed with default hint + ** values during boot, so hints should be sane even if we + ** can't reprogram them the way drivers want. + */ + + setup_ibase_imask(sba, ioc, ioc_num); + + /* + ** Program the IOC's ibase and enable IOVA translation + */ + WRITE_REG(ioc->ibase | 1, ioc->ioc_hpa+IOC_IBASE); + WRITE_REG(ioc->imask, ioc->ioc_hpa+IOC_IMASK); + + /* Set I/O PDIR Page size to 4K */ + WRITE_REG(0, ioc->ioc_hpa+IOC_TCNFG); + + /* + ** Clear I/O TLB of any possible entries. + ** (Yes. This is a bit paranoid...but so what) + */ + WRITE_REG(0 | 31, ioc->ioc_hpa+IOC_PCOM); + + ioc->ibase = 0; /* used by SBA_IOVA and related macros */ + + DBG_INIT("%s() DONE\n", __FUNCTION__); +} + + + +/************************************************************************** +** +** SBA initialization code (HW and SW) +** +** o identify SBA chip itself +** o initialize SBA chip modes (HardFail) +** o initialize SBA chip modes (HardFail) +** o FIXME: initialize DMA hints for reasonable defaults +** +**************************************************************************/ + +static void __iomem *ioc_remap(struct sba_device *sba_dev, int offset) +{ + return ioremap(sba_dev->dev->hpa + offset, SBA_FUNC_SIZE); +} + +static void sba_hw_init(struct sba_device *sba_dev) +{ + int i; + int num_ioc; + u64 ioc_ctl; + + if (!is_pdc_pat()) { + /* Shutdown the USB controller on Astro-based workstations. + ** Once we reprogram the IOMMU, the next DMA performed by + ** USB will HPMC the box. USB is only enabled if a + ** keyboard is present and found. + ** + ** With serial console, j6k v5.0 firmware says: + ** mem_kbd hpa 0xfee003f8 sba 0x0 pad 0x0 cl_class 0x7 + ** + ** FIXME: Using GFX+USB console at power up but direct + ** linux to serial console is still broken. + ** USB could generate DMA so we must reset USB. + ** The proper sequence would be: + ** o block console output + ** o reset USB device + ** o reprogram serial port + ** o unblock console output + */ + if (PAGE0->mem_kbd.cl_class == CL_KEYBD) { + pdc_io_reset_devices(); + } + + } + + +#if 0 +printk("sba_hw_init(): mem_boot 0x%x 0x%x 0x%x 0x%x\n", PAGE0->mem_boot.hpa, + PAGE0->mem_boot.spa, PAGE0->mem_boot.pad, PAGE0->mem_boot.cl_class); + + /* + ** Need to deal with DMA from LAN. + ** Maybe use page zero boot device as a handle to talk + ** to PDC about which device to shutdown. + ** + ** Netbooting, j6k v5.0 firmware says: + ** mem_boot hpa 0xf4008000 sba 0x0 pad 0x0 cl_class 0x1002 + ** ARGH! invalid class. + */ + if ((PAGE0->mem_boot.cl_class != CL_RANDOM) + && (PAGE0->mem_boot.cl_class != CL_SEQU)) { + pdc_io_reset(); + } +#endif + + if (!IS_PLUTO(sba_dev->iodc)) { + ioc_ctl = READ_REG(sba_dev->sba_hpa+IOC_CTRL); + DBG_INIT("%s() hpa 0x%lx ioc_ctl 0x%Lx ->", + __FUNCTION__, sba_dev->sba_hpa, ioc_ctl); + ioc_ctl &= ~(IOC_CTRL_RM | IOC_CTRL_NC | IOC_CTRL_CE); + ioc_ctl |= IOC_CTRL_DD | IOC_CTRL_D4 | IOC_CTRL_TC; + /* j6700 v1.6 firmware sets 0x294f */ + /* A500 firmware sets 0x4d */ + + WRITE_REG(ioc_ctl, sba_dev->sba_hpa+IOC_CTRL); + +#ifdef DEBUG_SBA_INIT + ioc_ctl = READ_REG64(sba_dev->sba_hpa+IOC_CTRL); + DBG_INIT(" 0x%Lx\n", ioc_ctl); +#endif + } /* if !PLUTO */ + + if (IS_ASTRO(sba_dev->iodc)) { + int err; + /* PAT_PDC (L-class) also reports the same goofy base */ + sba_dev->ioc[0].ioc_hpa = ioc_remap(sba_dev, ASTRO_IOC_OFFSET); + num_ioc = 1; + + sba_dev->chip_resv.name = "Astro Intr Ack"; + sba_dev->chip_resv.start = PCI_F_EXTEND | 0xfef00000UL; + sba_dev->chip_resv.end = PCI_F_EXTEND | (0xff000000UL - 1) ; + err = request_resource(&iomem_resource, &(sba_dev->chip_resv)); + if (err < 0) { + BUG(); + } + + } else if (IS_PLUTO(sba_dev->iodc)) { + int err; + + /* We use a negative value for IOC HPA so it gets + * corrected when we add it with IKE's IOC offset. + * Doesnt look clean, but fewer code. + */ + sba_dev->ioc[0].ioc_hpa = ioc_remap(sba_dev, PLUTO_IOC_OFFSET); + num_ioc = 1; + + sba_dev->chip_resv.name = "Pluto Intr/PIOP/VGA"; + sba_dev->chip_resv.start = PCI_F_EXTEND | 0xfee00000UL; + sba_dev->chip_resv.end = PCI_F_EXTEND | (0xff200000UL - 1); + err = request_resource(&iomem_resource, &(sba_dev->chip_resv)); + WARN_ON(err < 0); + + sba_dev->iommu_resv.name = "IOVA Space"; + sba_dev->iommu_resv.start = 0x40000000UL; + sba_dev->iommu_resv.end = 0x50000000UL - 1; + err = request_resource(&iomem_resource, &(sba_dev->iommu_resv)); + WARN_ON(err < 0); + } else { + /* IS_IKE (ie N-class, L3000, L1500) */ + sba_dev->ioc[0].ioc_hpa = ioc_remap(sba_dev, IKE_IOC_OFFSET(0)); + sba_dev->ioc[1].ioc_hpa = ioc_remap(sba_dev, IKE_IOC_OFFSET(1)); + num_ioc = 2; + + /* TODO - LOOKUP Ike/Stretch chipset mem map */ + } + /* XXX: What about Reo? */ + + sba_dev->num_ioc = num_ioc; + for (i = 0; i < num_ioc; i++) { + /* + ** Make sure the box crashes if we get any errors on a rope. + */ + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE0_CTL); + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE1_CTL); + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE2_CTL); + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE3_CTL); + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE4_CTL); + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE5_CTL); + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE6_CTL); + WRITE_REG(HF_ENABLE, sba_dev->ioc[i].ioc_hpa + ROPE7_CTL); + + /* flush out the writes */ + READ_REG(sba_dev->ioc[i].ioc_hpa + ROPE7_CTL); + + DBG_INIT(" ioc[%d] ROPE_CFG 0x%Lx ROPE_DBG 0x%Lx\n", + i, + READ_REG(sba_dev->ioc[i].ioc_hpa + 0x40), + READ_REG(sba_dev->ioc[i].ioc_hpa + 0x50) + ); + DBG_INIT(" STATUS_CONTROL 0x%Lx FLUSH_CTRL 0x%Lx\n", + READ_REG(sba_dev->ioc[i].ioc_hpa + 0x108), + READ_REG(sba_dev->ioc[i].ioc_hpa + 0x400) + ); + + if (IS_PLUTO(sba_dev->iodc)) { + sba_ioc_init_pluto(sba_dev->dev, &(sba_dev->ioc[i]), i); + } else { + sba_ioc_init(sba_dev->dev, &(sba_dev->ioc[i]), i); + } + } +} + +static void +sba_common_init(struct sba_device *sba_dev) +{ + int i; + + /* add this one to the head of the list (order doesn't matter) + ** This will be useful for debugging - especially if we get coredumps + */ + sba_dev->next = sba_list; + sba_list = sba_dev; + + for(i=0; i< sba_dev->num_ioc; i++) { + int res_size; +#ifdef DEBUG_DMB_TRAP + extern void iterate_pages(unsigned long , unsigned long , + void (*)(pte_t * , unsigned long), + unsigned long ); + void set_data_memory_break(pte_t * , unsigned long); +#endif + /* resource map size dictated by pdir_size */ + res_size = sba_dev->ioc[i].pdir_size/sizeof(u64); /* entries */ + + /* Second part of PIRANHA BUG */ + if (piranha_bad_128k) { + res_size -= (128*1024)/sizeof(u64); + } + + res_size >>= 3; /* convert bit count to byte count */ + DBG_INIT("%s() res_size 0x%x\n", + __FUNCTION__, res_size); + + sba_dev->ioc[i].res_size = res_size; + sba_dev->ioc[i].res_map = (char *) __get_free_pages(GFP_KERNEL, get_order(res_size)); + +#ifdef DEBUG_DMB_TRAP + iterate_pages( sba_dev->ioc[i].res_map, res_size, + set_data_memory_break, 0); +#endif + + if (NULL == sba_dev->ioc[i].res_map) + { + panic("%s:%s() could not allocate resource map\n", + __FILE__, __FUNCTION__ ); + } + + memset(sba_dev->ioc[i].res_map, 0, res_size); + /* next available IOVP - circular search */ + sba_dev->ioc[i].res_hint = (unsigned long *) + &(sba_dev->ioc[i].res_map[L1_CACHE_BYTES]); + +#ifdef ASSERT_PDIR_SANITY + /* Mark first bit busy - ie no IOVA 0 */ + sba_dev->ioc[i].res_map[0] = 0x80; + sba_dev->ioc[i].pdir_base[0] = 0xeeffc0addbba0080ULL; +#endif + + /* Third (and last) part of PIRANHA BUG */ + if (piranha_bad_128k) { + /* region from +1408K to +1536 is un-usable. */ + + int idx_start = (1408*1024/sizeof(u64)) >> 3; + int idx_end = (1536*1024/sizeof(u64)) >> 3; + long *p_start = (long *) &(sba_dev->ioc[i].res_map[idx_start]); + long *p_end = (long *) &(sba_dev->ioc[i].res_map[idx_end]); + + /* mark that part of the io pdir busy */ + while (p_start < p_end) + *p_start++ = -1; + + } + +#ifdef DEBUG_DMB_TRAP + iterate_pages( sba_dev->ioc[i].res_map, res_size, + set_data_memory_break, 0); + iterate_pages( sba_dev->ioc[i].pdir_base, sba_dev->ioc[i].pdir_size, + set_data_memory_break, 0); +#endif + + DBG_INIT("%s() %d res_map %x %p\n", + __FUNCTION__, i, res_size, sba_dev->ioc[i].res_map); + } + + spin_lock_init(&sba_dev->sba_lock); + ioc_needs_fdc = boot_cpu_data.pdc.capabilities & PDC_MODEL_IOPDIR_FDC; + +#ifdef DEBUG_SBA_INIT + /* + * If the PDC_MODEL capabilities has Non-coherent IO-PDIR bit set + * (bit #61, big endian), we have to flush and sync every time + * IO-PDIR is changed in Ike/Astro. + */ + if (boot_cpu_data.pdc.capabilities & PDC_MODEL_IOPDIR_FDC) { + printk(KERN_INFO MODULE_NAME " FDC/SYNC required.\n"); + } else { + printk(KERN_INFO MODULE_NAME " IOC has cache coherent PDIR.\n"); + } +#endif +} + +#ifdef CONFIG_PROC_FS +static int sba_proc_info(char *buf, char **start, off_t offset, int len) +{ + struct sba_device *sba_dev = sba_list; + struct ioc *ioc = &sba_dev->ioc[0]; /* FIXME: Multi-IOC support! */ + int total_pages = (int) (ioc->res_size << 3); /* 8 bits per byte */ + unsigned long i; +#ifdef SBA_COLLECT_STATS + unsigned long avg = 0, min, max; +#endif + + sprintf(buf, "%s rev %d.%d\n", + sba_dev->name, + (sba_dev->hw_rev & 0x7) + 1, + (sba_dev->hw_rev & 0x18) >> 3 + ); + sprintf(buf, "%sIO PDIR size : %d bytes (%d entries)\n", + buf, + (int) ((ioc->res_size << 3) * sizeof(u64)), /* 8 bits/byte */ + total_pages); + + sprintf(buf, "%sResource bitmap : %d bytes (%d pages)\n", + buf, ioc->res_size, ioc->res_size << 3); /* 8 bits per byte */ + + sprintf(buf, "%sLMMIO_BASE/MASK/ROUTE %08x %08x %08x\n", + buf, + READ_REG32(sba_dev->sba_hpa + LMMIO_DIST_BASE), + READ_REG32(sba_dev->sba_hpa + LMMIO_DIST_MASK), + READ_REG32(sba_dev->sba_hpa + LMMIO_DIST_ROUTE) + ); + + for (i=0; i<4; i++) + sprintf(buf, "%sDIR%ld_BASE/MASK/ROUTE %08x %08x %08x\n", + buf, i, + READ_REG32(sba_dev->sba_hpa + LMMIO_DIRECT0_BASE + i*0x18), + READ_REG32(sba_dev->sba_hpa + LMMIO_DIRECT0_MASK + i*0x18), + READ_REG32(sba_dev->sba_hpa + LMMIO_DIRECT0_ROUTE + i*0x18) + ); + +#ifdef SBA_COLLECT_STATS + sprintf(buf, "%sIO PDIR entries : %ld free %ld used (%d%%)\n", buf, + total_pages - ioc->used_pages, ioc->used_pages, + (int) (ioc->used_pages * 100 / total_pages)); + + min = max = ioc->avg_search[0]; + for (i = 0; i < SBA_SEARCH_SAMPLE; i++) { + avg += ioc->avg_search[i]; + if (ioc->avg_search[i] > max) max = ioc->avg_search[i]; + if (ioc->avg_search[i] < min) min = ioc->avg_search[i]; + } + avg /= SBA_SEARCH_SAMPLE; + sprintf(buf, "%s Bitmap search : %ld/%ld/%ld (min/avg/max CPU Cycles)\n", + buf, min, avg, max); + + sprintf(buf, "%spci_map_single(): %12ld calls %12ld pages (avg %d/1000)\n", + buf, ioc->msingle_calls, ioc->msingle_pages, + (int) ((ioc->msingle_pages * 1000)/ioc->msingle_calls)); + + /* KLUGE - unmap_sg calls unmap_single for each mapped page */ + min = ioc->usingle_calls; + max = ioc->usingle_pages - ioc->usg_pages; + sprintf(buf, "%spci_unmap_single: %12ld calls %12ld pages (avg %d/1000)\n", + buf, min, max, + (int) ((max * 1000)/min)); + + sprintf(buf, "%spci_map_sg() : %12ld calls %12ld pages (avg %d/1000)\n", + buf, ioc->msg_calls, ioc->msg_pages, + (int) ((ioc->msg_pages * 1000)/ioc->msg_calls)); + + sprintf(buf, "%spci_unmap_sg() : %12ld calls %12ld pages (avg %d/1000)\n", + buf, ioc->usg_calls, ioc->usg_pages, + (int) ((ioc->usg_pages * 1000)/ioc->usg_calls)); +#endif + + return strlen(buf); +} + +#if 0 +/* XXX too much output - exceeds 4k limit and needs to be re-written */ +static int +sba_resource_map(char *buf, char **start, off_t offset, int len) +{ + struct sba_device *sba_dev = sba_list; + struct ioc *ioc = &sba_dev->ioc[0]; /* FIXME: Mutli-IOC suppoer! */ + unsigned int *res_ptr = (unsigned int *)ioc->res_map; + int i; + + buf[0] = '\0'; + for(i = 0; i < (ioc->res_size / sizeof(unsigned int)); ++i, ++res_ptr) { + if ((i & 7) == 0) + strcat(buf,"\n "); + sprintf(buf, "%s %08x", buf, *res_ptr); + } + strcat(buf, "\n"); + + return strlen(buf); +} +#endif /* 0 */ +#endif /* CONFIG_PROC_FS */ + +static struct parisc_device_id sba_tbl[] = { + { HPHW_IOA, HVERSION_REV_ANY_ID, ASTRO_RUNWAY_PORT, 0xb }, + { HPHW_BCPORT, HVERSION_REV_ANY_ID, IKE_MERCED_PORT, 0xc }, + { HPHW_BCPORT, HVERSION_REV_ANY_ID, REO_MERCED_PORT, 0xc }, + { HPHW_BCPORT, HVERSION_REV_ANY_ID, REOG_MERCED_PORT, 0xc }, + { HPHW_IOA, HVERSION_REV_ANY_ID, PLUTO_MCKINLEY_PORT, 0xc }, + { 0, } +}; + +int sba_driver_callback(struct parisc_device *); + +static struct parisc_driver sba_driver = { + .name = MODULE_NAME, + .id_table = sba_tbl, + .probe = sba_driver_callback, +}; + +/* +** Determine if sba should claim this chip (return 0) or not (return 1). +** If so, initialize the chip and tell other partners in crime they +** have work to do. +*/ +int +sba_driver_callback(struct parisc_device *dev) +{ + struct sba_device *sba_dev; + u32 func_class; + int i; + char *version; + void __iomem *sba_addr = ioremap(dev->hpa, SBA_FUNC_SIZE); + + sba_dump_ranges(sba_addr); + + /* Read HW Rev First */ + func_class = READ_REG(sba_addr + SBA_FCLASS); + + if (IS_ASTRO(&dev->id)) { + unsigned long fclass; + static char astro_rev[]="Astro ?.?"; + + /* Astro is broken...Read HW Rev First */ + fclass = READ_REG(sba_addr); + + astro_rev[6] = '1' + (char) (fclass & 0x7); + astro_rev[8] = '0' + (char) ((fclass & 0x18) >> 3); + version = astro_rev; + + } else if (IS_IKE(&dev->id)) { + static char ike_rev[] = "Ike rev ?"; + ike_rev[8] = '0' + (char) (func_class & 0xff); + version = ike_rev; + } else if (IS_PLUTO(&dev->id)) { + static char pluto_rev[]="Pluto ?.?"; + pluto_rev[6] = '0' + (char) ((func_class & 0xf0) >> 4); + pluto_rev[8] = '0' + (char) (func_class & 0x0f); + version = pluto_rev; + } else { + static char reo_rev[] = "REO rev ?"; + reo_rev[8] = '0' + (char) (func_class & 0xff); + version = reo_rev; + } + + if (!global_ioc_cnt) { + global_ioc_cnt = count_parisc_driver(&sba_driver); + + /* Astro and Pluto have one IOC per SBA */ + if ((!IS_ASTRO(&dev->id)) || (!IS_PLUTO(&dev->id))) + global_ioc_cnt *= 2; + } + + printk(KERN_INFO "%s found %s at 0x%lx\n", + MODULE_NAME, version, dev->hpa); + + sba_dev = kmalloc(sizeof(struct sba_device), GFP_KERNEL); + if (!sba_dev) { + printk(KERN_ERR MODULE_NAME " - couldn't alloc sba_device\n"); + return -ENOMEM; + } + + parisc_set_drvdata(dev, sba_dev); + memset(sba_dev, 0, sizeof(struct sba_device)); + + for(i=0; i<MAX_IOC; i++) + spin_lock_init(&(sba_dev->ioc[i].res_lock)); + + sba_dev->dev = dev; + sba_dev->hw_rev = func_class; + sba_dev->iodc = &dev->id; + sba_dev->name = dev->name; + sba_dev->sba_hpa = sba_addr; + + sba_get_pat_resources(sba_dev); + sba_hw_init(sba_dev); + sba_common_init(sba_dev); + + hppa_dma_ops = &sba_ops; + +#ifdef CONFIG_PROC_FS + if (IS_ASTRO(&dev->id)) { + create_proc_info_entry("Astro", 0, proc_runway_root, sba_proc_info); + } else if (IS_IKE(&dev->id)) { + create_proc_info_entry("Ike", 0, proc_runway_root, sba_proc_info); + } else if (IS_PLUTO(&dev->id)) { + create_proc_info_entry("Pluto", 0, proc_mckinley_root, sba_proc_info); + } else { + create_proc_info_entry("Reo", 0, proc_runway_root, sba_proc_info); + } +#if 0 + create_proc_info_entry("bitmap", 0, proc_runway_root, sba_resource_map); +#endif +#endif + parisc_vmerge_boundary = IOVP_SIZE; + parisc_vmerge_max_size = IOVP_SIZE * BITS_PER_LONG; + parisc_has_iommu(); + return 0; +} + +/* +** One time initialization to let the world know the SBA was found. +** This is the only routine which is NOT static. +** Must be called exactly once before pci_init(). +*/ +void __init sba_init(void) +{ + register_parisc_driver(&sba_driver); +} + + +/** + * sba_get_iommu - Assign the iommu pointer for the pci bus controller. + * @dev: The parisc device. + * + * Returns the appropriate IOMMU data for the given parisc PCI controller. + * This is cached and used later for PCI DMA Mapping. + */ +void * sba_get_iommu(struct parisc_device *pci_hba) +{ + struct parisc_device *sba_dev = parisc_parent(pci_hba); + struct sba_device *sba = sba_dev->dev.driver_data; + char t = sba_dev->id.hw_type; + int iocnum = (pci_hba->hw_path >> 3); /* rope # */ + + WARN_ON((t != HPHW_IOA) && (t != HPHW_BCPORT)); + + return &(sba->ioc[iocnum]); +} + + +/** + * sba_directed_lmmio - return first directed LMMIO range routed to rope + * @pa_dev: The parisc device. + * @r: resource PCI host controller wants start/end fields assigned. + * + * For the given parisc PCI controller, determine if any direct ranges + * are routed down the corresponding rope. + */ +void sba_directed_lmmio(struct parisc_device *pci_hba, struct resource *r) +{ + struct parisc_device *sba_dev = parisc_parent(pci_hba); + struct sba_device *sba = sba_dev->dev.driver_data; + char t = sba_dev->id.hw_type; + int i; + int rope = (pci_hba->hw_path & (ROPES_PER_IOC-1)); /* rope # */ + + if ((t!=HPHW_IOA) && (t!=HPHW_BCPORT)) + BUG(); + + r->start = r->end = 0; + + /* Astro has 4 directed ranges. Not sure about Ike/Pluto/et al */ + for (i=0; i<4; i++) { + int base, size; + void __iomem *reg = sba->sba_hpa + i*0x18; + + base = READ_REG32(reg + LMMIO_DIRECT0_BASE); + if ((base & 1) == 0) + continue; /* not enabled */ + + size = READ_REG32(reg + LMMIO_DIRECT0_ROUTE); + + if ((size & (ROPES_PER_IOC-1)) != rope) + continue; /* directed down different rope */ + + r->start = (base & ~1UL) | PCI_F_EXTEND; + size = ~ READ_REG32(reg + LMMIO_DIRECT0_MASK); + r->end = r->start + size; + } +} + + +/** + * sba_distributed_lmmio - return portion of distributed LMMIO range + * @pa_dev: The parisc device. + * @r: resource PCI host controller wants start/end fields assigned. + * + * For the given parisc PCI controller, return portion of distributed LMMIO + * range. The distributed LMMIO is always present and it's just a question + * of the base address and size of the range. + */ +void sba_distributed_lmmio(struct parisc_device *pci_hba, struct resource *r ) +{ + struct parisc_device *sba_dev = parisc_parent(pci_hba); + struct sba_device *sba = sba_dev->dev.driver_data; + char t = sba_dev->id.hw_type; + int base, size; + int rope = (pci_hba->hw_path & (ROPES_PER_IOC-1)); /* rope # */ + + if ((t!=HPHW_IOA) && (t!=HPHW_BCPORT)) + BUG(); + + r->start = r->end = 0; + + base = READ_REG32(sba->sba_hpa + LMMIO_DIST_BASE); + if ((base & 1) == 0) { + BUG(); /* Gah! Distr Range wasn't enabled! */ + return; + } + + r->start = (base & ~1UL) | PCI_F_EXTEND; + + size = (~READ_REG32(sba->sba_hpa + LMMIO_DIST_MASK)) / ROPES_PER_IOC; + r->start += rope * (size + 1); /* adjust base for this rope */ + r->end = r->start + size; +} diff --git a/drivers/parisc/superio.c b/drivers/parisc/superio.c new file mode 100644 index 00000000000..e0efed796b9 --- /dev/null +++ b/drivers/parisc/superio.c @@ -0,0 +1,508 @@ +/* National Semiconductor NS87560UBD Super I/O controller used in + * HP [BCJ]x000 workstations. + * + * This chip is a horrid piece of engineering, and National + * denies any knowledge of its existence. Thus no datasheet is + * available off www.national.com. + * + * (C) Copyright 2000 Linuxcare, Inc. + * (C) Copyright 2000 Linuxcare Canada, Inc. + * (C) Copyright 2000 Martin K. Petersen <mkp@linuxcare.com> + * (C) Copyright 2000 Alex deVries <alex@onefishtwo.ca> + * (C) Copyright 2001 John Marvin <jsm fc hp com> + * (C) Copyright 2003 Grant Grundler <grundler parisc-linux 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. + * + * The initial version of this is by Martin Peterson. Alex deVries + * has spent a bit of time trying to coax it into working. + * + * Major changes to get basic interrupt infrastructure working to + * hopefully be able to support all SuperIO devices. Currently + * works with serial. -- John Marvin <jsm@fc.hp.com> + */ + + +/* NOTES: + * + * Function 0 is an IDE controller. It is identical to a PC87415 IDE + * controller (and identifies itself as such). + * + * Function 1 is a "Legacy I/O" controller. Under this function is a + * whole mess of legacy I/O peripherals. Of course, HP hasn't enabled + * all the functionality in hardware, but the following is available: + * + * Two 16550A compatible serial controllers + * An IEEE 1284 compatible parallel port + * A floppy disk controller + * + * Function 2 is a USB controller. + * + * We must be incredibly careful during initialization. Since all + * interrupts are routed through function 1 (which is not allowed by + * the PCI spec), we need to program the PICs on the legacy I/O port + * *before* we attempt to set up IDE and USB. @#$!& + * + * According to HP, devices are only enabled by firmware if they have + * a physical device connected. + * + * Configuration register bits: + * 0x5A: FDC, SP1, IDE1, SP2, IDE2, PAR, Reserved, P92 + * 0x5B: RTC, 8259, 8254, DMA1, DMA2, KBC, P61, APM + * + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/serial.h> +#include <linux/pci.h> +#include <linux/parport.h> +#include <linux/parport_pc.h> +#include <linux/termios.h> +#include <linux/tty.h> +#include <linux/serial_core.h> +#include <linux/delay.h> + +#include <asm/io.h> +#include <asm/hardware.h> +#include <asm/superio.h> + +static struct superio_device sio_dev; + + +#undef DEBUG_SUPERIO_INIT + +#ifdef DEBUG_SUPERIO_INIT +#define DBG_INIT(x...) printk(x) +#else +#define DBG_INIT(x...) +#endif + +static irqreturn_t +superio_interrupt(int parent_irq, void *devp, struct pt_regs *regs) +{ + u8 results; + u8 local_irq; + + /* Poll the 8259 to see if there's an interrupt. */ + outb (OCW3_POLL,IC_PIC1+0); + + results = inb(IC_PIC1+0); + + /* + * Bit 7: 1 = active Interrupt; 0 = no Interrupt pending + * Bits 6-3: zero + * Bits 2-0: highest priority, active requesting interrupt ID (0-7) + */ + if ((results & 0x80) == 0) { + /* I suspect "spurious" interrupts are from unmasking an IRQ. + * We don't know if an interrupt was/is pending and thus + * just call the handler for that IRQ as if it were pending. + */ + return IRQ_NONE; + } + + /* Check to see which device is interrupting */ + local_irq = results & 0x0f; + + if (local_irq == 2 || local_irq > 7) { + printk(KERN_ERR "SuperIO: slave interrupted!\n"); + return IRQ_HANDLED; + } + + if (local_irq == 7) { + + /* Could be spurious. Check in service bits */ + + outb(OCW3_ISR,IC_PIC1+0); + results = inb(IC_PIC1+0); + if ((results & 0x80) == 0) { /* if ISR7 not set: spurious */ + printk(KERN_WARNING "SuperIO: spurious interrupt!\n"); + return IRQ_HANDLED; + } + } + + /* Call the appropriate device's interrupt */ + __do_IRQ(local_irq, regs); + + /* set EOI - forces a new interrupt if a lower priority device + * still needs service. + */ + outb((OCW2_SEOI|local_irq),IC_PIC1 + 0); + return IRQ_HANDLED; +} + +/* Initialize Super I/O device */ + +static void __devinit +superio_init(struct superio_device *sio) +{ + struct pci_dev *pdev = sio->lio_pdev; + u16 word; + + if (sio->suckyio_irq_enabled) + return; + + if (!pdev) BUG(); + if (!sio->usb_pdev) BUG(); + + /* use the IRQ iosapic found for USB INT D... */ + pdev->irq = sio->usb_pdev->irq; + + /* ...then properly fixup the USB to point at suckyio PIC */ + sio->usb_pdev->irq = superio_fixup_irq(sio->usb_pdev); + + printk (KERN_INFO "SuperIO: Found NS87560 Legacy I/O device at %s (IRQ %i) \n", + pci_name(pdev),pdev->irq); + + pci_read_config_dword (pdev, SIO_SP1BAR, &sio->sp1_base); + sio->sp1_base &= ~1; + printk (KERN_INFO "SuperIO: Serial port 1 at 0x%x\n", sio->sp1_base); + + pci_read_config_dword (pdev, SIO_SP2BAR, &sio->sp2_base); + sio->sp2_base &= ~1; + printk (KERN_INFO "SuperIO: Serial port 2 at 0x%x\n", sio->sp2_base); + + pci_read_config_dword (pdev, SIO_PPBAR, &sio->pp_base); + sio->pp_base &= ~1; + printk (KERN_INFO "SuperIO: Parallel port at 0x%x\n", sio->pp_base); + + pci_read_config_dword (pdev, SIO_FDCBAR, &sio->fdc_base); + sio->fdc_base &= ~1; + printk (KERN_INFO "SuperIO: Floppy controller at 0x%x\n", sio->fdc_base); + pci_read_config_dword (pdev, SIO_ACPIBAR, &sio->acpi_base); + sio->acpi_base &= ~1; + printk (KERN_INFO "SuperIO: ACPI at 0x%x\n", sio->acpi_base); + + request_region (IC_PIC1, 0x1f, "pic1"); + request_region (IC_PIC2, 0x1f, "pic2"); + request_region (sio->acpi_base, 0x1f, "acpi"); + + /* Enable the legacy I/O function */ + pci_read_config_word (pdev, PCI_COMMAND, &word); + word |= PCI_COMMAND_SERR | PCI_COMMAND_PARITY | PCI_COMMAND_IO; + pci_write_config_word (pdev, PCI_COMMAND, word); + + pci_set_master (pdev); + pci_enable_device(pdev); + + /* + * Next project is programming the onboard interrupt controllers. + * PDC hasn't done this for us, since it's using polled I/O. + * + * XXX Use dword writes to avoid bugs in Elroy or Suckyio Config + * space access. PCI is by nature a 32-bit bus and config + * space can be sensitive to that. + */ + + /* 0x64 - 0x67 : + DMA Rtg 2 + DMA Rtg 3 + DMA Chan Ctl + TRIGGER_1 == 0x82 USB & IDE level triggered, rest to edge + */ + pci_write_config_dword (pdev, 0x64, 0x82000000U); + + /* 0x68 - 0x6b : + TRIGGER_2 == 0x00 all edge triggered (not used) + CFG_IR_SER == 0x43 SerPort1 = IRQ3, SerPort2 = IRQ4 + CFG_IR_PF == 0x65 ParPort = IRQ5, FloppyCtlr = IRQ6 + CFG_IR_IDE == 0x07 IDE1 = IRQ7, reserved + */ + pci_write_config_dword (pdev, TRIGGER_2, 0x07654300U); + + /* 0x6c - 0x6f : + CFG_IR_INTAB == 0x00 + CFG_IR_INTCD == 0x10 USB = IRQ1 + CFG_IR_PS2 == 0x00 + CFG_IR_FXBUS == 0x00 + */ + pci_write_config_dword (pdev, CFG_IR_INTAB, 0x00001000U); + + /* 0x70 - 0x73 : + CFG_IR_USB == 0x00 not used. USB is connected to INTD. + CFG_IR_ACPI == 0x00 not used. + DMA Priority == 0x4c88 Power on default value. NFC. + */ + pci_write_config_dword (pdev, CFG_IR_USB, 0x4c880000U); + + /* PIC1 Initialization Command Word register programming */ + outb (0x11,IC_PIC1+0); /* ICW1: ICW4 write req | ICW1 */ + outb (0x00,IC_PIC1+1); /* ICW2: interrupt vector table - not used */ + outb (0x04,IC_PIC1+1); /* ICW3: Cascade */ + outb (0x01,IC_PIC1+1); /* ICW4: x86 mode */ + + /* PIC1 Program Operational Control Words */ + outb (0xff,IC_PIC1+1); /* OCW1: Mask all interrupts */ + outb (0xc2,IC_PIC1+0); /* OCW2: priority (3-7,0-2) */ + + /* PIC2 Initialization Command Word register programming */ + outb (0x11,IC_PIC2+0); /* ICW1: ICW4 write req | ICW1 */ + outb (0x00,IC_PIC2+1); /* ICW2: N/A */ + outb (0x02,IC_PIC2+1); /* ICW3: Slave ID code */ + outb (0x01,IC_PIC2+1); /* ICW4: x86 mode */ + + /* Program Operational Control Words */ + outb (0xff,IC_PIC1+1); /* OCW1: Mask all interrupts */ + outb (0x68,IC_PIC1+0); /* OCW3: OCW3 select | ESMM | SMM */ + + /* Write master mask reg */ + outb (0xff,IC_PIC1+1); + + /* Setup USB power regulation */ + outb(1, sio->acpi_base + USB_REG_CR); + if (inb(sio->acpi_base + USB_REG_CR) & 1) + printk(KERN_INFO "SuperIO: USB regulator enabled\n"); + else + printk(KERN_ERR "USB regulator not initialized!\n"); + + if (request_irq(pdev->irq, superio_interrupt, SA_INTERRUPT, + "SuperIO", (void *)sio)) { + + printk(KERN_ERR "SuperIO: could not get irq\n"); + BUG(); + return; + } + + sio->suckyio_irq_enabled = 1; +} + + +static void superio_disable_irq(unsigned int irq) +{ + u8 r8; + + if ((irq < 1) || (irq == 2) || (irq > 7)) { + printk(KERN_ERR "SuperIO: Illegal irq number.\n"); + BUG(); + return; + } + + /* Mask interrupt */ + + r8 = inb(IC_PIC1+1); + r8 |= (1 << irq); + outb (r8,IC_PIC1+1); +} + +static void superio_enable_irq(unsigned int irq) +{ + u8 r8; + + if ((irq < 1) || (irq == 2) || (irq > 7)) { + printk(KERN_ERR "SuperIO: Illegal irq number (%d).\n", irq); + BUG(); + return; + } + + /* Unmask interrupt */ + r8 = inb(IC_PIC1+1); + r8 &= ~(1 << irq); + outb (r8,IC_PIC1+1); +} + +static unsigned int superio_startup_irq(unsigned int irq) +{ + superio_enable_irq(irq); + return 0; +} + +static struct hw_interrupt_type superio_interrupt_type = { + .typename = "SuperIO", + .startup = superio_startup_irq, + .shutdown = superio_disable_irq, + .enable = superio_enable_irq, + .disable = superio_disable_irq, + .ack = no_ack_irq, + .end = no_end_irq, +}; + +#ifdef DEBUG_SUPERIO_INIT +static unsigned short expected_device[3] = { + PCI_DEVICE_ID_NS_87415, + PCI_DEVICE_ID_NS_87560_LIO, + PCI_DEVICE_ID_NS_87560_USB +}; +#endif + +int superio_fixup_irq(struct pci_dev *pcidev) +{ + int local_irq, i; + +#ifdef DEBUG_SUPERIO_INIT + int fn; + fn = PCI_FUNC(pcidev->devfn); + + /* Verify the function number matches the expected device id. */ + if (expected_device[fn] != pcidev->device) { + BUG(); + return -1; + } + printk("superio_fixup_irq(%s) ven 0x%x dev 0x%x from %p\n", + pci_name(pcidev), + pcidev->vendor, pcidev->device, + __builtin_return_address(0)); +#endif + + for (i = 0; i < 16; i++) { + irq_desc[i].handler = &superio_interrupt_type; + } + + /* + * We don't allocate a SuperIO irq for the legacy IO function, + * since it is a "bridge". Instead, we will allocate irq's for + * each legacy device as they are initialized. + */ + + switch(pcidev->device) { + case PCI_DEVICE_ID_NS_87415: /* Function 0 */ + local_irq = IDE_IRQ; + break; + case PCI_DEVICE_ID_NS_87560_LIO: /* Function 1 */ + sio_dev.lio_pdev = pcidev; /* save for superio_init() */ + return -1; + case PCI_DEVICE_ID_NS_87560_USB: /* Function 2 */ + sio_dev.usb_pdev = pcidev; /* save for superio_init() */ + local_irq = USB_IRQ; + break; + default: + local_irq = -1; + BUG(); + break; + } + + return local_irq; +} + +static struct uart_port serial[] = { + { + .iotype = UPIO_PORT, + .line = 0, + .type = PORT_16550A, + .uartclk = 115200*16, + .fifosize = 16, + }, + { + .iotype = UPIO_PORT, + .line = 1, + .type = PORT_16550A, + .uartclk = 115200*16, + .fifosize = 16, + } +}; + +static void __devinit superio_serial_init(void) +{ +#ifdef CONFIG_SERIAL_8250 + int retval; + + serial[0].iobase = sio_dev.sp1_base; + serial[0].irq = SP1_IRQ; + + retval = early_serial_setup(&serial[0]); + if (retval < 0) { + printk(KERN_WARNING "SuperIO: Register Serial #0 failed.\n"); + return; + } + + serial[1].iobase = sio_dev.sp2_base; + serial[1].irq = SP2_IRQ; + retval = early_serial_setup(&serial[1]); + + if (retval < 0) + printk(KERN_WARNING "SuperIO: Register Serial #1 failed.\n"); +#endif /* CONFIG_SERIAL_8250 */ +} + + +static void __devinit superio_parport_init(void) +{ +#ifdef CONFIG_PARPORT_PC + if (!parport_pc_probe_port(sio_dev.pp_base, + 0 /*base_hi*/, + PAR_IRQ, + PARPORT_DMA_NONE /* dma */, + NULL /*struct pci_dev* */) ) + + printk(KERN_WARNING "SuperIO: Probing parallel port failed.\n"); +#endif /* CONFIG_PARPORT_PC */ +} + + +static void superio_fixup_pci(struct pci_dev *pdev) +{ + u8 prog; + + pdev->class |= 0x5; + pci_write_config_byte(pdev, PCI_CLASS_PROG, pdev->class); + + pci_read_config_byte(pdev, PCI_CLASS_PROG, &prog); + printk("PCI: Enabled native mode for NS87415 (pif=0x%x)\n", prog); +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_87415, superio_fixup_pci); + + +static int __devinit superio_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + + /* + ** superio_probe(00:0e.0) ven 0x100b dev 0x2 sv 0x0 sd 0x0 class 0x1018a + ** superio_probe(00:0e.1) ven 0x100b dev 0xe sv 0x0 sd 0x0 class 0x68000 + ** superio_probe(00:0e.2) ven 0x100b dev 0x12 sv 0x0 sd 0x0 class 0xc0310 + */ + DBG_INIT("superio_probe(%s) ven 0x%x dev 0x%x sv 0x%x sd 0x%x class 0x%x\n", + pci_name(dev), + dev->vendor, dev->device, + dev->subsystem_vendor, dev->subsystem_device, + dev->class); + + superio_init(&sio_dev); + + if (dev->device == PCI_DEVICE_ID_NS_87560_LIO) { /* Function 1 */ + superio_parport_init(); + superio_serial_init(); + /* REVISIT XXX : superio_fdc_init() ? */ + return 0; + } else if (dev->device == PCI_DEVICE_ID_NS_87415) { /* Function 0 */ + DBG_INIT("superio_probe: ignoring IDE 87415\n"); + } else if (dev->device == PCI_DEVICE_ID_NS_87560_USB) { /* Function 2 */ + DBG_INIT("superio_probe: ignoring USB OHCI controller\n"); + } else { + DBG_INIT("superio_probe: WTF? Fire Extinguisher?\n"); + } + + /* Let appropriate other driver claim this device. */ + return -ENODEV; +} + +static struct pci_device_id superio_tbl[] = { + { PCI_VENDOR_ID_NS, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0, } +}; + +static struct pci_driver superio_driver = { + .name = "SuperIO", + .id_table = superio_tbl, + .probe = superio_probe, +}; + +static int __init superio_modinit(void) +{ + return pci_register_driver(&superio_driver); +} + +static void __exit superio_exit(void) +{ + pci_unregister_driver(&superio_driver); +} + + +module_init(superio_modinit); +module_exit(superio_exit); diff --git a/drivers/parisc/wax.c b/drivers/parisc/wax.c new file mode 100644 index 00000000000..e547d7d024d --- /dev/null +++ b/drivers/parisc/wax.c @@ -0,0 +1,140 @@ +/* + * WAX Device Driver + * + * (c) Copyright 2000 The Puffin Group Inc. + * + * 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. + * + * by Helge Deller <deller@gmx.de> + */ + +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/types.h> + +#include <asm/io.h> +#include <asm/hardware.h> + +#include "gsc.h" + +#define WAX_GSC_IRQ 7 /* Hardcoded Interrupt for GSC */ + +static void wax_choose_irq(struct parisc_device *dev, void *ctrl) +{ + int irq; + + switch (dev->id.sversion) { + case 0x73: irq = 1; break; /* i8042 General */ + case 0x8c: irq = 6; break; /* Serial */ + case 0x90: irq = 10; break; /* EISA */ + default: return; /* Unknown */ + } + + gsc_asic_assign_irq(ctrl, irq, &dev->irq); + + switch (dev->id.sversion) { + case 0x73: irq = 2; break; /* i8042 High-priority */ + case 0x90: irq = 0; break; /* EISA NMI */ + default: return; /* No secondary IRQ */ + } + + gsc_asic_assign_irq(ctrl, irq, &dev->aux_irq); +} + +static void __init +wax_init_irq(struct gsc_asic *wax) +{ + unsigned long base = wax->hpa; + + /* Wax-off */ + gsc_writel(0x00000000, base+OFFSET_IMR); + + /* clear pending interrupts */ + gsc_readl(base+OFFSET_IRR); + + /* We're not really convinced we want to reset the onboard + * devices. Firmware does it for us... + */ + + /* Resets */ +// gsc_writel(0xFFFFFFFF, base+0x1000); /* HIL */ +// gsc_writel(0xFFFFFFFF, base+0x2000); /* RS232-B on Wax */ +} + +int __init +wax_init_chip(struct parisc_device *dev) +{ + struct gsc_asic *wax; + struct parisc_device *parent; + struct gsc_irq gsc_irq; + int ret; + + wax = kmalloc(sizeof(*wax), GFP_KERNEL); + if (!wax) + return -ENOMEM; + + wax->name = "wax"; + wax->hpa = dev->hpa; + + wax->version = 0; /* gsc_readb(wax->hpa+WAX_VER); */ + printk(KERN_INFO "%s at 0x%lx found.\n", wax->name, wax->hpa); + + /* Stop wax hissing for a bit */ + wax_init_irq(wax); + + /* the IRQ wax should use */ + dev->irq = gsc_claim_irq(&gsc_irq, WAX_GSC_IRQ); + if (dev->irq < 0) { + printk(KERN_ERR "%s(): cannot get GSC irq\n", + __FUNCTION__); + kfree(wax); + return -EBUSY; + } + + wax->eim = ((u32) gsc_irq.txn_addr) | gsc_irq.txn_data; + + ret = request_irq(gsc_irq.irq, gsc_asic_intr, 0, "wax", wax); + if (ret < 0) { + kfree(wax); + return ret; + } + + /* enable IRQ's for devices below WAX */ + gsc_writel(wax->eim, wax->hpa + OFFSET_IAR); + + /* Done init'ing, register this driver */ + ret = gsc_common_setup(dev, wax); + if (ret) { + kfree(wax); + return ret; + } + + gsc_fixup_irqs(dev, wax, wax_choose_irq); + /* On 715-class machines, Wax EISA is a sibling of Wax, not a child. */ + parent = parisc_parent(dev); + if (parent->id.hw_type != HPHW_IOA) { + gsc_fixup_irqs(parent, wax, wax_choose_irq); + } + + return ret; +} + +static struct parisc_device_id wax_tbl[] = { + { HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0008e }, + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, wax_tbl); + +struct parisc_driver wax_driver = { + .name = "wax", + .id_table = wax_tbl, + .probe = wax_init_chip, +}; |