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!
Этот коммит содержится в:
169
drivers/parisc/Kconfig
Обычный файл
169
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
|
27
drivers/parisc/Makefile
Обычный файл
27
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
|
||||
|
28
drivers/parisc/README.dino
Обычный файл
28
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"
|
||||
*/
|
||||
|
132
drivers/parisc/asp.c
Обычный файл
132
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,
|
||||
};
|
1593
drivers/parisc/ccio-dma.c
Обычный файл
1593
drivers/parisc/ccio-dma.c
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
201
drivers/parisc/ccio-rm-dma.c
Обычный файл
201
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);
|
||||
}
|
1044
drivers/parisc/dino.c
Обычный файл
1044
drivers/parisc/dino.c
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
464
drivers/parisc/eisa.c
Обычный файл
464
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);
|
||||
|
134
drivers/parisc/eisa_eeprom.c
Обычный файл
134
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);
|
521
drivers/parisc/eisa_enumerator.c
Обычный файл
521
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;
|
||||
}
|
||||
|
245
drivers/parisc/gsc.c
Обычный файл
245
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
|
||||
}
|
47
drivers/parisc/gsc.h
Обычный файл
47
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);
|
109
drivers/parisc/hppb.c
Обычный файл
109
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);
|
||||
}
|
171
drivers/parisc/iommu-helpers.h
Обычный файл
171
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;
|
||||
}
|
||||
|
921
drivers/parisc/iosapic.c
Обычный файл
921
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 */
|
188
drivers/parisc/iosapic_private.h
Обычный файл
188
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
|
||||
|
240
drivers/parisc/lasi.c
Обычный файл
240
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,
|
||||
};
|
1649
drivers/parisc/lba_pci.c
Обычный файл
1649
drivers/parisc/lba_pci.c
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
760
drivers/parisc/led.c
Обычный файл
760
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
|
735
drivers/parisc/pdc_stable.c
Обычный файл
735
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);
|
278
drivers/parisc/power.c
Обычный файл
278
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");
|
2165
drivers/parisc/sba_iommu.c
Обычный файл
2165
drivers/parisc/sba_iommu.c
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
508
drivers/parisc/superio.c
Обычный файл
508
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);
|
140
drivers/parisc/wax.c
Обычный файл
140
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,
|
||||
};
|
Ссылка в новой задаче
Block a user