[parisc-linux-cvs] superio serial patch

John Marvin jsm@udlkern.fc.hp.com
Thu, 29 Mar 2001 03:50:32 -0700 (MST)


Here is a patch I just committed to enable SuperIO serial support.
It also adds the interrupt infrastructure necessary to help get the
other SuperIO peripherals to work.

The changes to superio.c were large enough that I just included the
whole file, rather than a diff.

John

+++ arch/parisc/kernel/superio.c	Thu Mar 29 02:33:12 2001
/*      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@linuxcare.com>
 *     (C) Copyright 2001 John Marvin <jsm@fc.hp.com>
 *
 *     This program is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation; either version 2 of
 *     the License, or (at your option) any later version.
 *
 *     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/kernel_stat.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/timex.h>
#include <linux/malloc.h>
#include <linux/random.h>
#include <linux/serial.h>
#include <linux/pci.h>
#include <linux/ioport.h>
#include <asm/serial.h>
#include <linux/serial_reg.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/gsc.h>
#include <asm/irq.h>
#include <asm/superio.h>

static struct superio_device sio_dev = {
       iosapic_irq: -1
};

void
superio_inform_irq(int irq)
{
    if (sio_dev.iosapic_irq != -1) {
       printk("SuperIO: superio_inform_irq called twice! (more than one SuperIO?)\n");
       BUG();
       return;
    }

    sio_dev.iosapic_irq = irq;
}

extern void do_irq(struct irqaction *a, int i, struct pt_regs *p);

static void
superio_interrupt(int irq, void *devp, struct pt_regs *regs)
{
       struct superio_device *sio = (struct superio_device *)devp;
       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);

       if ((results & 0x80) == 0) {
	       BUG(); /* This shouldn't happen */
	       return;
       }

       /* Check to see which device is interrupting */

       local_irq = results & 0x7;

       if (local_irq == 2 || local_irq > 7) {
	       printk("superio: slave interrupted!\n");
	       BUG();
	       return;
       }

       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("spurious interrupt!\n");
		       return;
	       }
       }

       /* Call the appropriate device's interrupt */

       do_irq(&sio->irq_region->action[local_irq],
	       sio->irq_region->data.irqbase + local_irq,
	       regs);

       /* set EOI */

       outb((OCW2_SEOI|local_irq),IC_PIC1 + 0);
       return;
}

/* Initialize Super I/O device */

static void
superio_init(struct superio_device *sio)
{
       u8  i;
       u16 word;
       struct pci_dev *pdev = sio->lio_pdev;

       if (!pdev || sio->iosapic_irq == -1) {
	       printk("All SuperIO functions not found!\n");
	       BUG();
	       return;
       }

       printk ("SuperIO: Found NS87560 legacy I/O device at %s. iosapic irq = %i \n",
	       pdev->slot_name,sio->iosapic_irq);

       /* Find our I/O devices */
       pci_read_config_word (pdev, SIO_SP1BAR, &sio->sp1_base);
       sio->sp1_base &= ~1;
       printk ("SuperIO: Serial port 1 at 0x%x\n", sio->sp1_base);

       pci_read_config_word (pdev, SIO_SP2BAR, &sio->sp2_base);
       sio->sp2_base &= ~1;
       printk ("SuperIO: Serial port 2 at 0x%x\n", sio->sp2_base);

       pci_read_config_word (pdev, SIO_PPBAR, &sio->pp_base);
       sio->pp_base &= ~1;
       printk ("SuperIO: Parallel port at 0x%x\n", sio->pp_base);

       pci_read_config_word (pdev, SIO_FDCBAR, &sio->fdc_base);
       sio->fdc_base &= ~1;
       printk ("SuperIO: Floppy controller at 0x%x\n", sio->fdc_base);

       request_region (IC_PIC1, 0x1f, "pic1");
       request_region (IC_PIC2, 0x1f, "pic2");

       /* 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);

       /* Next project is programming the onboard interrupt
	* controllers.  PDC hasn't done this for us, since it's using
	* polled I/O.
	*/

       /* Set PIC interrupts to edge triggered */
       pci_write_config_byte (pdev, TRIGGER_1, 0x0);
       pci_write_config_byte (pdev, TRIGGER_2, 0x0);

       /* Disable all interrupt routing */
       for (i = IR_LOW ; i < IR_HIGH ; i++)
	       pci_write_config_byte (pdev, i, 0x0);

       /* PIC1 Initialization Command Word register programming */
       outb (0x11,IC_PIC1+0);  /* ICW1: ICW4 write req | ICW1 */
       outb (0x00,IC_PIC1+1);  /* ICW2: N/A */
       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);

       /* Set up interrupt routing */

       pci_write_config_byte (pdev, IR_USB, 0x10); /* USB on IRQ1 */
       pci_write_config_byte (pdev, IR_SER, 0x43); /* SP1 on IRQ3, SP2 on IRQ4 */
       pci_write_config_byte (pdev, IR_PFD, 0x65); /* PAR on IRQ5, FDC on IRQ6 */
       pci_write_config_byte (pdev, IR_IDE, 0x07); /* IDE1 on IRQ7 */

       /* Set USB and IDE to level triggered interrupts, rest to edge */
       pci_write_config_byte (pdev, TRIGGER_1, 0x81); /* IRQ 1 and 7 */

       pci_enable_device(pdev);

       if (request_irq(sio->iosapic_irq,superio_interrupt,SA_INTERRUPT,
		       "SuperIO",(void *)sio)) {

	       printk("SuperIO: could not get irq\n");
	       BUG();
	       return;
       }

       sio->iosapic_irq_enabled = 1;
}

static void
superio_disable_irq(void *dev, int local_irq)
{
       u8 r8;

       if ((local_irq < 1) || (local_irq == 2) || (local_irq > 7)) {
	   printk("SuperIO: Illegal irq number.\n");
	   BUG();
	   return;
       }

       /* Mask interrupt */

       r8 = inb(IC_PIC1+1);
       r8 |= (1 << local_irq);
       outb (r8,IC_PIC1+1);
}

static void
superio_enable_irq(void *dev, int local_irq)
{
       struct superio_device *sio = (struct superio_device *)dev;
       u8 r8;

       if ((local_irq < 1) || (local_irq == 2) || (local_irq > 7)) {
	   printk("SuperIO: Illegal irq number.\n");
	   BUG();
	   return;
       }

       /*
	* It's possible that we haven't initialized the legacy IO
	* function yet. If not, do it now.
	*/

       if (!sio->iosapic_irq_enabled)
	       superio_init(sio);

       /* Unmask interrupt */

       r8 = inb(IC_PIC1+1);
       r8 &= ~(1 << local_irq);
       outb (r8,IC_PIC1+1);
}

static void
superio_mask_irq(void *dev, int local_irq)
{
       BUG();
}

static void
superio_unmask_irq(void *dev, int local_irq)
{
       BUG();
}

static struct irq_region_ops superio_irq_ops = {
       disable_irq: superio_disable_irq,
       enable_irq: superio_enable_irq,
       mask_irq: superio_mask_irq,
       unmask_irq: superio_unmask_irq
};

static unsigned short expected_device[3] = {
       PCI_DEVICE_ID_NS_87415,
       PCI_DEVICE_ID_NS_87560_LIO,
       PCI_DEVICE_ID_NS_87560_USB
};

extern int
superio_fixup_irq(struct pci_dev *pcidev)
{
       int fn;
       int local_irq;

       /* Verify that the function number matches the expected device id. */

       fn = PCI_FUNC(pcidev->devfn);
       if (expected_device[fn] != pcidev->device) {
	       BUG();
	       return -1;
       }

       if (!sio_dev.irq_region) {

	       /* Allocate an irq region for SuperIO devices */

	       sio_dev.irq_region = alloc_irq_region(SUPERIO_NIRQS,
					       &superio_irq_ops,
					       IRQ_REG_DIS|IRQ_REG_MASK,
					       "SuperIO", (void *) &sio_dev);
	       if (!sio_dev.irq_region) {
		       printk(KERN_WARNING "SuperIO alloc_irq_region failed\n");
		       return -1;
	       }
       }

       /*
	* 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.
	*/

       if (fn == SUPERIO_LIO_FN) {
	       sio_dev.lio_pdev = pcidev; /* save for later initialization */
	       return -1;
       }

       if (fn == SUPERIO_IDE_FN)
	       local_irq = IDE_IRQ;
       else
	       local_irq = USB_IRQ;

       return(sio_dev.irq_region->data.irqbase + local_irq);
}

void
superio_serial_init(void)
{
       struct serial_struct *serial;
       int retval;

       if (!sio_dev.irq_region)
	       return; /* superio not present */

       if (!sio_dev.iosapic_irq_enabled)
	       superio_init(&sio_dev);

       serial = kmalloc(2 * sizeof (struct serial_struct), GFP_KERNEL);

       if (!serial) {
	       printk("superio: Could not get memory for serial struct.\n");
	       return;
       }

       memset(serial, 0, 2 * sizeof (struct serial_struct));

       serial->type = PORT_16550A;
       serial->line = 0;
       serial->port = sio_dev.sp1_base;
       serial->irq = sio_dev.irq_region->data.irqbase + SP1_IRQ;
       serial->io_type = SERIAL_IO_PORT;
       serial->flags = 0;
       serial->xmit_fifo_size = 16;
       serial->custom_divisor = 0;
       serial->baud_base = 115200;

       retval = register_serial(serial);

       if (retval < 0) {
	       printk("superio: Register Serial failed.\n");
	       kfree (serial);
	       return;
       }

       serial++;

       serial->type = PORT_16550A;
       serial->line = 1;
       serial->port = sio_dev.sp2_base;
       serial->irq = sio_dev.irq_region->data.irqbase + SP2_IRQ;
       serial->io_type = SERIAL_IO_PORT;
       serial->flags = 0;
       serial->xmit_fifo_size = 16;
       serial->custom_divisor = 0;
       serial->baud_base = 115200;

       retval = register_serial(serial);
       if (retval < 0)
	       printk("superio: Register Serial failed.\n");
}
--- arch/parisc/kernel/iosapic.c.old	Sun Mar 25 00:23:37 2001
+++ arch/parisc/kernel/iosapic.c	Wed Mar 28 09:21:24 2001
@@ -174,6 +174,9 @@
 #include <asm/segment.h>
 #include <asm/system.h>
 #include <asm/gsc.h>		/* gsc_read/write functions */
+#ifdef CONFIG_SUPERIO
+#include <asm/superio.h>
+#endif
 
 #include <asm/iosapic.h>
 #include "./iosapic_private.h"
@@ -632,12 +635,40 @@ iosapic_fixup_irq(void *isi_obj, struct 
 	struct vector_info *vi;
 	int isi_line;	/* line used by device */
 	int tmp;
+	int return_irq;
+#ifdef CONFIG_SUPERIO
+	int superio_irq = -1;
+#endif
 
 	if (NULL == isi) {
 		printk(KERN_WARNING MODULE_NAME ": 0x%p hpa not registered\n", isi->isi_hpa);
 		return(-1);
 	}
 
+#ifdef CONFIG_SUPERIO
+	if (is_superio_device(pcidev)) {
+		superio_irq = superio_fixup_irq(pcidev);
+		if (superio_irq == -1)
+		    return(-1);
+
+		if (PCI_FUNC(pcidev->devfn) != SUPERIO_USB_FN) {
+
+			/*
+			 * Only the SuperIO USB controller has an irt
+			 * entry. So, for other functions we just return
+			 * here, and let the USB controller hookup
+			 * the rest of the interrupt routing when it
+			 * comes through. Note that interrupts for all
+			 * three functions actually come through the
+			 * PIC's on function 1!
+			 */
+
+			pcidev->irq = superio_irq;
+			return(superio_irq);
+		}
+	}
+#endif /* CONFIG_SUPERIO */
+
 	/* lookup IRT entry for isi/slot/pin set */
 	irte = iosapic_xlate_pin(isi, pcidev);
 	if (NULL == irte) {
@@ -689,12 +720,21 @@ iosapic_fixup_irq(void *isi_obj, struct 
 	/*
 	** pcidev->irq still needs to be virtualized.
 	*/
-	pcidev->irq = isi->isi_region->data.irqbase + isi_line;
+
+	return_irq = isi->isi_region->data.irqbase + isi_line;
+
+#ifdef CONFIG_SUPERIO
+	if (superio_irq != -1) {
+		superio_inform_irq(return_irq);
+		return_irq = superio_irq;
+	}
+#endif
+	pcidev->irq = return_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);
+	PCI_FUNC(pcidev->devfn), pcidev->vendor, pcidev->device, isi_line, return_irq);
 
-	return(pcidev->irq);
+	return(return_irq);
 }
 
 
--- arch/parisc/defconfig.old	Sat Mar 24 02:26:57 2001
+++ arch/parisc/defconfig	Thu Mar 29 02:51:09 2001
@@ -28,7 +28,7 @@ CONFIG_PCI=y
 CONFIG_GSC_DINO=y
 CONFIG_PCI_LBA=y
 CONFIG_WAX_EISA=y
-# CONFIG_SUPERIO is not set
+CONFIG_SUPERIO=y
 CONFIG_IOSAPIC=y
 CONFIG_IOMMU_SBA=y
 CONFIG_PCI_NAMES=y
--- drivers/char/serial.c.old	Wed Mar 28 01:23:10 2001
+++ drivers/char/serial.c	Wed Mar 28 01:24:44 2001
@@ -5279,14 +5279,14 @@ static int __init rs_init(void)
 		tty_register_devfs(&callout_driver, 0,
 				   callout_driver.minor_start + state->line);
 	}
+#ifdef CONFIG_SERIAL_GSC
+	probe_serial_gsc();
+#endif
 #ifdef ENABLE_SERIAL_PCI
 	probe_serial_pci();
 #endif
 #ifdef ENABLE_SERIAL_PNP
 	probe_serial_pnp();
-#endif
-#ifdef CONFIG_SERIAL_GSC
-	probe_serial_gsc();
 #endif
 #ifdef CONFIG_SERIAL_CONSOLE
 	serial_console_init();
--- drivers/gsc/serial.c.old	Mon Mar 26 07:34:11 2001
+++ drivers/gsc/serial.c	Wed Mar 28 23:09:46 2001
@@ -29,10 +29,12 @@
 #include <asm/io.h>
 #include <asm/hardware.h>
 #include <asm/gsc.h>
+#ifdef CONFIG_SUPERIO
+#include <asm/superio.h>
+#endif
 
 #include "busdevice.h"
 
-
 static int serial_line_nr;
 
 static int __init 
@@ -103,5 +105,8 @@ static struct pa_iodc_driver serial_driv
 void __init 
 probe_serial_gsc(void)
 {
+#ifdef CONFIG_SUPERIO
+	superio_serial_init();
+#endif
 	register_driver(serial_drivers_for);
 }
+++ include/asm-parisc/superio.h	Wed Mar 28 23:09:46 2001
#ifndef _PARISC_SUPERIO_H
#define _PARISC_SUPERIO_H

/* Offsets to configuration and base address registers */
#define IC_PIC1    0x20                /* PCI I/O address of master 8259 */
#define IC_PIC2    0xA0                /* PCI I/O address of slave */
#define SIO_CR     0x5A                /* Configuration Register */
#define SIO_FDCBAR 0x90                /* Floppy Disk Controller BAR */
#define SIO_SP1BAR 0x94                /* Serial 1 BAR */
#define SIO_SP2BAR 0x98                /* Serial 2 BAR */
#define SIO_PPBAR  0x9C                /* Parallel BAR */

/* Interrupt triggers and routing */
#define TRIGGER_1  0x67                /* Edge/level trigger register 1 */
#define TRIGGER_2  0x68                /* Edge/level trigger register 2 */
#define IR_SER     0x69                /* Serial 1 [0:3] and Serial 2 [4:7] */
#define IR_PFD     0x6a                /* Parallel [0:3] and Floppy [4:7] */
#define IR_IDE     0x6b                /* IDE1 [0:3] and IDE2 [4:7] */
#define IR_USB     0x6d         /* USB [4:7] */
#define IR_LOW     0x69                /* Lowest interrupt routing reg */
#define IR_HIGH    0x71                /* Highest interrupt routing reg */

/* 8259 operational control words */
#define OCW2_EOI   0x20                /* Non-specific EOI */
#define OCW2_SEOI  0x60                /* Specific EOI */
#define OCW3_IIR   0x0A                /* Read request register */
#define OCW3_ISR   0x0B                /* Read service register */
#define OCW3_POLL  0x0C                /* Poll the PIC for an interrupt vector */

/* Interrupt lines. Only PIC1 is used */
#define USB_IRQ    1           /* USB */
#define SP1_IRQ    3           /* Serial port 1 */
#define SP2_IRQ    4           /* Serial port 2 */
#define PAR_IRQ    5           /* Parallel port */
#define FDC_IRQ    6           /* Floppy controller */
#define IDE_IRQ    7           /* IDE (pri+sec) */

#define SUPERIO_NIRQS   8

struct superio_device {
       u16 fdc_base;
       u16 sp1_base;
       u16 sp2_base;
       u16 pp_base;
       int iosapic_irq;
       int iosapic_irq_enabled;
       struct irq_region *irq_region;
       struct pci_dev *lio_pdev;       /* pci device for legacy IO fn */
};

/*
 * Does NS make a 87415 based plug in PCI card? If so, because of this
 * macro we currently don't support it being plugged into a machine
 * that contains a SuperIO chip AND has CONFIG_SUPERIO enabled.
 *
 * This could be fixed by checking to see if function 1 exists, and
 * if it is SuperIO Legacy IO; but really now, is this combination
 * going to EVER happen?
 */

#define SUPERIO_IDE_FN 0 /* Function number of IDE controller */
#define SUPERIO_LIO_FN 1 /* Function number of Legacy IO controller */
#define SUPERIO_USB_FN 2 /* Function number of USB controller */

#define is_superio_device(x) \
       (((x)->vendor == PCI_VENDOR_ID_NS) && \
       (  ((x)->device == PCI_DEVICE_ID_NS_87415) \
       || ((x)->device == PCI_DEVICE_ID_NS_87560_LIO) \
       || ((x)->device == PCI_DEVICE_ID_NS_87560_USB) ) )

extern void superio_inform_irq(int irq);
extern void superio_serial_init(void);
extern int superio_fixup_irq(struct pci_dev *pcidev);

#endif /* _PARISC_SUPERIO_H */
--- init/main.c.old	Wed Mar 28 07:15:50 2001
+++ init/main.c	Wed Mar 28 07:16:02 2001
@@ -722,9 +722,6 @@ static void __init do_basic_setup(void)
 #ifdef CONFIG_TC
 	tc_init();
 #endif
-#ifdef CONFIG_SUPERIO
-	superio_probe();
-#endif
 
 	/* Networking initialization needs a process context */ 
 	sock_init();