aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/sbus
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/sbus
downloadkernel_samsung_smdk4412-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz
kernel_samsung_smdk4412-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2
kernel_samsung_smdk4412-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/sbus')
-rw-r--r--drivers/sbus/Makefile9
-rw-r--r--drivers/sbus/char/Kconfig93
-rw-r--r--drivers/sbus/char/Makefile25
-rw-r--r--drivers/sbus/char/aurora.c2372
-rw-r--r--drivers/sbus/char/aurora.h276
-rw-r--r--drivers/sbus/char/bbc_envctrl.c645
-rw-r--r--drivers/sbus/char/bbc_i2c.c488
-rw-r--r--drivers/sbus/char/bbc_i2c.h20
-rw-r--r--drivers/sbus/char/bpp.c1079
-rw-r--r--drivers/sbus/char/cd180.h240
-rw-r--r--drivers/sbus/char/cpwatchdog.c832
-rw-r--r--drivers/sbus/char/display7seg.c234
-rw-r--r--drivers/sbus/char/envctrl.c1181
-rw-r--r--drivers/sbus/char/flash.c255
-rw-r--r--drivers/sbus/char/jsflash.c627
-rw-r--r--drivers/sbus/char/max1617.h27
-rw-r--r--drivers/sbus/char/openprom.c630
-rw-r--r--drivers/sbus/char/riowatchdog.c293
-rw-r--r--drivers/sbus/char/rtc.c178
-rw-r--r--drivers/sbus/char/uctrl.c422
-rw-r--r--drivers/sbus/char/vfc.h179
-rw-r--r--drivers/sbus/char/vfc_dev.c742
-rw-r--r--drivers/sbus/char/vfc_i2c.c347
-rw-r--r--drivers/sbus/char/vfc_i2c.h44
-rw-r--r--drivers/sbus/dvma.c137
-rw-r--r--drivers/sbus/sbus.c564
26 files changed, 11939 insertions, 0 deletions
diff --git a/drivers/sbus/Makefile b/drivers/sbus/Makefile
new file mode 100644
index 00000000000..7b1d24d9530
--- /dev/null
+++ b/drivers/sbus/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the linux kernel.
+#
+
+ifneq ($(ARCH),m68k)
+obj-y := sbus.o dvma.o
+endif
+
+obj-$(CONFIG_SBUSCHAR) += char/
diff --git a/drivers/sbus/char/Kconfig b/drivers/sbus/char/Kconfig
new file mode 100644
index 00000000000..90d8ef1f0bc
--- /dev/null
+++ b/drivers/sbus/char/Kconfig
@@ -0,0 +1,93 @@
+
+menu "Misc Linux/SPARC drivers"
+
+config SUN_OPENPROMIO
+ tristate "/dev/openprom device support"
+ help
+ This driver provides user programs with an interface to the SPARC
+ PROM device tree. The driver implements a SunOS-compatible
+ interface and a NetBSD-compatible interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called openprom.
+
+ If unsure, say Y.
+
+config SUN_MOSTEK_RTC
+ tristate "Mostek real time clock support"
+ help
+ The Mostek RTC chip is used on all known Sun computers except
+ some JavaStations. For a JavaStation you need to say Y both here
+ and to "Enhanced Real Time Clock Support".
+
+ Say Y here unless you are building a special purpose kernel.
+
+config OBP_FLASH
+ tristate "OBP Flash Device support"
+ depends on SPARC64
+ help
+ The OpenBoot PROM on Ultra systems is flashable. If you want to be
+ able to upgrade the OBP firmware, say Y here.
+
+config SUN_BPP
+ tristate "Bidirectional parallel port support (OBSOLETE)"
+ depends on EXPERIMENTAL
+ help
+ Say Y here to support Sun's obsolete variant of IEEE1284
+ bidirectional parallel port protocol as /dev/bppX. Can be built on
+ x86 machines.
+
+config SUN_VIDEOPIX
+ tristate "Videopix Frame Grabber (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && (BROKEN || !64BIT)
+ help
+ Say Y here to support the Videopix Frame Grabber from Sun
+ Microsystems, commonly found on SPARCstations. This card, which is
+ based on the Phillips SAA9051, can handle NTSC and PAL/SECAM and
+ SVIDEO signals.
+
+config SUN_AURORA
+ tristate "Aurora Multiboard 1600se (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && BROKEN
+ help
+ The Aurora Multiboard is a multi-port high-speed serial controller.
+ If you have one of these, say Y.
+
+config TADPOLE_TS102_UCTRL
+ tristate "Tadpole TS102 Microcontroller support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && SPARC32
+ help
+ Say Y here to directly support the TS102 Microcontroller interface
+ on the Tadpole Sparcbook 3. This device handles power-management
+ events, and can also notice the attachment/detachment of external
+ monitors and mice.
+
+config SUN_JSFLASH
+ tristate "JavaStation OS Flash SIMM (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && SPARC32
+ help
+ If you say Y here, you will be able to boot from your JavaStation's
+ Flash memory.
+
+# XXX Why don't we do "source drivers/char/Config.in" somewhere?
+# no shit
+config APM_RTC_IS_GMT
+ bool
+ depends on EXPERIMENTAL && SPARC32 && PCI
+ default y
+ help
+ Say Y here if your RTC (Real Time Clock a.k.a. hardware clock)
+ stores the time in GMT (Greenwich Mean Time). Say N if your RTC
+ stores localtime.
+
+ It is in fact recommended to store GMT in your RTC, because then you
+ don't have to worry about daylight savings time changes. The only
+ reason not to use GMT in your RTC is if you also run a broken OS
+ that doesn't understand GMT.
+
+config RTC
+ tristate "PC-style Real Time Clock Support"
+ depends on PCI && EXPERIMENTAL && SPARC32
+
+endmenu
+
diff --git a/drivers/sbus/char/Makefile b/drivers/sbus/char/Makefile
new file mode 100644
index 00000000000..3a5ea1dc789
--- /dev/null
+++ b/drivers/sbus/char/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for the kernel miscellaneous SPARC device drivers.
+#
+# Dave Redman Frame Buffer tuning support.
+#
+# 7 October 2000, Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+# Rewritten to use lists instead of if-statements.
+#
+
+vfc-objs := vfc_dev.o vfc_i2c.o
+bbc-objs := bbc_i2c.o bbc_envctrl.o
+
+obj-$(CONFIG_ENVCTRL) += envctrl.o
+obj-$(CONFIG_DISPLAY7SEG) += display7seg.o
+obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwatchdog.o
+obj-$(CONFIG_WATCHDOG_RIO) += riowatchdog.o
+obj-$(CONFIG_OBP_FLASH) += flash.o
+obj-$(CONFIG_SUN_OPENPROMIO) += openprom.o
+obj-$(CONFIG_SUN_MOSTEK_RTC) += rtc.o
+obj-$(CONFIG_SUN_BPP) += bpp.o
+obj-$(CONFIG_SUN_VIDEOPIX) += vfc.o
+obj-$(CONFIG_SUN_AURORA) += aurora.o
+obj-$(CONFIG_TADPOLE_TS102_UCTRL) += uctrl.o
+obj-$(CONFIG_SUN_JSFLASH) += jsflash.o
+obj-$(CONFIG_BBC_I2C) += bbc.o
diff --git a/drivers/sbus/char/aurora.c b/drivers/sbus/char/aurora.c
new file mode 100644
index 00000000000..e5fa1703856
--- /dev/null
+++ b/drivers/sbus/char/aurora.c
@@ -0,0 +1,2372 @@
+/* $Id: aurora.c,v 1.19 2002/01/08 16:00:16 davem Exp $
+ * linux/drivers/sbus/char/aurora.c -- Aurora multiport driver
+ *
+ * Copyright (c) 1999 by Oliver Aldulea (oli at bv dot ro)
+ *
+ * This code is based on the RISCom/8 multiport serial driver written
+ * by Dmitry Gorodchanin (pgmdsg@ibi.com), based on the Linux serial
+ * driver, written by Linus Torvalds, Theodore T'so and others.
+ * The Aurora multiport programming info was obtained mainly from the
+ * Cirrus Logic CD180 documentation (available on the web), and by
+ * doing heavy tests on the board. Many thanks to Eddie C. Dost for the
+ * help on the sbus interface.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Revision 1.0
+ *
+ * This is the first public release.
+ *
+ * Most of the information you need is in the aurora.h file. Please
+ * read that file before reading this one.
+ *
+ * Several parts of the code do not have comments yet.
+ *
+ * n.b. The board can support 115.2 bit rates, but only on a few
+ * ports. The total badwidth of one chip (ports 0-7 or 8-15) is equal
+ * to OSC_FREQ div 16. In case of my board, each chip can take 6
+ * channels of 115.2 kbaud. This information is not well-tested.
+ *
+ * Fixed to use tty_get_baud_rate().
+ * Theodore Ts'o <tytso@mit.edu>, 2001-Oct-12
+ */
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#ifdef AURORA_INT_DEBUG
+#include <linux/timer.h>
+#endif
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/oplib.h>
+#include <asm/system.h>
+#include <asm/kdebug.h>
+#include <asm/sbus.h>
+#include <asm/uaccess.h>
+
+#include "aurora.h"
+#include "cd180.h"
+
+unsigned char irqs[4] = {
+ 0, 0, 0, 0
+};
+
+#ifdef AURORA_INT_DEBUG
+int irqhit=0;
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+static struct tty_driver *aurora_driver;
+static struct Aurora_board aurora_board[AURORA_NBOARD] = {
+ {0,},
+};
+
+static struct Aurora_port aurora_port[AURORA_TNPORTS] = {
+ { 0, },
+};
+
+/* no longer used. static struct Aurora_board * IRQ_to_board[16] = { NULL, } ;*/
+static unsigned char * tmp_buf = NULL;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+DECLARE_TASK_QUEUE(tq_aurora);
+
+static inline int aurora_paranoia_check(struct Aurora_port const * port,
+ char *name, const char *routine)
+{
+#ifdef AURORA_PARANOIA_CHECK
+ static const char *badmagic =
+ KERN_DEBUG "aurora: Warning: bad aurora port magic number for device %s in %s\n";
+ static const char *badinfo =
+ KERN_DEBUG "aurora: Warning: null aurora port for device %s in %s\n";
+
+ if (!port) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (port->magic != AURORA_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/*
+ *
+ * Service functions for aurora driver.
+ *
+ */
+
+/* Get board number from pointer */
+extern inline int board_No (struct Aurora_board const * bp)
+{
+ return bp - aurora_board;
+}
+
+/* Get port number from pointer */
+extern inline int port_No (struct Aurora_port const * port)
+{
+ return AURORA_PORT(port - aurora_port);
+}
+
+/* Get pointer to board from pointer to port */
+extern inline struct Aurora_board * port_Board(struct Aurora_port const * port)
+{
+ return &aurora_board[AURORA_BOARD(port - aurora_port)];
+}
+
+/* Wait for Channel Command Register ready */
+extern inline void aurora_wait_CCR(struct aurora_reg128 * r)
+{
+ unsigned long delay;
+
+#ifdef AURORA_DEBUG
+printk("aurora_wait_CCR\n");
+#endif
+ /* FIXME: need something more descriptive than 100000 :) */
+ for (delay = 100000; delay; delay--)
+ if (!sbus_readb(&r->r[CD180_CCR]))
+ return;
+ printk(KERN_DEBUG "aurora: Timeout waiting for CCR.\n");
+}
+
+/*
+ * aurora probe functions.
+ */
+
+/* Must be called with enabled interrupts */
+extern inline void aurora_long_delay(unsigned long delay)
+{
+ unsigned long i;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_long_delay: start\n");
+#endif
+ for (i = jiffies + delay; time_before(jiffies, i); ) ;
+#ifdef AURORA_DEBUG
+ printk("aurora_long_delay: end\n");
+#endif
+}
+
+/* Reset and setup CD180 chip */
+static int aurora_init_CD180(struct Aurora_board * bp, int chip)
+{
+ unsigned long flags;
+ int id;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_init_CD180: start %d:%d\n",
+ board_No(bp), chip);
+#endif
+ save_flags(flags); cli();
+ sbus_writeb(0, &bp->r[chip]->r[CD180_CAR]);
+ sbus_writeb(0, &bp->r[chip]->r[CD180_GSVR]);
+
+ /* Wait for CCR ready */
+ aurora_wait_CCR(bp->r[chip]);
+
+ /* Reset CD180 chip */
+ sbus_writeb(CCR_HARDRESET, &bp->r[chip]->r[CD180_CCR]);
+ udelay(1);
+ sti();
+ id=1000;
+ while((--id) &&
+ (sbus_readb(&bp->r[chip]->r[CD180_GSVR])!=0xff))udelay(100);
+ if(!id) {
+ printk(KERN_ERR "aurora%d: Chip %d failed init.\n",
+ board_No(bp), chip);
+ restore_flags(flags);
+ return(-1);
+ }
+ cli();
+ sbus_writeb((board_No(bp)<<5)|((chip+1)<<3),
+ &bp->r[chip]->r[CD180_GSVR]); /* Set ID for this chip */
+ sbus_writeb(0x80|bp->ACK_MINT,
+ &bp->r[chip]->r[CD180_MSMR]); /* Prio for modem intr */
+ sbus_writeb(0x80|bp->ACK_TINT,
+ &bp->r[chip]->r[CD180_TSMR]); /* Prio for transmitter intr */
+ sbus_writeb(0x80|bp->ACK_RINT,
+ &bp->r[chip]->r[CD180_RSMR]); /* Prio for receiver intr */
+ /* Setting up prescaler. We need 4 tick per 1 ms */
+ sbus_writeb((bp->oscfreq/(1000000/AURORA_TPS)) >> 8,
+ &bp->r[chip]->r[CD180_PPRH]);
+ sbus_writeb((bp->oscfreq/(1000000/AURORA_TPS)) & 0xff,
+ &bp->r[chip]->r[CD180_PPRL]);
+
+ sbus_writeb(SRCR_AUTOPRI|SRCR_GLOBPRI,
+ &bp->r[chip]->r[CD180_SRCR]);
+
+ id = sbus_readb(&bp->r[chip]->r[CD180_GFRCR]);
+ printk(KERN_INFO "aurora%d: Chip %d id %02x: ",
+ board_No(bp), chip,id);
+ if(sbus_readb(&bp->r[chip]->r[CD180_SRCR]) & 128) {
+ switch (id) {
+ case 0x82:printk("CL-CD1864 rev A\n");break;
+ case 0x83:printk("CL-CD1865 rev A\n");break;
+ case 0x84:printk("CL-CD1865 rev B\n");break;
+ case 0x85:printk("CL-CD1865 rev C\n");break;
+ default:printk("Unknown.\n");
+ };
+ } else {
+ switch (id) {
+ case 0x81:printk("CL-CD180 rev B\n");break;
+ case 0x82:printk("CL-CD180 rev C\n");break;
+ default:printk("Unknown.\n");
+ };
+ }
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_init_CD180: end\n");
+#endif
+ return 0;
+}
+
+static int valid_irq(unsigned char irq)
+{
+int i;
+for(i=0;i<TYPE_1_IRQS;i++)
+ if (type_1_irq[i]==irq) return 1;
+return 0;
+}
+
+static irqreturn_t aurora_interrupt(int irq, void * dev_id, struct pt_regs * regs);
+
+/* Main probing routine, also sets irq. */
+static int aurora_probe(void)
+{
+ struct sbus_bus *sbus;
+ struct sbus_dev *sdev;
+ int grrr;
+ char buf[30];
+ int bn = 0;
+ struct Aurora_board *bp;
+
+ for_each_sbus(sbus) {
+ for_each_sbusdev(sdev, sbus) {
+/* printk("Try: %x %s\n",sdev,sdev->prom_name);*/
+ if (!strcmp(sdev->prom_name, "sio16")) {
+#ifdef AURORA_DEBUG
+ printk(KERN_INFO "aurora: sio16 at %p\n",sdev);
+#endif
+ if((sdev->reg_addrs[0].reg_size!=1) &&
+ (sdev->reg_addrs[1].reg_size!=128) &&
+ (sdev->reg_addrs[2].reg_size!=128) &&
+ (sdev->reg_addrs[3].reg_size!=4)) {
+ printk(KERN_ERR "aurora%d: registers' sizes "
+ "do not match.\n", bn);
+ break;
+ }
+ bp = &aurora_board[bn];
+ bp->r0 = (struct aurora_reg1 *)
+ sbus_ioremap(&sdev->resource[0], 0,
+ sdev->reg_addrs[0].reg_size,
+ "sio16");
+ if (bp->r0 == NULL) {
+ printk(KERN_ERR "aurora%d: can't map "
+ "reg_addrs[0]\n", bn);
+ break;
+ }
+#ifdef AURORA_DEBUG
+ printk("Map reg 0: %p\n", bp->r0);
+#endif
+ bp->r[0] = (struct aurora_reg128 *)
+ sbus_ioremap(&sdev->resource[1], 0,
+ sdev->reg_addrs[1].reg_size,
+ "sio16");
+ if (bp->r[0] == NULL) {
+ printk(KERN_ERR "aurora%d: can't map "
+ "reg_addrs[1]\n", bn);
+ break;
+ }
+#ifdef AURORA_DEBUG
+ printk("Map reg 1: %p\n", bp->r[0]);
+#endif
+ bp->r[1] = (struct aurora_reg128 *)
+ sbus_ioremap(&sdev->resource[2], 0,
+ sdev->reg_addrs[2].reg_size,
+ "sio16");
+ if (bp->r[1] == NULL) {
+ printk(KERN_ERR "aurora%d: can't map "
+ "reg_addrs[2]\n", bn);
+ break;
+ }
+#ifdef AURORA_DEBUG
+ printk("Map reg 2: %p\n", bp->r[1]);
+#endif
+ bp->r3 = (struct aurora_reg4 *)
+ sbus_ioremap(&sdev->resource[3], 0,
+ sdev->reg_addrs[3].reg_size,
+ "sio16");
+ if (bp->r3 == NULL) {
+ printk(KERN_ERR "aurora%d: can't map "
+ "reg_addrs[3]\n", bn);
+ break;
+ }
+#ifdef AURORA_DEBUG
+ printk("Map reg 3: %p\n", bp->r3);
+#endif
+ /* Variables setup */
+ bp->flags = 0;
+#ifdef AURORA_DEBUG
+ grrr=prom_getint(sdev->prom_node,"intr");
+ printk("intr pri %d\n", grrr);
+#endif
+ if ((bp->irq=irqs[bn]) && valid_irq(bp->irq) &&
+ !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+ free_irq(bp->irq|0x30, bp);
+ } else
+ if ((bp->irq=prom_getint(sdev->prom_node, "bintr")) && valid_irq(bp->irq) &&
+ !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+ free_irq(bp->irq|0x30, bp);
+ } else
+ if ((bp->irq=prom_getint(sdev->prom_node, "intr")) && valid_irq(bp->irq) &&
+ !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+ free_irq(bp->irq|0x30, bp);
+ } else
+ for(grrr=0;grrr<TYPE_1_IRQS;grrr++) {
+ if ((bp->irq=type_1_irq[grrr])&&!request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+ free_irq(bp->irq|0x30, bp);
+ break;
+ } else {
+ printk(KERN_ERR "aurora%d: Could not get an irq for this board !!!\n",bn);
+ bp->flags=0xff;
+ }
+ }
+ if(bp->flags==0xff)break;
+ printk(KERN_INFO "aurora%d: irq %d\n",bn,bp->irq&0x0f);
+ buf[0]=0;
+ grrr=prom_getproperty(sdev->prom_node,"dtr_rts",buf,sizeof(buf));
+ if(!strcmp(buf,"swapped")){
+ printk(KERN_INFO "aurora%d: Swapped DTR and RTS\n",bn);
+ bp->DTR=MSVR_RTS;
+ bp->RTS=MSVR_DTR;
+ bp->MSVDTR=CD180_MSVRTS;
+ bp->MSVRTS=CD180_MSVDTR;
+ bp->flags|=AURORA_BOARD_DTR_FLOW_OK;
+ }else{
+ #ifdef AURORA_FORCE_DTR_FLOW
+ printk(KERN_INFO "aurora%d: Forcing swapped DTR-RTS\n",bn);
+ bp->DTR=MSVR_RTS;
+ bp->RTS=MSVR_DTR;
+ bp->MSVDTR=CD180_MSVRTS;
+ bp->MSVRTS=CD180_MSVDTR;
+ bp->flags|=AURORA_BOARD_DTR_FLOW_OK;
+ #else
+ printk(KERN_INFO "aurora%d: Normal DTR and RTS\n",bn);
+ bp->DTR=MSVR_DTR;
+ bp->RTS=MSVR_RTS;
+ bp->MSVDTR=CD180_MSVDTR;
+ bp->MSVRTS=CD180_MSVRTS;
+ #endif
+ }
+ bp->oscfreq=prom_getint(sdev->prom_node,"clk")*100;
+ printk(KERN_INFO "aurora%d: Oscillator: %d Hz\n",bn,bp->oscfreq);
+ grrr=prom_getproperty(sdev->prom_node,"chip",buf,sizeof(buf));
+ printk(KERN_INFO "aurora%d: Chips: %s\n",bn,buf);
+ grrr=prom_getproperty(sdev->prom_node,"manu",buf,sizeof(buf));
+ printk(KERN_INFO "aurora%d: Manufacturer: %s\n",bn,buf);
+ grrr=prom_getproperty(sdev->prom_node,"model",buf,sizeof(buf));
+ printk(KERN_INFO "aurora%d: Model: %s\n",bn,buf);
+ grrr=prom_getproperty(sdev->prom_node,"rev",buf,sizeof(buf));
+ printk(KERN_INFO "aurora%d: Revision: %s\n",bn,buf);
+ grrr=prom_getproperty(sdev->prom_node,"mode",buf,sizeof(buf));
+ printk(KERN_INFO "aurora%d: Mode: %s\n",bn,buf);
+ #ifdef MODULE
+ bp->count=0;
+ #endif
+ bp->flags = AURORA_BOARD_PRESENT;
+ /* hardware ack */
+ bp->ACK_MINT=1;
+ bp->ACK_TINT=2;
+ bp->ACK_RINT=3;
+ bn++;
+ }
+ }
+ }
+ return bn;
+}
+
+static void aurora_release_io_range(struct Aurora_board *bp)
+{
+ sbus_iounmap((unsigned long)bp->r0, 1);
+ sbus_iounmap((unsigned long)bp->r[0], 128);
+ sbus_iounmap((unsigned long)bp->r[1], 128);
+ sbus_iounmap((unsigned long)bp->r3, 4);
+}
+
+extern inline void aurora_mark_event(struct Aurora_port * port, int event)
+{
+#ifdef AURORA_DEBUG
+ printk("aurora_mark_event: start\n");
+#endif
+ set_bit(event, &port->event);
+ queue_task(&port->tqueue, &tq_aurora);
+ mark_bh(AURORA_BH);
+#ifdef AURORA_DEBUG
+ printk("aurora_mark_event: end\n");
+#endif
+}
+
+static __inline__ struct Aurora_port * aurora_get_port(struct Aurora_board const * bp,
+ int chip,
+ unsigned char const *what)
+{
+ unsigned char channel;
+ struct Aurora_port * port;
+
+ channel = ((chip << 3) |
+ ((sbus_readb(&bp->r[chip]->r[CD180_GSCR]) & GSCR_CHAN) >> GSCR_CHAN_OFF));
+ port = &aurora_port[board_No(bp) * AURORA_NPORT * AURORA_NCD180 + channel];
+ if (port->flags & ASYNC_INITIALIZED)
+ return port;
+
+ printk(KERN_DEBUG "aurora%d: %s interrupt from invalid port %d\n",
+ board_No(bp), what, channel);
+ return NULL;
+}
+
+static void aurora_receive_exc(struct Aurora_board const * bp, int chip)
+{
+ struct Aurora_port *port;
+ struct tty_struct *tty;
+ unsigned char status;
+ unsigned char ch;
+
+ if (!(port = aurora_get_port(bp, chip, "Receive_x")))
+ return;
+
+ tty = port->tty;
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+#ifdef AURORA_INTNORM
+ printk("aurora%d: port %d: Working around flip buffer overflow.\n",
+ board_No(bp), port_No(port));
+#endif
+ return;
+ }
+
+#ifdef AURORA_REPORT_OVERRUN
+ status = sbus_readb(&bp->r[chip]->r[CD180_RCSR]);
+ if (status & RCSR_OE) {
+ port->overrun++;
+#if 1
+ printk("aurora%d: port %d: Overrun. Total %ld overruns.\n",
+ board_No(bp), port_No(port), port->overrun);
+#endif
+ }
+ status &= port->mark_mask;
+#else
+ status = sbus_readb(&bp->r[chip]->r[CD180_RCSR]) & port->mark_mask;
+#endif
+ ch = sbus_readb(&bp->r[chip]->r[CD180_RDR]);
+ if (!status)
+ return;
+
+ if (status & RCSR_TOUT) {
+/* printk("aurora%d: port %d: Receiver timeout. Hardware problems ?\n",
+ board_No(bp), port_No(port));*/
+ return;
+
+ } else if (status & RCSR_BREAK) {
+ printk(KERN_DEBUG "aurora%d: port %d: Handling break...\n",
+ board_No(bp), port_No(port));
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+ if (port->flags & ASYNC_SAK)
+ do_SAK(tty);
+
+ } else if (status & RCSR_PE)
+ *tty->flip.flag_buf_ptr++ = TTY_PARITY;
+
+ else if (status & RCSR_FE)
+ *tty->flip.flag_buf_ptr++ = TTY_FRAME;
+
+ else if (status & RCSR_OE)
+ *tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+
+ else
+ *tty->flip.flag_buf_ptr++ = 0;
+
+ *tty->flip.char_buf_ptr++ = ch;
+ tty->flip.count++;
+ queue_task(&tty->flip.tqueue, &tq_timer);
+}
+
+static void aurora_receive(struct Aurora_board const * bp, int chip)
+{
+ struct Aurora_port *port;
+ struct tty_struct *tty;
+ unsigned char count,cnt;
+
+ if (!(port = aurora_get_port(bp, chip, "Receive")))
+ return;
+
+ tty = port->tty;
+
+ count = sbus_readb(&bp->r[chip]->r[CD180_RDCR]);
+
+#ifdef AURORA_REPORT_FIFO
+ port->hits[count > 8 ? 9 : count]++;
+#endif
+
+ while (count--) {
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+#ifdef AURORA_INTNORM
+ printk("aurora%d: port %d: Working around flip buffer overflow.\n",
+ board_No(bp), port_No(port));
+#endif
+ break;
+ }
+ cnt = sbus_readb(&bp->r[chip]->r[CD180_RDR]);
+ *tty->flip.char_buf_ptr++ = cnt;
+ *tty->flip.flag_buf_ptr++ = 0;
+ tty->flip.count++;
+ }
+ queue_task(&tty->flip.tqueue, &tq_timer);
+}
+
+static void aurora_transmit(struct Aurora_board const * bp, int chip)
+{
+ struct Aurora_port *port;
+ struct tty_struct *tty;
+ unsigned char count;
+
+ if (!(port = aurora_get_port(bp, chip, "Transmit")))
+ return;
+
+ tty = port->tty;
+
+ if (port->SRER & SRER_TXEMPTY) {
+ /* FIFO drained */
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ port->SRER &= ~SRER_TXEMPTY;
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+ return;
+ }
+
+ if ((port->xmit_cnt <= 0 && !port->break_length)
+ || tty->stopped || tty->hw_stopped) {
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ port->SRER &= ~SRER_TXRDY;
+ sbus_writeb(port->SRER,
+ &bp->r[chip]->r[CD180_SRER]);
+ return;
+ }
+
+ if (port->break_length) {
+ if (port->break_length > 0) {
+ if (port->COR2 & COR2_ETC) {
+ sbus_writeb(CD180_C_ESC,
+ &bp->r[chip]->r[CD180_TDR]);
+ sbus_writeb(CD180_C_SBRK,
+ &bp->r[chip]->r[CD180_TDR]);
+ port->COR2 &= ~COR2_ETC;
+ }
+ count = MIN(port->break_length, 0xff);
+ sbus_writeb(CD180_C_ESC,
+ &bp->r[chip]->r[CD180_TDR]);
+ sbus_writeb(CD180_C_DELAY,
+ &bp->r[chip]->r[CD180_TDR]);
+ sbus_writeb(count,
+ &bp->r[chip]->r[CD180_TDR]);
+ if (!(port->break_length -= count))
+ port->break_length--;
+ } else {
+ sbus_writeb(CD180_C_ESC,
+ &bp->r[chip]->r[CD180_TDR]);
+ sbus_writeb(CD180_C_EBRK,
+ &bp->r[chip]->r[CD180_TDR]);
+ sbus_writeb(port->COR2,
+ &bp->r[chip]->r[CD180_COR2]);
+ aurora_wait_CCR(bp->r[chip]);
+ sbus_writeb(CCR_CORCHG2,
+ &bp->r[chip]->r[CD180_CCR]);
+ port->break_length = 0;
+ }
+ return;
+ }
+
+ count = CD180_NFIFO;
+ do {
+ u8 byte = port->xmit_buf[port->xmit_tail++];
+
+ sbus_writeb(byte, &bp->r[chip]->r[CD180_TDR]);
+ port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE-1);
+ if (--port->xmit_cnt <= 0)
+ break;
+ } while (--count > 0);
+
+ if (port->xmit_cnt <= 0) {
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ port->SRER &= ~SRER_TXRDY;
+ sbus_writeb(port->SRER,
+ &bp->r[chip]->r[CD180_SRER]);
+ }
+ if (port->xmit_cnt <= port->wakeup_chars)
+ aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+}
+
+static void aurora_check_modem(struct Aurora_board const * bp, int chip)
+{
+ struct Aurora_port *port;
+ struct tty_struct *tty;
+ unsigned char mcr;
+
+ if (!(port = aurora_get_port(bp, chip, "Modem")))
+ return;
+
+ tty = port->tty;
+
+ mcr = sbus_readb(&bp->r[chip]->r[CD180_MCR]);
+ if (mcr & MCR_CDCHG) {
+ if (sbus_readb(&bp->r[chip]->r[CD180_MSVR]) & MSVR_CD)
+ wake_up_interruptible(&port->open_wait);
+ else
+ schedule_task(&port->tqueue_hangup);
+ }
+
+/* We don't have such things yet. My aurora board has DTR and RTS swapped, but that doesn't count in this driver. Let's hope
+ * Aurora didn't made any boards with CTS or DSR broken...
+ */
+/* #ifdef AURORA_BRAIN_DAMAGED_CTS
+ if (mcr & MCR_CTSCHG) {
+ if (aurora_in(bp, CD180_MSVR) & MSVR_CTS) {
+ tty->hw_stopped = 0;
+ port->SRER |= SRER_TXRDY;
+ if (port->xmit_cnt <= port->wakeup_chars)
+ aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+ } else {
+ tty->hw_stopped = 1;
+ port->SRER &= ~SRER_TXRDY;
+ }
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+ }
+ if (mcr & MCR_DSRCHG) {
+ if (aurora_in(bp, CD180_MSVR) & MSVR_DSR) {
+ tty->hw_stopped = 0;
+ port->SRER |= SRER_TXRDY;
+ if (port->xmit_cnt <= port->wakeup_chars)
+ aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+ } else {
+ tty->hw_stopped = 1;
+ port->SRER &= ~SRER_TXRDY;
+ }
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+ }
+#endif AURORA_BRAIN_DAMAGED_CTS */
+
+ /* Clear change bits */
+ sbus_writeb(0, &bp->r[chip]->r[CD180_MCR]);
+}
+
+/* The main interrupt processing routine */
+static irqreturn_t aurora_interrupt(int irq, void * dev_id, struct pt_regs * regs)
+{
+ unsigned char status;
+ unsigned char ack,chip/*,chip_id*/;
+ struct Aurora_board * bp = (struct Aurora_board *) dev_id;
+ unsigned long loop = 0;
+
+#ifdef AURORA_INT_DEBUG
+ printk("IRQ%d %d\n",irq,++irqhit);
+#ifdef AURORA_FLOODPRO
+ if (irqhit>=AURORA_FLOODPRO)
+ sbus_writeb(8, &bp->r0->r);
+#endif
+#endif
+
+/* old bp = IRQ_to_board[irq&0x0f];*/
+
+ if (!bp || !(bp->flags & AURORA_BOARD_ACTIVE))
+ return IRQ_NONE;
+
+/* The while() below takes care of this.
+ status = sbus_readb(&bp->r[0]->r[CD180_SRSR]);
+#ifdef AURORA_INT_DEBUG
+ printk("mumu: %02x\n", status);
+#endif
+ if (!(status&SRSR_ANYINT))
+ return IRQ_NONE; * Nobody has anything to say, so exit *
+*/
+ while ((loop++ < 48) &&
+ (status = sbus_readb(&bp->r[0]->r[CD180_SRSR]) & SRSR_ANYINT)){
+#ifdef AURORA_INT_DEBUG
+ printk("SRSR: %02x\n", status);
+#endif
+ if (status & SRSR_REXT) {
+ ack = sbus_readb(&bp->r3->r[bp->ACK_RINT]);
+#ifdef AURORA_INT_DEBUG
+ printk("R-ACK %02x\n", ack);
+#endif
+ if ((ack >> 5) == board_No(bp)) {
+ if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) {
+ if ((ack&GSVR_ITMASK)==GSVR_IT_RGD) {
+ aurora_receive(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ } else if ((ack & GSVR_ITMASK) == GSVR_IT_REXC) {
+ aurora_receive_exc(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ }
+ }
+ }
+ } else if (status & SRSR_TEXT) {
+ ack = sbus_readb(&bp->r3->r[bp->ACK_TINT]);
+#ifdef AURORA_INT_DEBUG
+ printk("T-ACK %02x\n", ack);
+#endif
+ if ((ack >> 5) == board_No(bp)) {
+ if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) {
+ if ((ack&GSVR_ITMASK)==GSVR_IT_TX) {
+ aurora_transmit(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ }
+ }
+ }
+ } else if (status & SRSR_MEXT) {
+ ack = sbus_readb(&bp->r3->r[bp->ACK_MINT]);
+#ifdef AURORA_INT_DEBUG
+ printk("M-ACK %02x\n", ack);
+#endif
+ if ((ack >> 5) == board_No(bp)) {
+ if ((chip = ((ack>>3)&3)-1) < AURORA_NCD180) {
+ if ((ack&GSVR_ITMASK)==GSVR_IT_MDM) {
+ aurora_check_modem(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ }
+ }
+ }
+ }
+ }
+/* I guess this faster code can be used with CD1865, using AUROPRI and GLOBPRI. */
+#if 0
+ while ((loop++ < 48)&&(status=bp->r[0]->r[CD180_SRSR]&SRSR_ANYINT)){
+#ifdef AURORA_INT_DEBUG
+ printk("SRSR: %02x\n",status);
+#endif
+ ack = sbus_readb(&bp->r3->r[0]);
+#ifdef AURORA_INT_DEBUG
+ printk("ACK: %02x\n",ack);
+#endif
+ if ((ack>>5)==board_No(bp)) {
+ if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) {
+ ack&=GSVR_ITMASK;
+ if (ack==GSVR_IT_RGD) {
+ aurora_receive(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ } else if (ack==GSVR_IT_REXC) {
+ aurora_receive_exc(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ } else if (ack==GSVR_IT_TX) {
+ aurora_transmit(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ } else if (ack==GSVR_IT_MDM) {
+ aurora_check_modem(bp,chip);
+ sbus_writeb(0,
+ &bp->r[chip]->r[CD180_EOSRR]);
+ }
+ }
+ }
+ }
+#endif
+
+/* This is the old handling routine, used in riscom8 for only one CD180. I keep it here for reference. */
+#if 0
+ for(chip=0;chip<AURORA_NCD180;chip++){
+ chip_id=(board_No(bp)<<5)|((chip+1)<<3);
+ loop=0;
+ while ((loop++ < 1) &&
+ ((status = sbus_readb(&bp->r[chip]->r[CD180_SRSR])) &
+ (SRSR_TEXT | SRSR_MEXT | SRSR_REXT))) {
+
+ if (status & SRSR_REXT) {
+ ack = sbus_readb(&bp->r3->r[bp->ACK_RINT]);
+ if (ack == (chip_id | GSVR_IT_RGD)) {
+#ifdef AURORA_INTMSG
+ printk("RX ACK\n");
+#endif
+ aurora_receive(bp,chip);
+ } else if (ack == (chip_id | GSVR_IT_REXC)) {
+#ifdef AURORA_INTMSG
+ printk("RXC ACK\n");
+#endif
+ aurora_receive_exc(bp,chip);
+ } else {
+#ifdef AURORA_INTNORM
+ printk("aurora%d-%d: Bad receive ack 0x%02x.\n",
+ board_No(bp), chip, ack);
+#endif
+ }
+ } else if (status & SRSR_TEXT) {
+ ack = sbus_readb(&bp->r3->r[bp->ACK_TINT]);
+ if (ack == (chip_id | GSVR_IT_TX)){
+#ifdef AURORA_INTMSG
+ printk("TX ACK\n");
+#endif
+ aurora_transmit(bp,chip);
+ } else {
+#ifdef AURORA_INTNORM
+ printk("aurora%d-%d: Bad transmit ack 0x%02x.\n",
+ board_No(bp), chip, ack);
+#endif
+ }
+ } else if (status & SRSR_MEXT) {
+ ack = sbus_readb(&bp->r3->r[bp->ACK_MINT]);
+ if (ack == (chip_id | GSVR_IT_MDM)){
+#ifdef AURORA_INTMSG
+ printk("MDM ACK\n");
+#endif
+ aurora_check_modem(bp,chip);
+ } else {
+#ifdef AURORA_INTNORM
+ printk("aurora%d-%d: Bad modem ack 0x%02x.\n",
+ board_No(bp), chip, ack);
+#endif
+ }
+ }
+ sbus_writeb(0, &bp->r[chip]->r[CD180_EOSRR]);
+ }
+ }
+#endif
+
+ return IRQ_HANDLED;
+}
+
+#ifdef AURORA_INT_DEBUG
+static void aurora_timer (unsigned long ignored);
+
+static struct timer_list aurora_poll_timer =
+ TIMER_INITIALIZER(aurora_timer, 0, 0);
+
+static void
+aurora_timer (unsigned long ignored)
+{
+ unsigned long flags;
+ int i;
+
+ save_flags(flags); cli();
+
+ printk("SRSR: %02x,%02x - ",
+ sbus_readb(&aurora_board[0].r[0]->r[CD180_SRSR]),
+ sbus_readb(&aurora_board[0].r[1]->r[CD180_SRSR]));
+ for (i = 0; i < 4; i++) {
+ udelay(1);
+ printk("%02x ",
+ sbus_readb(&aurora_board[0].r3->r[i]));
+ }
+ printk("\n");
+
+ aurora_poll_timer.expires = jiffies + 300;
+ add_timer (&aurora_poll_timer);
+
+ restore_flags(flags);
+}
+#endif
+
+/*
+ * Routines for open & close processing.
+ */
+
+/* Called with disabled interrupts */
+static int aurora_setup_board(struct Aurora_board * bp)
+{
+ int error;
+
+#ifdef AURORA_ALLIRQ
+ int i;
+ for (i = 0; i < AURORA_ALLIRQ; i++) {
+ error = request_irq(allirq[i]|0x30, aurora_interrupt, SA_SHIRQ,
+ "sio16", bp);
+ if (error)
+ printk(KERN_ERR "IRQ%d request error %d\n",
+ allirq[i], error);
+ }
+#else
+ error = request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ,
+ "sio16", bp);
+ if (error) {
+ printk(KERN_ERR "IRQ request error %d\n", error);
+ return error;
+ }
+#endif
+ /* Board reset */
+ sbus_writeb(0, &bp->r0->r);
+ udelay(1);
+ if (bp->flags & AURORA_BOARD_TYPE_2) {
+ /* unknown yet */
+ } else {
+ sbus_writeb((AURORA_CFG_ENABLE_IO | AURORA_CFG_ENABLE_IRQ |
+ (((bp->irq)&0x0f)>>2)),
+ &bp->r0->r);
+ }
+ udelay(10000);
+
+ if (aurora_init_CD180(bp,0))error=1;error=0;
+ if (aurora_init_CD180(bp,1))error++;
+ if (error == AURORA_NCD180) {
+ printk(KERN_ERR "Both chips failed initialisation.\n");
+ return -EIO;
+ }
+
+#ifdef AURORA_INT_DEBUG
+ aurora_poll_timer.expires= jiffies + 1;
+ add_timer(&aurora_poll_timer);
+#endif
+#ifdef AURORA_DEBUG
+ printk("aurora_setup_board: end\n");
+#endif
+ return 0;
+}
+
+/* Called with disabled interrupts */
+static void aurora_shutdown_board(struct Aurora_board *bp)
+{
+ int i;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_shutdown_board: start\n");
+#endif
+
+#ifdef AURORA_INT_DEBUG
+ del_timer(&aurora_poll_timer);
+#endif
+
+#ifdef AURORA_ALLIRQ
+ for(i=0;i<AURORA_ALLIRQ;i++){
+ free_irq(allirq[i]|0x30, bp);
+/* IRQ_to_board[allirq[i]&0xf] = NULL;*/
+ }
+#else
+ free_irq(bp->irq|0x30, bp);
+/* IRQ_to_board[bp->irq&0xf] = NULL;*/
+#endif
+ /* Drop all DTR's */
+ for(i=0;i<16;i++){
+ sbus_writeb(i & 7, &bp->r[i>>3]->r[CD180_CAR]);
+ udelay(1);
+ sbus_writeb(0, &bp->r[i>>3]->r[CD180_MSVR]);
+ udelay(1);
+ }
+ /* Board shutdown */
+ sbus_writeb(0, &bp->r0->r);
+
+#ifdef AURORA_DEBUG
+ printk("aurora_shutdown_board: end\n");
+#endif
+}
+
+/* Setting up port characteristics.
+ * Must be called with disabled interrupts
+ */
+static void aurora_change_speed(struct Aurora_board *bp, struct Aurora_port *port)
+{
+ struct tty_struct *tty;
+ unsigned long baud;
+ long tmp;
+ unsigned char cor1 = 0, cor3 = 0;
+ unsigned char mcor1 = 0, mcor2 = 0,chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_change_speed: start\n");
+#endif
+ if (!(tty = port->tty) || !tty->termios)
+ return;
+
+ chip = AURORA_CD180(port_No(port));
+
+ port->SRER = 0;
+ port->COR2 = 0;
+ port->MSVR = MSVR_RTS|MSVR_DTR;
+
+ baud = tty_get_baud_rate(tty);
+
+ /* Select port on the board */
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+
+ if (!baud) {
+ /* Drop DTR & exit */
+ port->MSVR &= ~(bp->DTR|bp->RTS);
+ sbus_writeb(port->MSVR,
+ &bp->r[chip]->r[CD180_MSVR]);
+ return;
+ } else {
+ /* Set DTR on */
+ port->MSVR |= bp->DTR;
+ sbus_writeb(port->MSVR,
+ &bp->r[chip]->r[CD180_MSVR]);
+ }
+
+ /* Now we must calculate some speed dependent things. */
+
+ /* Set baud rate for port. */
+ tmp = (((bp->oscfreq + baud/2) / baud +
+ CD180_TPC/2) / CD180_TPC);
+
+/* tmp = (bp->oscfreq/7)/baud;
+ if((tmp%10)>4)tmp=tmp/10+1;else tmp=tmp/10;*/
+/* printk("Prescaler period: %d\n",tmp);*/
+
+ sbus_writeb((tmp >> 8) & 0xff,
+ &bp->r[chip]->r[CD180_RBPRH]);
+ sbus_writeb((tmp >> 8) & 0xff,
+ &bp->r[chip]->r[CD180_TBPRH]);
+ sbus_writeb(tmp & 0xff, &bp->r[chip]->r[CD180_RBPRL]);
+ sbus_writeb(tmp & 0xff, &bp->r[chip]->r[CD180_TBPRL]);
+
+ baud = (baud + 5) / 10; /* Estimated CPS */
+
+ /* Two timer ticks seems enough to wakeup something like SLIP driver */
+ tmp = ((baud + HZ/2) / HZ) * 2 - CD180_NFIFO;
+ port->wakeup_chars = (tmp < 0) ? 0 : ((tmp >= SERIAL_XMIT_SIZE) ?
+ SERIAL_XMIT_SIZE - 1 : tmp);
+
+ /* Receiver timeout will be transmission time for 1.5 chars */
+ tmp = (AURORA_TPS + AURORA_TPS/2 + baud/2) / baud;
+ tmp = (tmp > 0xff) ? 0xff : tmp;
+ sbus_writeb(tmp, &bp->r[chip]->r[CD180_RTPR]);
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ cor1 |= COR1_5BITS;
+ break;
+ case CS6:
+ cor1 |= COR1_6BITS;
+ break;
+ case CS7:
+ cor1 |= COR1_7BITS;
+ break;
+ case CS8:
+ cor1 |= COR1_8BITS;
+ break;
+ }
+
+ if (C_CSTOPB(tty))
+ cor1 |= COR1_2SB;
+
+ cor1 |= COR1_IGNORE;
+ if (C_PARENB(tty)) {
+ cor1 |= COR1_NORMPAR;
+ if (C_PARODD(tty))
+ cor1 |= COR1_ODDP;
+ if (I_INPCK(tty))
+ cor1 &= ~COR1_IGNORE;
+ }
+ /* Set marking of some errors */
+ port->mark_mask = RCSR_OE | RCSR_TOUT;
+ if (I_INPCK(tty))
+ port->mark_mask |= RCSR_FE | RCSR_PE;
+ if (I_BRKINT(tty) || I_PARMRK(tty))
+ port->mark_mask |= RCSR_BREAK;
+ if (I_IGNPAR(tty))
+ port->mark_mask &= ~(RCSR_FE | RCSR_PE);
+ if (I_IGNBRK(tty)) {
+ port->mark_mask &= ~RCSR_BREAK;
+ if (I_IGNPAR(tty))
+ /* Real raw mode. Ignore all */
+ port->mark_mask &= ~RCSR_OE;
+ }
+ /* Enable Hardware Flow Control */
+ if (C_CRTSCTS(tty)) {
+/*#ifdef AURORA_BRAIN_DAMAGED_CTS
+ port->SRER |= SRER_DSR | SRER_CTS;
+ mcor1 |= MCOR1_DSRZD | MCOR1_CTSZD;
+ mcor2 |= MCOR2_DSROD | MCOR2_CTSOD;
+ tty->hw_stopped = !(aurora_in(bp, CD180_MSVR) & (MSVR_CTS|MSVR_DSR));
+#else*/
+ port->COR2 |= COR2_CTSAE;
+/*#endif*/
+ if (bp->flags&AURORA_BOARD_DTR_FLOW_OK) {
+ mcor1 |= AURORA_RXTH;
+ }
+ }
+ /* Enable Software Flow Control. FIXME: I'm not sure about this */
+ /* Some people reported that it works, but I still doubt */
+ if (I_IXON(tty)) {
+ port->COR2 |= COR2_TXIBE;
+ cor3 |= (COR3_FCT | COR3_SCDE);
+ if (I_IXANY(tty))
+ port->COR2 |= COR2_IXM;
+ sbus_writeb(START_CHAR(tty),
+ &bp->r[chip]->r[CD180_SCHR1]);
+ sbus_writeb(STOP_CHAR(tty),
+ &bp->r[chip]->r[CD180_SCHR2]);
+ sbus_writeb(START_CHAR(tty),
+ &bp->r[chip]->r[CD180_SCHR3]);
+ sbus_writeb(STOP_CHAR(tty),
+ &bp->r[chip]->r[CD180_SCHR4]);
+ }
+ if (!C_CLOCAL(tty)) {
+ /* Enable CD check */
+ port->SRER |= SRER_CD;
+ mcor1 |= MCOR1_CDZD;
+ mcor2 |= MCOR2_CDOD;
+ }
+
+ if (C_CREAD(tty))
+ /* Enable receiver */
+ port->SRER |= SRER_RXD;
+
+ /* Set input FIFO size (1-8 bytes) */
+ cor3 |= AURORA_RXFIFO;
+ /* Setting up CD180 channel registers */
+ sbus_writeb(cor1, &bp->r[chip]->r[CD180_COR1]);
+ sbus_writeb(port->COR2, &bp->r[chip]->r[CD180_COR2]);
+ sbus_writeb(cor3, &bp->r[chip]->r[CD180_COR3]);
+ /* Make CD180 know about registers change */
+ aurora_wait_CCR(bp->r[chip]);
+ sbus_writeb(CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3,
+ &bp->r[chip]->r[CD180_CCR]);
+ /* Setting up modem option registers */
+ sbus_writeb(mcor1, &bp->r[chip]->r[CD180_MCOR1]);
+ sbus_writeb(mcor2, &bp->r[chip]->r[CD180_MCOR2]);
+ /* Enable CD180 transmitter & receiver */
+ aurora_wait_CCR(bp->r[chip]);
+ sbus_writeb(CCR_TXEN | CCR_RXEN, &bp->r[chip]->r[CD180_CCR]);
+ /* Enable interrupts */
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+ /* And finally set RTS on */
+ sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+#ifdef AURORA_DEBUG
+ printk("aurora_change_speed: end\n");
+#endif
+}
+
+/* Must be called with interrupts enabled */
+static int aurora_setup_port(struct Aurora_board *bp, struct Aurora_port *port)
+{
+ unsigned long flags;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_setup_port: start %d\n",port_No(port));
+#endif
+ if (port->flags & ASYNC_INITIALIZED)
+ return 0;
+
+ if (!port->xmit_buf) {
+ /* We may sleep in get_zeroed_page() */
+ unsigned long tmp;
+
+ if (!(tmp = get_zeroed_page(GFP_KERNEL)))
+ return -ENOMEM;
+
+ if (port->xmit_buf) {
+ free_page(tmp);
+ return -ERESTARTSYS;
+ }
+ port->xmit_buf = (unsigned char *) tmp;
+ }
+
+ save_flags(flags); cli();
+
+ if (port->tty)
+ clear_bit(TTY_IO_ERROR, &port->tty->flags);
+
+#ifdef MODULE
+ if ((port->count == 1) && ((++bp->count) == 1))
+ bp->flags |= AURORA_BOARD_ACTIVE;
+#endif
+
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ aurora_change_speed(bp, port);
+ port->flags |= ASYNC_INITIALIZED;
+
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_setup_port: end\n");
+#endif
+ return 0;
+}
+
+/* Must be called with interrupts disabled */
+static void aurora_shutdown_port(struct Aurora_board *bp, struct Aurora_port *port)
+{
+ struct tty_struct *tty;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_shutdown_port: start\n");
+#endif
+ if (!(port->flags & ASYNC_INITIALIZED))
+ return;
+
+ chip = AURORA_CD180(port_No(port));
+
+#ifdef AURORA_REPORT_OVERRUN
+ printk("aurora%d: port %d: Total %ld overruns were detected.\n",
+ board_No(bp), port_No(port), port->overrun);
+#endif
+#ifdef AURORA_REPORT_FIFO
+ {
+ int i;
+
+ printk("aurora%d: port %d: FIFO hits [ ",
+ board_No(bp), port_No(port));
+ for (i = 0; i < 10; i++) {
+ printk("%ld ", port->hits[i]);
+ }
+ printk("].\n");
+ }
+#endif
+ if (port->xmit_buf) {
+ free_page((unsigned long) port->xmit_buf);
+ port->xmit_buf = NULL;
+ }
+
+ if (!(tty = port->tty) || C_HUPCL(tty)) {
+ /* Drop DTR */
+ port->MSVR &= ~(bp->DTR|bp->RTS);
+ sbus_writeb(port->MSVR,
+ &bp->r[chip]->r[CD180_MSVR]);
+ }
+
+ /* Select port */
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+
+ /* Reset port */
+ aurora_wait_CCR(bp->r[chip]);
+ sbus_writeb(CCR_SOFTRESET, &bp->r[chip]->r[CD180_CCR]);
+
+ /* Disable all interrupts from this port */
+ port->SRER = 0;
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+
+ if (tty)
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ port->flags &= ~ASYNC_INITIALIZED;
+
+#ifdef MODULE
+ if (--bp->count < 0) {
+ printk(KERN_DEBUG "aurora%d: aurora_shutdown_port: "
+ "bad board count: %d\n",
+ board_No(bp), bp->count);
+ bp->count = 0;
+ }
+
+ if (!bp->count)
+ bp->flags &= ~AURORA_BOARD_ACTIVE;
+#endif
+
+#ifdef AURORA_DEBUG
+ printk("aurora_shutdown_port: end\n");
+#endif
+}
+
+
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct Aurora_port *port)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct Aurora_board *bp = port_Board(port);
+ int retval;
+ int do_clocal = 0;
+ int CD;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("block_til_ready: start\n");
+#endif
+ chip = AURORA_CD180(port_No(port));
+
+ /* If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&port->close_wait);
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+ }
+
+ /* If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (C_CLOCAL(tty))
+ do_clocal = 1;
+
+ /* Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * rs_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&port->open_wait, &wait);
+ cli();
+ if (!tty_hung_up_p(filp))
+ port->count--;
+ sti();
+ port->blocked_open++;
+ while (1) {
+ cli();
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ CD = sbus_readb(&bp->r[chip]->r[CD180_MSVR]) & MSVR_CD;
+ port->MSVR=bp->RTS;
+
+ /* auto drops DTR */
+ sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+ sti();
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(port->flags & ASYNC_INITIALIZED)) {
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!(port->flags & ASYNC_CLOSING) &&
+ (do_clocal || CD))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&port->open_wait, &wait);
+ if (!tty_hung_up_p(filp))
+ port->count++;
+ port->blocked_open--;
+ if (retval)
+ return retval;
+
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+#ifdef AURORA_DEBUG
+ printk("block_til_ready: end\n");
+#endif
+ return 0;
+}
+
+static int aurora_open(struct tty_struct * tty, struct file * filp)
+{
+ int board;
+ int error;
+ struct Aurora_port * port;
+ struct Aurora_board * bp;
+ unsigned long flags;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_open: start\n");
+#endif
+
+ board = AURORA_BOARD(tty->index);
+ if (board > AURORA_NBOARD ||
+ !(aurora_board[board].flags & AURORA_BOARD_PRESENT)) {
+#ifdef AURORA_DEBUG
+ printk("aurora_open: error board %d present %d\n",
+ board, aurora_board[board].flags & AURORA_BOARD_PRESENT);
+#endif
+ return -ENODEV;
+ }
+
+ bp = &aurora_board[board];
+ port = aurora_port + board * AURORA_NPORT * AURORA_NCD180 + AURORA_PORT(tty->index);
+ if ((aurora_paranoia_check(port, tty->name, "aurora_open")) {
+#ifdef AURORA_DEBUG
+ printk("aurora_open: error paranoia check\n");
+#endif
+ return -ENODEV;
+ }
+
+ port->count++;
+ tty->driver_data = port;
+ port->tty = tty;
+
+ if ((error = aurora_setup_port(bp, port))) {
+#ifdef AURORA_DEBUG
+ printk("aurora_open: error aurora_setup_port ret %d\n",error);
+#endif
+ return error;
+ }
+
+ if ((error = block_til_ready(tty, filp, port))) {
+#ifdef AURORA_DEBUG
+ printk("aurora_open: error block_til_ready ret %d\n",error);
+#endif
+ return error;
+ }
+
+#ifdef AURORA_DEBUG
+ printk("aurora_open: end\n");
+#endif
+ return 0;
+}
+
+static void aurora_close(struct tty_struct * tty, struct file * filp)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board *bp;
+ unsigned long flags;
+ unsigned long timeout;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_close: start\n");
+#endif
+
+ if (!port || (aurora_paranoia_check(port, tty->name, "close"))
+ return;
+
+ chip = AURORA_CD180(port_No(port));
+
+ save_flags(flags); cli();
+ if (tty_hung_up_p(filp)) {
+ restore_flags(flags);
+ return;
+ }
+
+ bp = port_Board(port);
+ if ((tty->count == 1) && (port->count != 1)) {
+ printk(KERN_DEBUG "aurora%d: aurora_close: bad port count; "
+ "tty->count is 1, port count is %d\n",
+ board_No(bp), port->count);
+ port->count = 1;
+ }
+ if (--port->count < 0) {
+ printk(KERN_DEBUG "aurora%d: aurora_close: bad port "
+ "count for tty%d: %d\n",
+ board_No(bp), port_No(port), port->count);
+ port->count = 0;
+ }
+ if (port->count) {
+ restore_flags(flags);
+ return;
+ }
+ port->flags |= ASYNC_CLOSING;
+
+ /* Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE){
+#ifdef AURORA_DEBUG
+ printk("aurora_close: waiting to flush...\n");
+#endif
+ tty_wait_until_sent(tty, port->closing_wait);
+ }
+
+ /* At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+ port->SRER &= ~SRER_RXD;
+ if (port->flags & ASYNC_INITIALIZED) {
+ port->SRER &= ~SRER_TXRDY;
+ port->SRER |= SRER_TXEMPTY;
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ timeout = jiffies+HZ;
+ while(port->SRER & SRER_TXEMPTY) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(port->timeout);
+ if (time_after(jiffies, timeout))
+ break;
+ }
+ }
+#ifdef AURORA_DEBUG
+ printk("aurora_close: shutdown_port\n");
+#endif
+ aurora_shutdown_port(bp, port);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+ tty->closing = 0;
+ port->event = 0;
+ port->tty = 0;
+ if (port->blocked_open) {
+ if (port->close_delay) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(port->close_delay);
+ }
+ wake_up_interruptible(&port->open_wait);
+ }
+ port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&port->close_wait);
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_close: end\n");
+#endif
+}
+
+static int aurora_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board *bp;
+ int c, total = 0;
+ unsigned long flags;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_write: start %d\n",count);
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_write"))
+ return 0;
+
+ chip = AURORA_CD180(port_No(port));
+
+ bp = port_Board(port);
+
+ if (!tty || !port->xmit_buf || !tmp_buf)
+ return 0;
+
+ save_flags(flags);
+ while (1) {
+ cli();
+ c = MIN(count, MIN(SERIAL_XMIT_SIZE - port->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - port->xmit_head));
+ if (c <= 0) {
+ restore_flags(flags);
+ break;
+ }
+ memcpy(port->xmit_buf + port->xmit_head, buf, c);
+ port->xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+ port->xmit_cnt += c;
+ restore_flags(flags);
+
+ buf += c;
+ count -= c;
+ total += c;
+ }
+
+ cli();
+ if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped &&
+ !(port->SRER & SRER_TXRDY)) {
+ port->SRER |= SRER_TXRDY;
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+ }
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_write: end %d\n",total);
+#endif
+ return total;
+}
+
+static void aurora_put_char(struct tty_struct * tty, unsigned char ch)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ unsigned long flags;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_put_char: start %c\n",ch);
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_put_char"))
+ return;
+
+ if (!tty || !port->xmit_buf)
+ return;
+
+ save_flags(flags); cli();
+
+ if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+ restore_flags(flags);
+ return;
+ }
+
+ port->xmit_buf[port->xmit_head++] = ch;
+ port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+ port->xmit_cnt++;
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_put_char: end\n");
+#endif
+}
+
+static void aurora_flush_chars(struct tty_struct * tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ unsigned long flags;
+ unsigned char chip;
+
+/*#ifdef AURORA_DEBUG
+ printk("aurora_flush_chars: start\n");
+#endif*/
+ if ((aurora_paranoia_check(port, tty->name, "aurora_flush_chars"))
+ return;
+
+ chip = AURORA_CD180(port_No(port));
+
+ if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+ !port->xmit_buf)
+ return;
+
+ save_flags(flags); cli();
+ port->SRER |= SRER_TXRDY;
+ sbus_writeb(port_No(port) & 7,
+ &port_Board(port)->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ sbus_writeb(port->SRER,
+ &port_Board(port)->r[chip]->r[CD180_SRER]);
+ restore_flags(flags);
+/*#ifdef AURORA_DEBUG
+ printk("aurora_flush_chars: end\n");
+#endif*/
+}
+
+static int aurora_write_room(struct tty_struct * tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ int ret;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_write_room: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_write_room"))
+ return 0;
+
+ ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+#ifdef AURORA_DEBUG
+ printk("aurora_write_room: end\n");
+#endif
+ return ret;
+}
+
+static int aurora_chars_in_buffer(struct tty_struct *tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+
+ if ((aurora_paranoia_check(port, tty->name, "aurora_chars_in_buffer"))
+ return 0;
+
+ return port->xmit_cnt;
+}
+
+static void aurora_flush_buffer(struct tty_struct *tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ unsigned long flags;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_flush_buffer: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_flush_buffer"))
+ return;
+
+ save_flags(flags); cli();
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ restore_flags(flags);
+
+ tty_wakeup(tty);
+#ifdef AURORA_DEBUG
+ printk("aurora_flush_buffer: end\n");
+#endif
+}
+
+static int aurora_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board * bp;
+ unsigned char status,chip;
+ unsigned int result;
+ unsigned long flags;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_get_modem_info: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, __FUNCTION__))
+ return -ENODEV;
+
+ chip = AURORA_CD180(port_No(port));
+
+ bp = port_Board(port);
+
+ save_flags(flags); cli();
+
+ sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+
+ status = sbus_readb(&bp->r[chip]->r[CD180_MSVR]);
+ result = 0/*bp->r[chip]->r[AURORA_RI] & (1u << port_No(port)) ? 0 : TIOCM_RNG*/;
+
+ restore_flags(flags);
+
+ result |= ((status & bp->RTS) ? TIOCM_RTS : 0)
+ | ((status & bp->DTR) ? TIOCM_DTR : 0)
+ | ((status & MSVR_CD) ? TIOCM_CAR : 0)
+ | ((status & MSVR_DSR) ? TIOCM_DSR : 0)
+ | ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+
+#ifdef AURORA_DEBUG
+ printk("aurora_get_modem_info: end\n");
+#endif
+ return result;
+}
+
+static int aurora_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ unsigned int arg;
+ unsigned long flags;
+ struct Aurora_board *bp = port_Board(port);
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_set_modem_info: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, __FUNCTION__))
+ return -ENODEV;
+
+ chip = AURORA_CD180(port_No(port));
+
+ save_flags(flags); cli();
+ if (set & TIOCM_RTS)
+ port->MSVR |= bp->RTS;
+ if (set & TIOCM_DTR)
+ port->MSVR |= bp->DTR;
+ if (clear & TIOCM_RTS)
+ port->MSVR &= ~bp->RTS;
+ if (clear & TIOCM_DTR)
+ port->MSVR &= ~bp->DTR;
+
+ sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+
+ sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_set_modem_info: end\n");
+#endif
+ return 0;
+}
+
+static void aurora_send_break(struct Aurora_port * port, unsigned long length)
+{
+ struct Aurora_board *bp = port_Board(port);
+ unsigned long flags;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_send_break: start\n");
+#endif
+ chip = AURORA_CD180(port_No(port));
+
+ save_flags(flags); cli();
+
+ port->break_length = AURORA_TPS / HZ * length;
+ port->COR2 |= COR2_ETC;
+ port->SRER |= SRER_TXRDY;
+ sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+
+ sbus_writeb(port->COR2, &bp->r[chip]->r[CD180_COR2]);
+ sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+ aurora_wait_CCR(bp->r[chip]);
+
+ sbus_writeb(CCR_CORCHG2, &bp->r[chip]->r[CD180_CCR]);
+ aurora_wait_CCR(bp->r[chip]);
+
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_send_break: end\n");
+#endif
+}
+
+static int aurora_set_serial_info(struct Aurora_port * port,
+ struct serial_struct * newinfo)
+{
+ struct serial_struct tmp;
+ struct Aurora_board *bp = port_Board(port);
+ int change_speed;
+ unsigned long flags;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_set_serial_info: start\n");
+#endif
+ if (copy_from_user(&tmp, newinfo, sizeof(tmp)))
+ return -EFAULT;
+#if 0
+ if ((tmp.irq != bp->irq) ||
+ (tmp.port != bp->base) ||
+ (tmp.type != PORT_CIRRUS) ||
+ (tmp.baud_base != (bp->oscfreq + CD180_TPC/2) / CD180_TPC) ||
+ (tmp.custom_divisor != 0) ||
+ (tmp.xmit_fifo_size != CD180_NFIFO) ||
+ (tmp.flags & ~AURORA_LEGAL_FLAGS))
+ return -EINVAL;
+#endif
+
+ change_speed = ((port->flags & ASYNC_SPD_MASK) !=
+ (tmp.flags & ASYNC_SPD_MASK));
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((tmp.close_delay != port->close_delay) ||
+ (tmp.closing_wait != port->closing_wait) ||
+ ((tmp.flags & ~ASYNC_USR_MASK) !=
+ (port->flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ port->flags = ((port->flags & ~ASYNC_USR_MASK) |
+ (tmp.flags & ASYNC_USR_MASK));
+ } else {
+ port->flags = ((port->flags & ~ASYNC_FLAGS) |
+ (tmp.flags & ASYNC_FLAGS));
+ port->close_delay = tmp.close_delay;
+ port->closing_wait = tmp.closing_wait;
+ }
+ if (change_speed) {
+ save_flags(flags); cli();
+ aurora_change_speed(bp, port);
+ restore_flags(flags);
+ }
+#ifdef AURORA_DEBUG
+ printk("aurora_set_serial_info: end\n");
+#endif
+ return 0;
+}
+
+extern int aurora_get_serial_info(struct Aurora_port * port,
+ struct serial_struct * retinfo)
+{
+ struct serial_struct tmp;
+ struct Aurora_board *bp = port_Board(port);
+
+#ifdef AURORA_DEBUG
+ printk("aurora_get_serial_info: start\n");
+#endif
+ if (!access_ok(VERIFY_WRITE, (void *) retinfo, sizeof(tmp)))
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = PORT_CIRRUS;
+ tmp.line = port - aurora_port;
+ tmp.port = 0;
+ tmp.irq = bp->irq;
+ tmp.flags = port->flags;
+ tmp.baud_base = (bp->oscfreq + CD180_TPC/2) / CD180_TPC;
+ tmp.close_delay = port->close_delay * HZ/100;
+ tmp.closing_wait = port->closing_wait * HZ/100;
+ tmp.xmit_fifo_size = CD180_NFIFO;
+ copy_to_user(retinfo, &tmp, sizeof(tmp));
+#ifdef AURORA_DEBUG
+printk("aurora_get_serial_info: end\n");
+#endif
+ return 0;
+}
+
+static int aurora_ioctl(struct tty_struct * tty, struct file * filp,
+ unsigned int cmd, unsigned long arg)
+
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ int retval;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_ioctl: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_ioctl"))
+ return -ENODEV;
+
+ switch (cmd) {
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ aurora_send_break(port, HZ/4); /* 1/4 second */
+ return 0;
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ aurora_send_break(port, arg ? arg*(HZ/10) : HZ/4);
+ return 0;
+ case TIOCGSOFTCAR:
+ return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *)arg);
+ case TIOCSSOFTCAR:
+ if (get_user(arg,(unsigned long *)arg))
+ return -EFAULT;
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ return 0;
+ case TIOCGSERIAL:
+ return aurora_get_serial_info(port, (struct serial_struct *) arg);
+ case TIOCSSERIAL:
+ return aurora_set_serial_info(port, (struct serial_struct *) arg);
+ default:
+ return -ENOIOCTLCMD;
+ };
+#ifdef AURORA_DEBUG
+ printk("aurora_ioctl: end\n");
+#endif
+ return 0;
+}
+
+static void aurora_throttle(struct tty_struct * tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board *bp;
+ unsigned long flags;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_throttle: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_throttle"))
+ return;
+
+ bp = port_Board(port);
+ chip = AURORA_CD180(port_No(port));
+
+ save_flags(flags); cli();
+ port->MSVR &= ~bp->RTS;
+ sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ if (I_IXOFF(tty)) {
+ aurora_wait_CCR(bp->r[chip]);
+ sbus_writeb(CCR_SSCH2, &bp->r[chip]->r[CD180_CCR]);
+ aurora_wait_CCR(bp->r[chip]);
+ }
+ sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_throttle: end\n");
+#endif
+}
+
+static void aurora_unthrottle(struct tty_struct * tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board *bp;
+ unsigned long flags;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_unthrottle: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_unthrottle"))
+ return;
+
+ bp = port_Board(port);
+
+ chip = AURORA_CD180(port_No(port));
+
+ save_flags(flags); cli();
+ port->MSVR |= bp->RTS;
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ if (I_IXOFF(tty)) {
+ aurora_wait_CCR(bp->r[chip]);
+ sbus_writeb(CCR_SSCH1,
+ &bp->r[chip]->r[CD180_CCR]);
+ aurora_wait_CCR(bp->r[chip]);
+ }
+ sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_unthrottle: end\n");
+#endif
+}
+
+static void aurora_stop(struct tty_struct * tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board *bp;
+ unsigned long flags;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_stop: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_stop"))
+ return;
+
+ bp = port_Board(port);
+
+ chip = AURORA_CD180(port_No(port));
+
+ save_flags(flags); cli();
+ port->SRER &= ~SRER_TXRDY;
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ sbus_writeb(port->SRER,
+ &bp->r[chip]->r[CD180_SRER]);
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_stop: end\n");
+#endif
+}
+
+static void aurora_start(struct tty_struct * tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board *bp;
+ unsigned long flags;
+ unsigned char chip;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_start: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_start"))
+ return;
+
+ bp = port_Board(port);
+
+ chip = AURORA_CD180(port_No(port));
+
+ save_flags(flags); cli();
+ if (port->xmit_cnt && port->xmit_buf && !(port->SRER & SRER_TXRDY)) {
+ port->SRER |= SRER_TXRDY;
+ sbus_writeb(port_No(port) & 7,
+ &bp->r[chip]->r[CD180_CAR]);
+ udelay(1);
+ sbus_writeb(port->SRER,
+ &bp->r[chip]->r[CD180_SRER]);
+ }
+ restore_flags(flags);
+#ifdef AURORA_DEBUG
+ printk("aurora_start: end\n");
+#endif
+}
+
+/*
+ * This routine is called from the scheduler tqueue when the interrupt
+ * routine has signalled that a hangup has occurred. The path of
+ * hangup processing is:
+ *
+ * serial interrupt routine -> (scheduler tqueue) ->
+ * do_aurora_hangup() -> tty->hangup() -> aurora_hangup()
+ *
+ */
+static void do_aurora_hangup(void *private_)
+{
+ struct Aurora_port *port = (struct Aurora_port *) private_;
+ struct tty_struct *tty;
+
+#ifdef AURORA_DEBUG
+ printk("do_aurora_hangup: start\n");
+#endif
+ tty = port->tty;
+ if (tty != NULL) {
+ tty_hangup(tty); /* FIXME: module removal race - AKPM */
+#ifdef AURORA_DEBUG
+ printk("do_aurora_hangup: end\n");
+#endif
+ }
+}
+
+static void aurora_hangup(struct tty_struct * tty)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ struct Aurora_board *bp;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_hangup: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_hangup"))
+ return;
+
+ bp = port_Board(port);
+
+ aurora_shutdown_port(bp, port);
+ port->event = 0;
+ port->count = 0;
+ port->flags &= ~ASYNC_NORMAL_ACTIVE;
+ port->tty = 0;
+ wake_up_interruptible(&port->open_wait);
+#ifdef AURORA_DEBUG
+ printk("aurora_hangup: end\n");
+#endif
+}
+
+static void aurora_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+ struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+ unsigned long flags;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_set_termios: start\n");
+#endif
+ if ((aurora_paranoia_check(port, tty->name, "aurora_set_termios"))
+ return;
+
+ if (tty->termios->c_cflag == old_termios->c_cflag &&
+ tty->termios->c_iflag == old_termios->c_iflag)
+ return;
+
+ save_flags(flags); cli();
+ aurora_change_speed(port_Board(port), port);
+ restore_flags(flags);
+
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ aurora_start(tty);
+ }
+#ifdef AURORA_DEBUG
+ printk("aurora_set_termios: end\n");
+#endif
+}
+
+static void do_aurora_bh(void)
+{
+ run_task_queue(&tq_aurora);
+}
+
+static void do_softint(void *private_)
+{
+ struct Aurora_port *port = (struct Aurora_port *) private_;
+ struct tty_struct *tty;
+
+#ifdef AURORA_DEBUG
+ printk("do_softint: start\n");
+#endif
+ tty = port->tty;
+ if (tty == NULL)
+ return;
+
+ if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) {
+ tty_wakeup(tty);
+ }
+#ifdef AURORA_DEBUG
+ printk("do_softint: end\n");
+#endif
+}
+
+static struct tty_operations aurora_ops = {
+ .open = aurora_open,
+ .close = aurora_close,
+ .write = aurora_write,
+ .put_char = aurora_put_char,
+ .flush_chars = aurora_flush_chars,
+ .write_room = aurora_write_room,
+ .chars_in_buffer = aurora_chars_in_buffer,
+ .flush_buffer = aurora_flush_buffer,
+ .ioctl = aurora_ioctl,
+ .throttle = aurora_throttle,
+ .unthrottle = aurora_unthrottle,
+ .set_termios = aurora_set_termios,
+ .stop = aurora_stop,
+ .start = aurora_start,
+ .hangup = aurora_hangup,
+ .tiocmget = aurora_tiocmget,
+ .tiocmset = aurora_tiocmset,
+};
+
+static int aurora_init_drivers(void)
+{
+ int error;
+ int i;
+
+#ifdef AURORA_DEBUG
+ printk("aurora_init_drivers: start\n");
+#endif
+ tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL);
+ if (tmp_buf == NULL) {
+ printk(KERN_ERR "aurora: Couldn't get free page.\n");
+ return 1;
+ }
+ init_bh(AURORA_BH, do_aurora_bh);
+ aurora_driver = alloc_tty_driver(AURORA_INPORTS);
+ if (!aurora_driver) {
+ printk(KERN_ERR "aurora: Couldn't allocate tty driver.\n");
+ free_page((unsigned long) tmp_buf);
+ return 1;
+ }
+ aurora_driver->owner = THIS_MODULE;
+ aurora_driver->name = "ttyA";
+ aurora_driver->major = AURORA_MAJOR;
+ aurora_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ aurora_driver->subtype = SERIAL_TYPE_NORMAL;
+ aurora_driver->init_termios = tty_std_termios;
+ aurora_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ aurora_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(aurora_driver, &aurora_ops);
+ error = tty_register_driver(aurora_driver);
+ if (error) {
+ put_tty_driver(aurora_driver);
+ free_page((unsigned long) tmp_buf);
+ printk(KERN_ERR "aurora: Couldn't register aurora driver, error = %d\n",
+ error);
+ return 1;
+ }
+
+ memset(aurora_port, 0, sizeof(aurora_port));
+ for (i = 0; i < AURORA_TNPORTS; i++) {
+ aurora_port[i].magic = AURORA_MAGIC;
+ aurora_port[i].tqueue.routine = do_softint;
+ aurora_port[i].tqueue.data = &aurora_port[i];
+ aurora_port[i].tqueue_hangup.routine = do_aurora_hangup;
+ aurora_port[i].tqueue_hangup.data = &aurora_port[i];
+ aurora_port[i].close_delay = 50 * HZ/100;
+ aurora_port[i].closing_wait = 3000 * HZ/100;
+ init_waitqueue_head(&aurora_port[i].open_wait);
+ init_waitqueue_head(&aurora_port[i].close_wait);
+ }
+#ifdef AURORA_DEBUG
+ printk("aurora_init_drivers: end\n");
+#endif
+ return 0;
+}
+
+static void aurora_release_drivers(void)
+{
+#ifdef AURORA_DEBUG
+ printk("aurora_release_drivers: start\n");
+#endif
+ free_page((unsigned long)tmp_buf);
+ tty_unregister_driver(aurora_driver);
+ put_tty_driver(aurora_driver);
+#ifdef AURORA_DEBUG
+ printk("aurora_release_drivers: end\n");
+#endif
+}
+
+/*
+ * Called at boot time.
+ *
+ * You can specify IO base for up to RC_NBOARD cards,
+ * using line "riscom8=0xiobase1,0xiobase2,.." at LILO prompt.
+ * Note that there will be no probing at default
+ * addresses in this case.
+ *
+ */
+void __init aurora_setup(char *str, int *ints)
+{
+ int i;
+
+ for(i=0;(i<ints[0])&&(i<4);i++) {
+ if (ints[i+1]) irqs[i]=ints[i+1];
+ }
+}
+
+static int __init aurora_real_init(void)
+{
+ int found;
+ int i;
+
+ printk(KERN_INFO "aurora: Driver starting.\n");
+ if(aurora_init_drivers())
+ return -EIO;
+ found = aurora_probe();
+ if(!found) {
+ aurora_release_drivers();
+ printk(KERN_INFO "aurora: No Aurora Multiport boards detected.\n");
+ return -EIO;
+ } else {
+ printk(KERN_INFO "aurora: %d boards found.\n", found);
+ }
+ for (i = 0; i < found; i++) {
+ int ret = aurora_setup_board(&aurora_board[i]);
+
+ if (ret) {
+#ifdef AURORA_DEBUG
+ printk(KERN_ERR "aurora_init: error aurora_setup_board ret %d\n",
+ ret);
+#endif
+ return ret;
+ }
+ }
+ return 0;
+}
+
+int irq = 0;
+int irq1 = 0;
+int irq2 = 0;
+int irq3 = 0;
+module_param(irq , int, 0);
+module_param(irq1, int, 0);
+module_param(irq2, int, 0);
+module_param(irq3, int, 0);
+
+static int __init aurora_init(void)
+{
+ if (irq ) irqs[0]=irq ;
+ if (irq1) irqs[1]=irq1;
+ if (irq2) irqs[2]=irq2;
+ if (irq3) irqs[3]=irq3;
+ return aurora_real_init();
+}
+
+static void __exit aurora_cleanup(void)
+{
+ int i;
+
+#ifdef AURORA_DEBUG
+printk("cleanup_module: aurora_release_drivers\n");
+#endif
+
+ aurora_release_drivers();
+ for (i = 0; i < AURORA_NBOARD; i++)
+ if (aurora_board[i].flags & AURORA_BOARD_PRESENT) {
+ aurora_shutdown_board(&aurora_board[i]);
+ aurora_release_io_range(&aurora_board[i]);
+ }
+}
+
+module_init(aurora_init);
+module_exit(aurora_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/aurora.h b/drivers/sbus/char/aurora.h
new file mode 100644
index 00000000000..b8b5476d986
--- /dev/null
+++ b/drivers/sbus/char/aurora.h
@@ -0,0 +1,276 @@
+/* $Id: aurora.h,v 1.6 2001/06/05 12:23:38 davem Exp $
+ * linux/drivers/sbus/char/aurora.h -- Aurora multiport driver
+ *
+ * Copyright (c) 1999 by Oliver Aldulea (oli@bv.ro)
+ *
+ * This code is based on the RISCom/8 multiport serial driver written
+ * by Dmitry Gorodchanin (pgmdsg@ibi.com), based on the Linux serial
+ * driver, written by Linus Torvalds, Theodore T'so and others.
+ * The Aurora multiport programming info was obtained mainly from the
+ * Cirrus Logic CD180 documentation (available on the web), and by
+ * doing heavy tests on the board. Many thanks to Eddie C. Dost for the
+ * help on the sbus interface.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Revision 1.0
+ *
+ * This is the first public release.
+ *
+ * This version needs a lot of feedback. This is the version that works
+ * with _my_ board. My board is model 1600se, revision '@(#)1600se.fth
+ * 1.2 3/28/95 1'. The driver might work with your board, but I do not
+ * guarantee it. If you have _any_ type of board, I need to know if the
+ * driver works or not, I need to know exactly your board parameters
+ * (get them with 'cd /proc/openprom/iommu/sbus/sio16/; ls *; cat *')
+ * Also, I need your board revision code, which is written on the board.
+ * Send me the output of my driver too (it outputs through klogd).
+ *
+ * If the driver does not work, you can try enabling the debug options
+ * to see what's wrong or what should be done.
+ *
+ * I'm sorry about the alignment of the code. It was written in a
+ * 128x48 environment.
+ *
+ * I must say that I do not like Aurora Technologies' policy. I asked
+ * them to help me do this driver faster, but they ended by something
+ * like "don't call us, we'll call you", and I never heard anything
+ * from them. They told me "knowing the way the board works, I don't
+ * doubt you and others on the net will make the driver."
+ * The truth about this board is that it has nothing intelligent on it.
+ * If you want to say to somebody what kind of board you have, say that
+ * it uses Cirrus Logic processors (CD180). The power of the board is
+ * in those two chips. The rest of the board is the interface to the
+ * sbus and to the peripherals. Still, they did something smart: they
+ * reversed DTR and RTS to make on-board automatic hardware flow
+ * control usable.
+ * Thanks to Aurora Technologies for wasting my time, nerves and money.
+ */
+
+#ifndef __LINUX_AURORA_H
+#define __LINUX_AURORA_H
+
+#include <linux/serial.h>
+#include <linux/serialP.h>
+
+#ifdef __KERNEL__
+
+/* This is the number of boards to support. I've only tested this driver with
+ * one board, so it might not work.
+ */
+#define AURORA_NBOARD 1
+
+/* Useful ? Yes. But you can safely comment the warnings if they annoy you
+ * (let me say that again: the warnings in the code, not this define).
+ */
+#define AURORA_PARANOIA_CHECK
+
+/* Well, after many lost nights, I found that the IRQ for this board is
+ * selected from four built-in values by writing some bits in the
+ * configuration register. This causes a little problem to occur: which
+ * IRQ to select ? Which one is the best for the user ? Well, I finally
+ * decided for the following algorithm: if the "bintr" value is not acceptable
+ * (not within type_1_irq[], then test the "intr" value, if that fails too,
+ * try each value from type_1_irq until succeded. Hope it's ok.
+ * You can safely reorder the irq's.
+ */
+#define TYPE_1_IRQS 4
+unsigned char type_1_irq[TYPE_1_IRQS] = {
+ 3, 5, 9, 13
+};
+/* I know something about another method of interrupt setting, but not enough.
+ * Also, this is for another type of board, so I first have to learn how to
+ * detect it.
+#define TYPE_2_IRQS 3
+unsigned char type_2_irq[TYPE_2_IRQS] = {
+ 0, 0, 0 ** could anyone find these for me ? (see AURORA_ALLIRQ below) **
+ };
+unsigned char type_2_mask[TYPE_2_IRQS] = {
+ 32, 64, 128
+ };
+*/
+
+/* The following section should only be modified by those who know what
+ * they're doing (or don't, but want to help with some feedback). Modifying
+ * anything raises a _big_ probability for your system to hang, but the
+ * sacrifice worths. (I sacrificed my ext2fs many, many times...)
+ */
+
+/* This one tries to dump to console the name of almost every function called,
+ * and many other debugging info.
+ */
+#undef AURORA_DEBUG
+
+/* These are the most dangerous and useful defines. They do printk() during
+ * the interrupt processing routine(s), so if you manage to get "flooded" by
+ * irq's, start thinking about the "Power off/on" button...
+ */
+#undef AURORA_INTNORM /* This one enables the "normal" messages, but some
+ * of them cause flood, so I preffered putting
+ * them under a define */
+#undef AURORA_INT_DEBUG /* This one is really bad. */
+
+/* Here's something helpful: after n irq's, the board will be disabled. This
+ * prevents irq flooding during debug (no need to think about power
+ * off/on anymore...)
+ */
+#define AURORA_FLOODPRO 10
+
+/* This one helps finding which irq the board calls, in case of a strange/
+ * unsupported board. AURORA_INT_DEBUG should be enabled, because I don't
+ * think /proc/interrupts or any command will be available in case of an irq
+ * flood... "allirq" is the list of all free irq's.
+ */
+/*
+#define AURORA_ALLIRQ 6
+int allirq[AURORA_ALLIRQ]={
+ 2,3,5,7,9,13
+ };
+*/
+
+/* These must not be modified. These values are assumed during the code for
+ * performance optimisations.
+ */
+#define AURORA_NCD180 2 /* two chips per board */
+#define AURORA_NPORT 8 /* 8 ports per chip */
+
+/* several utilities */
+#define AURORA_BOARD(line) (((line) >> 4) & 0x01)
+#define AURORA_CD180(line) (((line) >> 3) & 0x01)
+#define AURORA_PORT(line) ((line) & 15)
+
+#define AURORA_TNPORTS (AURORA_NBOARD*AURORA_NCD180*AURORA_NPORT)
+
+/* Ticks per sec. Used for setting receiver timeout and break length */
+#define AURORA_TPS 4000
+
+#define AURORA_MAGIC 0x0A18
+
+/* Yeah, after heavy testing I decided it must be 6.
+ * Sure, You can change it if needed.
+ */
+#define AURORA_RXFIFO 6 /* Max. receiver FIFO size (1-8) */
+
+#define AURORA_RXTH 7
+
+struct aurora_reg1 {
+ __volatile__ unsigned char r;
+};
+
+struct aurora_reg128 {
+ __volatile__ unsigned char r[128];
+};
+
+struct aurora_reg4 {
+ __volatile__ unsigned char r[4];
+};
+
+struct Aurora_board {
+ unsigned long flags;
+ struct aurora_reg1 * r0; /* This is the board configuration
+ * register (write-only). */
+ struct aurora_reg128 * r[2]; /* These are the registers for the
+ * two chips. */
+ struct aurora_reg4 * r3; /* These are used for hardware-based
+ * acknowledge. Software-based ack is
+ * not supported by CD180. */
+ unsigned int oscfreq; /* The on-board oscillator
+ * frequency, in Hz. */
+ unsigned char irq;
+#ifdef MODULE
+ signed char count; /* counts the use of the board */
+#endif
+ /* Values for the dtr_rts swapped mode. */
+ unsigned char DTR;
+ unsigned char RTS;
+ unsigned char MSVDTR;
+ unsigned char MSVRTS;
+ /* Values for hardware acknowledge. */
+ unsigned char ACK_MINT, ACK_TINT, ACK_RINT;
+};
+
+/* Board configuration register */
+#define AURORA_CFG_ENABLE_IO 8
+#define AURORA_CFG_ENABLE_IRQ 4
+
+/* Board flags */
+#define AURORA_BOARD_PRESENT 0x00000001
+#define AURORA_BOARD_ACTIVE 0x00000002
+#define AURORA_BOARD_TYPE_2 0x00000004 /* don't know how to
+ * detect this yet */
+#define AURORA_BOARD_DTR_FLOW_OK 0x00000008
+
+/* The story goes like this: Cirrus programmed the CD-180 chip to do automatic
+ * hardware flow control, and do it using CTS and DTR. CTS is ok, but, if you
+ * have a modem and the chip drops DTR, then the modem will drop the carrier
+ * (ain't that cute...). Luckily, the guys at Aurora decided to swap DTR and
+ * RTS, which makes the flow control usable. I hope that all the boards made
+ * by Aurora have these two signals swapped. If your's doesn't but you have a
+ * breakout box, you can try to reverse them yourself, then set the following
+ * flag.
+ */
+#undef AURORA_FORCE_DTR_FLOW
+
+/* In fact, a few more words have to be said about hardware flow control.
+ * This driver handles "output" flow control through the on-board facility
+ * CTS Auto Enable. For the "input" flow control there are two cases when
+ * the flow should be controlled. The first case is when the kernel is so
+ * busy that it cannot process IRQ's in time; this flow control can only be
+ * activated by the on-board chip, and if the board has RTS and DTR swapped,
+ * this facility is usable. The second case is when the application is so
+ * busy that it cannot receive bytes from the kernel, and this flow must be
+ * activated by software. This second case is not yet implemented in this
+ * driver. Unfortunately, I estimate that the second case is the one that
+ * occurs the most.
+ */
+
+
+struct Aurora_port {
+ int magic;
+ int baud_base;
+ int flags;
+ struct tty_struct * tty;
+ int count;
+ int blocked_open;
+ long event;
+ int timeout;
+ int close_delay;
+ unsigned char * xmit_buf;
+ int custom_divisor;
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ struct tq_struct tqueue;
+ struct tq_struct tqueue_hangup;
+ short wakeup_chars;
+ short break_length;
+ unsigned short closing_wait;
+ unsigned char mark_mask;
+ unsigned char SRER;
+ unsigned char MSVR;
+ unsigned char COR2;
+#ifdef AURORA_REPORT_OVERRUN
+ unsigned long overrun;
+#endif
+#ifdef AURORA_REPORT_FIFO
+ unsigned long hits[10];
+#endif
+};
+
+#endif
+#endif /*__LINUX_AURORA_H*/
+
diff --git a/drivers/sbus/char/bbc_envctrl.c b/drivers/sbus/char/bbc_envctrl.c
new file mode 100644
index 00000000000..d5259f7fee6
--- /dev/null
+++ b/drivers/sbus/char/bbc_envctrl.c
@@ -0,0 +1,645 @@
+/* $Id: bbc_envctrl.c,v 1.4 2001/04/06 16:48:08 davem Exp $
+ * bbc_envctrl.c: UltraSPARC-III environment control driver.
+ *
+ * Copyright (C) 2001 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <asm/oplib.h>
+#include <asm/ebus.h>
+#define __KERNEL_SYSCALLS__
+static int errno;
+#include <asm/unistd.h>
+
+#include "bbc_i2c.h"
+#include "max1617.h"
+
+#undef ENVCTRL_TRACE
+
+/* WARNING: Making changes to this driver is very dangerous.
+ * If you misprogram the sensor chips they can
+ * cut the power on you instantly.
+ */
+
+/* Two temperature sensors exist in the SunBLADE-1000 enclosure.
+ * Both are implemented using max1617 i2c devices. Each max1617
+ * monitors 2 temperatures, one for one of the cpu dies and the other
+ * for the ambient temperature.
+ *
+ * The max1617 is capable of being programmed with power-off
+ * temperature values, one low limit and one high limit. These
+ * can be controlled independently for the cpu or ambient temperature.
+ * If a limit is violated, the power is simply shut off. The frequency
+ * with which the max1617 does temperature sampling can be controlled
+ * as well.
+ *
+ * Three fans exist inside the machine, all three are controlled with
+ * an i2c digital to analog converter. There is a fan directed at the
+ * two processor slots, another for the rest of the enclosure, and the
+ * third is for the power supply. The first two fans may be speed
+ * controlled by changing the voltage fed to them. The third fan may
+ * only be completely off or on. The third fan is meant to only be
+ * disabled/enabled when entering/exiting the lowest power-saving
+ * mode of the machine.
+ *
+ * An environmental control kernel thread periodically monitors all
+ * temperature sensors. Based upon the samples it will adjust the
+ * fan speeds to try and keep the system within a certain temperature
+ * range (the goal being to make the fans as quiet as possible without
+ * allowing the system to get too hot).
+ *
+ * If the temperature begins to rise/fall outside of the acceptable
+ * operating range, a periodic warning will be sent to the kernel log.
+ * The fans will be put on full blast to attempt to deal with this
+ * situation. After exceeding the acceptable operating range by a
+ * certain threshold, the kernel thread will shut down the system.
+ * Here, the thread is attempting to shut the machine down cleanly
+ * before the hardware based power-off event is triggered.
+ */
+
+/* These settings are in Celsius. We use these defaults only
+ * if we cannot interrogate the cpu-fru SEEPROM.
+ */
+struct temp_limits {
+ s8 high_pwroff, high_shutdown, high_warn;
+ s8 low_warn, low_shutdown, low_pwroff;
+};
+
+static struct temp_limits cpu_temp_limits[2] = {
+ { 100, 85, 80, 5, -5, -10 },
+ { 100, 85, 80, 5, -5, -10 },
+};
+
+static struct temp_limits amb_temp_limits[2] = {
+ { 65, 55, 40, 5, -5, -10 },
+ { 65, 55, 40, 5, -5, -10 },
+};
+
+enum fan_action { FAN_SLOWER, FAN_SAME, FAN_FASTER, FAN_FULLBLAST, FAN_STATE_MAX };
+
+struct bbc_cpu_temperature {
+ struct bbc_cpu_temperature *next;
+
+ struct bbc_i2c_client *client;
+ int index;
+
+ /* Current readings, and history. */
+ s8 curr_cpu_temp;
+ s8 curr_amb_temp;
+ s8 prev_cpu_temp;
+ s8 prev_amb_temp;
+ s8 avg_cpu_temp;
+ s8 avg_amb_temp;
+
+ int sample_tick;
+
+ enum fan_action fan_todo[2];
+#define FAN_AMBIENT 0
+#define FAN_CPU 1
+};
+
+struct bbc_cpu_temperature *all_bbc_temps;
+
+struct bbc_fan_control {
+ struct bbc_fan_control *next;
+
+ struct bbc_i2c_client *client;
+ int index;
+
+ int psupply_fan_on;
+ int cpu_fan_speed;
+ int system_fan_speed;
+};
+
+struct bbc_fan_control *all_bbc_fans;
+
+#define CPU_FAN_REG 0xf0
+#define SYS_FAN_REG 0xf2
+#define PSUPPLY_FAN_REG 0xf4
+
+#define FAN_SPEED_MIN 0x0c
+#define FAN_SPEED_MAX 0x3f
+
+#define PSUPPLY_FAN_ON 0x1f
+#define PSUPPLY_FAN_OFF 0x00
+
+static void set_fan_speeds(struct bbc_fan_control *fp)
+{
+ /* Put temperatures into range so we don't mis-program
+ * the hardware.
+ */
+ if (fp->cpu_fan_speed < FAN_SPEED_MIN)
+ fp->cpu_fan_speed = FAN_SPEED_MIN;
+ if (fp->cpu_fan_speed > FAN_SPEED_MAX)
+ fp->cpu_fan_speed = FAN_SPEED_MAX;
+ if (fp->system_fan_speed < FAN_SPEED_MIN)
+ fp->system_fan_speed = FAN_SPEED_MIN;
+ if (fp->system_fan_speed > FAN_SPEED_MAX)
+ fp->system_fan_speed = FAN_SPEED_MAX;
+#ifdef ENVCTRL_TRACE
+ printk("fan%d: Changed fan speed to cpu(%02x) sys(%02x)\n",
+ fp->index,
+ fp->cpu_fan_speed, fp->system_fan_speed);
+#endif
+
+ bbc_i2c_writeb(fp->client, fp->cpu_fan_speed, CPU_FAN_REG);
+ bbc_i2c_writeb(fp->client, fp->system_fan_speed, SYS_FAN_REG);
+ bbc_i2c_writeb(fp->client,
+ (fp->psupply_fan_on ?
+ PSUPPLY_FAN_ON : PSUPPLY_FAN_OFF),
+ PSUPPLY_FAN_REG);
+}
+
+static void get_current_temps(struct bbc_cpu_temperature *tp)
+{
+ tp->prev_amb_temp = tp->curr_amb_temp;
+ bbc_i2c_readb(tp->client,
+ (unsigned char *) &tp->curr_amb_temp,
+ MAX1617_AMB_TEMP);
+ tp->prev_cpu_temp = tp->curr_cpu_temp;
+ bbc_i2c_readb(tp->client,
+ (unsigned char *) &tp->curr_cpu_temp,
+ MAX1617_CPU_TEMP);
+#ifdef ENVCTRL_TRACE
+ printk("temp%d: cpu(%d C) amb(%d C)\n",
+ tp->index,
+ (int) tp->curr_cpu_temp, (int) tp->curr_amb_temp);
+#endif
+}
+
+
+static void do_envctrl_shutdown(struct bbc_cpu_temperature *tp)
+{
+ static int shutting_down = 0;
+ static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
+ char *argv[] = { "/sbin/shutdown", "-h", "now", NULL };
+ char *type = "???";
+ s8 val = -1;
+
+ if (shutting_down != 0)
+ return;
+
+ if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown ||
+ tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) {
+ type = "ambient";
+ val = tp->curr_amb_temp;
+ } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown ||
+ tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) {
+ type = "CPU";
+ val = tp->curr_cpu_temp;
+ }
+
+ printk(KERN_CRIT "temp%d: Outside of safe %s "
+ "operating temperature, %d C.\n",
+ tp->index, type, val);
+
+ printk(KERN_CRIT "kenvctrld: Shutting down the system now.\n");
+
+ shutting_down = 1;
+ if (execve("/sbin/shutdown", argv, envp) < 0)
+ printk(KERN_CRIT "envctrl: shutdown execution failed\n");
+}
+
+#define WARN_INTERVAL (30 * HZ)
+
+static void analyze_ambient_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick)
+{
+ int ret = 0;
+
+ if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) {
+ if (tp->curr_amb_temp >=
+ amb_temp_limits[tp->index].high_warn) {
+ printk(KERN_WARNING "temp%d: "
+ "Above safe ambient operating temperature, %d C.\n",
+ tp->index, (int) tp->curr_amb_temp);
+ ret = 1;
+ } else if (tp->curr_amb_temp <
+ amb_temp_limits[tp->index].low_warn) {
+ printk(KERN_WARNING "temp%d: "
+ "Below safe ambient operating temperature, %d C.\n",
+ tp->index, (int) tp->curr_amb_temp);
+ ret = 1;
+ }
+ if (ret)
+ *last_warn = jiffies;
+ } else if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_warn ||
+ tp->curr_amb_temp < amb_temp_limits[tp->index].low_warn)
+ ret = 1;
+
+ /* Now check the shutdown limits. */
+ if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown ||
+ tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) {
+ do_envctrl_shutdown(tp);
+ ret = 1;
+ }
+
+ if (ret) {
+ tp->fan_todo[FAN_AMBIENT] = FAN_FULLBLAST;
+ } else if ((tick & (8 - 1)) == 0) {
+ s8 amb_goal_hi = amb_temp_limits[tp->index].high_warn - 10;
+ s8 amb_goal_lo;
+
+ amb_goal_lo = amb_goal_hi - 3;
+
+ /* We do not try to avoid 'too cold' events. Basically we
+ * only try to deal with over-heating and fan noise reduction.
+ */
+ if (tp->avg_amb_temp < amb_goal_hi) {
+ if (tp->avg_amb_temp >= amb_goal_lo)
+ tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
+ else
+ tp->fan_todo[FAN_AMBIENT] = FAN_SLOWER;
+ } else {
+ tp->fan_todo[FAN_AMBIENT] = FAN_FASTER;
+ }
+ } else {
+ tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
+ }
+}
+
+static void analyze_cpu_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick)
+{
+ int ret = 0;
+
+ if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) {
+ if (tp->curr_cpu_temp >=
+ cpu_temp_limits[tp->index].high_warn) {
+ printk(KERN_WARNING "temp%d: "
+ "Above safe CPU operating temperature, %d C.\n",
+ tp->index, (int) tp->curr_cpu_temp);
+ ret = 1;
+ } else if (tp->curr_cpu_temp <
+ cpu_temp_limits[tp->index].low_warn) {
+ printk(KERN_WARNING "temp%d: "
+ "Below safe CPU operating temperature, %d C.\n",
+ tp->index, (int) tp->curr_cpu_temp);
+ ret = 1;
+ }
+ if (ret)
+ *last_warn = jiffies;
+ } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_warn ||
+ tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_warn)
+ ret = 1;
+
+ /* Now check the shutdown limits. */
+ if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown ||
+ tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) {
+ do_envctrl_shutdown(tp);
+ ret = 1;
+ }
+
+ if (ret) {
+ tp->fan_todo[FAN_CPU] = FAN_FULLBLAST;
+ } else if ((tick & (8 - 1)) == 0) {
+ s8 cpu_goal_hi = cpu_temp_limits[tp->index].high_warn - 10;
+ s8 cpu_goal_lo;
+
+ cpu_goal_lo = cpu_goal_hi - 3;
+
+ /* We do not try to avoid 'too cold' events. Basically we
+ * only try to deal with over-heating and fan noise reduction.
+ */
+ if (tp->avg_cpu_temp < cpu_goal_hi) {
+ if (tp->avg_cpu_temp >= cpu_goal_lo)
+ tp->fan_todo[FAN_CPU] = FAN_SAME;
+ else
+ tp->fan_todo[FAN_CPU] = FAN_SLOWER;
+ } else {
+ tp->fan_todo[FAN_CPU] = FAN_FASTER;
+ }
+ } else {
+ tp->fan_todo[FAN_CPU] = FAN_SAME;
+ }
+}
+
+static void analyze_temps(struct bbc_cpu_temperature *tp, unsigned long *last_warn)
+{
+ tp->avg_amb_temp = (s8)((int)((int)tp->avg_amb_temp + (int)tp->curr_amb_temp) / 2);
+ tp->avg_cpu_temp = (s8)((int)((int)tp->avg_cpu_temp + (int)tp->curr_cpu_temp) / 2);
+
+ analyze_ambient_temp(tp, last_warn, tp->sample_tick);
+ analyze_cpu_temp(tp, last_warn, tp->sample_tick);
+
+ tp->sample_tick++;
+}
+
+static enum fan_action prioritize_fan_action(int which_fan)
+{
+ struct bbc_cpu_temperature *tp;
+ enum fan_action decision = FAN_STATE_MAX;
+
+ /* Basically, prioritize what the temperature sensors
+ * recommend we do, and perform that action on all the
+ * fans.
+ */
+ for (tp = all_bbc_temps; tp; tp = tp->next) {
+ if (tp->fan_todo[which_fan] == FAN_FULLBLAST) {
+ decision = FAN_FULLBLAST;
+ break;
+ }
+ if (tp->fan_todo[which_fan] == FAN_SAME &&
+ decision != FAN_FASTER)
+ decision = FAN_SAME;
+ else if (tp->fan_todo[which_fan] == FAN_FASTER)
+ decision = FAN_FASTER;
+ else if (decision != FAN_FASTER &&
+ decision != FAN_SAME &&
+ tp->fan_todo[which_fan] == FAN_SLOWER)
+ decision = FAN_SLOWER;
+ }
+ if (decision == FAN_STATE_MAX)
+ decision = FAN_SAME;
+
+ return decision;
+}
+
+static int maybe_new_ambient_fan_speed(struct bbc_fan_control *fp)
+{
+ enum fan_action decision = prioritize_fan_action(FAN_AMBIENT);
+ int ret;
+
+ if (decision == FAN_SAME)
+ return 0;
+
+ ret = 1;
+ if (decision == FAN_FULLBLAST) {
+ if (fp->system_fan_speed >= FAN_SPEED_MAX)
+ ret = 0;
+ else
+ fp->system_fan_speed = FAN_SPEED_MAX;
+ } else {
+ if (decision == FAN_FASTER) {
+ if (fp->system_fan_speed >= FAN_SPEED_MAX)
+ ret = 0;
+ else
+ fp->system_fan_speed += 2;
+ } else {
+ int orig_speed = fp->system_fan_speed;
+
+ if (orig_speed <= FAN_SPEED_MIN ||
+ orig_speed <= (fp->cpu_fan_speed - 3))
+ ret = 0;
+ else
+ fp->system_fan_speed -= 1;
+ }
+ }
+
+ return ret;
+}
+
+static int maybe_new_cpu_fan_speed(struct bbc_fan_control *fp)
+{
+ enum fan_action decision = prioritize_fan_action(FAN_CPU);
+ int ret;
+
+ if (decision == FAN_SAME)
+ return 0;
+
+ ret = 1;
+ if (decision == FAN_FULLBLAST) {
+ if (fp->cpu_fan_speed >= FAN_SPEED_MAX)
+ ret = 0;
+ else
+ fp->cpu_fan_speed = FAN_SPEED_MAX;
+ } else {
+ if (decision == FAN_FASTER) {
+ if (fp->cpu_fan_speed >= FAN_SPEED_MAX)
+ ret = 0;
+ else {
+ fp->cpu_fan_speed += 2;
+ if (fp->system_fan_speed <
+ (fp->cpu_fan_speed - 3))
+ fp->system_fan_speed =
+ fp->cpu_fan_speed - 3;
+ }
+ } else {
+ if (fp->cpu_fan_speed <= FAN_SPEED_MIN)
+ ret = 0;
+ else
+ fp->cpu_fan_speed -= 1;
+ }
+ }
+
+ return ret;
+}
+
+static void maybe_new_fan_speeds(struct bbc_fan_control *fp)
+{
+ int new;
+
+ new = maybe_new_ambient_fan_speed(fp);
+ new |= maybe_new_cpu_fan_speed(fp);
+
+ if (new)
+ set_fan_speeds(fp);
+}
+
+static void fans_full_blast(void)
+{
+ struct bbc_fan_control *fp;
+
+ /* Since we will not be monitoring things anymore, put
+ * the fans on full blast.
+ */
+ for (fp = all_bbc_fans; fp; fp = fp->next) {
+ fp->cpu_fan_speed = FAN_SPEED_MAX;
+ fp->system_fan_speed = FAN_SPEED_MAX;
+ fp->psupply_fan_on = 1;
+ set_fan_speeds(fp);
+ }
+}
+
+#define POLL_INTERVAL (5 * 1000)
+static unsigned long last_warning_jiffies;
+static struct task_struct *kenvctrld_task;
+
+static int kenvctrld(void *__unused)
+{
+ daemonize("kenvctrld");
+ allow_signal(SIGKILL);
+ kenvctrld_task = current;
+
+ printk(KERN_INFO "bbc_envctrl: kenvctrld starting...\n");
+ last_warning_jiffies = jiffies - WARN_INTERVAL;
+ for (;;) {
+ struct bbc_cpu_temperature *tp;
+ struct bbc_fan_control *fp;
+
+ msleep_interruptible(POLL_INTERVAL);
+ if (signal_pending(current))
+ break;
+
+ for (tp = all_bbc_temps; tp; tp = tp->next) {
+ get_current_temps(tp);
+ analyze_temps(tp, &last_warning_jiffies);
+ }
+ for (fp = all_bbc_fans; fp; fp = fp->next)
+ maybe_new_fan_speeds(fp);
+ }
+ printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n");
+
+ fans_full_blast();
+
+ return 0;
+}
+
+static void attach_one_temp(struct linux_ebus_child *echild, int temp_idx)
+{
+ struct bbc_cpu_temperature *tp = kmalloc(sizeof(*tp), GFP_KERNEL);
+
+ if (!tp)
+ return;
+ memset(tp, 0, sizeof(*tp));
+ tp->client = bbc_i2c_attach(echild);
+ if (!tp->client) {
+ kfree(tp);
+ return;
+ }
+
+ tp->index = temp_idx;
+ {
+ struct bbc_cpu_temperature **tpp = &all_bbc_temps;
+ while (*tpp)
+ tpp = &((*tpp)->next);
+ tp->next = NULL;
+ *tpp = tp;
+ }
+
+ /* Tell it to convert once every 5 seconds, clear all cfg
+ * bits.
+ */
+ bbc_i2c_writeb(tp->client, 0x00, MAX1617_WR_CFG_BYTE);
+ bbc_i2c_writeb(tp->client, 0x02, MAX1617_WR_CVRATE_BYTE);
+
+ /* Program the hard temperature limits into the chip. */
+ bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].high_pwroff,
+ MAX1617_WR_AMB_HIGHLIM);
+ bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].low_pwroff,
+ MAX1617_WR_AMB_LOWLIM);
+ bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].high_pwroff,
+ MAX1617_WR_CPU_HIGHLIM);
+ bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].low_pwroff,
+ MAX1617_WR_CPU_LOWLIM);
+
+ get_current_temps(tp);
+ tp->prev_cpu_temp = tp->avg_cpu_temp = tp->curr_cpu_temp;
+ tp->prev_amb_temp = tp->avg_amb_temp = tp->curr_amb_temp;
+
+ tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
+ tp->fan_todo[FAN_CPU] = FAN_SAME;
+}
+
+static void attach_one_fan(struct linux_ebus_child *echild, int fan_idx)
+{
+ struct bbc_fan_control *fp = kmalloc(sizeof(*fp), GFP_KERNEL);
+
+ if (!fp)
+ return;
+ memset(fp, 0, sizeof(*fp));
+ fp->client = bbc_i2c_attach(echild);
+ if (!fp->client) {
+ kfree(fp);
+ return;
+ }
+
+ fp->index = fan_idx;
+
+ {
+ struct bbc_fan_control **fpp = &all_bbc_fans;
+ while (*fpp)
+ fpp = &((*fpp)->next);
+ fp->next = NULL;
+ *fpp = fp;
+ }
+
+ /* The i2c device controlling the fans is write-only.
+ * So the only way to keep track of the current power
+ * level fed to the fans is via software. Choose half
+ * power for cpu/system and 'on' fo the powersupply fan
+ * and set it now.
+ */
+ fp->psupply_fan_on = 1;
+ fp->cpu_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2;
+ fp->cpu_fan_speed += FAN_SPEED_MIN;
+ fp->system_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2;
+ fp->system_fan_speed += FAN_SPEED_MIN;
+
+ set_fan_speeds(fp);
+}
+
+int bbc_envctrl_init(void)
+{
+ struct linux_ebus_child *echild;
+ int temp_index = 0;
+ int fan_index = 0;
+ int devidx = 0;
+ int err = 0;
+
+ while ((echild = bbc_i2c_getdev(devidx++)) != NULL) {
+ if (!strcmp(echild->prom_name, "temperature"))
+ attach_one_temp(echild, temp_index++);
+ if (!strcmp(echild->prom_name, "fan-control"))
+ attach_one_fan(echild, fan_index++);
+ }
+ if (temp_index != 0 && fan_index != 0)
+ err = kernel_thread(kenvctrld, NULL, CLONE_FS | CLONE_FILES);
+ return err;
+}
+
+static void destroy_one_temp(struct bbc_cpu_temperature *tp)
+{
+ bbc_i2c_detach(tp->client);
+ kfree(tp);
+}
+
+static void destroy_one_fan(struct bbc_fan_control *fp)
+{
+ bbc_i2c_detach(fp->client);
+ kfree(fp);
+}
+
+void bbc_envctrl_cleanup(void)
+{
+ struct bbc_cpu_temperature *tp;
+ struct bbc_fan_control *fp;
+
+ if (kenvctrld_task != NULL) {
+ force_sig(SIGKILL, kenvctrld_task);
+ for (;;) {
+ struct task_struct *p;
+ int found = 0;
+
+ read_lock(&tasklist_lock);
+ for_each_process(p) {
+ if (p == kenvctrld_task) {
+ found = 1;
+ break;
+ }
+ }
+ read_unlock(&tasklist_lock);
+ if (!found)
+ break;
+ msleep(1000);
+ }
+ kenvctrld_task = NULL;
+ }
+
+ tp = all_bbc_temps;
+ while (tp != NULL) {
+ struct bbc_cpu_temperature *next = tp->next;
+ destroy_one_temp(tp);
+ tp = next;
+ }
+ all_bbc_temps = NULL;
+
+ fp = all_bbc_fans;
+ while (fp != NULL) {
+ struct bbc_fan_control *next = fp->next;
+ destroy_one_fan(fp);
+ fp = next;
+ }
+ all_bbc_fans = NULL;
+}
diff --git a/drivers/sbus/char/bbc_i2c.c b/drivers/sbus/char/bbc_i2c.c
new file mode 100644
index 00000000000..1c8b612d823
--- /dev/null
+++ b/drivers/sbus/char/bbc_i2c.c
@@ -0,0 +1,488 @@
+/* $Id: bbc_i2c.c,v 1.2 2001/04/02 09:59:08 davem Exp $
+ * bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III
+ * platforms.
+ *
+ * Copyright (C) 2001 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <asm/oplib.h>
+#include <asm/ebus.h>
+#include <asm/spitfire.h>
+#include <asm/bbc.h>
+
+#include "bbc_i2c.h"
+
+/* Convert this driver to use i2c bus layer someday... */
+#define I2C_PCF_PIN 0x80
+#define I2C_PCF_ESO 0x40
+#define I2C_PCF_ES1 0x20
+#define I2C_PCF_ES2 0x10
+#define I2C_PCF_ENI 0x08
+#define I2C_PCF_STA 0x04
+#define I2C_PCF_STO 0x02
+#define I2C_PCF_ACK 0x01
+
+#define I2C_PCF_START (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ENI | I2C_PCF_STA | I2C_PCF_ACK)
+#define I2C_PCF_STOP (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK)
+#define I2C_PCF_REPSTART ( I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK)
+#define I2C_PCF_IDLE (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ACK)
+
+#define I2C_PCF_INI 0x40 /* 1 if not initialized */
+#define I2C_PCF_STS 0x20
+#define I2C_PCF_BER 0x10
+#define I2C_PCF_AD0 0x08
+#define I2C_PCF_LRB 0x08
+#define I2C_PCF_AAS 0x04
+#define I2C_PCF_LAB 0x02
+#define I2C_PCF_BB 0x01
+
+/* The BBC devices have two I2C controllers. The first I2C controller
+ * connects mainly to configuration proms (NVRAM, cpu configuration,
+ * dimm types, etc.). Whereas the second I2C controller connects to
+ * environmental control devices such as fans and temperature sensors.
+ * The second controller also connects to the smartcard reader, if present.
+ */
+
+#define NUM_CHILDREN 8
+struct bbc_i2c_bus {
+ struct bbc_i2c_bus *next;
+ int index;
+ spinlock_t lock;
+ void __iomem *i2c_bussel_reg;
+ void __iomem *i2c_control_regs;
+ unsigned char own, clock;
+
+ wait_queue_head_t wq;
+ volatile int waiting;
+
+ struct linux_ebus_device *bus_edev;
+ struct {
+ struct linux_ebus_child *device;
+ int client_claimed;
+ } devs[NUM_CHILDREN];
+};
+
+static struct bbc_i2c_bus *all_bbc_i2c;
+
+struct bbc_i2c_client {
+ struct bbc_i2c_bus *bp;
+ struct linux_ebus_child *echild;
+ int bus;
+ int address;
+};
+
+static int find_device(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild)
+{
+ int i;
+
+ for (i = 0; i < NUM_CHILDREN; i++) {
+ if (bp->devs[i].device == echild) {
+ if (bp->devs[i].client_claimed)
+ return 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void set_device_claimage(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild, int val)
+{
+ int i;
+
+ for (i = 0; i < NUM_CHILDREN; i++) {
+ if (bp->devs[i].device == echild) {
+ bp->devs[i].client_claimed = val;
+ return;
+ }
+ }
+}
+
+#define claim_device(BP,ECHILD) set_device_claimage(BP,ECHILD,1)
+#define release_device(BP,ECHILD) set_device_claimage(BP,ECHILD,0)
+
+static struct bbc_i2c_bus *find_bus_for_device(struct linux_ebus_child *echild)
+{
+ struct bbc_i2c_bus *bp = all_bbc_i2c;
+
+ while (bp != NULL) {
+ if (find_device(bp, echild) != 0)
+ break;
+ bp = bp->next;
+ }
+
+ return bp;
+}
+
+struct linux_ebus_child *bbc_i2c_getdev(int index)
+{
+ struct bbc_i2c_bus *bp = all_bbc_i2c;
+ struct linux_ebus_child *echild = NULL;
+ int curidx = 0;
+
+ while (bp != NULL) {
+ struct bbc_i2c_bus *next = bp->next;
+ int i;
+
+ for (i = 0; i < NUM_CHILDREN; i++) {
+ if (!(echild = bp->devs[i].device))
+ break;
+ if (curidx == index)
+ goto out;
+ echild = NULL;
+ curidx++;
+ }
+ bp = next;
+ }
+out:
+ if (curidx == index)
+ return echild;
+ return NULL;
+}
+
+struct bbc_i2c_client *bbc_i2c_attach(struct linux_ebus_child *echild)
+{
+ struct bbc_i2c_bus *bp = find_bus_for_device(echild);
+ struct bbc_i2c_client *client;
+
+ if (!bp)
+ return NULL;
+ client = kmalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return NULL;
+ memset(client, 0, sizeof(*client));
+ client->bp = bp;
+ client->echild = echild;
+ client->bus = echild->resource[0].start;
+ client->address = echild->resource[1].start;
+
+ claim_device(bp, echild);
+
+ return client;
+}
+
+void bbc_i2c_detach(struct bbc_i2c_client *client)
+{
+ struct bbc_i2c_bus *bp = client->bp;
+ struct linux_ebus_child *echild = client->echild;
+
+ release_device(bp, echild);
+ kfree(client);
+}
+
+static int wait_for_pin(struct bbc_i2c_bus *bp, u8 *status)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int limit = 32;
+ int ret = 1;
+
+ bp->waiting = 1;
+ add_wait_queue(&bp->wq, &wait);
+ while (limit-- > 0) {
+ u8 val;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ *status = val = readb(bp->i2c_control_regs + 0);
+ if ((val & I2C_PCF_PIN) == 0) {
+ ret = 0;
+ break;
+ }
+ msleep_interruptible(250);
+ }
+ remove_wait_queue(&bp->wq, &wait);
+ bp->waiting = 0;
+ current->state = TASK_RUNNING;
+
+ return ret;
+}
+
+int bbc_i2c_writeb(struct bbc_i2c_client *client, unsigned char val, int off)
+{
+ struct bbc_i2c_bus *bp = client->bp;
+ int address = client->address;
+ u8 status;
+ int ret = -1;
+
+ if (bp->i2c_bussel_reg != NULL)
+ writeb(client->bus, bp->i2c_bussel_reg);
+
+ writeb(address, bp->i2c_control_regs + 0x1);
+ writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0);
+ if (wait_for_pin(bp, &status))
+ goto out;
+
+ writeb(off, bp->i2c_control_regs + 0x1);
+ if (wait_for_pin(bp, &status) ||
+ (status & I2C_PCF_LRB) != 0)
+ goto out;
+
+ writeb(val, bp->i2c_control_regs + 0x1);
+ if (wait_for_pin(bp, &status))
+ goto out;
+
+ ret = 0;
+
+out:
+ writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0);
+ return ret;
+}
+
+int bbc_i2c_readb(struct bbc_i2c_client *client, unsigned char *byte, int off)
+{
+ struct bbc_i2c_bus *bp = client->bp;
+ unsigned char address = client->address, status;
+ int ret = -1;
+
+ if (bp->i2c_bussel_reg != NULL)
+ writeb(client->bus, bp->i2c_bussel_reg);
+
+ writeb(address, bp->i2c_control_regs + 0x1);
+ writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0);
+ if (wait_for_pin(bp, &status))
+ goto out;
+
+ writeb(off, bp->i2c_control_regs + 0x1);
+ if (wait_for_pin(bp, &status) ||
+ (status & I2C_PCF_LRB) != 0)
+ goto out;
+
+ writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0);
+
+ address |= 0x1; /* READ */
+
+ writeb(address, bp->i2c_control_regs + 0x1);
+ writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0);
+ if (wait_for_pin(bp, &status))
+ goto out;
+
+ /* Set PIN back to one so the device sends the first
+ * byte.
+ */
+ (void) readb(bp->i2c_control_regs + 0x1);
+ if (wait_for_pin(bp, &status))
+ goto out;
+
+ writeb(I2C_PCF_ESO | I2C_PCF_ENI, bp->i2c_control_regs + 0x0);
+ *byte = readb(bp->i2c_control_regs + 0x1);
+ if (wait_for_pin(bp, &status))
+ goto out;
+
+ ret = 0;
+
+out:
+ writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0);
+ (void) readb(bp->i2c_control_regs + 0x1);
+
+ return ret;
+}
+
+int bbc_i2c_write_buf(struct bbc_i2c_client *client,
+ char *buf, int len, int off)
+{
+ int ret = 0;
+
+ while (len > 0) {
+ int err = bbc_i2c_writeb(client, *buf, off);
+
+ if (err < 0) {
+ ret = err;
+ break;
+ }
+
+ len--;
+ buf++;
+ off++;
+ }
+ return ret;
+}
+
+int bbc_i2c_read_buf(struct bbc_i2c_client *client,
+ char *buf, int len, int off)
+{
+ int ret = 0;
+
+ while (len > 0) {
+ int err = bbc_i2c_readb(client, buf, off);
+ if (err < 0) {
+ ret = err;
+ break;
+ }
+ len--;
+ buf++;
+ off++;
+ }
+
+ return ret;
+}
+
+EXPORT_SYMBOL(bbc_i2c_getdev);
+EXPORT_SYMBOL(bbc_i2c_attach);
+EXPORT_SYMBOL(bbc_i2c_detach);
+EXPORT_SYMBOL(bbc_i2c_writeb);
+EXPORT_SYMBOL(bbc_i2c_readb);
+EXPORT_SYMBOL(bbc_i2c_write_buf);
+EXPORT_SYMBOL(bbc_i2c_read_buf);
+
+static irqreturn_t bbc_i2c_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct bbc_i2c_bus *bp = dev_id;
+
+ /* PIN going from set to clear is the only event which
+ * makes the i2c assert an interrupt.
+ */
+ if (bp->waiting &&
+ !(readb(bp->i2c_control_regs + 0x0) & I2C_PCF_PIN))
+ wake_up(&bp->wq);
+
+ return IRQ_HANDLED;
+}
+
+static void __init reset_one_i2c(struct bbc_i2c_bus *bp)
+{
+ writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0);
+ writeb(bp->own, bp->i2c_control_regs + 0x1);
+ writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0);
+ writeb(bp->clock, bp->i2c_control_regs + 0x1);
+ writeb(I2C_PCF_IDLE, bp->i2c_control_regs + 0x0);
+}
+
+static int __init attach_one_i2c(struct linux_ebus_device *edev, int index)
+{
+ struct bbc_i2c_bus *bp = kmalloc(sizeof(*bp), GFP_KERNEL);
+ struct linux_ebus_child *echild;
+ int entry;
+
+ if (!bp)
+ return -ENOMEM;
+ memset(bp, 0, sizeof(*bp));
+
+ bp->i2c_control_regs = ioremap(edev->resource[0].start, 0x2);
+ if (!bp->i2c_control_regs)
+ goto fail;
+
+ if (edev->num_addrs == 2) {
+ bp->i2c_bussel_reg = ioremap(edev->resource[1].start, 0x1);
+ if (!bp->i2c_bussel_reg)
+ goto fail;
+ }
+
+ bp->waiting = 0;
+ init_waitqueue_head(&bp->wq);
+ if (request_irq(edev->irqs[0], bbc_i2c_interrupt,
+ SA_SHIRQ, "bbc_i2c", bp))
+ goto fail;
+
+ bp->index = index;
+ bp->bus_edev = edev;
+
+ spin_lock_init(&bp->lock);
+ bp->next = all_bbc_i2c;
+ all_bbc_i2c = bp;
+
+ entry = 0;
+ for (echild = edev->children;
+ echild && entry < 8;
+ echild = echild->next, entry++) {
+ bp->devs[entry].device = echild;
+ bp->devs[entry].client_claimed = 0;
+ }
+
+ writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0);
+ bp->own = readb(bp->i2c_control_regs + 0x01);
+ writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0);
+ bp->clock = readb(bp->i2c_control_regs + 0x01);
+
+ printk(KERN_INFO "i2c-%d: Regs at %p, %d devices, own %02x, clock %02x.\n",
+ bp->index, bp->i2c_control_regs, entry, bp->own, bp->clock);
+
+ reset_one_i2c(bp);
+
+ return 0;
+
+fail:
+ if (bp->i2c_bussel_reg)
+ iounmap(bp->i2c_bussel_reg);
+ if (bp->i2c_control_regs)
+ iounmap(bp->i2c_control_regs);
+ kfree(bp);
+ return -EINVAL;
+}
+
+static int __init bbc_present(void)
+{
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, "bbc"))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+extern int bbc_envctrl_init(void);
+extern void bbc_envctrl_cleanup(void);
+static void bbc_i2c_cleanup(void);
+
+static int __init bbc_i2c_init(void)
+{
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+ int err, index = 0;
+
+ if (tlb_type != cheetah || !bbc_present())
+ return -ENODEV;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, "i2c")) {
+ if (!attach_one_i2c(edev, index))
+ index++;
+ }
+ }
+ }
+
+ if (!index)
+ return -ENODEV;
+
+ err = bbc_envctrl_init();
+ if (err)
+ bbc_i2c_cleanup();
+ return err;
+}
+
+static void bbc_i2c_cleanup(void)
+{
+ struct bbc_i2c_bus *bp = all_bbc_i2c;
+
+ bbc_envctrl_cleanup();
+
+ while (bp != NULL) {
+ struct bbc_i2c_bus *next = bp->next;
+
+ free_irq(bp->bus_edev->irqs[0], bp);
+
+ if (bp->i2c_bussel_reg)
+ iounmap(bp->i2c_bussel_reg);
+ if (bp->i2c_control_regs)
+ iounmap(bp->i2c_control_regs);
+
+ kfree(bp);
+
+ bp = next;
+ }
+ all_bbc_i2c = NULL;
+}
+
+module_init(bbc_i2c_init);
+module_exit(bbc_i2c_cleanup);
diff --git a/drivers/sbus/char/bbc_i2c.h b/drivers/sbus/char/bbc_i2c.h
new file mode 100644
index 00000000000..fb01bd17704
--- /dev/null
+++ b/drivers/sbus/char/bbc_i2c.h
@@ -0,0 +1,20 @@
+/* $Id: bbc_i2c.h,v 1.2 2001/04/02 09:59:25 davem Exp $ */
+#ifndef _BBC_I2C_H
+#define _BBC_I2C_H
+
+#include <asm/ebus.h>
+
+struct bbc_i2c_client;
+
+/* Probing and attachment. */
+extern struct linux_ebus_child *bbc_i2c_getdev(int);
+extern struct bbc_i2c_client *bbc_i2c_attach(struct linux_ebus_child *);
+extern void bbc_i2c_detach(struct bbc_i2c_client *);
+
+/* Register read/write. NOTE: Blocking! */
+extern int bbc_i2c_writeb(struct bbc_i2c_client *, unsigned char val, int off);
+extern int bbc_i2c_readb(struct bbc_i2c_client *, unsigned char *byte, int off);
+extern int bbc_i2c_write_buf(struct bbc_i2c_client *, char *buf, int len, int off);
+extern int bbc_i2c_read_buf(struct bbc_i2c_client *, char *buf, int len, int off);
+
+#endif /* _BBC_I2C_H */
diff --git a/drivers/sbus/char/bpp.c b/drivers/sbus/char/bpp.c
new file mode 100644
index 00000000000..8f0f46907a8
--- /dev/null
+++ b/drivers/sbus/char/bpp.c
@@ -0,0 +1,1079 @@
+/*
+ * drivers/sbus/char/bpp.c
+ *
+ * Copyright (c) 1995 Picture Elements
+ * Stephen Williams (steve@icarus.com)
+ * Gus Baldauf (gbaldauf@ix.netcom.com)
+ *
+ * Linux/SPARC port by Peter Zaitcev.
+ * Integration into SPARC tree by Tom Dyas.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/ioport.h>
+#include <linux/major.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#if defined(__i386__)
+# include <asm/system.h>
+#endif
+
+#if defined(__sparc__)
+# include <linux/init.h>
+# include <linux/delay.h> /* udelay() */
+
+# include <asm/oplib.h> /* OpenProm Library */
+# include <asm/sbus.h>
+#endif
+
+#include <asm/bpp.h>
+
+#define BPP_PROBE_CODE 0x55
+#define BPP_DELAY 100
+
+static const unsigned BPP_MAJOR = LP_MAJOR;
+static const char* dev_name = "bpp";
+
+/* When switching from compatibility to a mode where I can read, try
+ the following mode first. */
+
+/* const unsigned char DEFAULT_ECP = 0x10; */
+static const unsigned char DEFAULT_ECP = 0x30;
+static const unsigned char DEFAULT_NIBBLE = 0x00;
+
+/*
+ * These are 1284 time constraints, in units of jiffies.
+ */
+
+static const unsigned long TIME_PSetup = 1;
+static const unsigned long TIME_PResponse = 6;
+static const unsigned long TIME_IDLE_LIMIT = 2000;
+
+/*
+ * One instance per supported subdevice...
+ */
+# define BPP_NO 3
+
+enum IEEE_Mode { COMPATIBILITY, NIBBLE, ECP, ECP_RLE, EPP };
+
+struct inst {
+ unsigned present : 1; /* True if the hardware exists */
+ unsigned enhanced : 1; /* True if the hardware in "enhanced" */
+ unsigned opened : 1; /* True if the device is opened already */
+ unsigned run_flag : 1; /* True if waiting for a repeate byte */
+
+ unsigned char direction; /* 0 --> out, 0x20 --> IN */
+ unsigned char pp_state; /* State of host controlled pins. */
+ enum IEEE_Mode mode;
+
+ unsigned char run_length;
+ unsigned char repeat_byte;
+
+ /* These members manage timeouts for programmed delays */
+ wait_queue_head_t wait_queue;
+ struct timer_list timer_list;
+};
+
+static struct inst instances[BPP_NO];
+
+#if defined(__i386__)
+
+static const unsigned short base_addrs[BPP_NO] = { 0x278, 0x378, 0x3bc };
+
+/*
+ * These are for data access.
+ * Control lines accesses are hidden in set_bits() and get_bits().
+ * The exception is the probe procedure, which is system-dependent.
+ */
+#define bpp_outb_p(data, base) outb_p((data), (base))
+#define bpp_inb(base) inb(base)
+#define bpp_inb_p(base) inb_p(base)
+
+/*
+ * This method takes the pin values mask and sets the hardware pins to
+ * the requested value: 1 == high voltage, 0 == low voltage. This
+ * burries the annoying PC bit inversion and preserves the direction
+ * flag.
+ */
+static void set_pins(unsigned short pins, unsigned minor)
+{
+ unsigned char bits = instances[minor].direction; /* == 0x20 */
+
+ if (! (pins & BPP_PP_nStrobe)) bits |= 1;
+ if (! (pins & BPP_PP_nAutoFd)) bits |= 2;
+ if ( pins & BPP_PP_nInit) bits |= 4;
+ if (! (pins & BPP_PP_nSelectIn)) bits |= 8;
+
+ instances[minor].pp_state = bits;
+
+ outb_p(bits, base_addrs[minor]+2);
+}
+
+static unsigned short get_pins(unsigned minor)
+{
+ unsigned short bits = 0;
+
+ unsigned value = instances[minor].pp_state;
+ if (! (value & 0x01)) bits |= BPP_PP_nStrobe;
+ if (! (value & 0x02)) bits |= BPP_PP_nAutoFd;
+ if (value & 0x04) bits |= BPP_PP_nInit;
+ if (! (value & 0x08)) bits |= BPP_PP_nSelectIn;
+
+ value = inb_p(base_addrs[minor]+1);
+ if (value & 0x08) bits |= BPP_GP_nFault;
+ if (value & 0x10) bits |= BPP_GP_Select;
+ if (value & 0x20) bits |= BPP_GP_PError;
+ if (value & 0x40) bits |= BPP_GP_nAck;
+ if (! (value & 0x80)) bits |= BPP_GP_Busy;
+
+ return bits;
+}
+
+#endif /* __i386__ */
+
+#if defined(__sparc__)
+
+/*
+ * Register block
+ */
+ /* DMA registers */
+#define BPP_CSR 0x00
+#define BPP_ADDR 0x04
+#define BPP_BCNT 0x08
+#define BPP_TST_CSR 0x0C
+ /* Parallel Port registers */
+#define BPP_HCR 0x10
+#define BPP_OCR 0x12
+#define BPP_DR 0x14
+#define BPP_TCR 0x15
+#define BPP_OR 0x16
+#define BPP_IR 0x17
+#define BPP_ICR 0x18
+#define BPP_SIZE 0x1A
+
+/* BPP_CSR. Bits of type RW1 are cleared with writting '1'. */
+#define P_DEV_ID_MASK 0xf0000000 /* R */
+#define P_DEV_ID_ZEBRA 0x40000000
+#define P_DEV_ID_L64854 0xa0000000 /* == NCR 89C100+89C105. Pity. */
+#define P_NA_LOADED 0x08000000 /* R NA wirtten but was not used */
+#define P_A_LOADED 0x04000000 /* R */
+#define P_DMA_ON 0x02000000 /* R DMA is not disabled */
+#define P_EN_NEXT 0x01000000 /* RW */
+#define P_TCI_DIS 0x00800000 /* RW TCI forbidden from interrupts */
+#define P_DIAG 0x00100000 /* RW Disables draining and resetting
+ of P-FIFO on loading of P_ADDR*/
+#define P_BURST_SIZE 0x000c0000 /* RW SBus burst size */
+#define P_BURST_8 0x00000000
+#define P_BURST_4 0x00040000
+#define P_BURST_1 0x00080000 /* "No burst" write */
+#define P_TC 0x00004000 /* RW1 Term Count, can be cleared when
+ P_EN_NEXT=1 */
+#define P_EN_CNT 0x00002000 /* RW */
+#define P_EN_DMA 0x00000200 /* RW */
+#define P_WRITE 0x00000100 /* R DMA dir, 1=to ram, 0=to port */
+#define P_RESET 0x00000080 /* RW */
+#define P_SLAVE_ERR 0x00000040 /* RW1 Access size error */
+#define P_INVALIDATE 0x00000020 /* W Drop P-FIFO */
+#define P_INT_EN 0x00000010 /* RW OK to P_INT_PEND||P_ERR_PEND */
+#define P_DRAINING 0x0000000c /* R P-FIFO is draining to memory */
+#define P_ERR_PEND 0x00000002 /* R */
+#define P_INT_PEND 0x00000001 /* R */
+
+/* BPP_HCR. Time is in increments of SBus clock. */
+#define P_HCR_TEST 0x8000 /* Allows buried counters to be read */
+#define P_HCR_DSW 0x7f00 /* Data strobe width (in ticks) */
+#define P_HCR_DDS 0x007f /* Data setup before strobe (in ticks) */
+
+/* BPP_OCR. */
+#define P_OCR_MEM_CLR 0x8000
+#define P_OCR_DATA_SRC 0x4000 /* ) */
+#define P_OCR_DS_DSEL 0x2000 /* ) Bidirectional */
+#define P_OCR_BUSY_DSEL 0x1000 /* ) selects */
+#define P_OCR_ACK_DSEL 0x0800 /* ) */
+#define P_OCR_EN_DIAG 0x0400
+#define P_OCR_BUSY_OP 0x0200 /* Busy operation */
+#define P_OCR_ACK_OP 0x0100 /* Ack operation */
+#define P_OCR_SRST 0x0080 /* Reset state machines. Not selfcleaning. */
+#define P_OCR_IDLE 0x0008 /* PP data transfer state machine is idle */
+#define P_OCR_V_ILCK 0x0002 /* Versatec faded. Zebra only. */
+#define P_OCR_EN_VER 0x0001 /* Enable Versatec (0 - enable). Zebra only. */
+
+/* BPP_TCR */
+#define P_TCR_DIR 0x08
+#define P_TCR_BUSY 0x04
+#define P_TCR_ACK 0x02
+#define P_TCR_DS 0x01 /* Strobe */
+
+/* BPP_OR */
+#define P_OR_V3 0x20 /* ) */
+#define P_OR_V2 0x10 /* ) on Zebra only */
+#define P_OR_V1 0x08 /* ) */
+#define P_OR_INIT 0x04
+#define P_OR_AFXN 0x02 /* Auto Feed */
+#define P_OR_SLCT_IN 0x01
+
+/* BPP_IR */
+#define P_IR_PE 0x04
+#define P_IR_SLCT 0x02
+#define P_IR_ERR 0x01
+
+/* BPP_ICR */
+#define P_DS_IRQ 0x8000 /* RW1 */
+#define P_ACK_IRQ 0x4000 /* RW1 */
+#define P_BUSY_IRQ 0x2000 /* RW1 */
+#define P_PE_IRQ 0x1000 /* RW1 */
+#define P_SLCT_IRQ 0x0800 /* RW1 */
+#define P_ERR_IRQ 0x0400 /* RW1 */
+#define P_DS_IRQ_EN 0x0200 /* RW Always on rising edge */
+#define P_ACK_IRQ_EN 0x0100 /* RW Always on rising edge */
+#define P_BUSY_IRP 0x0080 /* RW 1= rising edge */
+#define P_BUSY_IRQ_EN 0x0040 /* RW */
+#define P_PE_IRP 0x0020 /* RW 1= rising edge */
+#define P_PE_IRQ_EN 0x0010 /* RW */
+#define P_SLCT_IRP 0x0008 /* RW 1= rising edge */
+#define P_SLCT_IRQ_EN 0x0004 /* RW */
+#define P_ERR_IRP 0x0002 /* RW1 1= rising edge */
+#define P_ERR_IRQ_EN 0x0001 /* RW */
+
+static void __iomem *base_addrs[BPP_NO];
+
+#define bpp_outb_p(data, base) sbus_writeb(data, (base) + BPP_DR)
+#define bpp_inb_p(base) sbus_readb((base) + BPP_DR)
+#define bpp_inb(base) sbus_readb((base) + BPP_DR)
+
+static void set_pins(unsigned short pins, unsigned minor)
+{
+ void __iomem *base = base_addrs[minor];
+ unsigned char bits_tcr = 0, bits_or = 0;
+
+ if (instances[minor].direction & 0x20) bits_tcr |= P_TCR_DIR;
+ if ( pins & BPP_PP_nStrobe) bits_tcr |= P_TCR_DS;
+
+ if ( pins & BPP_PP_nAutoFd) bits_or |= P_OR_AFXN;
+ if (! (pins & BPP_PP_nInit)) bits_or |= P_OR_INIT;
+ if (! (pins & BPP_PP_nSelectIn)) bits_or |= P_OR_SLCT_IN;
+
+ sbus_writeb(bits_or, base + BPP_OR);
+ sbus_writeb(bits_tcr, base + BPP_TCR);
+}
+
+/*
+ * i386 people read output pins from a software image.
+ * We may get them back from hardware.
+ * Again, inversion of pins must he buried here.
+ */
+static unsigned short get_pins(unsigned minor)
+{
+ void __iomem *base = base_addrs[minor];
+ unsigned short bits = 0;
+ unsigned value_tcr = sbus_readb(base + BPP_TCR);
+ unsigned value_ir = sbus_readb(base + BPP_IR);
+ unsigned value_or = sbus_readb(base + BPP_OR);
+
+ if (value_tcr & P_TCR_DS) bits |= BPP_PP_nStrobe;
+ if (value_or & P_OR_AFXN) bits |= BPP_PP_nAutoFd;
+ if (! (value_or & P_OR_INIT)) bits |= BPP_PP_nInit;
+ if (! (value_or & P_OR_SLCT_IN)) bits |= BPP_PP_nSelectIn;
+
+ if (value_ir & P_IR_ERR) bits |= BPP_GP_nFault;
+ if (! (value_ir & P_IR_SLCT)) bits |= BPP_GP_Select;
+ if (! (value_ir & P_IR_PE)) bits |= BPP_GP_PError;
+ if (! (value_tcr & P_TCR_ACK)) bits |= BPP_GP_nAck;
+ if (value_tcr & P_TCR_BUSY) bits |= BPP_GP_Busy;
+
+ return bits;
+}
+
+#endif /* __sparc__ */
+
+static void bpp_wake_up(unsigned long val)
+{ wake_up(&instances[val].wait_queue); }
+
+static void snooze(unsigned long snooze_time, unsigned minor)
+{
+ init_timer(&instances[minor].timer_list);
+ instances[minor].timer_list.expires = jiffies + snooze_time + 1;
+ instances[minor].timer_list.data = minor;
+ add_timer(&instances[minor].timer_list);
+ sleep_on (&instances[minor].wait_queue);
+}
+
+static int wait_for(unsigned short set, unsigned short clr,
+ unsigned long delay, unsigned minor)
+{
+ unsigned short pins = get_pins(minor);
+
+ unsigned long extime = 0;
+
+ /*
+ * Try a real fast scan for the first jiffy, in case the device
+ * responds real good. The first while loop guesses an expire
+ * time accounting for possible wraparound of jiffies.
+ */
+ while (time_after_eq(jiffies, extime)) extime = jiffies + 1;
+ while ( (time_before(jiffies, extime))
+ && (((pins & set) != set) || ((pins & clr) != 0)) ) {
+ pins = get_pins(minor);
+ }
+
+ delay -= 1;
+
+ /*
+ * If my delay expired or the pins are still not where I want
+ * them, then resort to using the timer and greatly reduce my
+ * sample rate. If the peripheral is going to be slow, this will
+ * give the CPU up to some more worthy process.
+ */
+ while ( delay && (((pins & set) != set) || ((pins & clr) != 0)) ) {
+
+ snooze(1, minor);
+ pins = get_pins(minor);
+ delay -= 1;
+ }
+
+ if (delay == 0) return -1;
+ else return pins;
+}
+
+/*
+ * Return ZERO(0) If the negotiation succeeds, an errno otherwise. An
+ * errno means something broke, and I do not yet know how to fix it.
+ */
+static int negotiate(unsigned char mode, unsigned minor)
+{
+ int rc;
+ unsigned short pins = get_pins(minor);
+ if (pins & BPP_PP_nSelectIn) return -EIO;
+
+
+ /* Event 0: Write the mode to the data lines */
+ bpp_outb_p(mode, base_addrs[minor]);
+
+ snooze(TIME_PSetup, minor);
+
+ /* Event 1: Strobe the mode code into the peripheral */
+ set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+ /* Wait for Event 2: Peripheral responds as a 1284 device. */
+ rc = wait_for(BPP_GP_PError|BPP_GP_Select|BPP_GP_nFault,
+ BPP_GP_nAck,
+ TIME_PResponse,
+ minor);
+
+ if (rc == -1) return -ETIMEDOUT;
+
+ /* Event 3: latch extensibility request */
+ set_pins(BPP_PP_nSelectIn|BPP_PP_nInit, minor);
+
+ /* ... quick nap while peripheral ponders the byte i'm sending...*/
+ snooze(1, minor);
+
+ /* Event 4: restore strobe, to ACK peripheral's response. */
+ set_pins(BPP_PP_nSelectIn|BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+ /* Wait for Event 6: Peripheral latches response bits */
+ rc = wait_for(BPP_GP_nAck, 0, TIME_PSetup+TIME_PResponse, minor);
+ if (rc == -1) return -EIO;
+
+ /* A 1284 device cannot refuse nibble mode */
+ if (mode == DEFAULT_NIBBLE) return 0;
+
+ if (pins & BPP_GP_Select) return 0;
+
+ return -EPROTONOSUPPORT;
+}
+
+static int terminate(unsigned minor)
+{
+ int rc;
+
+ /* Event 22: Request termination of 1284 mode */
+ set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+ /* Wait for Events 23 and 24: ACK termination request. */
+ rc = wait_for(BPP_GP_Busy|BPP_GP_nFault,
+ BPP_GP_nAck,
+ TIME_PSetup+TIME_PResponse,
+ minor);
+
+ instances[minor].direction = 0;
+ instances[minor].mode = COMPATIBILITY;
+
+ if (rc == -1) {
+ return -EIO;
+ }
+
+ /* Event 25: Handshake by lowering nAutoFd */
+ set_pins(BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+ /* Event 26: Peripheral wiggles lines... */
+
+ /* Event 27: Peripheral sets nAck HIGH to ack handshake */
+ rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+ if (rc == -1) {
+ set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+ return -EIO;
+ }
+
+ /* Event 28: Finish phase by raising nAutoFd */
+ set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+ return 0;
+}
+
+static DEFINE_SPINLOCK(bpp_open_lock);
+
+/*
+ * Allow only one process to open the device at a time.
+ */
+static int bpp_open(struct inode *inode, struct file *f)
+{
+ unsigned minor = iminor(inode);
+ int ret;
+
+ spin_lock(&bpp_open_lock);
+ ret = 0;
+ if (minor >= BPP_NO) {
+ ret = -ENODEV;
+ } else {
+ if (! instances[minor].present) {
+ ret = -ENODEV;
+ } else {
+ if (instances[minor].opened)
+ ret = -EBUSY;
+ else
+ instances[minor].opened = 1;
+ }
+ }
+ spin_unlock(&bpp_open_lock);
+
+ return ret;
+}
+
+/*
+ * When the process closes the device, this method is called to clean
+ * up and reset the hardware. Always leave the device in compatibility
+ * mode as this is a reasonable place to clean up from messes made by
+ * ioctls, or other mayhem.
+ */
+static int bpp_release(struct inode *inode, struct file *f)
+{
+ unsigned minor = iminor(inode);
+
+ spin_lock(&bpp_open_lock);
+ instances[minor].opened = 0;
+
+ if (instances[minor].mode != COMPATIBILITY)
+ terminate(minor);
+
+ spin_unlock(&bpp_open_lock);
+
+ return 0;
+}
+
+static long read_nibble(unsigned minor, char __user *c, unsigned long cnt)
+{
+ unsigned long remaining = cnt;
+ long rc;
+
+ while (remaining > 0) {
+ unsigned char byte = 0;
+ int pins;
+
+ /* Event 7: request nibble */
+ set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe, minor);
+
+ /* Wait for event 9: Peripher strobes first nibble */
+ pins = wait_for(0, BPP_GP_nAck, TIME_IDLE_LIMIT, minor);
+ if (pins == -1) return -ETIMEDOUT;
+
+ /* Event 10: I handshake nibble */
+ set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe|BPP_PP_nAutoFd, minor);
+ if (pins & BPP_GP_nFault) byte |= 0x01;
+ if (pins & BPP_GP_Select) byte |= 0x02;
+ if (pins & BPP_GP_PError) byte |= 0x04;
+ if (pins & BPP_GP_Busy) byte |= 0x08;
+
+ /* Wait for event 11: Peripheral handshakes nibble */
+ rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+
+ /* Event 7: request nibble */
+ set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe, minor);
+
+ /* Wait for event 9: Peripher strobes first nibble */
+ pins = wait_for(0, BPP_GP_nAck, TIME_PResponse, minor);
+ if (rc == -1) return -ETIMEDOUT;
+
+ /* Event 10: I handshake nibble */
+ set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe|BPP_PP_nAutoFd, minor);
+ if (pins & BPP_GP_nFault) byte |= 0x10;
+ if (pins & BPP_GP_Select) byte |= 0x20;
+ if (pins & BPP_GP_PError) byte |= 0x40;
+ if (pins & BPP_GP_Busy) byte |= 0x80;
+
+ if (put_user(byte, c))
+ return -EFAULT;
+ c += 1;
+ remaining -= 1;
+
+ /* Wait for event 11: Peripheral handshakes nibble */
+ rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+ if (rc == -1) return -EIO;
+ }
+
+ return cnt - remaining;
+}
+
+static long read_ecp(unsigned minor, char __user *c, unsigned long cnt)
+{
+ unsigned long remaining;
+ long rc;
+
+ /* Turn ECP mode from forward to reverse if needed. */
+ if (! instances[minor].direction) {
+ unsigned short pins = get_pins(minor);
+
+ /* Event 38: Turn the bus around */
+ instances[minor].direction = 0x20;
+ pins &= ~BPP_PP_nAutoFd;
+ set_pins(pins, minor);
+
+ /* Event 39: Set pins for reverse mode. */
+ snooze(TIME_PSetup, minor);
+ set_pins(BPP_PP_nStrobe|BPP_PP_nSelectIn, minor);
+
+ /* Wait for event 40: Peripheral ready to be strobed */
+ rc = wait_for(0, BPP_GP_PError, TIME_PResponse, minor);
+ if (rc == -1) return -ETIMEDOUT;
+ }
+
+ remaining = cnt;
+
+ while (remaining > 0) {
+
+ /* If there is a run length for a repeated byte, repeat */
+ /* that byte a few times. */
+ if (instances[minor].run_length && !instances[minor].run_flag) {
+
+ char buffer[128];
+ unsigned idx;
+ unsigned repeat = remaining < instances[minor].run_length
+ ? remaining
+ : instances[minor].run_length;
+
+ for (idx = 0 ; idx < repeat ; idx += 1)
+ buffer[idx] = instances[minor].repeat_byte;
+
+ if (copy_to_user(c, buffer, repeat))
+ return -EFAULT;
+ remaining -= repeat;
+ c += repeat;
+ instances[minor].run_length -= repeat;
+ }
+
+ if (remaining == 0) break;
+
+
+ /* Wait for Event 43: Data active on the bus. */
+ rc = wait_for(0, BPP_GP_nAck, TIME_IDLE_LIMIT, minor);
+ if (rc == -1) break;
+
+ if (rc & BPP_GP_Busy) {
+ /* OK, this is data. read it in. */
+ unsigned char byte = bpp_inb(base_addrs[minor]);
+ if (put_user(byte, c))
+ return -EFAULT;
+ c += 1;
+ remaining -= 1;
+
+ if (instances[minor].run_flag) {
+ instances[minor].repeat_byte = byte;
+ instances[minor].run_flag = 0;
+ }
+
+ } else {
+ unsigned char byte = bpp_inb(base_addrs[minor]);
+ if (byte & 0x80) {
+ printk("bpp%d: "
+ "Ignoring ECP channel %u from device.\n",
+ minor, byte & 0x7f);
+ } else {
+ instances[minor].run_length = byte;
+ instances[minor].run_flag = 1;
+ }
+ }
+
+ /* Event 44: I got it. */
+ set_pins(BPP_PP_nStrobe|BPP_PP_nAutoFd|BPP_PP_nSelectIn, minor);
+
+ /* Wait for event 45: peripheral handshake */
+ rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+ if (rc == -1) return -ETIMEDOUT;
+
+ /* Event 46: Finish handshake */
+ set_pins(BPP_PP_nStrobe|BPP_PP_nSelectIn, minor);
+
+ }
+
+
+ return cnt - remaining;
+}
+
+static ssize_t bpp_read(struct file *f, char __user *c, size_t cnt, loff_t * ppos)
+{
+ long rc;
+ unsigned minor = iminor(f->f_dentry->d_inode);
+ if (minor >= BPP_NO) return -ENODEV;
+ if (!instances[minor].present) return -ENODEV;
+
+ switch (instances[minor].mode) {
+
+ default:
+ if (instances[minor].mode != COMPATIBILITY)
+ terminate(minor);
+
+ if (instances[minor].enhanced) {
+ /* For now, do all reads with ECP-RLE mode */
+ unsigned short pins;
+
+ rc = negotiate(DEFAULT_ECP, minor);
+ if (rc < 0) break;
+
+ instances[minor].mode = ECP_RLE;
+
+ /* Event 30: set nAutoFd low to setup for ECP mode */
+ pins = get_pins(minor);
+ pins &= ~BPP_PP_nAutoFd;
+ set_pins(pins, minor);
+
+ /* Wait for Event 31: peripheral ready */
+ rc = wait_for(BPP_GP_PError, 0, TIME_PResponse, minor);
+ if (rc == -1) return -ETIMEDOUT;
+
+ rc = read_ecp(minor, c, cnt);
+
+ } else {
+ rc = negotiate(DEFAULT_NIBBLE, minor);
+ if (rc < 0) break;
+
+ instances[minor].mode = NIBBLE;
+
+ rc = read_nibble(minor, c, cnt);
+ }
+ break;
+
+ case NIBBLE:
+ rc = read_nibble(minor, c, cnt);
+ break;
+
+ case ECP:
+ case ECP_RLE:
+ rc = read_ecp(minor, c, cnt);
+ break;
+
+ }
+
+
+ return rc;
+}
+
+/*
+ * Compatibility mode handshaking is a matter of writing data,
+ * strobing it, and waiting for the printer to stop being busy.
+ */
+static long write_compat(unsigned minor, const char __user *c, unsigned long cnt)
+{
+ long rc;
+ unsigned short pins = get_pins(minor);
+
+ unsigned long remaining = cnt;
+
+
+ while (remaining > 0) {
+ unsigned char byte;
+
+ if (get_user(byte, c))
+ return -EFAULT;
+ c += 1;
+
+ rc = wait_for(BPP_GP_nAck, BPP_GP_Busy, TIME_IDLE_LIMIT, minor);
+ if (rc == -1) return -ETIMEDOUT;
+
+ bpp_outb_p(byte, base_addrs[minor]);
+ remaining -= 1;
+ /* snooze(1, minor); */
+
+ pins &= ~BPP_PP_nStrobe;
+ set_pins(pins, minor);
+
+ rc = wait_for(BPP_GP_Busy, 0, TIME_PResponse, minor);
+
+ pins |= BPP_PP_nStrobe;
+ set_pins(pins, minor);
+ }
+
+ return cnt - remaining;
+}
+
+/*
+ * Write data using ECP mode. Watch out that the port may be set up
+ * for reading. If so, turn the port around.
+ */
+static long write_ecp(unsigned minor, const char __user *c, unsigned long cnt)
+{
+ unsigned short pins = get_pins(minor);
+ unsigned long remaining = cnt;
+
+ if (instances[minor].direction) {
+ int rc;
+
+ /* Event 47 Request bus be turned around */
+ pins |= BPP_PP_nInit;
+ set_pins(pins, minor);
+
+ /* Wait for Event 49: Peripheral relinquished bus */
+ rc = wait_for(BPP_GP_PError, 0, TIME_PResponse, minor);
+
+ pins |= BPP_PP_nAutoFd;
+ instances[minor].direction = 0;
+ set_pins(pins, minor);
+ }
+
+ while (remaining > 0) {
+ unsigned char byte;
+ int rc;
+
+ if (get_user(byte, c))
+ return -EFAULT;
+
+ rc = wait_for(0, BPP_GP_Busy, TIME_PResponse, minor);
+ if (rc == -1) return -ETIMEDOUT;
+
+ c += 1;
+
+ bpp_outb_p(byte, base_addrs[minor]);
+
+ pins &= ~BPP_PP_nStrobe;
+ set_pins(pins, minor);
+
+ pins |= BPP_PP_nStrobe;
+ rc = wait_for(BPP_GP_Busy, 0, TIME_PResponse, minor);
+ if (rc == -1) return -EIO;
+
+ set_pins(pins, minor);
+ }
+
+ return cnt - remaining;
+}
+
+/*
+ * Write to the peripheral. Be sensitive of the current mode. If I'm
+ * in a mode that can be turned around (ECP) then just do
+ * that. Otherwise, terminate and do my writing in compat mode. This
+ * is the safest course as any device can handle it.
+ */
+static ssize_t bpp_write(struct file *f, const char __user *c, size_t cnt, loff_t * ppos)
+{
+ long errno = 0;
+ unsigned minor = iminor(f->f_dentry->d_inode);
+ if (minor >= BPP_NO) return -ENODEV;
+ if (!instances[minor].present) return -ENODEV;
+
+ switch (instances[minor].mode) {
+
+ case ECP:
+ case ECP_RLE:
+ errno = write_ecp(minor, c, cnt);
+ break;
+ case COMPATIBILITY:
+ errno = write_compat(minor, c, cnt);
+ break;
+ default:
+ terminate(minor);
+ errno = write_compat(minor, c, cnt);
+ }
+
+ return errno;
+}
+
+static int bpp_ioctl(struct inode *inode, struct file *f, unsigned int cmd,
+ unsigned long arg)
+{
+ int errno = 0;
+
+ unsigned minor = iminor(inode);
+ if (minor >= BPP_NO) return -ENODEV;
+ if (!instances[minor].present) return -ENODEV;
+
+
+ switch (cmd) {
+
+ case BPP_PUT_PINS:
+ set_pins(arg, minor);
+ break;
+
+ case BPP_GET_PINS:
+ errno = get_pins(minor);
+ break;
+
+ case BPP_PUT_DATA:
+ bpp_outb_p(arg, base_addrs[minor]);
+ break;
+
+ case BPP_GET_DATA:
+ errno = bpp_inb_p(base_addrs[minor]);
+ break;
+
+ case BPP_SET_INPUT:
+ if (arg)
+ if (instances[minor].enhanced) {
+ unsigned short bits = get_pins(minor);
+ instances[minor].direction = 0x20;
+ set_pins(bits, minor);
+ } else {
+ errno = -ENOTTY;
+ }
+ else {
+ unsigned short bits = get_pins(minor);
+ instances[minor].direction = 0x00;
+ set_pins(bits, minor);
+ }
+ break;
+
+ default:
+ errno = -EINVAL;
+ }
+
+ return errno;
+}
+
+static struct file_operations bpp_fops = {
+ .owner = THIS_MODULE,
+ .read = bpp_read,
+ .write = bpp_write,
+ .ioctl = bpp_ioctl,
+ .open = bpp_open,
+ .release = bpp_release,
+};
+
+#if defined(__i386__)
+
+#define collectLptPorts() {}
+
+static void probeLptPort(unsigned idx)
+{
+ unsigned int testvalue;
+ const unsigned short lpAddr = base_addrs[idx];
+
+ instances[idx].present = 0;
+ instances[idx].enhanced = 0;
+ instances[idx].direction = 0;
+ instances[idx].mode = COMPATIBILITY;
+ instances[idx].wait_queue = 0;
+ instances[idx].run_length = 0;
+ instances[idx].run_flag = 0;
+ init_timer(&instances[idx].timer_list);
+ instances[idx].timer_list.function = bpp_wake_up;
+ if (!request_region(lpAddr,3, dev_name)) return;
+
+ /*
+ * First, make sure the instance exists. Do this by writing to
+ * the data latch and reading the value back. If the port *is*
+ * present, test to see if it supports extended-mode
+ * operation. This will be required for IEEE1284 reverse
+ * transfers.
+ */
+
+ outb_p(BPP_PROBE_CODE, lpAddr);
+ for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+ ;
+ testvalue = inb_p(lpAddr);
+ if (testvalue == BPP_PROBE_CODE) {
+ unsigned save;
+ instances[idx].present = 1;
+
+ save = inb_p(lpAddr+2);
+ for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+ ;
+ outb_p(save|0x20, lpAddr+2);
+ for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+ ;
+ outb_p(~BPP_PROBE_CODE, lpAddr);
+ for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+ ;
+ testvalue = inb_p(lpAddr);
+ if ((testvalue&0xff) == (0xff&~BPP_PROBE_CODE))
+ instances[idx].enhanced = 0;
+ else
+ instances[idx].enhanced = 1;
+ outb_p(save, lpAddr+2);
+ }
+ else {
+ release_region(lpAddr,3);
+ }
+ /*
+ * Leave the port in compat idle mode.
+ */
+ set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, idx);
+
+ printk("bpp%d: Port at 0x%03x: Enhanced mode %s\n", idx, base_addrs[idx],
+ instances[idx].enhanced? "SUPPORTED" : "UNAVAILABLE");
+}
+
+static inline void freeLptPort(int idx)
+{
+ release_region(base_addrs[idx], 3);
+}
+
+#endif
+
+#if defined(__sparc__)
+
+static void __iomem *map_bpp(struct sbus_dev *dev, int idx)
+{
+ return sbus_ioremap(&dev->resource[0], 0, BPP_SIZE, "bpp");
+}
+
+static int collectLptPorts(void)
+{
+ struct sbus_bus *bus;
+ struct sbus_dev *dev;
+ int count;
+
+ count = 0;
+ for_all_sbusdev(dev, bus) {
+ if (strcmp(dev->prom_name, "SUNW,bpp") == 0) {
+ if (count >= BPP_NO) {
+ printk(KERN_NOTICE
+ "bpp: More than %d bpp ports,"
+ " rest is ignored\n", BPP_NO);
+ return count;
+ }
+ base_addrs[count] = map_bpp(dev, count);
+ count++;
+ }
+ }
+ return count;
+}
+
+static void probeLptPort(unsigned idx)
+{
+ void __iomem *rp = base_addrs[idx];
+ __u32 csr;
+ char *brand;
+
+ instances[idx].present = 0;
+ instances[idx].enhanced = 0;
+ instances[idx].direction = 0;
+ instances[idx].mode = COMPATIBILITY;
+ init_waitqueue_head(&instances[idx].wait_queue);
+ instances[idx].run_length = 0;
+ instances[idx].run_flag = 0;
+ init_timer(&instances[idx].timer_list);
+ instances[idx].timer_list.function = bpp_wake_up;
+
+ if (!rp) return;
+
+ instances[idx].present = 1;
+ instances[idx].enhanced = 1; /* Sure */
+
+ csr = sbus_readl(rp + BPP_CSR);
+ if ((csr & P_DRAINING) != 0 && (csr & P_ERR_PEND) == 0) {
+ udelay(20);
+ csr = sbus_readl(rp + BPP_CSR);
+ if ((csr & P_DRAINING) != 0 && (csr & P_ERR_PEND) == 0) {
+ printk("bpp%d: DRAINING still active (0x%08x)\n", idx, csr);
+ }
+ }
+ printk("bpp%d: reset with 0x%08x ..", idx, csr);
+ sbus_writel((csr | P_RESET) & ~P_INT_EN, rp + BPP_CSR);
+ udelay(500);
+ sbus_writel(sbus_readl(rp + BPP_CSR) & ~P_RESET, rp + BPP_CSR);
+ csr = sbus_readl(rp + BPP_CSR);
+ printk(" done with csr=0x%08x ocr=0x%04x\n",
+ csr, sbus_readw(rp + BPP_OCR));
+
+ switch (csr & P_DEV_ID_MASK) {
+ case P_DEV_ID_ZEBRA:
+ brand = "Zebra";
+ break;
+ case P_DEV_ID_L64854:
+ brand = "DMA2";
+ break;
+ default:
+ brand = "Unknown";
+ }
+ printk("bpp%d: %s at %p\n", idx, brand, rp);
+
+ /*
+ * Leave the port in compat idle mode.
+ */
+ set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, idx);
+
+ return;
+}
+
+static inline void freeLptPort(int idx)
+{
+ sbus_iounmap(base_addrs[idx], BPP_SIZE);
+}
+
+#endif
+
+static int __init bpp_init(void)
+{
+ int rc;
+ unsigned idx;
+
+ rc = collectLptPorts();
+ if (rc == 0)
+ return -ENODEV;
+
+ rc = register_chrdev(BPP_MAJOR, dev_name, &bpp_fops);
+ if (rc < 0)
+ return rc;
+
+ for (idx = 0; idx < BPP_NO; idx++) {
+ instances[idx].opened = 0;
+ probeLptPort(idx);
+ }
+ devfs_mk_dir("bpp");
+ for (idx = 0; idx < BPP_NO; idx++) {
+ devfs_mk_cdev(MKDEV(BPP_MAJOR, idx),
+ S_IFCHR | S_IRUSR | S_IWUSR, "bpp/%d", idx);
+ }
+
+ return 0;
+}
+
+static void __exit bpp_cleanup(void)
+{
+ unsigned idx;
+
+ for (idx = 0; idx < BPP_NO; idx++)
+ devfs_remove("bpp/%d", idx);
+ devfs_remove("bpp");
+ unregister_chrdev(BPP_MAJOR, dev_name);
+
+ for (idx = 0; idx < BPP_NO; idx++) {
+ if (instances[idx].present)
+ freeLptPort(idx);
+ }
+}
+
+module_init(bpp_init);
+module_exit(bpp_cleanup);
+
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/sbus/char/cd180.h b/drivers/sbus/char/cd180.h
new file mode 100644
index 00000000000..445b86cc65e
--- /dev/null
+++ b/drivers/sbus/char/cd180.h
@@ -0,0 +1,240 @@
+
+/* Definitions for Cirrus Logic CL-CD180 8-port async mux chip */
+#define CD180_NCH 8 /* Total number of channels */
+#define CD180_TPC 16 /* Ticks per character */
+#define CD180_NFIFO 8 /* TX FIFO size */
+
+/* Global registers */
+#define CD180_GFRCR 0x6b /* Global Firmware Revision Code Register */
+#define CD180_SRCR 0x66 /* Service Request Configuration Register */
+#define CD180_PPRH 0x70 /* Prescaler Period Register High */
+#define CD180_PPRL 0x71 /* Prescaler Period Register Low */
+#define CD180_MSMR 0x61 /* Modem Service Match Register */
+#define CD180_TSMR 0x62 /* Transmit Service Match Register */
+#define CD180_RSMR 0x63 /* Receive Service Match Register */
+#define CD180_GSVR 0x40 /* Global Service Vector Register */
+#define CD180_SRSR 0x65 /* Service Request Status Register */
+#define CD180_GSCR 0x41 /* Global Service Channel Register */
+#define CD180_CAR 0x64 /* Channel Access Register */
+
+/* Indexed registers */
+#define CD180_RDCR 0x07 /* Receive Data Count Register */
+#define CD180_RDR 0x78 /* Receiver Data Register */
+#define CD180_RCSR 0x7a /* Receiver Character Status Register */
+#define CD180_TDR 0x7b /* Transmit Data Register */
+#define CD180_EOSRR 0x7f /* End of Service Request Register */
+
+/* Channel Registers */
+#define CD180_SRER 0x02 /* Service Request Enable Register */
+#define CD180_CCR 0x01 /* Channel Command Register */
+#define CD180_COR1 0x03 /* Channel Option Register 1 */
+#define CD180_COR2 0x04 /* Channel Option Register 2 */
+#define CD180_COR3 0x05 /* Channel Option Register 3 */
+#define CD180_CCSR 0x06 /* Channel Control Status Register */
+#define CD180_RTPR 0x18 /* Receive Timeout Period Register */
+#define CD180_RBPRH 0x31 /* Receive Bit Rate Period Register High */
+#define CD180_RBPRL 0x32 /* Receive Bit Rate Period Register Low */
+#define CD180_TBPRH 0x39 /* Transmit Bit Rate Period Register High */
+#define CD180_TBPRL 0x3a /* Transmit Bit Rate Period Register Low */
+#define CD180_SCHR1 0x09 /* Special Character Register 1 */
+#define CD180_SCHR2 0x0a /* Special Character Register 2 */
+#define CD180_SCHR3 0x0b /* Special Character Register 3 */
+#define CD180_SCHR4 0x0c /* Special Character Register 4 */
+#define CD180_MCR 0x12 /* Modem Change Register */
+#define CD180_MCOR1 0x10 /* Modem Change Option 1 Register */
+#define CD180_MCOR2 0x11 /* Modem Change Option 2 Register */
+#define CD180_MSVR 0x28 /* Modem Signal Value Register */
+#define CD180_MSVRTS 0x29 /* Modem Signal Value RTS */
+#define CD180_MSVDTR 0x2a /* Modem Signal Value DTR */
+
+/* Global Interrupt Vector Register (R/W) */
+
+#define GSVR_ITMASK 0x07 /* Interrupt type mask */
+#define GSVR_IT_MDM 0x01 /* Modem Signal Change Interrupt */
+#define GSVR_IT_TX 0x02 /* Transmit Data Interrupt */
+#define GSVR_IT_RGD 0x03 /* Receive Good Data Interrupt */
+#define GSVR_IT_REXC 0x07 /* Receive Exception Interrupt */
+
+
+/* Global Interrupt Channel Register (R/W) */
+
+#define GSCR_CHAN 0x1c /* Channel Number Mask */
+#define GSCR_CHAN_OFF 2 /* Channel Number Offset */
+
+
+/* Channel Address Register (R/W) */
+
+#define CAR_CHAN 0x07 /* Channel Number Mask */
+
+
+/* Receive Character Status Register (R/O) */
+
+#define RCSR_TOUT 0x80 /* Rx Timeout */
+#define RCSR_SCDET 0x70 /* Special Character Detected Mask */
+#define RCSR_NO_SC 0x00 /* No Special Characters Detected */
+#define RCSR_SC_1 0x10 /* Special Char 1 (or 1 & 3) Detected */
+#define RCSR_SC_2 0x20 /* Special Char 2 (or 2 & 4) Detected */
+#define RCSR_SC_3 0x30 /* Special Char 3 Detected */
+#define RCSR_SC_4 0x40 /* Special Char 4 Detected */
+#define RCSR_BREAK 0x08 /* Break has been detected */
+#define RCSR_PE 0x04 /* Parity Error */
+#define RCSR_FE 0x02 /* Frame Error */
+#define RCSR_OE 0x01 /* Overrun Error */
+
+
+/* Channel Command Register (R/W) (commands in groups can be OR-ed) */
+
+#define CCR_HARDRESET 0x81 /* Reset the chip */
+
+#define CCR_SOFTRESET 0x80 /* Soft Channel Reset */
+
+#define CCR_CORCHG1 0x42 /* Channel Option Register 1 Changed */
+#define CCR_CORCHG2 0x44 /* Channel Option Register 2 Changed */
+#define CCR_CORCHG3 0x48 /* Channel Option Register 3 Changed */
+
+#define CCR_SSCH1 0x21 /* Send Special Character 1 */
+
+#define CCR_SSCH2 0x22 /* Send Special Character 2 */
+
+#define CCR_SSCH3 0x23 /* Send Special Character 3 */
+
+#define CCR_SSCH4 0x24 /* Send Special Character 4 */
+
+#define CCR_TXEN 0x18 /* Enable Transmitter */
+#define CCR_RXEN 0x12 /* Enable Receiver */
+
+#define CCR_TXDIS 0x14 /* Disable Transmitter */
+#define CCR_RXDIS 0x11 /* Disable Receiver */
+
+
+/* Service Request Enable Register (R/W) */
+
+#define SRER_DSR 0x80 /* Enable interrupt on DSR change */
+#define SRER_CD 0x40 /* Enable interrupt on CD change */
+#define SRER_CTS 0x20 /* Enable interrupt on CTS change */
+#define SRER_RXD 0x10 /* Enable interrupt on Receive Data */
+#define SRER_RXSC 0x08 /* Enable interrupt on Receive Spec. Char */
+#define SRER_TXRDY 0x04 /* Enable interrupt on TX FIFO empty */
+#define SRER_TXEMPTY 0x02 /* Enable interrupt on TX completely empty */
+#define SRER_RET 0x01 /* Enable interrupt on RX Exc. Timeout */
+
+
+/* Channel Option Register 1 (R/W) */
+
+#define COR1_ODDP 0x80 /* Odd Parity */
+#define COR1_PARMODE 0x60 /* Parity Mode mask */
+#define COR1_NOPAR 0x00 /* No Parity */
+#define COR1_FORCEPAR 0x20 /* Force Parity */
+#define COR1_NORMPAR 0x40 /* Normal Parity */
+#define COR1_IGNORE 0x10 /* Ignore Parity on RX */
+#define COR1_STOPBITS 0x0c /* Number of Stop Bits */
+#define COR1_1SB 0x00 /* 1 Stop Bit */
+#define COR1_15SB 0x04 /* 1.5 Stop Bits */
+#define COR1_2SB 0x08 /* 2 Stop Bits */
+#define COR1_CHARLEN 0x03 /* Character Length */
+#define COR1_5BITS 0x00 /* 5 bits */
+#define COR1_6BITS 0x01 /* 6 bits */
+#define COR1_7BITS 0x02 /* 7 bits */
+#define COR1_8BITS 0x03 /* 8 bits */
+
+
+/* Channel Option Register 2 (R/W) */
+
+#define COR2_IXM 0x80 /* Implied XON mode */
+#define COR2_TXIBE 0x40 /* Enable In-Band (XON/XOFF) Flow Control */
+#define COR2_ETC 0x20 /* Embedded Tx Commands Enable */
+#define COR2_LLM 0x10 /* Local Loopback Mode */
+#define COR2_RLM 0x08 /* Remote Loopback Mode */
+#define COR2_RTSAO 0x04 /* RTS Automatic Output Enable */
+#define COR2_CTSAE 0x02 /* CTS Automatic Enable */
+#define COR2_DSRAE 0x01 /* DSR Automatic Enable */
+
+
+/* Channel Option Register 3 (R/W) */
+
+#define COR3_XONCH 0x80 /* XON is a pair of characters (1 & 3) */
+#define COR3_XOFFCH 0x40 /* XOFF is a pair of characters (2 & 4) */
+#define COR3_FCT 0x20 /* Flow-Control Transparency Mode */
+#define COR3_SCDE 0x10 /* Special Character Detection Enable */
+#define COR3_RXTH 0x0f /* RX FIFO Threshold value (1-8) */
+
+
+/* Channel Control Status Register (R/O) */
+
+#define CCSR_RXEN 0x80 /* Receiver Enabled */
+#define CCSR_RXFLOFF 0x40 /* Receive Flow Off (XOFF was sent) */
+#define CCSR_RXFLON 0x20 /* Receive Flow On (XON was sent) */
+#define CCSR_TXEN 0x08 /* Transmitter Enabled */
+#define CCSR_TXFLOFF 0x04 /* Transmit Flow Off (got XOFF) */
+#define CCSR_TXFLON 0x02 /* Transmit Flow On (got XON) */
+
+
+/* Modem Change Option Register 1 (R/W) */
+
+#define MCOR1_DSRZD 0x80 /* Detect 0->1 transition of DSR */
+#define MCOR1_CDZD 0x40 /* Detect 0->1 transition of CD */
+#define MCOR1_CTSZD 0x20 /* Detect 0->1 transition of CTS */
+#define MCOR1_DTRTH 0x0f /* Auto DTR flow control Threshold (1-8) */
+#define MCOR1_NODTRFC 0x0 /* Automatic DTR flow control disabled */
+
+
+/* Modem Change Option Register 2 (R/W) */
+
+#define MCOR2_DSROD 0x80 /* Detect 1->0 transition of DSR */
+#define MCOR2_CDOD 0x40 /* Detect 1->0 transition of CD */
+#define MCOR2_CTSOD 0x20 /* Detect 1->0 transition of CTS */
+
+
+/* Modem Change Register (R/W) */
+
+#define MCR_DSRCHG 0x80 /* DSR Changed */
+#define MCR_CDCHG 0x40 /* CD Changed */
+#define MCR_CTSCHG 0x20 /* CTS Changed */
+
+
+/* Modem Signal Value Register (R/W) */
+
+#define MSVR_DSR 0x80 /* Current state of DSR input */
+#define MSVR_CD 0x40 /* Current state of CD input */
+#define MSVR_CTS 0x20 /* Current state of CTS input */
+#define MSVR_DTR 0x02 /* Current state of DTR output */
+#define MSVR_RTS 0x01 /* Current state of RTS output */
+
+
+/* Service Request Status Register */
+
+#define SRSR_CMASK 0xC0 /* Current Service Context Mask */
+#define SRSR_CNONE 0x00 /* Not in a service context */
+#define SRSR_CRX 0x40 /* Rx Context */
+#define SRSR_CTX 0x80 /* Tx Context */
+#define SRSR_CMDM 0xC0 /* Modem Context */
+#define SRSR_ANYINT 0x6F /* Any interrupt flag */
+#define SRSR_RINT 0x10 /* Receive Interrupt */
+#define SRSR_TINT 0x04 /* Transmit Interrupt */
+#define SRSR_MINT 0x01 /* Modem Interrupt */
+#define SRSR_REXT 0x20 /* Receive External Interrupt */
+#define SRSR_TEXT 0x08 /* Transmit External Interrupt */
+#define SRSR_MEXT 0x02 /* Modem External Interrupt */
+
+
+/* Service Request Configuration Register */
+
+#define SRCR_PKGTYPE 0x80
+#define SRCR_REGACKEN 0x40
+#define SRCR_DAISYEN 0x20
+#define SRCR_GLOBPRI 0x10
+#define SRCR_UNFAIR 0x08
+#define SRCR_AUTOPRI 0x02
+#define SRCR_PRISEL 0x01
+
+/* Values for register-based Interrupt ACKs */
+#define CD180_ACK_MINT 0x75 /* goes to MSMR */
+#define CD180_ACK_TINT 0x76 /* goes to TSMR */
+#define CD180_ACK_RINT 0x77 /* goes to RSMR */
+
+/* Escape characters */
+
+#define CD180_C_ESC 0x00 /* Escape character */
+#define CD180_C_SBRK 0x81 /* Start sending BREAK */
+#define CD180_C_DELAY 0x82 /* Delay output */
+#define CD180_C_EBRK 0x83 /* Stop sending BREAK */
diff --git a/drivers/sbus/char/cpwatchdog.c b/drivers/sbus/char/cpwatchdog.c
new file mode 100644
index 00000000000..c82abeb59d3
--- /dev/null
+++ b/drivers/sbus/char/cpwatchdog.c
@@ -0,0 +1,832 @@
+/* cpwatchdog.c - driver implementation for hardware watchdog
+ * timers found on Sun Microsystems CP1400 and CP1500 boards.
+ *
+ * This device supports both the generic Linux watchdog
+ * interface and Solaris-compatible ioctls as best it is
+ * able.
+ *
+ * NOTE: CP1400 systems appear to have a defective intr_mask
+ * register on the PLD, preventing the disabling of
+ * timer interrupts. We use a timer to periodically
+ * reset 'stopped' watchdogs on affected platforms.
+ *
+ * TODO: DevFS support (/dev/watchdogs/0 ... /dev/watchdogs/2)
+ *
+ * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <asm/irq.h>
+#include <asm/ebus.h>
+#include <asm/oplib.h>
+#include <asm/uaccess.h>
+
+#include <asm/watchdog.h>
+
+#define WD_OBPNAME "watchdog"
+#define WD_BADMODEL "SUNW,501-5336"
+#define WD_BTIMEOUT (jiffies + (HZ * 1000))
+#define WD_BLIMIT 0xFFFF
+
+#define WD0_DEVNAME "watchdog0"
+#define WD1_DEVNAME "watchdog1"
+#define WD2_DEVNAME "watchdog2"
+
+#define WD0_MINOR 212
+#define WD1_MINOR 213
+#define WD2_MINOR 214
+
+
+/* Internal driver definitions
+ */
+#define WD0_ID 0 /* Watchdog0 */
+#define WD1_ID 1 /* Watchdog1 */
+#define WD2_ID 2 /* Watchdog2 */
+#define WD_NUMDEVS 3 /* Device contains 3 timers */
+
+#define WD_INTR_OFF 0 /* Interrupt disable value */
+#define WD_INTR_ON 1 /* Interrupt enable value */
+
+#define WD_STAT_INIT 0x01 /* Watchdog timer is initialized */
+#define WD_STAT_BSTOP 0x02 /* Watchdog timer is brokenstopped */
+#define WD_STAT_SVCD 0x04 /* Watchdog interrupt occurred */
+
+/* Register value definitions
+ */
+#define WD0_INTR_MASK 0x01 /* Watchdog device interrupt masks */
+#define WD1_INTR_MASK 0x02
+#define WD2_INTR_MASK 0x04
+
+#define WD_S_RUNNING 0x01 /* Watchdog device status running */
+#define WD_S_EXPIRED 0x02 /* Watchdog device status expired */
+
+/* Sun uses Altera PLD EPF8820ATC144-4
+ * providing three hardware watchdogs:
+ *
+ * 1) RIC - sends an interrupt when triggered
+ * 2) XIR - asserts XIR_B_RESET when triggered, resets CPU
+ * 3) POR - asserts POR_B_RESET when triggered, resets CPU, backplane, board
+ *
+ *** Timer register block definition (struct wd_timer_regblk)
+ *
+ * dcntr and limit registers (halfword access):
+ * -------------------
+ * | 15 | ...| 1 | 0 |
+ * -------------------
+ * |- counter val -|
+ * -------------------
+ * dcntr - Current 16-bit downcounter value.
+ * When downcounter reaches '0' watchdog expires.
+ * Reading this register resets downcounter with 'limit' value.
+ * limit - 16-bit countdown value in 1/10th second increments.
+ * Writing this register begins countdown with input value.
+ * Reading from this register does not affect counter.
+ * NOTES: After watchdog reset, dcntr and limit contain '1'
+ *
+ * status register (byte access):
+ * ---------------------------
+ * | 7 | ... | 2 | 1 | 0 |
+ * --------------+------------
+ * |- UNUSED -| EXP | RUN |
+ * ---------------------------
+ * status- Bit 0 - Watchdog is running
+ * Bit 1 - Watchdog has expired
+ *
+ *** PLD register block definition (struct wd_pld_regblk)
+ *
+ * intr_mask register (byte access):
+ * ---------------------------------
+ * | 7 | ... | 3 | 2 | 1 | 0 |
+ * +-------------+------------------
+ * |- UNUSED -| WD3 | WD2 | WD1 |
+ * ---------------------------------
+ * WD3 - 1 == Interrupt disabled for watchdog 3
+ * WD2 - 1 == Interrupt disabled for watchdog 2
+ * WD1 - 1 == Interrupt disabled for watchdog 1
+ *
+ * pld_status register (byte access):
+ * UNKNOWN, MAGICAL MYSTERY REGISTER
+ *
+ */
+#define WD_TIMER_REGSZ 16
+#define WD0_OFF 0
+#define WD1_OFF (WD_TIMER_REGSZ * 1)
+#define WD2_OFF (WD_TIMER_REGSZ * 2)
+#define PLD_OFF (WD_TIMER_REGSZ * 3)
+
+#define WD_DCNTR 0x00
+#define WD_LIMIT 0x04
+#define WD_STATUS 0x08
+
+#define PLD_IMASK (PLD_OFF + 0x00)
+#define PLD_STATUS (PLD_OFF + 0x04)
+
+/* Individual timer structure
+ */
+struct wd_timer {
+ __u16 timeout;
+ __u8 intr_mask;
+ unsigned char runstatus;
+ void __iomem *regs;
+};
+
+/* Device structure
+ */
+struct wd_device {
+ int irq;
+ spinlock_t lock;
+ unsigned char isbaddoggie; /* defective PLD */
+ unsigned char opt_enable;
+ unsigned char opt_reboot;
+ unsigned short opt_timeout;
+ unsigned char initialized;
+ struct wd_timer watchdog[WD_NUMDEVS];
+ void __iomem *regs;
+};
+
+static struct wd_device wd_dev = {
+ 0, SPIN_LOCK_UNLOCKED, 0, 0, 0, 0,
+};
+
+static struct timer_list wd_timer;
+
+static int wd0_timeout = 0;
+static int wd1_timeout = 0;
+static int wd2_timeout = 0;
+
+#ifdef MODULE
+module_param (wd0_timeout, int, 0);
+MODULE_PARM_DESC(wd0_timeout, "Default watchdog0 timeout in 1/10secs");
+module_param (wd1_timeout, int, 0);
+MODULE_PARM_DESC(wd1_timeout, "Default watchdog1 timeout in 1/10secs");
+module_param (wd2_timeout, int, 0);
+MODULE_PARM_DESC(wd2_timeout, "Default watchdog2 timeout in 1/10secs");
+
+MODULE_AUTHOR
+ ("Eric Brower <ebrower@usa.net>");
+MODULE_DESCRIPTION
+ ("Hardware watchdog driver for Sun Microsystems CP1400/1500");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE
+ ("watchdog");
+#endif /* ifdef MODULE */
+
+/* Forward declarations of internal methods
+ */
+#ifdef WD_DEBUG
+static void wd_dumpregs(void);
+#endif
+static irqreturn_t wd_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static void wd_toggleintr(struct wd_timer* pTimer, int enable);
+static void wd_pingtimer(struct wd_timer* pTimer);
+static void wd_starttimer(struct wd_timer* pTimer);
+static void wd_resetbrokentimer(struct wd_timer* pTimer);
+static void wd_stoptimer(struct wd_timer* pTimer);
+static void wd_brokentimer(unsigned long data);
+static int wd_getstatus(struct wd_timer* pTimer);
+
+/* PLD expects words to be written in LSB format,
+ * so we must flip all words prior to writing them to regs
+ */
+static inline unsigned short flip_word(unsigned short word)
+{
+ return ((word & 0xff) << 8) | ((word >> 8) & 0xff);
+}
+
+#define wd_writew(val, addr) (writew(flip_word(val), addr))
+#define wd_readw(addr) (flip_word(readw(addr)))
+#define wd_writeb(val, addr) (writeb(val, addr))
+#define wd_readb(addr) (readb(addr))
+
+
+/* CP1400s seem to have broken PLD implementations--
+ * the interrupt_mask register cannot be written, so
+ * no timer interrupts can be masked within the PLD.
+ */
+static inline int wd_isbroken(void)
+{
+ /* we could test this by read/write/read/restore
+ * on the interrupt mask register only if OBP
+ * 'watchdog-enable?' == FALSE, but it seems
+ * ubiquitous on CP1400s
+ */
+ char val[32];
+ prom_getproperty(prom_root_node, "model", val, sizeof(val));
+ return((!strcmp(val, WD_BADMODEL)) ? 1 : 0);
+}
+
+/* Retrieve watchdog-enable? option from OBP
+ * Returns 0 if false, 1 if true
+ */
+static inline int wd_opt_enable(void)
+{
+ int opt_node;
+
+ opt_node = prom_getchild(prom_root_node);
+ opt_node = prom_searchsiblings(opt_node, "options");
+ return((-1 == prom_getint(opt_node, "watchdog-enable?")) ? 0 : 1);
+}
+
+/* Retrieve watchdog-reboot? option from OBP
+ * Returns 0 if false, 1 if true
+ */
+static inline int wd_opt_reboot(void)
+{
+ int opt_node;
+
+ opt_node = prom_getchild(prom_root_node);
+ opt_node = prom_searchsiblings(opt_node, "options");
+ return((-1 == prom_getint(opt_node, "watchdog-reboot?")) ? 0 : 1);
+}
+
+/* Retrieve watchdog-timeout option from OBP
+ * Returns OBP value, or 0 if not located
+ */
+static inline int wd_opt_timeout(void)
+{
+ int opt_node;
+ char value[32];
+ char *p = value;
+
+ opt_node = prom_getchild(prom_root_node);
+ opt_node = prom_searchsiblings(opt_node, "options");
+ opt_node = prom_getproperty(opt_node,
+ "watchdog-timeout",
+ value,
+ sizeof(value));
+ if(-1 != opt_node) {
+ /* atoi implementation */
+ for(opt_node = 0; /* nop */; p++) {
+ if(*p >= '0' && *p <= '9') {
+ opt_node = (10*opt_node)+(*p-'0');
+ }
+ else {
+ break;
+ }
+ }
+ }
+ return((-1 == opt_node) ? (0) : (opt_node));
+}
+
+static int wd_open(struct inode *inode, struct file *f)
+{
+ switch(iminor(inode))
+ {
+ case WD0_MINOR:
+ f->private_data = &wd_dev.watchdog[WD0_ID];
+ break;
+ case WD1_MINOR:
+ f->private_data = &wd_dev.watchdog[WD1_ID];
+ break;
+ case WD2_MINOR:
+ f->private_data = &wd_dev.watchdog[WD2_ID];
+ break;
+ default:
+ return(-ENODEV);
+ }
+
+ /* Register IRQ on first open of device */
+ if(0 == wd_dev.initialized)
+ {
+ if (request_irq(wd_dev.irq,
+ &wd_interrupt,
+ SA_SHIRQ,
+ WD_OBPNAME,
+ (void *)wd_dev.regs)) {
+ printk("%s: Cannot register IRQ %s\n",
+ WD_OBPNAME, __irq_itoa(wd_dev.irq));
+ return(-EBUSY);
+ }
+ wd_dev.initialized = 1;
+ }
+
+ return(nonseekable_open(inode, f));
+}
+
+static int wd_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int wd_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int setopt = 0;
+ struct wd_timer* pTimer = (struct wd_timer*)file->private_data;
+ void __user *argp = (void __user *)arg;
+ struct watchdog_info info = {
+ 0,
+ 0,
+ "Altera EPF8820ATC144-4"
+ };
+
+ if(NULL == pTimer) {
+ return(-EINVAL);
+ }
+
+ switch(cmd)
+ {
+ /* Generic Linux IOCTLs */
+ case WDIOC_GETSUPPORT:
+ if(copy_to_user(argp, &info, sizeof(struct watchdog_info))) {
+ return(-EFAULT);
+ }
+ break;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(0, (int __user *)argp))
+ return -EFAULT;
+ break;
+ case WDIOC_KEEPALIVE:
+ wd_pingtimer(pTimer);
+ break;
+ case WDIOC_SETOPTIONS:
+ if(copy_from_user(&setopt, argp, sizeof(unsigned int))) {
+ return -EFAULT;
+ }
+ if(setopt & WDIOS_DISABLECARD) {
+ if(wd_dev.opt_enable) {
+ printk(
+ "%s: cannot disable watchdog in ENABLED mode\n",
+ WD_OBPNAME);
+ return(-EINVAL);
+ }
+ wd_stoptimer(pTimer);
+ }
+ else if(setopt & WDIOS_ENABLECARD) {
+ wd_starttimer(pTimer);
+ }
+ else {
+ return(-EINVAL);
+ }
+ break;
+ /* Solaris-compatible IOCTLs */
+ case WIOCGSTAT:
+ setopt = wd_getstatus(pTimer);
+ if(copy_to_user(argp, &setopt, sizeof(unsigned int))) {
+ return(-EFAULT);
+ }
+ break;
+ case WIOCSTART:
+ wd_starttimer(pTimer);
+ break;
+ case WIOCSTOP:
+ if(wd_dev.opt_enable) {
+ printk("%s: cannot disable watchdog in ENABLED mode\n",
+ WD_OBPNAME);
+ return(-EINVAL);
+ }
+ wd_stoptimer(pTimer);
+ break;
+ default:
+ return(-EINVAL);
+ }
+ return(0);
+}
+
+static ssize_t wd_write(struct file *file,
+ const char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ struct wd_timer* pTimer = (struct wd_timer*)file->private_data;
+
+ if(NULL == pTimer) {
+ return(-EINVAL);
+ }
+
+ if (count) {
+ wd_pingtimer(pTimer);
+ return 1;
+ }
+ return 0;
+}
+
+static ssize_t wd_read(struct file * file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+#ifdef WD_DEBUG
+ wd_dumpregs();
+ return(0);
+#else
+ return(-EINVAL);
+#endif /* ifdef WD_DEBUG */
+}
+
+static irqreturn_t wd_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ /* Only WD0 will interrupt-- others are NMI and we won't
+ * see them here....
+ */
+ spin_lock_irq(&wd_dev.lock);
+ if((unsigned long)wd_dev.regs == (unsigned long)dev_id)
+ {
+ wd_stoptimer(&wd_dev.watchdog[WD0_ID]);
+ wd_dev.watchdog[WD0_ID].runstatus |= WD_STAT_SVCD;
+ }
+ spin_unlock_irq(&wd_dev.lock);
+ return IRQ_HANDLED;
+}
+
+static struct file_operations wd_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = wd_ioctl,
+ .open = wd_open,
+ .write = wd_write,
+ .read = wd_read,
+ .release = wd_release,
+};
+
+static struct miscdevice wd0_miscdev = { WD0_MINOR, WD0_DEVNAME, &wd_fops };
+static struct miscdevice wd1_miscdev = { WD1_MINOR, WD1_DEVNAME, &wd_fops };
+static struct miscdevice wd2_miscdev = { WD2_MINOR, WD2_DEVNAME, &wd_fops };
+
+#ifdef WD_DEBUG
+static void wd_dumpregs(void)
+{
+ /* Reading from downcounters initiates watchdog countdown--
+ * Example is included below for illustration purposes.
+ */
+ int i;
+ printk("%s: dumping register values\n", WD_OBPNAME);
+ for(i = WD0_ID; i < WD_NUMDEVS; ++i) {
+ /* printk("\t%s%i: dcntr at 0x%lx: 0x%x\n",
+ * WD_OBPNAME,
+ * i,
+ * (unsigned long)(&wd_dev.watchdog[i].regs->dcntr),
+ * readw(&wd_dev.watchdog[i].regs->dcntr));
+ */
+ printk("\t%s%i: limit at 0x%lx: 0x%x\n",
+ WD_OBPNAME,
+ i,
+ (unsigned long)(&wd_dev.watchdog[i].regs->limit),
+ readw(&wd_dev.watchdog[i].regs->limit));
+ printk("\t%s%i: status at 0x%lx: 0x%x\n",
+ WD_OBPNAME,
+ i,
+ (unsigned long)(&wd_dev.watchdog[i].regs->status),
+ readb(&wd_dev.watchdog[i].regs->status));
+ printk("\t%s%i: driver status: 0x%x\n",
+ WD_OBPNAME,
+ i,
+ wd_getstatus(&wd_dev.watchdog[i]));
+ }
+ printk("\tintr_mask at %p: 0x%x\n",
+ wd_dev.regs + PLD_IMASK,
+ readb(wd_dev.regs + PLD_IMASK));
+ printk("\tpld_status at %p: 0x%x\n",
+ wd_dev.regs + PLD_STATUS,
+ readb(wd_dev.regs + PLD_STATUS));
+}
+#endif
+
+/* Enable or disable watchdog interrupts
+ * Because of the CP1400 defect this should only be
+ * called during initialzation or by wd_[start|stop]timer()
+ *
+ * pTimer - pointer to timer device, or NULL to indicate all timers
+ * enable - non-zero to enable interrupts, zero to disable
+ */
+static void wd_toggleintr(struct wd_timer* pTimer, int enable)
+{
+ unsigned char curregs = wd_readb(wd_dev.regs + PLD_IMASK);
+ unsigned char setregs =
+ (NULL == pTimer) ?
+ (WD0_INTR_MASK | WD1_INTR_MASK | WD2_INTR_MASK) :
+ (pTimer->intr_mask);
+
+ (WD_INTR_ON == enable) ?
+ (curregs &= ~setregs):
+ (curregs |= setregs);
+
+ wd_writeb(curregs, wd_dev.regs + PLD_IMASK);
+ return;
+}
+
+/* Reset countdown timer with 'limit' value and continue countdown.
+ * This will not start a stopped timer.
+ *
+ * pTimer - pointer to timer device
+ */
+static void wd_pingtimer(struct wd_timer* pTimer)
+{
+ if (wd_readb(pTimer->regs + WD_STATUS) & WD_S_RUNNING) {
+ wd_readw(pTimer->regs + WD_DCNTR);
+ }
+}
+
+/* Stop a running watchdog timer-- the timer actually keeps
+ * running, but the interrupt is masked so that no action is
+ * taken upon expiration.
+ *
+ * pTimer - pointer to timer device
+ */
+static void wd_stoptimer(struct wd_timer* pTimer)
+{
+ if(wd_readb(pTimer->regs + WD_STATUS) & WD_S_RUNNING) {
+ wd_toggleintr(pTimer, WD_INTR_OFF);
+
+ if(wd_dev.isbaddoggie) {
+ pTimer->runstatus |= WD_STAT_BSTOP;
+ wd_brokentimer((unsigned long)&wd_dev);
+ }
+ }
+}
+
+/* Start a watchdog timer with the specified limit value
+ * If the watchdog is running, it will be restarted with
+ * the provided limit value.
+ *
+ * This function will enable interrupts on the specified
+ * watchdog.
+ *
+ * pTimer - pointer to timer device
+ * limit - limit (countdown) value in 1/10th seconds
+ */
+static void wd_starttimer(struct wd_timer* pTimer)
+{
+ if(wd_dev.isbaddoggie) {
+ pTimer->runstatus &= ~WD_STAT_BSTOP;
+ }
+ pTimer->runstatus &= ~WD_STAT_SVCD;
+
+ wd_writew(pTimer->timeout, pTimer->regs + WD_LIMIT);
+ wd_toggleintr(pTimer, WD_INTR_ON);
+}
+
+/* Restarts timer with maximum limit value and
+ * does not unset 'brokenstop' value.
+ */
+static void wd_resetbrokentimer(struct wd_timer* pTimer)
+{
+ wd_toggleintr(pTimer, WD_INTR_ON);
+ wd_writew(WD_BLIMIT, pTimer->regs + WD_LIMIT);
+}
+
+/* Timer device initialization helper.
+ * Returns 0 on success, other on failure
+ */
+static int wd_inittimer(int whichdog)
+{
+ struct miscdevice *whichmisc;
+ void __iomem *whichregs;
+ char whichident[8];
+ int whichmask;
+ __u16 whichlimit;
+
+ switch(whichdog)
+ {
+ case WD0_ID:
+ whichmisc = &wd0_miscdev;
+ strcpy(whichident, "RIC");
+ whichregs = wd_dev.regs + WD0_OFF;
+ whichmask = WD0_INTR_MASK;
+ whichlimit= (0 == wd0_timeout) ?
+ (wd_dev.opt_timeout):
+ (wd0_timeout);
+ break;
+ case WD1_ID:
+ whichmisc = &wd1_miscdev;
+ strcpy(whichident, "XIR");
+ whichregs = wd_dev.regs + WD1_OFF;
+ whichmask = WD1_INTR_MASK;
+ whichlimit= (0 == wd1_timeout) ?
+ (wd_dev.opt_timeout):
+ (wd1_timeout);
+ break;
+ case WD2_ID:
+ whichmisc = &wd2_miscdev;
+ strcpy(whichident, "POR");
+ whichregs = wd_dev.regs + WD2_OFF;
+ whichmask = WD2_INTR_MASK;
+ whichlimit= (0 == wd2_timeout) ?
+ (wd_dev.opt_timeout):
+ (wd2_timeout);
+ break;
+ default:
+ printk("%s: %s: invalid watchdog id: %i\n",
+ WD_OBPNAME, __FUNCTION__, whichdog);
+ return(1);
+ }
+ if(0 != misc_register(whichmisc))
+ {
+ return(1);
+ }
+ wd_dev.watchdog[whichdog].regs = whichregs;
+ wd_dev.watchdog[whichdog].timeout = whichlimit;
+ wd_dev.watchdog[whichdog].intr_mask = whichmask;
+ wd_dev.watchdog[whichdog].runstatus &= ~WD_STAT_BSTOP;
+ wd_dev.watchdog[whichdog].runstatus |= WD_STAT_INIT;
+
+ printk("%s%i: %s hardware watchdog [%01i.%i sec] %s\n",
+ WD_OBPNAME,
+ whichdog,
+ whichident,
+ wd_dev.watchdog[whichdog].timeout / 10,
+ wd_dev.watchdog[whichdog].timeout % 10,
+ (0 != wd_dev.opt_enable) ? "in ENABLED mode" : "");
+ return(0);
+}
+
+/* Timer method called to reset stopped watchdogs--
+ * because of the PLD bug on CP1400, we cannot mask
+ * interrupts within the PLD so me must continually
+ * reset the timers ad infinitum.
+ */
+static void wd_brokentimer(unsigned long data)
+{
+ struct wd_device* pDev = (struct wd_device*)data;
+ int id, tripped = 0;
+
+ /* kill a running timer instance, in case we
+ * were called directly instead of by kernel timer
+ */
+ if(timer_pending(&wd_timer)) {
+ del_timer(&wd_timer);
+ }
+
+ for(id = WD0_ID; id < WD_NUMDEVS; ++id) {
+ if(pDev->watchdog[id].runstatus & WD_STAT_BSTOP) {
+ ++tripped;
+ wd_resetbrokentimer(&pDev->watchdog[id]);
+ }
+ }
+
+ if(tripped) {
+ /* there is at least one timer brokenstopped-- reschedule */
+ init_timer(&wd_timer);
+ wd_timer.expires = WD_BTIMEOUT;
+ add_timer(&wd_timer);
+ }
+}
+
+static int wd_getstatus(struct wd_timer* pTimer)
+{
+ unsigned char stat = wd_readb(pTimer->regs + WD_STATUS);
+ unsigned char intr = wd_readb(wd_dev.regs + PLD_IMASK);
+ unsigned char ret = WD_STOPPED;
+
+ /* determine STOPPED */
+ if(0 == stat ) {
+ return(ret);
+ }
+ /* determine EXPIRED vs FREERUN vs RUNNING */
+ else if(WD_S_EXPIRED & stat) {
+ ret = WD_EXPIRED;
+ }
+ else if(WD_S_RUNNING & stat) {
+ if(intr & pTimer->intr_mask) {
+ ret = WD_FREERUN;
+ }
+ else {
+ /* Fudge WD_EXPIRED status for defective CP1400--
+ * IF timer is running
+ * AND brokenstop is set
+ * AND an interrupt has been serviced
+ * we are WD_EXPIRED.
+ *
+ * IF timer is running
+ * AND brokenstop is set
+ * AND no interrupt has been serviced
+ * we are WD_FREERUN.
+ */
+ if(wd_dev.isbaddoggie && (pTimer->runstatus & WD_STAT_BSTOP)) {
+ if(pTimer->runstatus & WD_STAT_SVCD) {
+ ret = WD_EXPIRED;
+ }
+ else {
+ /* we could as well pretend we are expired */
+ ret = WD_FREERUN;
+ }
+ }
+ else {
+ ret = WD_RUNNING;
+ }
+ }
+ }
+
+ /* determine SERVICED */
+ if(pTimer->runstatus & WD_STAT_SVCD) {
+ ret |= WD_SERVICED;
+ }
+
+ return(ret);
+}
+
+static int __init wd_init(void)
+{
+ int id;
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, WD_OBPNAME))
+ goto ebus_done;
+ }
+ }
+
+ebus_done:
+ if(!edev) {
+ printk("%s: unable to locate device\n", WD_OBPNAME);
+ return -ENODEV;
+ }
+
+ wd_dev.regs =
+ ioremap(edev->resource[0].start, 4 * WD_TIMER_REGSZ); /* ? */
+
+ if(NULL == wd_dev.regs) {
+ printk("%s: unable to map registers\n", WD_OBPNAME);
+ return(-ENODEV);
+ }
+
+ /* initialize device structure from OBP parameters */
+ wd_dev.irq = edev->irqs[0];
+ wd_dev.opt_enable = wd_opt_enable();
+ wd_dev.opt_reboot = wd_opt_reboot();
+ wd_dev.opt_timeout = wd_opt_timeout();
+ wd_dev.isbaddoggie = wd_isbroken();
+
+ /* disable all interrupts unless watchdog-enabled? == true */
+ if(! wd_dev.opt_enable) {
+ wd_toggleintr(NULL, WD_INTR_OFF);
+ }
+
+ /* register miscellaneous devices */
+ for(id = WD0_ID; id < WD_NUMDEVS; ++id) {
+ if(0 != wd_inittimer(id)) {
+ printk("%s%i: unable to initialize\n", WD_OBPNAME, id);
+ }
+ }
+
+ /* warn about possible defective PLD */
+ if(wd_dev.isbaddoggie) {
+ init_timer(&wd_timer);
+ wd_timer.function = wd_brokentimer;
+ wd_timer.data = (unsigned long)&wd_dev;
+ wd_timer.expires = WD_BTIMEOUT;
+
+ printk("%s: PLD defect workaround enabled for model %s\n",
+ WD_OBPNAME, WD_BADMODEL);
+ }
+ return(0);
+}
+
+static void __exit wd_cleanup(void)
+{
+ int id;
+
+ /* if 'watchdog-enable?' == TRUE, timers are not stopped
+ * when module is unloaded. All brokenstopped timers will
+ * also now eventually trip.
+ */
+ for(id = WD0_ID; id < WD_NUMDEVS; ++id) {
+ if(WD_S_RUNNING == wd_readb(wd_dev.watchdog[id].regs + WD_STATUS)) {
+ if(wd_dev.opt_enable) {
+ printk(KERN_WARNING "%s%i: timer not stopped at release\n",
+ WD_OBPNAME, id);
+ }
+ else {
+ wd_stoptimer(&wd_dev.watchdog[id]);
+ if(wd_dev.watchdog[id].runstatus & WD_STAT_BSTOP) {
+ wd_resetbrokentimer(&wd_dev.watchdog[id]);
+ printk(KERN_WARNING
+ "%s%i: defect workaround disabled at release, "\
+ "timer expires in ~%01i sec\n",
+ WD_OBPNAME, id,
+ wd_readw(wd_dev.watchdog[id].regs + WD_LIMIT) / 10);
+ }
+ }
+ }
+ }
+
+ if(wd_dev.isbaddoggie && timer_pending(&wd_timer)) {
+ del_timer(&wd_timer);
+ }
+ if(0 != (wd_dev.watchdog[WD0_ID].runstatus & WD_STAT_INIT)) {
+ misc_deregister(&wd0_miscdev);
+ }
+ if(0 != (wd_dev.watchdog[WD1_ID].runstatus & WD_STAT_INIT)) {
+ misc_deregister(&wd1_miscdev);
+ }
+ if(0 != (wd_dev.watchdog[WD2_ID].runstatus & WD_STAT_INIT)) {
+ misc_deregister(&wd2_miscdev);
+ }
+ if(0 != wd_dev.initialized) {
+ free_irq(wd_dev.irq, (void *)wd_dev.regs);
+ }
+ iounmap(wd_dev.regs);
+}
+
+module_init(wd_init);
+module_exit(wd_cleanup);
diff --git a/drivers/sbus/char/display7seg.c b/drivers/sbus/char/display7seg.c
new file mode 100644
index 00000000000..dbad7f35eb0
--- /dev/null
+++ b/drivers/sbus/char/display7seg.c
@@ -0,0 +1,234 @@
+/* $Id: display7seg.c,v 1.6 2002/01/08 16:00:16 davem Exp $
+ *
+ * display7seg - Driver implementation for the 7-segment display
+ * present on Sun Microsystems CP1400 and CP1500
+ *
+ * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h> /* request_region, check_region */
+#include <asm/atomic.h>
+#include <asm/ebus.h> /* EBus device */
+#include <asm/oplib.h> /* OpenProm Library */
+#include <asm/uaccess.h> /* put_/get_user */
+
+#include <asm/display7seg.h>
+
+#define D7S_MINOR 193
+#define D7S_OBPNAME "display7seg"
+#define D7S_DEVNAME "d7s"
+
+static int sol_compat = 0; /* Solaris compatibility mode */
+
+#ifdef MODULE
+
+/* Solaris compatibility flag -
+ * The Solaris implementation omits support for several
+ * documented driver features (ref Sun doc 806-0180-03).
+ * By default, this module supports the documented driver
+ * abilities, rather than the Solaris implementation:
+ *
+ * 1) Device ALWAYS reverts to OBP-specified FLIPPED mode
+ * upon closure of device or module unload.
+ * 2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of
+ * FLIP bit
+ *
+ * If you wish the device to operate as under Solaris,
+ * omitting above features, set this parameter to non-zero.
+ */
+module_param
+ (sol_compat, int, 0);
+MODULE_PARM_DESC
+ (sol_compat,
+ "Disables documented functionality omitted from Solaris driver");
+
+MODULE_AUTHOR
+ ("Eric Brower <ebrower@usa.net>");
+MODULE_DESCRIPTION
+ ("7-Segment Display driver for Sun Microsystems CP1400/1500");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE
+ ("d7s");
+#endif /* ifdef MODULE */
+
+/*
+ * Register block address- see header for details
+ * -----------------------------------------
+ * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 |
+ * -----------------------------------------
+ *
+ * DP - Toggles decimal point on/off
+ * ALARM - Toggles "Alarm" LED green/red
+ * FLIP - Inverts display for upside-down mounted board
+ * bits 0-4 - 7-segment display contents
+ */
+static void __iomem* d7s_regs;
+
+static inline void d7s_free(void)
+{
+ iounmap(d7s_regs);
+}
+
+static inline int d7s_obpflipped(void)
+{
+ int opt_node;
+
+ opt_node = prom_getchild(prom_root_node);
+ opt_node = prom_searchsiblings(opt_node, "options");
+ return ((-1 != prom_getintdefault(opt_node, "d7s-flipped?", -1)) ? 0 : 1);
+}
+
+static atomic_t d7s_users = ATOMIC_INIT(0);
+
+static int d7s_open(struct inode *inode, struct file *f)
+{
+ if (D7S_MINOR != iminor(inode))
+ return -ENODEV;
+ atomic_inc(&d7s_users);
+ return 0;
+}
+
+static int d7s_release(struct inode *inode, struct file *f)
+{
+ /* Reset flipped state to OBP default only if
+ * no other users have the device open and we
+ * are not operating in solaris-compat mode
+ */
+ if (atomic_dec_and_test(&d7s_users) && !sol_compat) {
+ int regval = 0;
+
+ regval = readb(d7s_regs);
+ (0 == d7s_obpflipped()) ?
+ writeb(regval |= D7S_FLIP, d7s_regs):
+ writeb(regval &= ~D7S_FLIP, d7s_regs);
+ }
+
+ return 0;
+}
+
+static int d7s_ioctl(struct inode *inode, struct file *f,
+ unsigned int cmd, unsigned long arg)
+{
+ __u8 regs = readb(d7s_regs);
+ __u8 ireg = 0;
+
+ if (D7S_MINOR != iminor(inode))
+ return -ENODEV;
+
+ switch (cmd) {
+ case D7SIOCWR:
+ /* assign device register values
+ * we mask-out D7S_FLIP if in sol_compat mode
+ */
+ if (get_user(ireg, (int __user *) arg))
+ return -EFAULT;
+ if (0 != sol_compat) {
+ (regs & D7S_FLIP) ?
+ (ireg |= D7S_FLIP) : (ireg &= ~D7S_FLIP);
+ }
+ writeb(ireg, d7s_regs);
+ break;
+
+ case D7SIOCRD:
+ /* retrieve device register values
+ * NOTE: Solaris implementation returns D7S_FLIP bit
+ * as toggled by user, even though it does not honor it.
+ * This driver will not misinform you about the state
+ * of your hardware while in sol_compat mode
+ */
+ if (put_user(regs, (int __user *) arg))
+ return -EFAULT;
+ break;
+
+ case D7SIOCTM:
+ /* toggle device mode-- flip display orientation */
+ (regs & D7S_FLIP) ?
+ (regs &= ~D7S_FLIP) : (regs |= D7S_FLIP);
+ writeb(regs, d7s_regs);
+ break;
+ };
+
+ return 0;
+}
+
+static struct file_operations d7s_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = d7s_ioctl,
+ .open = d7s_open,
+ .release = d7s_release,
+};
+
+static struct miscdevice d7s_miscdev = { D7S_MINOR, D7S_DEVNAME, &d7s_fops };
+
+static int __init d7s_init(void)
+{
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+ int iTmp = 0, regs = 0;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, D7S_OBPNAME))
+ goto ebus_done;
+ }
+ }
+
+ebus_done:
+ if(!edev) {
+ printk("%s: unable to locate device\n", D7S_DEVNAME);
+ return -ENODEV;
+ }
+
+ d7s_regs = ioremap(edev->resource[0].start, sizeof(__u8));
+
+ iTmp = misc_register(&d7s_miscdev);
+ if (0 != iTmp) {
+ printk("%s: unable to acquire miscdevice minor %i\n",
+ D7S_DEVNAME, D7S_MINOR);
+ iounmap(d7s_regs);
+ return iTmp;
+ }
+
+ /* OBP option "d7s-flipped?" is honored as default
+ * for the device, and reset default when detached
+ */
+ regs = readb(d7s_regs);
+ iTmp = d7s_obpflipped();
+ (0 == iTmp) ?
+ writeb(regs |= D7S_FLIP, d7s_regs):
+ writeb(regs &= ~D7S_FLIP, d7s_regs);
+
+ printk("%s: 7-Segment Display%s at 0x%lx %s\n",
+ D7S_DEVNAME,
+ (0 == iTmp) ? (" (FLIPPED)") : (""),
+ edev->resource[0].start,
+ (0 != sol_compat) ? ("in sol_compat mode") : (""));
+
+ return 0;
+}
+
+static void __exit d7s_cleanup(void)
+{
+ int regs = readb(d7s_regs);
+
+ /* Honor OBP d7s-flipped? unless operating in solaris-compat mode */
+ if (0 == sol_compat) {
+ (0 == d7s_obpflipped()) ?
+ writeb(regs |= D7S_FLIP, d7s_regs):
+ writeb(regs &= ~D7S_FLIP, d7s_regs);
+ }
+
+ misc_deregister(&d7s_miscdev);
+ d7s_free();
+}
+
+module_init(d7s_init);
+module_exit(d7s_cleanup);
diff --git a/drivers/sbus/char/envctrl.c b/drivers/sbus/char/envctrl.c
new file mode 100644
index 00000000000..f6ed35b24f4
--- /dev/null
+++ b/drivers/sbus/char/envctrl.c
@@ -0,0 +1,1181 @@
+/* $Id: envctrl.c,v 1.25 2002/01/15 09:01:26 davem Exp $
+ * envctrl.c: Temperature and Fan monitoring on Machines providing it.
+ *
+ * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be)
+ * Copyright (C) 2000 Vinh Truong (vinh.truong@eng.sun.com)
+ * VT - The implementation is to support Sun Microelectronics (SME) platform
+ * environment monitoring. SME platforms use pcf8584 as the i2c bus
+ * controller to access pcf8591 (8-bit A/D and D/A converter) and
+ * pcf8571 (256 x 8-bit static low-voltage RAM with I2C-bus interface).
+ * At board level, it follows SME Firmware I2C Specification. Reference:
+ * http://www-eu2.semiconductors.com/pip/PCF8584P
+ * http://www-eu2.semiconductors.com/pip/PCF8574AP
+ * http://www-eu2.semiconductors.com/pip/PCF8591P
+ *
+ * EB - Added support for CP1500 Global Address and PS/Voltage monitoring.
+ * Eric Brower <ebrower@usa.net>
+ *
+ * DB - Audit every copy_to_user in envctrl_read.
+ * Daniele Bellucci <bellucda@tiscali.it>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#include <asm/ebus.h>
+#include <asm/uaccess.h>
+#include <asm/envctrl.h>
+
+#define __KERNEL_SYSCALLS__
+static int errno;
+#include <asm/unistd.h>
+
+#define ENVCTRL_MINOR 162
+
+#define PCF8584_ADDRESS 0x55
+
+#define CONTROL_PIN 0x80
+#define CONTROL_ES0 0x40
+#define CONTROL_ES1 0x20
+#define CONTROL_ES2 0x10
+#define CONTROL_ENI 0x08
+#define CONTROL_STA 0x04
+#define CONTROL_STO 0x02
+#define CONTROL_ACK 0x01
+
+#define STATUS_PIN 0x80
+#define STATUS_STS 0x20
+#define STATUS_BER 0x10
+#define STATUS_LRB 0x08
+#define STATUS_AD0 0x08
+#define STATUS_AAB 0x04
+#define STATUS_LAB 0x02
+#define STATUS_BB 0x01
+
+/*
+ * CLK Mode Register.
+ */
+#define BUS_CLK_90 0x00
+#define BUS_CLK_45 0x01
+#define BUS_CLK_11 0x02
+#define BUS_CLK_1_5 0x03
+
+#define CLK_3 0x00
+#define CLK_4_43 0x10
+#define CLK_6 0x14
+#define CLK_8 0x18
+#define CLK_12 0x1c
+
+#define OBD_SEND_START 0xc5 /* value to generate I2c_bus START condition */
+#define OBD_SEND_STOP 0xc3 /* value to generate I2c_bus STOP condition */
+
+/* Monitor type of i2c child device.
+ * Firmware definitions.
+ */
+#define PCF8584_MAX_CHANNELS 8
+#define PCF8584_GLOBALADDR_TYPE 6 /* global address monitor */
+#define PCF8584_FANSTAT_TYPE 3 /* fan status monitor */
+#define PCF8584_VOLTAGE_TYPE 2 /* voltage monitor */
+#define PCF8584_TEMP_TYPE 1 /* temperature monitor*/
+
+/* Monitor type of i2c child device.
+ * Driver definitions.
+ */
+#define ENVCTRL_NOMON 0
+#define ENVCTRL_CPUTEMP_MON 1 /* cpu temperature monitor */
+#define ENVCTRL_CPUVOLTAGE_MON 2 /* voltage monitor */
+#define ENVCTRL_FANSTAT_MON 3 /* fan status monitor */
+#define ENVCTRL_ETHERTEMP_MON 4 /* ethernet temperarture */
+ /* monitor */
+#define ENVCTRL_VOLTAGESTAT_MON 5 /* voltage status monitor */
+#define ENVCTRL_MTHRBDTEMP_MON 6 /* motherboard temperature */
+#define ENVCTRL_SCSITEMP_MON 7 /* scsi temperarture */
+#define ENVCTRL_GLOBALADDR_MON 8 /* global address */
+
+/* Child device type.
+ * Driver definitions.
+ */
+#define I2C_ADC 0 /* pcf8591 */
+#define I2C_GPIO 1 /* pcf8571 */
+
+/* Data read from child device may need to decode
+ * through a data table and a scale.
+ * Translation type as defined by firmware.
+ */
+#define ENVCTRL_TRANSLATE_NO 0
+#define ENVCTRL_TRANSLATE_PARTIAL 1
+#define ENVCTRL_TRANSLATE_COMBINED 2
+#define ENVCTRL_TRANSLATE_FULL 3 /* table[data] */
+#define ENVCTRL_TRANSLATE_SCALE 4 /* table[data]/scale */
+
+/* Driver miscellaneous definitions. */
+#define ENVCTRL_MAX_CPU 4
+#define CHANNEL_DESC_SZ 256
+
+/* Mask values for combined GlobalAddress/PowerStatus node */
+#define ENVCTRL_GLOBALADDR_ADDR_MASK 0x1F
+#define ENVCTRL_GLOBALADDR_PSTAT_MASK 0x60
+
+/* Node 0x70 ignored on CompactPCI CP1400/1500 platforms
+ * (see envctrl_init_i2c_child)
+ */
+#define ENVCTRL_CPCI_IGNORED_NODE 0x70
+
+#define PCF8584_DATA 0x00
+#define PCF8584_CSR 0x01
+
+/* Each child device can be monitored by up to PCF8584_MAX_CHANNELS.
+ * Property of a port or channel as defined by the firmware.
+ */
+struct pcf8584_channel {
+ unsigned char chnl_no;
+ unsigned char io_direction;
+ unsigned char type;
+ unsigned char last;
+};
+
+/* Each child device may have one or more tables of bytes to help decode
+ * data. Table property as defined by the firmware.
+ */
+struct pcf8584_tblprop {
+ unsigned int type;
+ unsigned int scale;
+ unsigned int offset; /* offset from the beginning of the table */
+ unsigned int size;
+};
+
+/* i2c child */
+struct i2c_child_t {
+ /* Either ADC or GPIO. */
+ unsigned char i2ctype;
+ unsigned long addr;
+ struct pcf8584_channel chnl_array[PCF8584_MAX_CHANNELS];
+
+ /* Channel info. */
+ unsigned int total_chnls; /* Number of monitor channels. */
+ unsigned char fan_mask; /* Byte mask for fan status channels. */
+ unsigned char voltage_mask; /* Byte mask for voltage status channels. */
+ struct pcf8584_tblprop tblprop_array[PCF8584_MAX_CHANNELS];
+
+ /* Properties of all monitor channels. */
+ unsigned int total_tbls; /* Number of monitor tables. */
+ char *tables; /* Pointer to table(s). */
+ char chnls_desc[CHANNEL_DESC_SZ]; /* Channel description. */
+ char mon_type[PCF8584_MAX_CHANNELS];
+};
+
+static void __iomem *i2c;
+static struct i2c_child_t i2c_childlist[ENVCTRL_MAX_CPU*2];
+static unsigned char chnls_mask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+static unsigned int warning_temperature = 0;
+static unsigned int shutdown_temperature = 0;
+static char read_cpu;
+
+/* Forward declarations. */
+static struct i2c_child_t *envctrl_get_i2c_child(unsigned char);
+
+/* Function Description: Test the PIN bit (Pending Interrupt Not)
+ * to test when serial transmission is completed .
+ * Return : None.
+ */
+static void envtrl_i2c_test_pin(void)
+{
+ int limit = 1000000;
+
+ while (--limit > 0) {
+ if (!(readb(i2c + PCF8584_CSR) & STATUS_PIN))
+ break;
+ udelay(1);
+ }
+
+ if (limit <= 0)
+ printk(KERN_INFO "envctrl: Pin status will not clear.\n");
+}
+
+/* Function Description: Test busy bit.
+ * Return : None.
+ */
+static void envctrl_i2c_test_bb(void)
+{
+ int limit = 1000000;
+
+ while (--limit > 0) {
+ /* Busy bit 0 means busy. */
+ if (readb(i2c + PCF8584_CSR) & STATUS_BB)
+ break;
+ udelay(1);
+ }
+
+ if (limit <= 0)
+ printk(KERN_INFO "envctrl: Busy bit will not clear.\n");
+}
+
+/* Function Description: Send the address for a read access.
+ * Return : 0 if not acknowledged, otherwise acknowledged.
+ */
+static int envctrl_i2c_read_addr(unsigned char addr)
+{
+ envctrl_i2c_test_bb();
+
+ /* Load address. */
+ writeb(addr + 1, i2c + PCF8584_DATA);
+
+ envctrl_i2c_test_bb();
+
+ writeb(OBD_SEND_START, i2c + PCF8584_CSR);
+
+ /* Wait for PIN. */
+ envtrl_i2c_test_pin();
+
+ /* CSR 0 means acknowledged. */
+ if (!(readb(i2c + PCF8584_CSR) & STATUS_LRB)) {
+ return readb(i2c + PCF8584_DATA);
+ } else {
+ writeb(OBD_SEND_STOP, i2c + PCF8584_CSR);
+ return 0;
+ }
+}
+
+/* Function Description: Send the address for write mode.
+ * Return : None.
+ */
+static void envctrl_i2c_write_addr(unsigned char addr)
+{
+ envctrl_i2c_test_bb();
+ writeb(addr, i2c + PCF8584_DATA);
+
+ /* Generate Start condition. */
+ writeb(OBD_SEND_START, i2c + PCF8584_CSR);
+}
+
+/* Function Description: Read 1 byte of data from addr
+ * set by envctrl_i2c_read_addr()
+ * Return : Data from address set by envctrl_i2c_read_addr().
+ */
+static unsigned char envctrl_i2c_read_data(void)
+{
+ envtrl_i2c_test_pin();
+ writeb(CONTROL_ES0, i2c + PCF8584_CSR); /* Send neg ack. */
+ return readb(i2c + PCF8584_DATA);
+}
+
+/* Function Description: Instruct the device which port to read data from.
+ * Return : None.
+ */
+static void envctrl_i2c_write_data(unsigned char port)
+{
+ envtrl_i2c_test_pin();
+ writeb(port, i2c + PCF8584_DATA);
+}
+
+/* Function Description: Generate Stop condition after last byte is sent.
+ * Return : None.
+ */
+static void envctrl_i2c_stop(void)
+{
+ envtrl_i2c_test_pin();
+ writeb(OBD_SEND_STOP, i2c + PCF8584_CSR);
+}
+
+/* Function Description: Read adc device.
+ * Return : Data at address and port.
+ */
+static unsigned char envctrl_i2c_read_8591(unsigned char addr, unsigned char port)
+{
+ /* Send address. */
+ envctrl_i2c_write_addr(addr);
+
+ /* Setup port to read. */
+ envctrl_i2c_write_data(port);
+ envctrl_i2c_stop();
+
+ /* Read port. */
+ envctrl_i2c_read_addr(addr);
+
+ /* Do a single byte read and send stop. */
+ envctrl_i2c_read_data();
+ envctrl_i2c_stop();
+
+ return readb(i2c + PCF8584_DATA);
+}
+
+/* Function Description: Read gpio device.
+ * Return : Data at address.
+ */
+static unsigned char envctrl_i2c_read_8574(unsigned char addr)
+{
+ unsigned char rd;
+
+ envctrl_i2c_read_addr(addr);
+
+ /* Do a single byte read and send stop. */
+ rd = envctrl_i2c_read_data();
+ envctrl_i2c_stop();
+ return rd;
+}
+
+/* Function Description: Decode data read from an adc device using firmware
+ * table.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_i2c_data_translate(unsigned char data, int translate_type,
+ int scale, char *tbl, char *bufdata)
+{
+ int len = 0;
+
+ switch (translate_type) {
+ case ENVCTRL_TRANSLATE_NO:
+ /* No decode necessary. */
+ len = 1;
+ bufdata[0] = data;
+ break;
+
+ case ENVCTRL_TRANSLATE_FULL:
+ /* Decode this way: data = table[data]. */
+ len = 1;
+ bufdata[0] = tbl[data];
+ break;
+
+ case ENVCTRL_TRANSLATE_SCALE:
+ /* Decode this way: data = table[data]/scale */
+ sprintf(bufdata,"%d ", (tbl[data] * 10) / (scale));
+ len = strlen(bufdata);
+ bufdata[len - 1] = bufdata[len - 2];
+ bufdata[len - 2] = '.';
+ break;
+
+ default:
+ break;
+ };
+
+ return len;
+}
+
+/* Function Description: Read cpu-related data such as cpu temperature, voltage.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_read_cpu_info(int cpu, struct i2c_child_t *pchild,
+ char mon_type, unsigned char *bufdata)
+{
+ unsigned char data;
+ int i;
+ char *tbl, j = -1;
+
+ /* Find the right monitor type and channel. */
+ for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
+ if (pchild->mon_type[i] == mon_type) {
+ if (++j == cpu) {
+ break;
+ }
+ }
+ }
+
+ if (j != cpu)
+ return 0;
+
+ /* Read data from address and port. */
+ data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
+ (unsigned char)pchild->chnl_array[i].chnl_no);
+
+ /* Find decoding table. */
+ tbl = pchild->tables + pchild->tblprop_array[i].offset;
+
+ return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
+ pchild->tblprop_array[i].scale,
+ tbl, bufdata);
+}
+
+/* Function Description: Read noncpu-related data such as motherboard
+ * temperature.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_read_noncpu_info(struct i2c_child_t *pchild,
+ char mon_type, unsigned char *bufdata)
+{
+ unsigned char data;
+ int i;
+ char *tbl = NULL;
+
+ for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
+ if (pchild->mon_type[i] == mon_type)
+ break;
+ }
+
+ if (i >= PCF8584_MAX_CHANNELS)
+ return 0;
+
+ /* Read data from address and port. */
+ data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
+ (unsigned char)pchild->chnl_array[i].chnl_no);
+
+ /* Find decoding table. */
+ tbl = pchild->tables + pchild->tblprop_array[i].offset;
+
+ return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
+ pchild->tblprop_array[i].scale,
+ tbl, bufdata);
+}
+
+/* Function Description: Read fan status.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static int envctrl_i2c_fan_status(struct i2c_child_t *pchild,
+ unsigned char data,
+ char *bufdata)
+{
+ unsigned char tmp, ret = 0;
+ int i, j = 0;
+
+ tmp = data & pchild->fan_mask;
+
+ if (tmp == pchild->fan_mask) {
+ /* All bits are on. All fans are functioning. */
+ ret = ENVCTRL_ALL_FANS_GOOD;
+ } else if (tmp == 0) {
+ /* No bits are on. No fans are functioning. */
+ ret = ENVCTRL_ALL_FANS_BAD;
+ } else {
+ /* Go through all channels, mark 'on' the matched bits.
+ * Notice that fan_mask may have discontiguous bits but
+ * return mask are always contiguous. For example if we
+ * monitor 4 fans at channels 0,1,2,4, the return mask
+ * should be 00010000 if only fan at channel 4 is working.
+ */
+ for (i = 0; i < PCF8584_MAX_CHANNELS;i++) {
+ if (pchild->fan_mask & chnls_mask[i]) {
+ if (!(chnls_mask[i] & tmp))
+ ret |= chnls_mask[j];
+
+ j++;
+ }
+ }
+ }
+
+ bufdata[0] = ret;
+ return 1;
+}
+
+/* Function Description: Read global addressing line.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static int envctrl_i2c_globaladdr(struct i2c_child_t *pchild,
+ unsigned char data,
+ char *bufdata)
+{
+ /* Translatation table is not necessary, as global
+ * addr is the integer value of the GA# bits.
+ *
+ * NOTE: MSB is documented as zero, but I see it as '1' always....
+ *
+ * -----------------------------------------------
+ * | 0 | FAL | DEG | GA4 | GA3 | GA2 | GA1 | GA0 |
+ * -----------------------------------------------
+ * GA0 - GA4 integer value of Global Address (backplane slot#)
+ * DEG 0 = cPCI Power supply output is starting to degrade
+ * 1 = cPCI Power supply output is OK
+ * FAL 0 = cPCI Power supply has failed
+ * 1 = cPCI Power supply output is OK
+ */
+ bufdata[0] = (data & ENVCTRL_GLOBALADDR_ADDR_MASK);
+ return 1;
+}
+
+/* Function Description: Read standard voltage and power supply status.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static unsigned char envctrl_i2c_voltage_status(struct i2c_child_t *pchild,
+ unsigned char data,
+ char *bufdata)
+{
+ unsigned char tmp, ret = 0;
+ int i, j = 0;
+
+ tmp = data & pchild->voltage_mask;
+
+ /* Two channels are used to monitor voltage and power supply. */
+ if (tmp == pchild->voltage_mask) {
+ /* All bits are on. Voltage and power supply are okay. */
+ ret = ENVCTRL_VOLTAGE_POWERSUPPLY_GOOD;
+ } else if (tmp == 0) {
+ /* All bits are off. Voltage and power supply are bad */
+ ret = ENVCTRL_VOLTAGE_POWERSUPPLY_BAD;
+ } else {
+ /* Either voltage or power supply has problem. */
+ for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
+ if (pchild->voltage_mask & chnls_mask[i]) {
+ j++;
+
+ /* Break out when there is a mismatch. */
+ if (!(chnls_mask[i] & tmp))
+ break;
+ }
+ }
+
+ /* Make a wish that hardware will always use the
+ * first channel for voltage and the second for
+ * power supply.
+ */
+ if (j == 1)
+ ret = ENVCTRL_VOLTAGE_BAD;
+ else
+ ret = ENVCTRL_POWERSUPPLY_BAD;
+ }
+
+ bufdata[0] = ret;
+ return 1;
+}
+
+/* Function Description: Read a byte from /dev/envctrl. Mapped to user read().
+ * Return: Number of read bytes. 0 for error.
+ */
+static ssize_t
+envctrl_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ struct i2c_child_t *pchild;
+ unsigned char data[10];
+ int ret = 0;
+
+ /* Get the type of read as decided in ioctl() call.
+ * Find the appropriate i2c child.
+ * Get the data and put back to the user buffer.
+ */
+
+ switch ((int)(long)file->private_data) {
+ case ENVCTRL_RD_WARNING_TEMPERATURE:
+ if (warning_temperature == 0)
+ return 0;
+
+ data[0] = (unsigned char)(warning_temperature);
+ ret = 1;
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
+ if (shutdown_temperature == 0)
+ return 0;
+
+ data[0] = (unsigned char)(shutdown_temperature);
+ ret = 1;
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_MTHRBD_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_MTHRBDTEMP_MON)))
+ return 0;
+ ret = envctrl_read_noncpu_info(pchild, ENVCTRL_MTHRBDTEMP_MON, data);
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_CPU_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON)))
+ return 0;
+ ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUTEMP_MON, data);
+
+ /* Reset cpu to the default cpu0. */
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_CPU_VOLTAGE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUVOLTAGE_MON)))
+ return 0;
+ ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUVOLTAGE_MON, data);
+
+ /* Reset cpu to the default cpu0. */
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_SCSI_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_SCSITEMP_MON)))
+ return 0;
+ ret = envctrl_read_noncpu_info(pchild, ENVCTRL_SCSITEMP_MON, data);
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_ETHERNET_TEMPERATURE:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_ETHERTEMP_MON)))
+ return 0;
+ ret = envctrl_read_noncpu_info(pchild, ENVCTRL_ETHERTEMP_MON, data);
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_FAN_STATUS:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_FANSTAT_MON)))
+ return 0;
+ data[0] = envctrl_i2c_read_8574(pchild->addr);
+ ret = envctrl_i2c_fan_status(pchild,data[0], data);
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_GLOBALADDRESS:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON)))
+ return 0;
+ data[0] = envctrl_i2c_read_8574(pchild->addr);
+ ret = envctrl_i2c_globaladdr(pchild, data[0], data);
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ case ENVCTRL_RD_VOLTAGE_STATUS:
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_VOLTAGESTAT_MON)))
+ /* If voltage monitor not present, check for CPCI equivalent */
+ if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON)))
+ return 0;
+ data[0] = envctrl_i2c_read_8574(pchild->addr);
+ ret = envctrl_i2c_voltage_status(pchild, data[0], data);
+ if (copy_to_user(buf, data, ret))
+ ret = -EFAULT;
+ break;
+
+ default:
+ break;
+
+ };
+
+ return ret;
+}
+
+/* Function Description: Command what to read. Mapped to user ioctl().
+ * Return: Gives 0 for implemented commands, -EINVAL otherwise.
+ */
+static int
+envctrl_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ char __user *infobuf;
+
+ switch (cmd) {
+ case ENVCTRL_RD_WARNING_TEMPERATURE:
+ case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
+ case ENVCTRL_RD_MTHRBD_TEMPERATURE:
+ case ENVCTRL_RD_FAN_STATUS:
+ case ENVCTRL_RD_VOLTAGE_STATUS:
+ case ENVCTRL_RD_ETHERNET_TEMPERATURE:
+ case ENVCTRL_RD_SCSI_TEMPERATURE:
+ case ENVCTRL_RD_GLOBALADDRESS:
+ file->private_data = (void *)(long)cmd;
+ break;
+
+ case ENVCTRL_RD_CPU_TEMPERATURE:
+ case ENVCTRL_RD_CPU_VOLTAGE:
+ /* Check to see if application passes in any cpu number,
+ * the default is cpu0.
+ */
+ infobuf = (char __user *) arg;
+ if (infobuf == NULL) {
+ read_cpu = 0;
+ }else {
+ get_user(read_cpu, infobuf);
+ }
+
+ /* Save the command for use when reading. */
+ file->private_data = (void *)(long)cmd;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+/* Function Description: open device. Mapped to user open().
+ * Return: Always 0.
+ */
+static int
+envctrl_open(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+ return 0;
+}
+
+/* Function Description: Open device. Mapped to user close().
+ * Return: Always 0.
+ */
+static int
+envctrl_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static struct file_operations envctrl_fops = {
+ .owner = THIS_MODULE,
+ .read = envctrl_read,
+ .ioctl = envctrl_ioctl,
+ .open = envctrl_open,
+ .release = envctrl_release,
+};
+
+static struct miscdevice envctrl_dev = {
+ ENVCTRL_MINOR,
+ "envctrl",
+ &envctrl_fops
+};
+
+/* Function Description: Set monitor type based on firmware description.
+ * Return: None.
+ */
+static void envctrl_set_mon(struct i2c_child_t *pchild,
+ char *chnl_desc,
+ int chnl_no)
+{
+ /* Firmware only has temperature type. It does not distinguish
+ * different kinds of temperatures. We use channel description
+ * to disinguish them.
+ */
+ if (!(strcmp(chnl_desc,"temp,cpu")) ||
+ !(strcmp(chnl_desc,"temp,cpu0")) ||
+ !(strcmp(chnl_desc,"temp,cpu1")) ||
+ !(strcmp(chnl_desc,"temp,cpu2")) ||
+ !(strcmp(chnl_desc,"temp,cpu3")))
+ pchild->mon_type[chnl_no] = ENVCTRL_CPUTEMP_MON;
+
+ if (!(strcmp(chnl_desc,"vddcore,cpu0")) ||
+ !(strcmp(chnl_desc,"vddcore,cpu1")) ||
+ !(strcmp(chnl_desc,"vddcore,cpu2")) ||
+ !(strcmp(chnl_desc,"vddcore,cpu3")))
+ pchild->mon_type[chnl_no] = ENVCTRL_CPUVOLTAGE_MON;
+
+ if (!(strcmp(chnl_desc,"temp,motherboard")))
+ pchild->mon_type[chnl_no] = ENVCTRL_MTHRBDTEMP_MON;
+
+ if (!(strcmp(chnl_desc,"temp,scsi")))
+ pchild->mon_type[chnl_no] = ENVCTRL_SCSITEMP_MON;
+
+ if (!(strcmp(chnl_desc,"temp,ethernet")))
+ pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON;
+}
+
+/* Function Description: Initialize monitor channel with channel desc,
+ * decoding tables, monitor type, optional properties.
+ * Return: None.
+ */
+static void envctrl_init_adc(struct i2c_child_t *pchild, int node)
+{
+ char chnls_desc[CHANNEL_DESC_SZ];
+ int i = 0, len;
+ char *pos = chnls_desc;
+
+ /* Firmware describe channels into a stream separated by a '\0'. */
+ len = prom_getproperty(node, "channels-description", chnls_desc,
+ CHANNEL_DESC_SZ);
+ chnls_desc[CHANNEL_DESC_SZ - 1] = '\0';
+
+ while (len > 0) {
+ int l = strlen(pos) + 1;
+ envctrl_set_mon(pchild, pos, i++);
+ len -= l;
+ pos += l;
+ }
+
+ /* Get optional properties. */
+ len = prom_getproperty(node, "warning-temp", (char *)&warning_temperature,
+ sizeof(warning_temperature));
+ len = prom_getproperty(node, "shutdown-temp", (char *)&shutdown_temperature,
+ sizeof(shutdown_temperature));
+}
+
+/* Function Description: Initialize child device monitoring fan status.
+ * Return: None.
+ */
+static void envctrl_init_fanstat(struct i2c_child_t *pchild)
+{
+ int i;
+
+ /* Go through all channels and set up the mask. */
+ for (i = 0; i < pchild->total_chnls; i++)
+ pchild->fan_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
+
+ /* We only need to know if this child has fan status monitored.
+ * We don't care which channels since we have the mask already.
+ */
+ pchild->mon_type[0] = ENVCTRL_FANSTAT_MON;
+}
+
+/* Function Description: Initialize child device for global addressing line.
+ * Return: None.
+ */
+static void envctrl_init_globaladdr(struct i2c_child_t *pchild)
+{
+ int i;
+
+ /* Voltage/PowerSupply monitoring is piggybacked
+ * with Global Address on CompactPCI. See comments
+ * within envctrl_i2c_globaladdr for bit assignments.
+ *
+ * The mask is created here by assigning mask bits to each
+ * bit position that represents PCF8584_VOLTAGE_TYPE data.
+ * Channel numbers are not consecutive within the globaladdr
+ * node (why?), so we use the actual counter value as chnls_mask
+ * index instead of the chnl_array[x].chnl_no value.
+ *
+ * NOTE: This loop could be replaced with a constant representing
+ * a mask of bits 5&6 (ENVCTRL_GLOBALADDR_PSTAT_MASK).
+ */
+ for (i = 0; i < pchild->total_chnls; i++) {
+ if (PCF8584_VOLTAGE_TYPE == pchild->chnl_array[i].type) {
+ pchild->voltage_mask |= chnls_mask[i];
+ }
+ }
+
+ /* We only need to know if this child has global addressing
+ * line monitored. We don't care which channels since we know
+ * the mask already (ENVCTRL_GLOBALADDR_ADDR_MASK).
+ */
+ pchild->mon_type[0] = ENVCTRL_GLOBALADDR_MON;
+}
+
+/* Initialize child device monitoring voltage status. */
+static void envctrl_init_voltage_status(struct i2c_child_t *pchild)
+{
+ int i;
+
+ /* Go through all channels and set up the mask. */
+ for (i = 0; i < pchild->total_chnls; i++)
+ pchild->voltage_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
+
+ /* We only need to know if this child has voltage status monitored.
+ * We don't care which channels since we have the mask already.
+ */
+ pchild->mon_type[0] = ENVCTRL_VOLTAGESTAT_MON;
+}
+
+/* Function Description: Initialize i2c child device.
+ * Return: None.
+ */
+static void envctrl_init_i2c_child(struct linux_ebus_child *edev_child,
+ struct i2c_child_t *pchild)
+{
+ int node, len, i, tbls_size = 0;
+
+ node = edev_child->prom_node;
+
+ /* Get device address. */
+ len = prom_getproperty(node, "reg",
+ (char *) &(pchild->addr),
+ sizeof(pchild->addr));
+
+ /* Get tables property. Read firmware temperature tables. */
+ len = prom_getproperty(node, "translation",
+ (char *) pchild->tblprop_array,
+ (PCF8584_MAX_CHANNELS *
+ sizeof(struct pcf8584_tblprop)));
+ if (len > 0) {
+ pchild->total_tbls = len / sizeof(struct pcf8584_tblprop);
+ for (i = 0; i < pchild->total_tbls; i++) {
+ if ((pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset) > tbls_size) {
+ tbls_size = pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset;
+ }
+ }
+
+ pchild->tables = kmalloc(tbls_size, GFP_KERNEL);
+ if (pchild->tables == NULL){
+ printk("envctrl: Failed to allocate table.\n");
+ return;
+ }
+ len = prom_getproperty(node, "tables",
+ (char *) pchild->tables, tbls_size);
+ if (len <= 0) {
+ printk("envctrl: Failed to get table.\n");
+ return;
+ }
+ }
+
+ /* SPARCengine ASM Reference Manual (ref. SMI doc 805-7581-04)
+ * sections 2.5, 3.5, 4.5 state node 0x70 for CP1400/1500 is
+ * "For Factory Use Only."
+ *
+ * We ignore the node on these platforms by assigning the
+ * 'NULL' monitor type.
+ */
+ if (ENVCTRL_CPCI_IGNORED_NODE == pchild->addr) {
+ int len;
+ char prop[56];
+
+ len = prom_getproperty(prom_root_node, "name", prop, sizeof(prop));
+ if (0 < len && (0 == strncmp(prop, "SUNW,UltraSPARC-IIi-cEngine", len)))
+ {
+ for (len = 0; len < PCF8584_MAX_CHANNELS; ++len) {
+ pchild->mon_type[len] = ENVCTRL_NOMON;
+ }
+ return;
+ }
+ }
+
+ /* Get the monitor channels. */
+ len = prom_getproperty(node, "channels-in-use",
+ (char *) pchild->chnl_array,
+ (PCF8584_MAX_CHANNELS *
+ sizeof(struct pcf8584_channel)));
+ pchild->total_chnls = len / sizeof(struct pcf8584_channel);
+
+ for (i = 0; i < pchild->total_chnls; i++) {
+ switch (pchild->chnl_array[i].type) {
+ case PCF8584_TEMP_TYPE:
+ envctrl_init_adc(pchild, node);
+ break;
+
+ case PCF8584_GLOBALADDR_TYPE:
+ envctrl_init_globaladdr(pchild);
+ i = pchild->total_chnls;
+ break;
+
+ case PCF8584_FANSTAT_TYPE:
+ envctrl_init_fanstat(pchild);
+ i = pchild->total_chnls;
+ break;
+
+ case PCF8584_VOLTAGE_TYPE:
+ if (pchild->i2ctype == I2C_ADC) {
+ envctrl_init_adc(pchild,node);
+ } else {
+ envctrl_init_voltage_status(pchild);
+ }
+ i = pchild->total_chnls;
+ break;
+
+ default:
+ break;
+ };
+ }
+}
+
+/* Function Description: Search the child device list for a device.
+ * Return : The i2c child if found. NULL otherwise.
+ */
+static struct i2c_child_t *envctrl_get_i2c_child(unsigned char mon_type)
+{
+ int i, j;
+
+ for (i = 0; i < ENVCTRL_MAX_CPU*2; i++) {
+ for (j = 0; j < PCF8584_MAX_CHANNELS; j++) {
+ if (i2c_childlist[i].mon_type[j] == mon_type) {
+ return (struct i2c_child_t *)(&(i2c_childlist[i]));
+ }
+ }
+ }
+ return NULL;
+}
+
+static void envctrl_do_shutdown(void)
+{
+ static int inprog = 0;
+ static char *envp[] = {
+ "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
+ char *argv[] = {
+ "/sbin/shutdown", "-h", "now", NULL };
+
+ if (inprog != 0)
+ return;
+
+ inprog = 1;
+ printk(KERN_CRIT "kenvctrld: WARNING: Shutting down the system now.\n");
+ if (0 > execve("/sbin/shutdown", argv, envp)) {
+ printk(KERN_CRIT "kenvctrld: WARNING: system shutdown failed!\n");
+ inprog = 0; /* unlikely to succeed, but we could try again */
+ }
+}
+
+static struct task_struct *kenvctrld_task;
+
+static int kenvctrld(void *__unused)
+{
+ int poll_interval;
+ int whichcpu;
+ char tempbuf[10];
+ struct i2c_child_t *cputemp;
+
+ if (NULL == (cputemp = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) {
+ printk(KERN_ERR
+ "envctrl: kenvctrld unable to monitor CPU temp-- exiting\n");
+ return -ENODEV;
+ }
+
+ poll_interval = 5 * HZ; /* TODO env_mon_interval */
+
+ daemonize("kenvctrld");
+ allow_signal(SIGKILL);
+
+ kenvctrld_task = current;
+
+ printk(KERN_INFO "envctrl: %s starting...\n", current->comm);
+ for (;;) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(poll_interval);
+
+ if(signal_pending(current))
+ break;
+
+ for (whichcpu = 0; whichcpu < ENVCTRL_MAX_CPU; ++whichcpu) {
+ if (0 < envctrl_read_cpu_info(whichcpu, cputemp,
+ ENVCTRL_CPUTEMP_MON,
+ tempbuf)) {
+ if (tempbuf[0] >= shutdown_temperature) {
+ printk(KERN_CRIT
+ "%s: WARNING: CPU%i temperature %i C meets or exceeds "\
+ "shutdown threshold %i C\n",
+ current->comm, whichcpu,
+ tempbuf[0], shutdown_temperature);
+ envctrl_do_shutdown();
+ }
+ }
+ }
+ }
+ printk(KERN_INFO "envctrl: %s exiting...\n", current->comm);
+ return 0;
+}
+
+static int __init envctrl_init(void)
+{
+#ifdef CONFIG_PCI
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+ struct linux_ebus_child *edev_child = NULL;
+ int err, i = 0;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, "bbc")) {
+ /* If we find a boot-bus controller node,
+ * then this envctrl driver is not for us.
+ */
+ return -ENODEV;
+ }
+ }
+ }
+
+ /* Traverse through ebus and ebus device list for i2c device and
+ * adc and gpio nodes.
+ */
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, "i2c")) {
+ i2c = ioremap(edev->resource[0].start, 0x2);
+ for_each_edevchild(edev, edev_child) {
+ if (!strcmp("gpio", edev_child->prom_name)) {
+ i2c_childlist[i].i2ctype = I2C_GPIO;
+ envctrl_init_i2c_child(edev_child, &(i2c_childlist[i++]));
+ }
+ if (!strcmp("adc", edev_child->prom_name)) {
+ i2c_childlist[i].i2ctype = I2C_ADC;
+ envctrl_init_i2c_child(edev_child, &(i2c_childlist[i++]));
+ }
+ }
+ goto done;
+ }
+ }
+ }
+
+done:
+ if (!edev) {
+ printk("envctrl: I2C device not found.\n");
+ return -ENODEV;
+ }
+
+ /* Set device address. */
+ writeb(CONTROL_PIN, i2c + PCF8584_CSR);
+ writeb(PCF8584_ADDRESS, i2c + PCF8584_DATA);
+
+ /* Set system clock and SCL frequencies. */
+ writeb(CONTROL_PIN | CONTROL_ES1, i2c + PCF8584_CSR);
+ writeb(CLK_4_43 | BUS_CLK_90, i2c + PCF8584_DATA);
+
+ /* Enable serial interface. */
+ writeb(CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK, i2c + PCF8584_CSR);
+ udelay(200);
+
+ /* Register the device as a minor miscellaneous device. */
+ err = misc_register(&envctrl_dev);
+ if (err) {
+ printk("envctrl: Unable to get misc minor %d\n",
+ envctrl_dev.minor);
+ goto out_iounmap;
+ }
+
+ /* Note above traversal routine post-incremented 'i' to accommodate
+ * a next child device, so we decrement before reverse-traversal of
+ * child devices.
+ */
+ printk("envctrl: initialized ");
+ for (--i; i >= 0; --i) {
+ printk("[%s 0x%lx]%s",
+ (I2C_ADC == i2c_childlist[i].i2ctype) ? ("adc") :
+ ((I2C_GPIO == i2c_childlist[i].i2ctype) ? ("gpio") : ("unknown")),
+ i2c_childlist[i].addr, (0 == i) ? ("\n") : (" "));
+ }
+
+ err = kernel_thread(kenvctrld, NULL, CLONE_FS | CLONE_FILES);
+ if (err < 0)
+ goto out_deregister;
+
+ return 0;
+
+out_deregister:
+ misc_deregister(&envctrl_dev);
+out_iounmap:
+ iounmap(i2c);
+ for (i = 0; i < ENVCTRL_MAX_CPU * 2; i++) {
+ if (i2c_childlist[i].tables)
+ kfree(i2c_childlist[i].tables);
+ }
+ return err;
+#else
+ return -ENODEV;
+#endif
+}
+
+static void __exit envctrl_cleanup(void)
+{
+ int i;
+
+ if (NULL != kenvctrld_task) {
+ force_sig(SIGKILL, kenvctrld_task);
+ for (;;) {
+ struct task_struct *p;
+ int found = 0;
+
+ read_lock(&tasklist_lock);
+ for_each_process(p) {
+ if (p == kenvctrld_task) {
+ found = 1;
+ break;
+ }
+ }
+ read_unlock(&tasklist_lock);
+
+ if (!found)
+ break;
+
+ msleep(1000);
+ }
+ kenvctrld_task = NULL;
+ }
+
+ iounmap(i2c);
+ misc_deregister(&envctrl_dev);
+
+ for (i = 0; i < ENVCTRL_MAX_CPU * 2; i++) {
+ if (i2c_childlist[i].tables)
+ kfree(i2c_childlist[i].tables);
+ }
+}
+
+module_init(envctrl_init);
+module_exit(envctrl_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/flash.c b/drivers/sbus/char/flash.c
new file mode 100644
index 00000000000..6bdd768b731
--- /dev/null
+++ b/drivers/sbus/char/flash.c
@@ -0,0 +1,255 @@
+/* $Id: flash.c,v 1.25 2001/12/21 04:56:16 davem Exp $
+ * flash.c: Allow mmap access to the OBP Flash, for OBP updates.
+ *
+ * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/sbus.h>
+#include <asm/ebus.h>
+#include <asm/upa.h>
+
+static DEFINE_SPINLOCK(flash_lock);
+static struct {
+ unsigned long read_base; /* Physical read address */
+ unsigned long write_base; /* Physical write address */
+ unsigned long read_size; /* Size of read area */
+ unsigned long write_size; /* Size of write area */
+ unsigned long busy; /* In use? */
+} flash;
+
+#define FLASH_MINOR 152
+
+static int
+flash_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ unsigned long addr;
+ unsigned long size;
+
+ spin_lock(&flash_lock);
+ if (flash.read_base == flash.write_base) {
+ addr = flash.read_base;
+ size = flash.read_size;
+ } else {
+ if ((vma->vm_flags & VM_READ) &&
+ (vma->vm_flags & VM_WRITE)) {
+ spin_unlock(&flash_lock);
+ return -EINVAL;
+ }
+ if (vma->vm_flags & VM_READ) {
+ addr = flash.read_base;
+ size = flash.read_size;
+ } else if (vma->vm_flags & VM_WRITE) {
+ addr = flash.write_base;
+ size = flash.write_size;
+ } else {
+ spin_unlock(&flash_lock);
+ return -ENXIO;
+ }
+ }
+ spin_unlock(&flash_lock);
+
+ if ((vma->vm_pgoff << PAGE_SHIFT) > size)
+ return -ENXIO;
+ addr = vma->vm_pgoff + (addr >> PAGE_SHIFT);
+
+ if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size)
+ size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT));
+
+ pgprot_val(vma->vm_page_prot) &= ~(_PAGE_CACHE);
+ pgprot_val(vma->vm_page_prot) |= _PAGE_E;
+ vma->vm_flags |= (VM_SHM | VM_LOCKED);
+
+ if (io_remap_pfn_range(vma, vma->vm_start, addr, size, vma->vm_page_prot))
+ return -EAGAIN;
+
+ return 0;
+}
+
+static long long
+flash_llseek(struct file *file, long long offset, int origin)
+{
+ lock_kernel();
+ switch (origin) {
+ case 0:
+ file->f_pos = offset;
+ break;
+ case 1:
+ file->f_pos += offset;
+ if (file->f_pos > flash.read_size)
+ file->f_pos = flash.read_size;
+ break;
+ case 2:
+ file->f_pos = flash.read_size;
+ break;
+ default:
+ unlock_kernel();
+ return -EINVAL;
+ }
+ unlock_kernel();
+ return file->f_pos;
+}
+
+static ssize_t
+flash_read(struct file * file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long p = file->f_pos;
+ int i;
+
+ if (count > flash.read_size - p)
+ count = flash.read_size - p;
+
+ for (i = 0; i < count; i++) {
+ u8 data = upa_readb(flash.read_base + p + i);
+ if (put_user(data, buf))
+ return -EFAULT;
+ buf++;
+ }
+
+ file->f_pos += count;
+ return count;
+}
+
+static int
+flash_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, (void *)&flash.busy) != 0)
+ return -EBUSY;
+
+ return 0;
+}
+
+static int
+flash_release(struct inode *inode, struct file *file)
+{
+ spin_lock(&flash_lock);
+ flash.busy = 0;
+ spin_unlock(&flash_lock);
+
+ return 0;
+}
+
+static struct file_operations flash_fops = {
+ /* no write to the Flash, use mmap
+ * and play flash dependent tricks.
+ */
+ .owner = THIS_MODULE,
+ .llseek = flash_llseek,
+ .read = flash_read,
+ .mmap = flash_mmap,
+ .open = flash_open,
+ .release = flash_release,
+};
+
+static struct miscdevice flash_dev = { FLASH_MINOR, "flash", &flash_fops };
+
+static int __init flash_init(void)
+{
+ struct sbus_bus *sbus;
+ struct sbus_dev *sdev = NULL;
+#ifdef CONFIG_PCI
+ struct linux_ebus *ebus;
+ struct linux_ebus_device *edev = NULL;
+ struct linux_prom_registers regs[2];
+ int len, nregs;
+#endif
+ int err;
+
+ for_all_sbusdev(sdev, sbus) {
+ if (!strcmp(sdev->prom_name, "flashprom")) {
+ if (sdev->reg_addrs[0].phys_addr == sdev->reg_addrs[1].phys_addr) {
+ flash.read_base = ((unsigned long)sdev->reg_addrs[0].phys_addr) |
+ (((unsigned long)sdev->reg_addrs[0].which_io)<<32UL);
+ flash.read_size = sdev->reg_addrs[0].reg_size;
+ flash.write_base = flash.read_base;
+ flash.write_size = flash.read_size;
+ } else {
+ flash.read_base = ((unsigned long)sdev->reg_addrs[0].phys_addr) |
+ (((unsigned long)sdev->reg_addrs[0].which_io)<<32UL);
+ flash.read_size = sdev->reg_addrs[0].reg_size;
+ flash.write_base = ((unsigned long)sdev->reg_addrs[1].phys_addr) |
+ (((unsigned long)sdev->reg_addrs[1].which_io)<<32UL);
+ flash.write_size = sdev->reg_addrs[1].reg_size;
+ }
+ flash.busy = 0;
+ break;
+ }
+ }
+ if (!sdev) {
+#ifdef CONFIG_PCI
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, "flashprom"))
+ goto ebus_done;
+ }
+ }
+ ebus_done:
+ if (!edev)
+ return -ENODEV;
+
+ len = prom_getproperty(edev->prom_node, "reg", (void *)regs, sizeof(regs));
+ if ((len % sizeof(regs[0])) != 0) {
+ printk("flash: Strange reg property size %d\n", len);
+ return -ENODEV;
+ }
+
+ nregs = len / sizeof(regs[0]);
+
+ flash.read_base = edev->resource[0].start;
+ flash.read_size = regs[0].reg_size;
+
+ if (nregs == 1) {
+ flash.write_base = edev->resource[0].start;
+ flash.write_size = regs[0].reg_size;
+ } else if (nregs == 2) {
+ flash.write_base = edev->resource[1].start;
+ flash.write_size = regs[1].reg_size;
+ } else {
+ printk("flash: Strange number of regs %d\n", nregs);
+ return -ENODEV;
+ }
+
+ flash.busy = 0;
+
+#else
+ return -ENODEV;
+#endif
+ }
+
+ printk("OBP Flash: RD %lx[%lx] WR %lx[%lx]\n",
+ flash.read_base, flash.read_size,
+ flash.write_base, flash.write_size);
+
+ err = misc_register(&flash_dev);
+ if (err) {
+ printk(KERN_ERR "flash: unable to get misc minor\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static void __exit flash_cleanup(void)
+{
+ misc_deregister(&flash_dev);
+}
+
+module_init(flash_init);
+module_exit(flash_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/jsflash.c b/drivers/sbus/char/jsflash.c
new file mode 100644
index 00000000000..c12c5046e2f
--- /dev/null
+++ b/drivers/sbus/char/jsflash.c
@@ -0,0 +1,627 @@
+/*
+ * drivers/sbus/char/jsflash.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds (drivers/char/mem.c)
+ * Copyright (C) 1997 Eddie C. Dost (drivers/sbus/char/flash.c)
+ * Copyright (C) 1997-2000 Pavel Machek <pavel@ucw.cz> (drivers/block/nbd.c)
+ * Copyright (C) 1999-2000 Pete Zaitcev
+ *
+ * This driver is used to program OS into a Flash SIMM on
+ * Krups and Espresso platforms.
+ *
+ * TODO: do not allow erase/programming if file systems are mounted.
+ * TODO: Erase/program both banks of a 8MB SIMM.
+ *
+ * It is anticipated that programming an OS Flash will be a routine
+ * procedure. In the same time it is exeedingly dangerous because
+ * a user can program its OBP flash with OS image and effectively
+ * kill the machine.
+ *
+ * This driver uses an interface different from Eddie's flash.c
+ * as a silly safeguard.
+ *
+ * XXX The flash.c manipulates page caching characteristics in a certain
+ * dubious way; also it assumes that remap_pfn_range() can remap
+ * PCI bus locations, which may be false. ioremap() must be used
+ * instead. We should discuss this.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/smp_lock.h>
+#include <linux/genhd.h>
+#include <linux/blkdev.h>
+
+#define MAJOR_NR JSFD_MAJOR
+
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/pcic.h>
+#include <asm/oplib.h>
+
+#include <asm/jsflash.h> /* ioctl arguments. <linux/> ?? */
+#define JSFIDSZ (sizeof(struct jsflash_ident_arg))
+#define JSFPRGSZ (sizeof(struct jsflash_program_arg))
+
+/*
+ * Our device numbers have no business in system headers.
+ * The only thing a user knows is the device name /dev/jsflash.
+ *
+ * Block devices are laid out like this:
+ * minor+0 - Bootstrap, for 8MB SIMM 0x20400000[0x800000]
+ * minor+1 - Filesystem to mount, normally 0x20400400[0x7ffc00]
+ * minor+2 - Whole flash area for any case... 0x20000000[0x01000000]
+ * Total 3 minors per flash device.
+ *
+ * It is easier to have static size vectors, so we define
+ * a total minor range JSF_MAX, which must cover all minors.
+ */
+/* character device */
+#define JSF_MINOR 178 /* 178 is registered with hpa */
+/* block device */
+#define JSF_MAX 3 /* 3 minors wasted total so far. */
+#define JSF_NPART 3 /* 3 minors per flash device */
+#define JSF_PART_BITS 2 /* 2 bits of minors to cover JSF_NPART */
+#define JSF_PART_MASK 0x3 /* 2 bits mask */
+
+/*
+ * Access functions.
+ * We could ioremap(), but it's easier this way.
+ */
+static unsigned int jsf_inl(unsigned long addr)
+{
+ unsigned long retval;
+
+ __asm__ __volatile__("lda [%1] %2, %0\n\t" :
+ "=r" (retval) :
+ "r" (addr), "i" (ASI_M_BYPASS));
+ return retval;
+}
+
+static void jsf_outl(unsigned long addr, __u32 data)
+{
+
+ __asm__ __volatile__("sta %0, [%1] %2\n\t" : :
+ "r" (data), "r" (addr), "i" (ASI_M_BYPASS) :
+ "memory");
+}
+
+/*
+ * soft carrier
+ */
+
+struct jsfd_part {
+ unsigned long dbase;
+ unsigned long dsize;
+};
+
+struct jsflash {
+ unsigned long base;
+ unsigned long size;
+ unsigned long busy; /* In use? */
+ struct jsflash_ident_arg id;
+ /* int mbase; */ /* Minor base, typically zero */
+ struct jsfd_part dv[JSF_NPART];
+};
+
+/*
+ * We do not map normal memory or obio as a safety precaution.
+ * But offsets are real, for ease of userland programming.
+ */
+#define JSF_BASE_TOP 0x30000000
+#define JSF_BASE_ALL 0x20000000
+
+#define JSF_BASE_JK 0x20400000
+
+/*
+ */
+static struct gendisk *jsfd_disk[JSF_MAX];
+
+/*
+ * Let's pretend we may have several of these...
+ */
+static struct jsflash jsf0;
+
+/*
+ * Wait for AMD to finish its embedded algorithm.
+ * We use the Toggle bit DQ6 (0x40) because it does not
+ * depend on the data value as /DATA bit DQ7 does.
+ *
+ * XXX Do we need any timeout here? So far it never hanged, beware broken hw.
+ */
+static void jsf_wait(unsigned long p) {
+ unsigned int x1, x2;
+
+ for (;;) {
+ x1 = jsf_inl(p);
+ x2 = jsf_inl(p);
+ if ((x1 & 0x40404040) == (x2 & 0x40404040)) return;
+ }
+}
+
+/*
+ * Programming will only work if Flash is clean,
+ * we leave it to the programmer application.
+ *
+ * AMD must be programmed one byte at a time;
+ * thus, Simple Tech SIMM must be written 4 bytes at a time.
+ *
+ * Write waits for the chip to become ready after the write
+ * was finished. This is done so that application would read
+ * consistent data after the write is done.
+ */
+static void jsf_write4(unsigned long fa, u32 data) {
+
+ jsf_outl(fa, 0xAAAAAAAA); /* Unlock 1 Write 1 */
+ jsf_outl(fa, 0x55555555); /* Unlock 1 Write 2 */
+ jsf_outl(fa, 0xA0A0A0A0); /* Byte Program */
+ jsf_outl(fa, data);
+
+ jsf_wait(fa);
+}
+
+/*
+ */
+static void jsfd_read(char *buf, unsigned long p, size_t togo) {
+ union byte4 {
+ char s[4];
+ unsigned int n;
+ } b;
+
+ while (togo >= 4) {
+ togo -= 4;
+ b.n = jsf_inl(p);
+ memcpy(buf, b.s, 4);
+ p += 4;
+ buf += 4;
+ }
+}
+
+static void jsfd_do_request(request_queue_t *q)
+{
+ struct request *req;
+
+ while ((req = elv_next_request(q)) != NULL) {
+ struct jsfd_part *jdp = req->rq_disk->private_data;
+ unsigned long offset = req->sector << 9;
+ size_t len = req->current_nr_sectors << 9;
+
+ if ((offset + len) > jdp->dsize) {
+ end_request(req, 0);
+ continue;
+ }
+
+ if (rq_data_dir(req) != READ) {
+ printk(KERN_ERR "jsfd: write\n");
+ end_request(req, 0);
+ continue;
+ }
+
+ if ((jdp->dbase & 0xff000000) != 0x20000000) {
+ printk(KERN_ERR "jsfd: bad base %x\n", (int)jdp->dbase);
+ end_request(req, 0);
+ continue;
+ }
+
+ jsfd_read(req->buffer, jdp->dbase + offset, len);
+
+ end_request(req, 1);
+ }
+}
+
+/*
+ * The memory devices use the full 32/64 bits of the offset, and so we cannot
+ * check against negative addresses: they are ok. The return value is weird,
+ * though, in that case (0).
+ *
+ * also note that seeking relative to the "end of file" isn't supported:
+ * it has no meaning, so it returns -EINVAL.
+ */
+static loff_t jsf_lseek(struct file * file, loff_t offset, int orig)
+{
+ loff_t ret;
+
+ lock_kernel();
+ switch (orig) {
+ case 0:
+ file->f_pos = offset;
+ ret = file->f_pos;
+ break;
+ case 1:
+ file->f_pos += offset;
+ ret = file->f_pos;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ unlock_kernel();
+ return ret;
+}
+
+/*
+ * OS SIMM Cannot be read in other size but a 32bits word.
+ */
+static ssize_t jsf_read(struct file * file, char * buf,
+ size_t togo, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ char *tmp = buf;
+
+ union byte4 {
+ char s[4];
+ unsigned int n;
+ } b;
+
+ if (p < JSF_BASE_ALL || p >= JSF_BASE_TOP) {
+ return 0;
+ }
+
+ if ((p + togo) < p /* wrap */
+ || (p + togo) >= JSF_BASE_TOP) {
+ togo = JSF_BASE_TOP - p;
+ }
+
+ if (p < JSF_BASE_ALL && togo != 0) {
+#if 0 /* __bzero XXX */
+ size_t x = JSF_BASE_ALL - p;
+ if (x > togo) x = togo;
+ clear_user(tmp, x);
+ tmp += x;
+ p += x;
+ togo -= x;
+#else
+ /*
+ * Implementation of clear_user() calls __bzero
+ * without regard to modversions,
+ * so we cannot build a module.
+ */
+ return 0;
+#endif
+ }
+
+ while (togo >= 4) {
+ togo -= 4;
+ b.n = jsf_inl(p);
+ if (copy_to_user(tmp, b.s, 4))
+ return -EFAULT;
+ tmp += 4;
+ p += 4;
+ }
+
+ /*
+ * XXX Small togo may remain if 1 byte is ordered.
+ * It would be nice if we did a word size read and unpacked it.
+ */
+
+ *ppos = p;
+ return tmp-buf;
+}
+
+static ssize_t jsf_write(struct file * file, const char * buf,
+ size_t count, loff_t *ppos)
+{
+ return -ENOSPC;
+}
+
+/*
+ */
+static int jsf_ioctl_erase(unsigned long arg)
+{
+ unsigned long p;
+
+ /* p = jsf0.base; hits wrong bank */
+ p = 0x20400000;
+
+ jsf_outl(p, 0xAAAAAAAA); /* Unlock 1 Write 1 */
+ jsf_outl(p, 0x55555555); /* Unlock 1 Write 2 */
+ jsf_outl(p, 0x80808080); /* Erase setup */
+ jsf_outl(p, 0xAAAAAAAA); /* Unlock 2 Write 1 */
+ jsf_outl(p, 0x55555555); /* Unlock 2 Write 2 */
+ jsf_outl(p, 0x10101010); /* Chip erase */
+
+#if 0
+ /*
+ * This code is ok, except that counter based timeout
+ * has no place in this world. Let's just drop timeouts...
+ */
+ {
+ int i;
+ __u32 x;
+ for (i = 0; i < 1000000; i++) {
+ x = jsf_inl(p);
+ if ((x & 0x80808080) == 0x80808080) break;
+ }
+ if ((x & 0x80808080) != 0x80808080) {
+ printk("jsf0: erase timeout with 0x%08x\n", x);
+ } else {
+ printk("jsf0: erase done with 0x%08x\n", x);
+ }
+ }
+#else
+ jsf_wait(p);
+#endif
+
+ return 0;
+}
+
+/*
+ * Program a block of flash.
+ * Very simple because we can do it byte by byte anyway.
+ */
+static int jsf_ioctl_program(unsigned long arg)
+{
+ struct jsflash_program_arg abuf;
+ char *uptr;
+ unsigned long p;
+ unsigned int togo;
+ union {
+ unsigned int n;
+ char s[4];
+ } b;
+
+ if (copy_from_user(&abuf, (char *)arg, JSFPRGSZ))
+ return -EFAULT;
+ p = abuf.off;
+ togo = abuf.size;
+ if ((togo & 3) || (p & 3)) return -EINVAL;
+
+ uptr = (char *) (unsigned long) abuf.data;
+ while (togo != 0) {
+ togo -= 4;
+ if (copy_from_user(&b.s[0], uptr, 4))
+ return -EFAULT;
+ jsf_write4(p, b.n);
+ p += 4;
+ uptr += 4;
+ }
+
+ return 0;
+}
+
+static int jsf_ioctl(struct inode *inode, struct file *f, unsigned int cmd,
+ unsigned long arg)
+{
+ int error = -ENOTTY;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ switch (cmd) {
+ case JSFLASH_IDENT:
+ if (copy_to_user((void *)arg, &jsf0.id, JSFIDSZ))
+ return -EFAULT;
+ break;
+ case JSFLASH_ERASE:
+ error = jsf_ioctl_erase(arg);
+ break;
+ case JSFLASH_PROGRAM:
+ error = jsf_ioctl_program(arg);
+ break;
+ }
+
+ return error;
+}
+
+static int jsf_mmap(struct file * file, struct vm_area_struct * vma)
+{
+ return -ENXIO;
+}
+
+static int jsf_open(struct inode * inode, struct file * filp)
+{
+
+ if (jsf0.base == 0) return -ENXIO;
+ if (test_and_set_bit(0, (void *)&jsf0.busy) != 0)
+ return -EBUSY;
+
+ return 0; /* XXX What security? */
+}
+
+static int jsf_release(struct inode *inode, struct file *file)
+{
+ jsf0.busy = 0;
+ return 0;
+}
+
+static struct file_operations jsf_fops = {
+ .owner = THIS_MODULE,
+ .llseek = jsf_lseek,
+ .read = jsf_read,
+ .write = jsf_write,
+ .ioctl = jsf_ioctl,
+ .mmap = jsf_mmap,
+ .open = jsf_open,
+ .release = jsf_release,
+};
+
+static struct miscdevice jsf_dev = { JSF_MINOR, "jsflash", &jsf_fops };
+
+static struct block_device_operations jsfd_fops = {
+ .owner = THIS_MODULE,
+};
+
+static int jsflash_init(void)
+{
+ int rc;
+ struct jsflash *jsf;
+ int node;
+ char banner[128];
+ struct linux_prom_registers reg0;
+
+ node = prom_getchild(prom_root_node);
+ node = prom_searchsiblings(node, "flash-memory");
+ if (node != 0 && node != -1) {
+ if (prom_getproperty(node, "reg",
+ (char *)&reg0, sizeof(reg0)) == -1) {
+ printk("jsflash: no \"reg\" property\n");
+ return -ENXIO;
+ }
+ if (reg0.which_io != 0) {
+ printk("jsflash: bus number nonzero: 0x%x:%x\n",
+ reg0.which_io, reg0.phys_addr);
+ return -ENXIO;
+ }
+ /*
+ * Flash may be somewhere else, for instance on Ebus.
+ * So, don't do the following check for IIep flash space.
+ */
+#if 0
+ if ((reg0.phys_addr >> 24) != 0x20) {
+ printk("jsflash: suspicious address: 0x%x:%x\n",
+ reg0.which_io, reg0.phys_addr);
+ return -ENXIO;
+ }
+#endif
+ if ((int)reg0.reg_size <= 0) {
+ printk("jsflash: bad size 0x%x\n", (int)reg0.reg_size);
+ return -ENXIO;
+ }
+ } else {
+ /* XXX Remove this code once PROLL ID12 got widespread */
+ printk("jsflash: no /flash-memory node, use PROLL >= 12\n");
+ prom_getproperty(prom_root_node, "banner-name", banner, 128);
+ if (strcmp (banner, "JavaStation-NC") != 0 &&
+ strcmp (banner, "JavaStation-E") != 0) {
+ return -ENXIO;
+ }
+ reg0.which_io = 0;
+ reg0.phys_addr = 0x20400000;
+ reg0.reg_size = 0x00800000;
+ }
+
+ /* Let us be really paranoid for modifications to probing code. */
+ /* extern enum sparc_cpu sparc_cpu_model; */ /* in <asm/system.h> */
+ if (sparc_cpu_model != sun4m) {
+ /* We must be on sun4m because we use MMU Bypass ASI. */
+ return -ENXIO;
+ }
+
+ if (jsf0.base == 0) {
+ jsf = &jsf0;
+
+ jsf->base = reg0.phys_addr;
+ jsf->size = reg0.reg_size;
+
+ /* XXX Redo the userland interface. */
+ jsf->id.off = JSF_BASE_ALL;
+ jsf->id.size = 0x01000000; /* 16M - all segments */
+ strcpy(jsf->id.name, "Krups_all");
+
+ jsf->dv[0].dbase = jsf->base;
+ jsf->dv[0].dsize = jsf->size;
+ jsf->dv[1].dbase = jsf->base + 1024;
+ jsf->dv[1].dsize = jsf->size - 1024;
+ jsf->dv[2].dbase = JSF_BASE_ALL;
+ jsf->dv[2].dsize = 0x01000000;
+
+ printk("Espresso Flash @0x%lx [%d MB]\n", jsf->base,
+ (int) (jsf->size / (1024*1024)));
+ }
+
+ if ((rc = misc_register(&jsf_dev)) != 0) {
+ printk(KERN_ERR "jsf: unable to get misc minor %d\n",
+ JSF_MINOR);
+ jsf0.base = 0;
+ return rc;
+ }
+
+ return 0;
+}
+
+static struct request_queue *jsf_queue;
+
+static int jsfd_init(void)
+{
+ static DEFINE_SPINLOCK(lock);
+ struct jsflash *jsf;
+ struct jsfd_part *jdp;
+ int err;
+ int i;
+
+ if (jsf0.base == 0)
+ return -ENXIO;
+
+ err = -ENOMEM;
+ for (i = 0; i < JSF_MAX; i++) {
+ struct gendisk *disk = alloc_disk(1);
+ if (!disk)
+ goto out;
+ jsfd_disk[i] = disk;
+ }
+
+ if (register_blkdev(JSFD_MAJOR, "jsfd")) {
+ err = -EIO;
+ goto out;
+ }
+
+ jsf_queue = blk_init_queue(jsfd_do_request, &lock);
+ if (!jsf_queue) {
+ err = -ENOMEM;
+ unregister_blkdev(JSFD_MAJOR, "jsfd");
+ goto out;
+ }
+
+ for (i = 0; i < JSF_MAX; i++) {
+ struct gendisk *disk = jsfd_disk[i];
+ if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
+ jsf = &jsf0; /* actually, &jsfv[i >> JSF_PART_BITS] */
+ jdp = &jsf->dv[i&JSF_PART_MASK];
+
+ disk->major = JSFD_MAJOR;
+ disk->first_minor = i;
+ sprintf(disk->disk_name, "jsfd%d", i);
+ disk->fops = &jsfd_fops;
+ set_capacity(disk, jdp->dsize >> 9);
+ disk->private_data = jdp;
+ disk->queue = jsf_queue;
+ add_disk(disk);
+ set_disk_ro(disk, 1);
+ }
+ return 0;
+out:
+ while (i--)
+ put_disk(jsfd_disk[i]);
+ return err;
+}
+
+MODULE_LICENSE("GPL");
+
+static int __init jsflash_init_module(void) {
+ int rc;
+
+ if ((rc = jsflash_init()) == 0) {
+ jsfd_init();
+ return 0;
+ }
+ return rc;
+}
+
+static void __exit jsflash_cleanup_module(void)
+{
+ int i;
+
+ for (i = 0; i < JSF_MAX; i++) {
+ if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
+ del_gendisk(jsfd_disk[i]);
+ put_disk(jsfd_disk[i]);
+ }
+ if (jsf0.busy)
+ printk("jsf0: cleaning busy unit\n");
+ jsf0.base = 0;
+ jsf0.busy = 0;
+
+ misc_deregister(&jsf_dev);
+ if (unregister_blkdev(JSFD_MAJOR, "jsfd") != 0)
+ printk("jsfd: cleanup_module failed\n");
+ blk_cleanup_queue(jsf_queue);
+}
+
+module_init(jsflash_init_module);
+module_exit(jsflash_cleanup_module);
diff --git a/drivers/sbus/char/max1617.h b/drivers/sbus/char/max1617.h
new file mode 100644
index 00000000000..0bb09c286cb
--- /dev/null
+++ b/drivers/sbus/char/max1617.h
@@ -0,0 +1,27 @@
+/* $Id: max1617.h,v 1.1 2001/04/02 09:59:08 davem Exp $ */
+#ifndef _MAX1617_H
+#define _MAX1617_H
+
+#define MAX1617_AMB_TEMP 0x00 /* Ambient temp in C */
+#define MAX1617_CPU_TEMP 0x01 /* Processor die temp in C */
+#define MAX1617_STATUS 0x02 /* Chip status bits */
+
+/* Read-only versions of changable registers. */
+#define MAX1617_RD_CFG_BYTE 0x03 /* Config register */
+#define MAX1617_RD_CVRATE_BYTE 0x04 /* Temp conversion rate */
+#define MAX1617_RD_AMB_HIGHLIM 0x05 /* Ambient high limit */
+#define MAX1617_RD_AMB_LOWLIM 0x06 /* Ambient low limit */
+#define MAX1617_RD_CPU_HIGHLIM 0x07 /* Processor high limit */
+#define MAX1617_RD_CPU_LOWLIM 0x08 /* Processor low limit */
+
+/* Write-only versions of the same. */
+#define MAX1617_WR_CFG_BYTE 0x09
+#define MAX1617_WR_CVRATE_BYTE 0x0a
+#define MAX1617_WR_AMB_HIGHLIM 0x0b
+#define MAX1617_WR_AMB_LOWLIM 0x0c
+#define MAX1617_WR_CPU_HIGHLIM 0x0d
+#define MAX1617_WR_CPU_LOWLIM 0x0e
+
+#define MAX1617_ONESHOT 0x0f
+
+#endif /* _MAX1617_H */
diff --git a/drivers/sbus/char/openprom.c b/drivers/sbus/char/openprom.c
new file mode 100644
index 00000000000..58ed3374957
--- /dev/null
+++ b/drivers/sbus/char/openprom.c
@@ -0,0 +1,630 @@
+/*
+ * Linux/SPARC PROM Configuration Driver
+ * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
+ * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be)
+ *
+ * This character device driver allows user programs to access the
+ * PROM device tree. It is compatible with the SunOS /dev/openprom
+ * driver and the NetBSD /dev/openprom driver. The SunOS eeprom
+ * utility works without any modifications.
+ *
+ * The driver uses a minor number under the misc device major. The
+ * file read/write mode determines the type of access to the PROM.
+ * Interrupts are disabled whenever the driver calls into the PROM for
+ * sanity's sake.
+ */
+
+/* This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#define PROMLIB_INTERNAL
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <asm/oplib.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/openpromio.h>
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#include <asm/pbm.h>
+#endif
+
+/* Private data kept by the driver for each descriptor. */
+typedef struct openprom_private_data
+{
+ int current_node; /* Current node for SunOS ioctls. */
+ int lastnode; /* Last valid node used by BSD ioctls. */
+} DATA;
+
+/* ID of the PROM node containing all of the EEPROM options. */
+static int options_node = 0;
+
+/*
+ * Copy an openpromio structure into kernel space from user space.
+ * This routine does error checking to make sure that all memory
+ * accesses are within bounds. A pointer to the allocated openpromio
+ * structure will be placed in "*opp_p". Return value is the length
+ * of the user supplied buffer.
+ */
+static int copyin(struct openpromio __user *info, struct openpromio **opp_p)
+{
+ unsigned int bufsize;
+
+ if (!info || !opp_p)
+ return -EFAULT;
+
+ if (get_user(bufsize, &info->oprom_size))
+ return -EFAULT;
+
+ if (bufsize == 0)
+ return -EINVAL;
+
+ /* If the bufsize is too large, just limit it.
+ * Fix from Jason Rappleye.
+ */
+ if (bufsize > OPROMMAXPARAM)
+ bufsize = OPROMMAXPARAM;
+
+ if (!(*opp_p = kmalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
+ return -ENOMEM;
+ memset(*opp_p, 0, sizeof(int) + bufsize + 1);
+
+ if (copy_from_user(&(*opp_p)->oprom_array,
+ &info->oprom_array, bufsize)) {
+ kfree(*opp_p);
+ return -EFAULT;
+ }
+ return bufsize;
+}
+
+static int getstrings(struct openpromio __user *info, struct openpromio **opp_p)
+{
+ int n, bufsize;
+ char c;
+
+ if (!info || !opp_p)
+ return -EFAULT;
+
+ if (!(*opp_p = kmalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
+ return -ENOMEM;
+
+ memset(*opp_p, 0, sizeof(int) + OPROMMAXPARAM + 1);
+ (*opp_p)->oprom_size = 0;
+
+ n = bufsize = 0;
+ while ((n < 2) && (bufsize < OPROMMAXPARAM)) {
+ if (get_user(c, &info->oprom_array[bufsize])) {
+ kfree(*opp_p);
+ return -EFAULT;
+ }
+ if (c == '\0')
+ n++;
+ (*opp_p)->oprom_array[bufsize++] = c;
+ }
+ if (!n) {
+ kfree(*opp_p);
+ return -EINVAL;
+ }
+ return bufsize;
+}
+
+/*
+ * Copy an openpromio structure in kernel space back to user space.
+ */
+static int copyout(void __user *info, struct openpromio *opp, int len)
+{
+ if (copy_to_user(info, opp, len))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * SunOS and Solaris /dev/openprom ioctl calls.
+ */
+static int openprom_sunos_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg, int node)
+{
+ DATA *data = (DATA *) file->private_data;
+ char buffer[OPROMMAXPARAM+1], *buf;
+ struct openpromio *opp;
+ int bufsize, len, error = 0;
+ static int cnt;
+ void __user *argp = (void __user *)arg;
+
+ if (cmd == OPROMSETOPT)
+ bufsize = getstrings(argp, &opp);
+ else
+ bufsize = copyin(argp, &opp);
+
+ if (bufsize < 0)
+ return bufsize;
+
+ switch (cmd) {
+ case OPROMGETOPT:
+ case OPROMGETPROP:
+ len = prom_getproplen(node, opp->oprom_array);
+
+ if (len <= 0 || len > bufsize) {
+ error = copyout(argp, opp, sizeof(int));
+ break;
+ }
+
+ len = prom_getproperty(node, opp->oprom_array, buffer, bufsize);
+
+ memcpy(opp->oprom_array, buffer, len);
+ opp->oprom_array[len] = '\0';
+ opp->oprom_size = len;
+
+ error = copyout(argp, opp, sizeof(int) + bufsize);
+ break;
+
+ case OPROMNXTOPT:
+ case OPROMNXTPROP:
+ buf = prom_nextprop(node, opp->oprom_array, buffer);
+
+ len = strlen(buf);
+ if (len == 0 || len + 1 > bufsize) {
+ error = copyout(argp, opp, sizeof(int));
+ break;
+ }
+
+ memcpy(opp->oprom_array, buf, len);
+ opp->oprom_array[len] = '\0';
+ opp->oprom_size = ++len;
+
+ error = copyout(argp, opp, sizeof(int) + bufsize);
+ break;
+
+ case OPROMSETOPT:
+ case OPROMSETOPT2:
+ buf = opp->oprom_array + strlen(opp->oprom_array) + 1;
+ len = opp->oprom_array + bufsize - buf;
+
+ error = prom_setprop(options_node, opp->oprom_array,
+ buf, len);
+
+ if (error < 0)
+ error = -EINVAL;
+ break;
+
+ case OPROMNEXT:
+ case OPROMCHILD:
+ case OPROMSETCUR:
+ if (bufsize < sizeof(int)) {
+ error = -EINVAL;
+ break;
+ }
+
+ node = *((int *) opp->oprom_array);
+
+ switch (cmd) {
+ case OPROMNEXT: node = __prom_getsibling(node); break;
+ case OPROMCHILD: node = __prom_getchild(node); break;
+ case OPROMSETCUR: break;
+ }
+
+ data->current_node = node;
+ *((int *)opp->oprom_array) = node;
+ opp->oprom_size = sizeof(int);
+
+ error = copyout(argp, opp, bufsize + sizeof(int));
+ break;
+
+ case OPROMPCI2NODE:
+ error = -EINVAL;
+
+ if (bufsize >= 2*sizeof(int)) {
+#ifdef CONFIG_PCI
+ struct pci_dev *pdev;
+ struct pcidev_cookie *pcp;
+ pdev = pci_find_slot (((int *) opp->oprom_array)[0],
+ ((int *) opp->oprom_array)[1]);
+
+ pcp = pdev->sysdata;
+ if (pcp != NULL && pcp->prom_node != -1 && pcp->prom_node) {
+ node = pcp->prom_node;
+ data->current_node = node;
+ *((int *)opp->oprom_array) = node;
+ opp->oprom_size = sizeof(int);
+ error = copyout(argp, opp, bufsize + sizeof(int));
+ }
+#endif
+ }
+ break;
+
+ case OPROMPATH2NODE:
+ node = prom_finddevice(opp->oprom_array);
+ data->current_node = node;
+ *((int *)opp->oprom_array) = node;
+ opp->oprom_size = sizeof(int);
+
+ error = copyout(argp, opp, bufsize + sizeof(int));
+ break;
+
+ case OPROMGETBOOTARGS:
+ buf = saved_command_line;
+
+ len = strlen(buf);
+
+ if (len > bufsize) {
+ error = -EINVAL;
+ break;
+ }
+
+ strcpy(opp->oprom_array, buf);
+ opp->oprom_size = len;
+
+ error = copyout(argp, opp, bufsize + sizeof(int));
+ break;
+
+ case OPROMU2P:
+ case OPROMGETCONS:
+ case OPROMGETFBNAME:
+ if (cnt++ < 10)
+ printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n");
+ error = -EINVAL;
+ break;
+ default:
+ if (cnt++ < 10)
+ printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
+ error = -EINVAL;
+ break;
+ }
+
+ kfree(opp);
+ return error;
+}
+
+
+/* Return nonzero if a specific node is in the PROM device tree. */
+static int intree(int root, int node)
+{
+ for (; root != 0; root = prom_getsibling(root))
+ if (root == node || intree(prom_getchild(root),node))
+ return 1;
+ return 0;
+}
+
+/* Return nonzero if a specific node is "valid". */
+static int goodnode(int n, DATA *data)
+{
+ if (n == data->lastnode || n == prom_root_node || n == options_node)
+ return 1;
+ if (n == 0 || n == -1 || !intree(prom_root_node,n))
+ return 0;
+ data->lastnode = n;
+ return 1;
+}
+
+/* Copy in a whole string from userspace into kernelspace. */
+static int copyin_string(char __user *user, size_t len, char **ptr)
+{
+ char *tmp;
+
+ if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
+ return -EINVAL;
+
+ tmp = kmalloc(len + 1, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ if(copy_from_user(tmp, user, len)) {
+ kfree(tmp);
+ return -EFAULT;
+ }
+
+ tmp[len] = '\0';
+
+ *ptr = tmp;
+
+ return 0;
+}
+
+/*
+ * NetBSD /dev/openprom ioctl calls.
+ */
+static int openprom_bsd_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ DATA *data = (DATA *) file->private_data;
+ void __user *argp = (void __user *)arg;
+ struct opiocdesc op;
+ int error, node, len;
+ char *str, *tmp;
+ char buffer[64];
+ static int cnt;
+
+ switch (cmd) {
+ case OPIOCGET:
+ if (copy_from_user(&op, argp, sizeof(op)))
+ return -EFAULT;
+
+ if (!goodnode(op.op_nodeid,data))
+ return -EINVAL;
+
+ error = copyin_string(op.op_name, op.op_namelen, &str);
+ if (error)
+ return error;
+
+ len = prom_getproplen(op.op_nodeid,str);
+
+ if (len > op.op_buflen) {
+ kfree(str);
+ return -ENOMEM;
+ }
+
+ op.op_buflen = len;
+
+ if (len <= 0) {
+ kfree(str);
+ /* Verified by the above copy_from_user */
+ if (__copy_to_user(argp, &op,
+ sizeof(op)))
+ return -EFAULT;
+ return 0;
+ }
+
+ tmp = kmalloc(len + 1, GFP_KERNEL);
+ if (!tmp) {
+ kfree(str);
+ return -ENOMEM;
+ }
+
+ prom_getproperty(op.op_nodeid, str, tmp, len);
+
+ tmp[len] = '\0';
+
+ if (__copy_to_user(argp, &op, sizeof(op)) != 0
+ || copy_to_user(op.op_buf, tmp, len) != 0)
+ error = -EFAULT;
+
+ kfree(tmp);
+ kfree(str);
+
+ return error;
+
+ case OPIOCNEXTPROP:
+ if (copy_from_user(&op, argp, sizeof(op)))
+ return -EFAULT;
+
+ if (!goodnode(op.op_nodeid,data))
+ return -EINVAL;
+
+ error = copyin_string(op.op_name, op.op_namelen, &str);
+ if (error)
+ return error;
+
+ tmp = prom_nextprop(op.op_nodeid,str,buffer);
+
+ if (tmp) {
+ len = strlen(tmp);
+ if (len > op.op_buflen)
+ len = op.op_buflen;
+ else
+ op.op_buflen = len;
+ } else {
+ len = op.op_buflen = 0;
+ }
+
+ if (!access_ok(VERIFY_WRITE, argp, sizeof(op))) {
+ kfree(str);
+ return -EFAULT;
+ }
+
+ if (!access_ok(VERIFY_WRITE, op.op_buf, len)) {
+ kfree(str);
+ return -EFAULT;
+ }
+
+ error = __copy_to_user(argp, &op, sizeof(op));
+ if (!error) error = __copy_to_user(op.op_buf, tmp, len);
+
+ kfree(str);
+
+ return error;
+
+ case OPIOCSET:
+ if (copy_from_user(&op, argp, sizeof(op)))
+ return -EFAULT;
+
+ if (!goodnode(op.op_nodeid,data))
+ return -EINVAL;
+
+ error = copyin_string(op.op_name, op.op_namelen, &str);
+ if (error)
+ return error;
+
+ error = copyin_string(op.op_buf, op.op_buflen, &tmp);
+ if (error) {
+ kfree(str);
+ return error;
+ }
+
+ len = prom_setprop(op.op_nodeid,str,tmp,op.op_buflen+1);
+
+ if (len != op.op_buflen)
+ return -EINVAL;
+
+ kfree(str);
+ kfree(tmp);
+
+ return 0;
+
+ case OPIOCGETOPTNODE:
+ if (copy_to_user(argp, &options_node, sizeof(int)))
+ return -EFAULT;
+ return 0;
+
+ case OPIOCGETNEXT:
+ case OPIOCGETCHILD:
+ if (copy_from_user(&node, argp, sizeof(int)))
+ return -EFAULT;
+
+ if (cmd == OPIOCGETNEXT)
+ node = __prom_getsibling(node);
+ else
+ node = __prom_getchild(node);
+
+ if (__copy_to_user(argp, &node, sizeof(int)))
+ return -EFAULT;
+
+ return 0;
+
+ default:
+ if (cnt++ < 10)
+ printk(KERN_INFO "openprom_bsd_ioctl: cmd 0x%X\n", cmd);
+ return -EINVAL;
+
+ }
+}
+
+
+/*
+ * Handoff control to the correct ioctl handler.
+ */
+static int openprom_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ DATA *data = (DATA *) file->private_data;
+ static int cnt;
+
+ switch (cmd) {
+ case OPROMGETOPT:
+ case OPROMNXTOPT:
+ if ((file->f_mode & FMODE_READ) == 0)
+ return -EPERM;
+ return openprom_sunos_ioctl(inode, file, cmd, arg,
+ options_node);
+
+ case OPROMSETOPT:
+ case OPROMSETOPT2:
+ if ((file->f_mode & FMODE_WRITE) == 0)
+ return -EPERM;
+ return openprom_sunos_ioctl(inode, file, cmd, arg,
+ options_node);
+
+ case OPROMNEXT:
+ case OPROMCHILD:
+ case OPROMGETPROP:
+ case OPROMNXTPROP:
+ if ((file->f_mode & FMODE_READ) == 0)
+ return -EPERM;
+ return openprom_sunos_ioctl(inode, file, cmd, arg,
+ data->current_node);
+
+ case OPROMU2P:
+ case OPROMGETCONS:
+ case OPROMGETFBNAME:
+ case OPROMGETBOOTARGS:
+ case OPROMSETCUR:
+ case OPROMPCI2NODE:
+ case OPROMPATH2NODE:
+ if ((file->f_mode & FMODE_READ) == 0)
+ return -EPERM;
+ return openprom_sunos_ioctl(inode, file, cmd, arg, 0);
+
+ case OPIOCGET:
+ case OPIOCNEXTPROP:
+ case OPIOCGETOPTNODE:
+ case OPIOCGETNEXT:
+ case OPIOCGETCHILD:
+ if ((file->f_mode & FMODE_READ) == 0)
+ return -EBADF;
+ return openprom_bsd_ioctl(inode,file,cmd,arg);
+
+ case OPIOCSET:
+ if ((file->f_mode & FMODE_WRITE) == 0)
+ return -EBADF;
+ return openprom_bsd_ioctl(inode,file,cmd,arg);
+
+ default:
+ if (cnt++ < 10)
+ printk("openprom_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
+ return -EINVAL;
+ }
+}
+
+static int openprom_open(struct inode * inode, struct file * file)
+{
+ DATA *data;
+
+ data = (DATA *) kmalloc(sizeof(DATA), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->current_node = prom_root_node;
+ data->lastnode = prom_root_node;
+ file->private_data = (void *)data;
+
+ return 0;
+}
+
+static int openprom_release(struct inode * inode, struct file * file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
+static struct file_operations openprom_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .ioctl = openprom_ioctl,
+ .open = openprom_open,
+ .release = openprom_release,
+};
+
+static struct miscdevice openprom_dev = {
+ SUN_OPENPROM_MINOR, "openprom", &openprom_fops
+};
+
+static int __init openprom_init(void)
+{
+ int error;
+
+ error = misc_register(&openprom_dev);
+ if (error) {
+ printk(KERN_ERR "openprom: unable to get misc minor\n");
+ return error;
+ }
+
+ options_node = prom_getchild(prom_root_node);
+ options_node = prom_searchsiblings(options_node,"options");
+
+ if (options_node == 0 || options_node == -1) {
+ printk(KERN_ERR "openprom: unable to find options node\n");
+ misc_deregister(&openprom_dev);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void __exit openprom_cleanup(void)
+{
+ misc_deregister(&openprom_dev);
+}
+
+module_init(openprom_init);
+module_exit(openprom_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/riowatchdog.c b/drivers/sbus/char/riowatchdog.c
new file mode 100644
index 00000000000..d1babff6a53
--- /dev/null
+++ b/drivers/sbus/char/riowatchdog.c
@@ -0,0 +1,293 @@
+/* $Id: riowatchdog.c,v 1.3.2.2 2002/01/23 18:48:02 davem Exp $
+ * riowatchdog.c - driver for hw watchdog inside Super I/O of RIO
+ *
+ * Copyright (C) 2001 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+
+#include <asm/io.h>
+#include <asm/ebus.h>
+#include <asm/bbc.h>
+#include <asm/oplib.h>
+#include <asm/uaccess.h>
+
+#include <asm/watchdog.h>
+
+/* RIO uses the NatSemi Super I/O power management logical device
+ * as its' watchdog.
+ *
+ * When the watchdog triggers, it asserts a line to the BBC (Boot Bus
+ * Controller) of the machine. The BBC can only be configured to
+ * trigger a power-on reset when the signal is asserted. The BBC
+ * can be configured to ignore the signal entirely as well.
+ *
+ * The only Super I/O device register we care about is at index
+ * 0x05 (WDTO_INDEX) which is the watchdog time-out in minutes (1-255).
+ * If set to zero, this disables the watchdog. When set, the system
+ * must periodically (before watchdog expires) clear (set to zero) and
+ * re-set the watchdog else it will trigger.
+ *
+ * There are two other indexed watchdog registers inside this Super I/O
+ * logical device, but they are unused. The first, at index 0x06 is
+ * the watchdog control and can be used to make the watchdog timer re-set
+ * when the PS/2 mouse or serial lines show activity. The second, at
+ * index 0x07 is merely a sampling of the line from the watchdog to the
+ * BBC.
+ *
+ * The watchdog device generates no interrupts.
+ */
+
+MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
+MODULE_DESCRIPTION("Hardware watchdog driver for Sun RIO");
+MODULE_SUPPORTED_DEVICE("watchdog");
+MODULE_LICENSE("GPL");
+
+#define RIOWD_NAME "pmc"
+#define RIOWD_MINOR 215
+
+static DEFINE_SPINLOCK(riowd_lock);
+
+static void __iomem *bbc_regs;
+static void __iomem *riowd_regs;
+#define WDTO_INDEX 0x05
+
+static int riowd_timeout = 1; /* in minutes */
+module_param(riowd_timeout, int, 0);
+MODULE_PARM_DESC(riowd_timeout, "Watchdog timeout in minutes");
+
+#if 0 /* Currently unused. */
+static u8 riowd_readreg(int index)
+{
+ unsigned long flags;
+ u8 ret;
+
+ spin_lock_irqsave(&riowd_lock, flags);
+ writeb(index, riowd_regs + 0);
+ ret = readb(riowd_regs + 1);
+ spin_unlock_irqrestore(&riowd_lock, flags);
+
+ return ret;
+}
+#endif
+
+static void riowd_writereg(u8 val, int index)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&riowd_lock, flags);
+ writeb(index, riowd_regs + 0);
+ writeb(val, riowd_regs + 1);
+ spin_unlock_irqrestore(&riowd_lock, flags);
+}
+
+static void riowd_pingtimer(void)
+{
+ riowd_writereg(riowd_timeout, WDTO_INDEX);
+}
+
+static void riowd_stoptimer(void)
+{
+ u8 val;
+
+ riowd_writereg(0, WDTO_INDEX);
+
+ val = readb(bbc_regs + BBC_WDACTION);
+ val &= ~BBC_WDACTION_RST;
+ writeb(val, bbc_regs + BBC_WDACTION);
+}
+
+static void riowd_starttimer(void)
+{
+ u8 val;
+
+ riowd_writereg(riowd_timeout, WDTO_INDEX);
+
+ val = readb(bbc_regs + BBC_WDACTION);
+ val |= BBC_WDACTION_RST;
+ writeb(val, bbc_regs + BBC_WDACTION);
+}
+
+static int riowd_open(struct inode *inode, struct file *filp)
+{
+ nonseekable_open(inode, filp);
+ return 0;
+}
+
+static int riowd_release(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+static int riowd_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ static struct watchdog_info info = {
+ WDIOF_SETTIMEOUT, 0, "Natl. Semiconductor PC97317"
+ };
+ void __user *argp = (void __user *)arg;
+ unsigned int options;
+ int new_margin;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(0, (int __user *)argp))
+ return -EFAULT;
+ break;
+
+ case WDIOC_KEEPALIVE:
+ riowd_pingtimer();
+ break;
+
+ case WDIOC_SETOPTIONS:
+ if (copy_from_user(&options, argp, sizeof(options)))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD)
+ riowd_stoptimer();
+ else if (options & WDIOS_ENABLECARD)
+ riowd_starttimer();
+ else
+ return -EINVAL;
+
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, (int __user *)argp))
+ return -EFAULT;
+ if ((new_margin < 60) || (new_margin > (255 * 60)))
+ return -EINVAL;
+ riowd_timeout = (new_margin + 59) / 60;
+ riowd_pingtimer();
+ /* Fall */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(riowd_timeout * 60, (int __user *)argp);
+
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+static ssize_t riowd_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ if (count) {
+ riowd_pingtimer();
+ return 1;
+ }
+
+ return 0;
+}
+
+static struct file_operations riowd_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = riowd_ioctl,
+ .open = riowd_open,
+ .write = riowd_write,
+ .release = riowd_release,
+};
+
+static struct miscdevice riowd_miscdev = { RIOWD_MINOR, RIOWD_NAME, &riowd_fops };
+
+static int __init riowd_bbc_init(void)
+{
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+ u8 val;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, "bbc"))
+ goto found_bbc;
+ }
+ }
+
+found_bbc:
+ if (!edev)
+ return -ENODEV;
+ bbc_regs = ioremap(edev->resource[0].start, BBC_REGS_SIZE);
+ if (!bbc_regs)
+ return -ENODEV;
+
+ /* Turn it off. */
+ val = readb(bbc_regs + BBC_WDACTION);
+ val &= ~BBC_WDACTION_RST;
+ writeb(val, bbc_regs + BBC_WDACTION);
+
+ return 0;
+}
+
+static int __init riowd_init(void)
+{
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, RIOWD_NAME))
+ goto ebus_done;
+ }
+ }
+
+ebus_done:
+ if (!edev)
+ goto fail;
+
+ riowd_regs = ioremap(edev->resource[0].start, 2);
+ if (riowd_regs == NULL) {
+ printk(KERN_ERR "pmc: Cannot map registers.\n");
+ return -ENODEV;
+ }
+
+ if (riowd_bbc_init()) {
+ printk(KERN_ERR "pmc: Failure initializing BBC config.\n");
+ goto fail;
+ }
+
+ if (misc_register(&riowd_miscdev)) {
+ printk(KERN_ERR "pmc: Cannot register watchdog misc device.\n");
+ goto fail;
+ }
+
+ printk(KERN_INFO "pmc: Hardware watchdog [%i minutes], "
+ "regs at %p\n", riowd_timeout, riowd_regs);
+
+ return 0;
+
+fail:
+ if (riowd_regs) {
+ iounmap(riowd_regs);
+ riowd_regs = NULL;
+ }
+ if (bbc_regs) {
+ iounmap(bbc_regs);
+ bbc_regs = NULL;
+ }
+ return -ENODEV;
+}
+
+static void __exit riowd_cleanup(void)
+{
+ misc_deregister(&riowd_miscdev);
+ iounmap(riowd_regs);
+ riowd_regs = NULL;
+ iounmap(bbc_regs);
+ bbc_regs = NULL;
+}
+
+module_init(riowd_init);
+module_exit(riowd_cleanup);
diff --git a/drivers/sbus/char/rtc.c b/drivers/sbus/char/rtc.c
new file mode 100644
index 00000000000..bf3273eb1c8
--- /dev/null
+++ b/drivers/sbus/char/rtc.c
@@ -0,0 +1,178 @@
+/* $Id: rtc.c,v 1.28 2001/10/08 22:19:51 davem Exp $
+ *
+ * Linux/SPARC Real Time Clock Driver
+ * Copyright (C) 1996 Thomas K. Dyas (tdyas@eden.rutgers.edu)
+ *
+ * This is a little driver that lets a user-level program access
+ * the SPARC Mostek real time clock chip. It is no use unless you
+ * use the modified clock utility.
+ *
+ * Get the modified clock utility from:
+ * ftp://vger.kernel.org/pub/linux/Sparc/userland/clock.c
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <asm/io.h>
+#include <asm/mostek.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/rtc.h>
+
+static int rtc_busy = 0;
+
+/* Retrieve the current date and time from the real time clock. */
+static void get_rtc_time(struct rtc_time *t)
+{
+ void * __iomem regs = mstk48t02_regs;
+ u8 tmp;
+
+ spin_lock_irq(&mostek_lock);
+
+ tmp = mostek_read(regs + MOSTEK_CREG);
+ tmp |= MSTK_CREG_READ;
+ mostek_write(regs + MOSTEK_CREG, tmp);
+
+ t->sec = MSTK_REG_SEC(regs);
+ t->min = MSTK_REG_MIN(regs);
+ t->hour = MSTK_REG_HOUR(regs);
+ t->dow = MSTK_REG_DOW(regs);
+ t->dom = MSTK_REG_DOM(regs);
+ t->month = MSTK_REG_MONTH(regs);
+ t->year = MSTK_CVT_YEAR( MSTK_REG_YEAR(regs) );
+
+ tmp = mostek_read(regs + MOSTEK_CREG);
+ tmp &= ~MSTK_CREG_READ;
+ mostek_write(regs + MOSTEK_CREG, tmp);
+
+ spin_unlock_irq(&mostek_lock);
+}
+
+/* Set the current date and time inthe real time clock. */
+void set_rtc_time(struct rtc_time *t)
+{
+ void * __iomem regs = mstk48t02_regs;
+ u8 tmp;
+
+ spin_lock_irq(&mostek_lock);
+
+ tmp = mostek_read(regs + MOSTEK_CREG);
+ tmp |= MSTK_CREG_WRITE;
+ mostek_write(regs + MOSTEK_CREG, tmp);
+
+ MSTK_SET_REG_SEC(regs,t->sec);
+ MSTK_SET_REG_MIN(regs,t->min);
+ MSTK_SET_REG_HOUR(regs,t->hour);
+ MSTK_SET_REG_DOW(regs,t->dow);
+ MSTK_SET_REG_DOM(regs,t->dom);
+ MSTK_SET_REG_MONTH(regs,t->month);
+ MSTK_SET_REG_YEAR(regs,t->year - MSTK_YEAR_ZERO);
+
+ tmp = mostek_read(regs + MOSTEK_CREG);
+ tmp &= ~MSTK_CREG_WRITE;
+ mostek_write(regs + MOSTEK_CREG, tmp);
+
+ spin_unlock_irq(&mostek_lock);
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rtc_time rtc_tm;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd)
+ {
+ case RTCGET:
+ memset(&rtc_tm, 0, sizeof(struct rtc_time));
+ get_rtc_time(&rtc_tm);
+
+ if (copy_to_user(argp, &rtc_tm, sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ return 0;
+
+
+ case RTCSET:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+
+ if (copy_from_user(&rtc_tm, argp, sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ set_rtc_time(&rtc_tm);
+
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int rtc_open(struct inode *inode, struct file *file)
+{
+ int ret;
+
+ spin_lock_irq(&mostek_lock);
+ if (rtc_busy) {
+ ret = -EBUSY;
+ } else {
+ rtc_busy = 1;
+ ret = 0;
+ }
+ spin_unlock_irq(&mostek_lock);
+
+ return ret;
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+ rtc_busy = 0;
+
+ return 0;
+}
+
+static struct file_operations rtc_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .ioctl = rtc_ioctl,
+ .open = rtc_open,
+ .release = rtc_release,
+};
+
+static struct miscdevice rtc_dev = { RTC_MINOR, "rtc", &rtc_fops };
+
+static int __init rtc_sun_init(void)
+{
+ int error;
+
+ /* It is possible we are being driven by some other RTC chip
+ * and thus another RTC driver is handling things.
+ */
+ if (mstk48t02_regs == 0)
+ return -ENODEV;
+
+ error = misc_register(&rtc_dev);
+ if (error) {
+ printk(KERN_ERR "rtc: unable to get misc minor for Mostek\n");
+ return error;
+ }
+
+ return 0;
+}
+
+static void __exit rtc_sun_cleanup(void)
+{
+ misc_deregister(&rtc_dev);
+}
+
+module_init(rtc_sun_init);
+module_exit(rtc_sun_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/uctrl.c b/drivers/sbus/char/uctrl.c
new file mode 100644
index 00000000000..858cc683f85
--- /dev/null
+++ b/drivers/sbus/char/uctrl.c
@@ -0,0 +1,422 @@
+/* $Id: uctrl.c,v 1.12 2001/10/08 22:19:51 davem Exp $
+ * uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3
+ *
+ * Copyright 1999 Derrick J Brashear (shadow@dementia.org)
+ */
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/sbus.h>
+
+#define UCTRL_MINOR 174
+
+#define DEBUG 1
+#ifdef DEBUG
+#define dprintk(x) printk x
+#else
+#define dprintk(x)
+#endif
+
+struct uctrl_regs {
+ volatile u32 uctrl_intr;
+ volatile u32 uctrl_data;
+ volatile u32 uctrl_stat;
+ volatile u32 uctrl_xxx[5];
+};
+
+struct ts102_regs {
+ volatile u32 card_a_intr;
+ volatile u32 card_a_stat;
+ volatile u32 card_a_ctrl;
+ volatile u32 card_a_xxx;
+ volatile u32 card_b_intr;
+ volatile u32 card_b_stat;
+ volatile u32 card_b_ctrl;
+ volatile u32 card_b_xxx;
+ volatile u32 uctrl_intr;
+ volatile u32 uctrl_data;
+ volatile u32 uctrl_stat;
+ volatile u32 uctrl_xxx;
+ volatile u32 ts102_xxx[4];
+};
+
+/* Bits for uctrl_intr register */
+#define UCTRL_INTR_TXE_REQ 0x01 /* transmit FIFO empty int req */
+#define UCTRL_INTR_TXNF_REQ 0x02 /* transmit FIFO not full int req */
+#define UCTRL_INTR_RXNE_REQ 0x04 /* receive FIFO not empty int req */
+#define UCTRL_INTR_RXO_REQ 0x08 /* receive FIFO overflow int req */
+#define UCTRL_INTR_TXE_MSK 0x10 /* transmit FIFO empty mask */
+#define UCTRL_INTR_TXNF_MSK 0x20 /* transmit FIFO not full mask */
+#define UCTRL_INTR_RXNE_MSK 0x40 /* receive FIFO not empty mask */
+#define UCTRL_INTR_RXO_MSK 0x80 /* receive FIFO overflow mask */
+
+/* Bits for uctrl_stat register */
+#define UCTRL_STAT_TXE_STA 0x01 /* transmit FIFO empty status */
+#define UCTRL_STAT_TXNF_STA 0x02 /* transmit FIFO not full status */
+#define UCTRL_STAT_RXNE_STA 0x04 /* receive FIFO not empty status */
+#define UCTRL_STAT_RXO_STA 0x08 /* receive FIFO overflow status */
+
+static const char *uctrl_extstatus[16] = {
+ "main power available",
+ "internal battery attached",
+ "external battery attached",
+ "external VGA attached",
+ "external keyboard attached",
+ "external mouse attached",
+ "lid down",
+ "internal battery currently charging",
+ "external battery currently charging",
+ "internal battery currently discharging",
+ "external battery currently discharging",
+};
+
+/* Everything required for one transaction with the uctrl */
+struct uctrl_txn {
+ u8 opcode;
+ u8 inbits;
+ u8 outbits;
+ u8 *inbuf;
+ u8 *outbuf;
+};
+
+struct uctrl_status {
+ u8 current_temp; /* 0x07 */
+ u8 reset_status; /* 0x0b */
+ u16 event_status; /* 0x0c */
+ u16 error_status; /* 0x10 */
+ u16 external_status; /* 0x11, 0x1b */
+ u8 internal_charge; /* 0x18 */
+ u8 external_charge; /* 0x19 */
+ u16 control_lcd; /* 0x20 */
+ u8 control_bitport; /* 0x21 */
+ u8 speaker_volume; /* 0x23 */
+ u8 control_tft_brightness; /* 0x24 */
+ u8 control_kbd_repeat_delay; /* 0x28 */
+ u8 control_kbd_repeat_period; /* 0x29 */
+ u8 control_screen_contrast; /* 0x2F */
+};
+
+enum uctrl_opcode {
+ READ_SERIAL_NUMBER=0x1,
+ READ_ETHERNET_ADDRESS=0x2,
+ READ_HARDWARE_VERSION=0x3,
+ READ_MICROCONTROLLER_VERSION=0x4,
+ READ_MAX_TEMPERATURE=0x5,
+ READ_MIN_TEMPERATURE=0x6,
+ READ_CURRENT_TEMPERATURE=0x7,
+ READ_SYSTEM_VARIANT=0x8,
+ READ_POWERON_CYCLES=0x9,
+ READ_POWERON_SECONDS=0xA,
+ READ_RESET_STATUS=0xB,
+ READ_EVENT_STATUS=0xC,
+ READ_REAL_TIME_CLOCK=0xD,
+ READ_EXTERNAL_VGA_PORT=0xE,
+ READ_MICROCONTROLLER_ROM_CHECKSUM=0xF,
+ READ_ERROR_STATUS=0x10,
+ READ_EXTERNAL_STATUS=0x11,
+ READ_USER_CONFIGURATION_AREA=0x12,
+ READ_MICROCONTROLLER_VOLTAGE=0x13,
+ READ_INTERNAL_BATTERY_VOLTAGE=0x14,
+ READ_DCIN_VOLTAGE=0x15,
+ READ_HORIZONTAL_POINTER_VOLTAGE=0x16,
+ READ_VERTICAL_POINTER_VOLTAGE=0x17,
+ READ_INTERNAL_BATTERY_CHARGE_LEVEL=0x18,
+ READ_EXTERNAL_BATTERY_CHARGE_LEVEL=0x19,
+ READ_REAL_TIME_CLOCK_ALARM=0x1A,
+ READ_EVENT_STATUS_NO_RESET=0x1B,
+ READ_INTERNAL_KEYBOARD_LAYOUT=0x1C,
+ READ_EXTERNAL_KEYBOARD_LAYOUT=0x1D,
+ READ_EEPROM_STATUS=0x1E,
+ CONTROL_LCD=0x20,
+ CONTROL_BITPORT=0x21,
+ SPEAKER_VOLUME=0x23,
+ CONTROL_TFT_BRIGHTNESS=0x24,
+ CONTROL_WATCHDOG=0x25,
+ CONTROL_FACTORY_EEPROM_AREA=0x26,
+ CONTROL_KBD_TIME_UNTIL_REPEAT=0x28,
+ CONTROL_KBD_TIME_BETWEEN_REPEATS=0x29,
+ CONTROL_TIMEZONE=0x2A,
+ CONTROL_MARK_SPACE_RATIO=0x2B,
+ CONTROL_DIAGNOSTIC_MODE=0x2E,
+ CONTROL_SCREEN_CONTRAST=0x2F,
+ RING_BELL=0x30,
+ SET_DIAGNOSTIC_STATUS=0x32,
+ CLEAR_KEY_COMBINATION_TABLE=0x33,
+ PERFORM_SOFTWARE_RESET=0x34,
+ SET_REAL_TIME_CLOCK=0x35,
+ RECALIBRATE_POINTING_STICK=0x36,
+ SET_BELL_FREQUENCY=0x37,
+ SET_INTERNAL_BATTERY_CHARGE_RATE=0x39,
+ SET_EXTERNAL_BATTERY_CHARGE_RATE=0x3A,
+ SET_REAL_TIME_CLOCK_ALARM=0x3B,
+ READ_EEPROM=0x40,
+ WRITE_EEPROM=0x41,
+ WRITE_TO_STATUS_DISPLAY=0x42,
+ DEFINE_SPECIAL_CHARACTER=0x43,
+ DEFINE_KEY_COMBINATION_ENTRY=0x50,
+ DEFINE_STRING_TABLE_ENTRY=0x51,
+ DEFINE_STATUS_SCREEN_DISPLAY=0x52,
+ PERFORM_EMU_COMMANDS=0x64,
+ READ_EMU_REGISTER=0x65,
+ WRITE_EMU_REGISTER=0x66,
+ READ_EMU_RAM=0x67,
+ WRITE_EMU_RAM=0x68,
+ READ_BQ_REGISTER=0x69,
+ WRITE_BQ_REGISTER=0x6A,
+ SET_USER_PASSWORD=0x70,
+ VERIFY_USER_PASSWORD=0x71,
+ GET_SYSTEM_PASSWORD_KEY=0x72,
+ VERIFY_SYSTEM_PASSWORD=0x73,
+ POWER_OFF=0x82,
+ POWER_RESTART=0x83,
+};
+
+struct uctrl_driver {
+ struct uctrl_regs *regs;
+ int irq;
+ int pending;
+ struct uctrl_status status;
+};
+
+static struct uctrl_driver drv;
+
+void uctrl_get_event_status(void);
+void uctrl_get_external_status(void);
+
+static int
+uctrl_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int
+uctrl_open(struct inode *inode, struct file *file)
+{
+ uctrl_get_event_status();
+ uctrl_get_external_status();
+ return 0;
+}
+
+static irqreturn_t uctrl_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct uctrl_driver *driver = (struct uctrl_driver *)dev_id;
+ printk("in uctrl_interrupt\n");
+ return IRQ_HANDLED;
+}
+
+static struct file_operations uctrl_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .ioctl = uctrl_ioctl,
+ .open = uctrl_open,
+};
+
+static struct miscdevice uctrl_dev = {
+ UCTRL_MINOR,
+ "uctrl",
+ &uctrl_fops
+};
+
+/* Wait for space to write, then write to it */
+#define WRITEUCTLDATA(value) \
+{ \
+ unsigned int i; \
+ for (i = 0; i < 10000; i++) { \
+ if (UCTRL_STAT_TXNF_STA & driver->regs->uctrl_stat) \
+ break; \
+ } \
+ dprintk(("write data 0x%02x\n", value)); \
+ driver->regs->uctrl_data = value; \
+}
+
+/* Wait for something to read, read it, then clear the bit */
+#define READUCTLDATA(value) \
+{ \
+ unsigned int i; \
+ value = 0; \
+ for (i = 0; i < 10000; i++) { \
+ if ((UCTRL_STAT_RXNE_STA & driver->regs->uctrl_stat) == 0) \
+ break; \
+ udelay(1); \
+ } \
+ value = driver->regs->uctrl_data; \
+ dprintk(("read data 0x%02x\n", value)); \
+ driver->regs->uctrl_stat = UCTRL_STAT_RXNE_STA; \
+}
+
+void uctrl_set_video(int status)
+{
+ struct uctrl_driver *driver = &drv;
+
+}
+
+static void uctrl_do_txn(struct uctrl_txn *txn)
+{
+ struct uctrl_driver *driver = &drv;
+ int stat, incnt, outcnt, bytecnt, intr;
+ u32 byte;
+
+ stat = driver->regs->uctrl_stat;
+ intr = driver->regs->uctrl_intr;
+ driver->regs->uctrl_stat = stat;
+
+ dprintk(("interrupt stat 0x%x int 0x%x\n", stat, intr));
+
+ incnt = txn->inbits;
+ outcnt = txn->outbits;
+ byte = (txn->opcode << 8);
+ WRITEUCTLDATA(byte);
+
+ bytecnt = 0;
+ while (incnt > 0) {
+ byte = (txn->inbuf[bytecnt] << 8);
+ WRITEUCTLDATA(byte);
+ incnt--;
+ bytecnt++;
+ }
+
+ /* Get the ack */
+ READUCTLDATA(byte);
+ dprintk(("ack was %x\n", (byte >> 8)));
+
+ bytecnt = 0;
+ while (outcnt > 0) {
+ READUCTLDATA(byte);
+ txn->outbuf[bytecnt] = (byte >> 8);
+ dprintk(("set byte to %02x\n", byte));
+ outcnt--;
+ bytecnt++;
+ }
+}
+
+void uctrl_get_event_status()
+{
+ struct uctrl_driver *driver = &drv;
+ struct uctrl_txn txn;
+ u8 outbits[2];
+
+ txn.opcode = READ_EVENT_STATUS;
+ txn.inbits = 0;
+ txn.outbits = 2;
+ txn.inbuf = 0;
+ txn.outbuf = outbits;
+
+ uctrl_do_txn(&txn);
+
+ dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff)));
+ driver->status.event_status =
+ ((outbits[0] & 0xff) << 8) | (outbits[1] & 0xff);
+ dprintk(("ev is %x\n", driver->status.event_status));
+}
+
+void uctrl_get_external_status()
+{
+ struct uctrl_driver *driver = &drv;
+ struct uctrl_txn txn;
+ u8 outbits[2];
+ int i, v;
+
+ txn.opcode = READ_EXTERNAL_STATUS;
+ txn.inbits = 0;
+ txn.outbits = 2;
+ txn.inbuf = 0;
+ txn.outbuf = outbits;
+
+ uctrl_do_txn(&txn);
+
+ dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff)));
+ driver->status.external_status =
+ ((outbits[0] * 256) + (outbits[1]));
+ dprintk(("ex is %x\n", driver->status.external_status));
+ v = driver->status.external_status;
+ for (i = 0; v != 0; i++, v >>= 1) {
+ if (v & 1) {
+ dprintk(("%s%s", " ", uctrl_extstatus[i]));
+ }
+ }
+ dprintk(("\n"));
+
+}
+
+static int __init ts102_uctrl_init(void)
+{
+ struct uctrl_driver *driver = &drv;
+ int len, i;
+ struct linux_prom_irqs tmp_irq[2];
+ unsigned int vaddr[2] = { 0, 0 };
+ int tmpnode, uctrlnode = prom_getchild(prom_root_node);
+
+ tmpnode = prom_searchsiblings(uctrlnode, "obio");
+
+ if (tmpnode)
+ uctrlnode = prom_getchild(tmpnode);
+
+ uctrlnode = prom_searchsiblings(uctrlnode, "uctrl");
+
+ if (!uctrlnode)
+ return -ENODEV;
+
+ /* the prom mapped it for us */
+ len = prom_getproperty(uctrlnode, "address", (void *) vaddr,
+ sizeof(vaddr));
+ driver->regs = (struct uctrl_regs *)vaddr[0];
+
+ len = prom_getproperty(uctrlnode, "intr", (char *) tmp_irq,
+ sizeof(tmp_irq));
+
+ /* Flush device */
+ READUCTLDATA(len);
+
+ if(!driver->irq)
+ driver->irq = tmp_irq[0].pri;
+
+ request_irq(driver->irq, uctrl_interrupt, 0, "uctrl", driver);
+
+ if (misc_register(&uctrl_dev)) {
+ printk("%s: unable to get misc minor %d\n",
+ __FUNCTION__, uctrl_dev.minor);
+ free_irq(driver->irq, driver);
+ return -ENODEV;
+ }
+
+ driver->regs->uctrl_intr = UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK;
+ printk("uctrl: 0x%x (irq %s)\n", driver->regs, __irq_itoa(driver->irq));
+ uctrl_get_event_status();
+ uctrl_get_external_status();
+ return 0;
+}
+
+static void __exit ts102_uctrl_cleanup(void)
+{
+ struct uctrl_driver *driver = &drv;
+
+ misc_deregister(&uctrl_dev);
+ if (driver->irq)
+ free_irq(driver->irq, driver);
+ if (driver->regs)
+ driver->regs = 0;
+}
+
+module_init(ts102_uctrl_init);
+module_exit(ts102_uctrl_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/vfc.h b/drivers/sbus/char/vfc.h
new file mode 100644
index 00000000000..e56a43af0f6
--- /dev/null
+++ b/drivers/sbus/char/vfc.h
@@ -0,0 +1,179 @@
+#ifndef _LINUX_VFC_H_
+#define _LINUX_VFC_H_
+
+#include <linux/devfs_fs_kernel.h>
+
+/*
+ * The control register for the vfc is at offset 0x4000
+ * The first field ram bank is located at offset 0x5000
+ * The second field ram bank is at offset 0x7000
+ * i2c_reg address the Phillips PCF8584(see notes in vfc_i2c.c)
+ * data and transmit register.
+ * i2c_s1 controls register s1 of the PCF8584
+ * i2c_write seems to be similar to i2c_write but I am not
+ * quite sure why sun uses it
+ *
+ * I am also not sure whether or not you can read the fram bank as a
+ * whole or whether you must read each word individually from offset
+ * 0x5000 as soon as I figure it out I will update this file */
+
+struct vfc_regs {
+ char pad1[0x4000];
+ unsigned int control; /* Offset 0x4000 */
+ char pad2[0xffb]; /* from offset 0x4004 to 0x5000 */
+ unsigned int fram_bank1; /* Offset 0x5000 */
+ char pad3[0xffb]; /* from offset 0x5004 to 0x6000 */
+ unsigned int i2c_reg; /* Offset 0x6000 */
+ unsigned int i2c_magic2; /* Offset 0x6004 */
+ unsigned int i2c_s1; /* Offset 0x6008 */
+ unsigned int i2c_write; /* Offset 0x600c */
+ char pad4[0xff0]; /* from offset 0x6010 to 0x7000 */
+ unsigned int fram_bank2; /* Offset 0x7000 */
+ char pad5[0x1000];
+};
+
+#define VFC_SAA9051_NR (13)
+#define VFC_SAA9051_ADDR (0x8a)
+ /* The saa9051 returns the following for its status
+ * bit 0 - 0
+ * bit 1 - SECAM color detected (1=found,0=not found)
+ * bit 2 - COLOR detected (1=found,0=not found)
+ * bit 3 - 0
+ * bit 4 - Field frequency bit (1=60Hz (NTSC), 0=50Hz (PAL))
+ * bit 5 - 1
+ * bit 6 - horizontal frequency lock (1=transmitter found,
+ * 0=no transmitter)
+ * bit 7 - Power on reset bit (1=reset,0=at least one successful
+ * read of the status byte)
+ */
+
+#define VFC_SAA9051_PONRES (0x80)
+#define VFC_SAA9051_HLOCK (0x40)
+#define VFC_SAA9051_FD (0x10)
+#define VFC_SAA9051_CD (0x04)
+#define VFC_SAA9051_CS (0x02)
+
+
+/* The various saa9051 sub addresses */
+
+#define VFC_SAA9051_IDEL (0)
+#define VFC_SAA9051_HSY_START (1)
+#define VFC_SAA9051_HSY_STOP (2)
+#define VFC_SAA9051_HC_START (3)
+#define VFC_SAA9051_HC_STOP (4)
+#define VFC_SAA9051_HS_START (5)
+#define VFC_SAA9051_HORIZ_PEAK (6)
+#define VFC_SAA9051_HUE (7)
+#define VFC_SAA9051_C1 (8)
+#define VFC_SAA9051_C2 (9)
+#define VFC_SAA9051_C3 (0xa)
+#define VFC_SAA9051_SECAM_DELAY (0xb)
+
+
+/* Bit settings for saa9051 sub address 0x06 */
+
+#define VFC_SAA9051_AP1 (0x01)
+#define VFC_SAA9051_AP2 (0x02)
+#define VFC_SAA9051_COR1 (0x04)
+#define VFC_SAA9051_COR2 (0x08)
+#define VFC_SAA9051_BP1 (0x10)
+#define VFC_SAA9051_BP2 (0x20)
+#define VFC_SAA9051_PF (0x40)
+#define VFC_SAA9051_BY (0x80)
+
+
+/* Bit settings for saa9051 sub address 0x08 */
+
+#define VFC_SAA9051_CCFR0 (0x01)
+#define VFC_SAA9051_CCFR1 (0x02)
+#define VFC_SAA9051_YPN (0x04)
+#define VFC_SAA9051_ALT (0x08)
+#define VFC_SAA9051_CO (0x10)
+#define VFC_SAA9051_VTR (0x20)
+#define VFC_SAA9051_FS (0x40)
+#define VFC_SAA9051_HPLL (0x80)
+
+
+/* Bit settings for saa9051 sub address 9 */
+
+#define VFC_SAA9051_SS0 (0x01)
+#define VFC_SAA9051_SS1 (0x02)
+#define VFC_SAA9051_AFCC (0x04)
+#define VFC_SAA9051_CI (0x08)
+#define VFC_SAA9051_SA9D4 (0x10) /* Don't care bit */
+#define VFC_SAA9051_OEC (0x20)
+#define VFC_SAA9051_OEY (0x40)
+#define VFC_SAA9051_VNL (0x80)
+
+
+/* Bit settings for saa9051 sub address 0x0A */
+
+#define VFC_SAA9051_YDL0 (0x01)
+#define VFC_SAA9051_YDL1 (0x02)
+#define VFC_SAA9051_YDL2 (0x04)
+#define VFC_SAA9051_SS2 (0x08)
+#define VFC_SAA9051_SS3 (0x10)
+#define VFC_SAA9051_YC (0x20)
+#define VFC_SAA9051_CT (0x40)
+#define VFC_SAA9051_SYC (0x80)
+
+
+#define VFC_SAA9051_SA(a,b) ((a)->saa9051_state_array[(b)+1])
+#define vfc_update_saa9051(a) (vfc_i2c_sendbuf((a),VFC_SAA9051_ADDR,\
+ (a)->saa9051_state_array,\
+ VFC_SAA9051_NR))
+
+
+struct vfc_dev {
+ volatile struct vfc_regs *regs;
+ struct vfc_regs *phys_regs;
+ unsigned int control_reg;
+ struct semaphore device_lock_sem;
+ struct timer_list poll_timer;
+ wait_queue_head_t poll_wait;
+ int instance;
+ int busy;
+ unsigned long which_io;
+ unsigned char saa9051_state_array[VFC_SAA9051_NR];
+};
+
+extern struct vfc_dev **vfc_dev_lst;
+
+void captstat_reset(struct vfc_dev *);
+void memptr_reset(struct vfc_dev *);
+
+int vfc_pcf8584_init(struct vfc_dev *);
+void vfc_i2c_delay_no_busy(struct vfc_dev *, unsigned long);
+void vfc_i2c_delay(struct vfc_dev *);
+int vfc_i2c_sendbuf(struct vfc_dev *, unsigned char, char *, int) ;
+int vfc_i2c_recvbuf(struct vfc_dev *, unsigned char, char *, int) ;
+int vfc_i2c_reset_bus(struct vfc_dev *);
+int vfc_init_i2c_bus(struct vfc_dev *);
+void vfc_lock_device(struct vfc_dev *);
+void vfc_unlock_device(struct vfc_dev *);
+
+#define VFC_CONTROL_DIAGMODE 0x10000000
+#define VFC_CONTROL_MEMPTR 0x20000000
+#define VFC_CONTROL_CAPTURE 0x02000000
+#define VFC_CONTROL_CAPTRESET 0x04000000
+
+#define VFC_STATUS_CAPTURE 0x08000000
+
+#ifdef VFC_IOCTL_DEBUG
+#define VFC_IOCTL_DEBUG_PRINTK(a) printk a
+#else
+#define VFC_IOCTL_DEBUG_PRINTK(a)
+#endif
+
+#ifdef VFC_I2C_DEBUG
+#define VFC_I2C_DEBUG_PRINTK(a) printk a
+#else
+#define VFC_I2C_DEBUG_PRINTK(a)
+#endif
+
+#endif /* _LINUX_VFC_H_ */
+
+
+
+
+
diff --git a/drivers/sbus/char/vfc_dev.c b/drivers/sbus/char/vfc_dev.c
new file mode 100644
index 00000000000..86ce5413095
--- /dev/null
+++ b/drivers/sbus/char/vfc_dev.c
@@ -0,0 +1,742 @@
+/*
+ * drivers/sbus/char/vfc_dev.c
+ *
+ * Driver for the Videopix Frame Grabber.
+ *
+ * In order to use the VFC you need to program the video controller
+ * chip. This chip is the Phillips SAA9051. You need to call their
+ * documentation ordering line to get the docs.
+ *
+ * There is very little documentation on the VFC itself. There is
+ * some useful info that can be found in the manuals that come with
+ * the card. I will hopefully write some better docs at a later date.
+ *
+ * Copyright (C) 1996 Manish Vachharajani (mvachhar@noc.rutgers.edu)
+ * */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/smp_lock.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/sbus.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/uaccess.h>
+
+#define VFC_MAJOR (60)
+
+#if 0
+#define VFC_IOCTL_DEBUG
+#endif
+
+#include "vfc.h"
+#include <asm/vfc_ioctls.h>
+
+static struct file_operations vfc_fops;
+struct vfc_dev **vfc_dev_lst;
+static char vfcstr[]="vfc";
+static unsigned char saa9051_init_array[VFC_SAA9051_NR] = {
+ 0x00, 0x64, 0x72, 0x52,
+ 0x36, 0x18, 0xff, 0x20,
+ 0xfc, 0x77, 0xe3, 0x50,
+ 0x3e
+};
+
+void vfc_lock_device(struct vfc_dev *dev)
+{
+ down(&dev->device_lock_sem);
+}
+
+void vfc_unlock_device(struct vfc_dev *dev)
+{
+ up(&dev->device_lock_sem);
+}
+
+
+void vfc_captstat_reset(struct vfc_dev *dev)
+{
+ dev->control_reg |= VFC_CONTROL_CAPTRESET;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ dev->control_reg &= ~VFC_CONTROL_CAPTRESET;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ dev->control_reg |= VFC_CONTROL_CAPTRESET;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+}
+
+void vfc_memptr_reset(struct vfc_dev *dev)
+{
+ dev->control_reg |= VFC_CONTROL_MEMPTR;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ dev->control_reg &= ~VFC_CONTROL_MEMPTR;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ dev->control_reg |= VFC_CONTROL_MEMPTR;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+}
+
+int vfc_csr_init(struct vfc_dev *dev)
+{
+ dev->control_reg = 0x80000000;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ udelay(200);
+ dev->control_reg &= ~0x80000000;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ udelay(100);
+ sbus_writel(0x0f000000, &dev->regs->i2c_magic2);
+
+ vfc_memptr_reset(dev);
+
+ dev->control_reg &= ~VFC_CONTROL_DIAGMODE;
+ dev->control_reg &= ~VFC_CONTROL_CAPTURE;
+ dev->control_reg |= 0x40000000;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+
+ vfc_captstat_reset(dev);
+
+ return 0;
+}
+
+int vfc_saa9051_init(struct vfc_dev *dev)
+{
+ int i;
+
+ for (i = 0; i < VFC_SAA9051_NR; i++)
+ dev->saa9051_state_array[i] = saa9051_init_array[i];
+
+ vfc_i2c_sendbuf(dev,VFC_SAA9051_ADDR,
+ dev->saa9051_state_array, VFC_SAA9051_NR);
+ return 0;
+}
+
+int init_vfc_hw(struct vfc_dev *dev)
+{
+ vfc_lock_device(dev);
+ vfc_csr_init(dev);
+
+ vfc_pcf8584_init(dev);
+ vfc_init_i2c_bus(dev); /* hopefully this doesn't undo the magic
+ sun code above*/
+ vfc_saa9051_init(dev);
+ vfc_unlock_device(dev);
+ return 0;
+}
+
+int init_vfc_devstruct(struct vfc_dev *dev, int instance)
+{
+ dev->instance=instance;
+ init_MUTEX(&dev->device_lock_sem);
+ dev->control_reg=0;
+ init_waitqueue_head(&dev->poll_wait);
+ dev->busy=0;
+ return 0;
+}
+
+int init_vfc_device(struct sbus_dev *sdev,struct vfc_dev *dev, int instance)
+{
+ if(dev == NULL) {
+ printk(KERN_ERR "VFC: Bogus pointer passed\n");
+ return -ENOMEM;
+ }
+ printk("Initializing vfc%d\n",instance);
+ dev->regs = NULL;
+ dev->regs = (volatile struct vfc_regs *)
+ sbus_ioremap(&sdev->resource[0], 0,
+ sizeof(struct vfc_regs), vfcstr);
+ dev->which_io = sdev->reg_addrs[0].which_io;
+ dev->phys_regs = (struct vfc_regs *) sdev->reg_addrs[0].phys_addr;
+ if (dev->regs == NULL)
+ return -EIO;
+
+ printk("vfc%d: registers mapped at phys_addr: 0x%lx\n virt_addr: 0x%lx\n",
+ instance,(unsigned long)sdev->reg_addrs[0].phys_addr,(unsigned long)dev->regs);
+
+ if (init_vfc_devstruct(dev, instance))
+ return -EINVAL;
+ if (init_vfc_hw(dev))
+ return -EIO;
+
+ devfs_mk_cdev(MKDEV(VFC_MAJOR, instance),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "vfc/%d", instance);
+ return 0;
+}
+
+
+struct vfc_dev *vfc_get_dev_ptr(int instance)
+{
+ return vfc_dev_lst[instance];
+}
+
+static DEFINE_SPINLOCK(vfc_dev_lock);
+
+static int vfc_open(struct inode *inode, struct file *file)
+{
+ struct vfc_dev *dev;
+
+ spin_lock(&vfc_dev_lock);
+ dev = vfc_get_dev_ptr(iminor(inode));
+ if (dev == NULL) {
+ spin_unlock(&vfc_dev_lock);
+ return -ENODEV;
+ }
+ if (dev->busy) {
+ spin_unlock(&vfc_dev_lock);
+ return -EBUSY;
+ }
+
+ dev->busy = 1;
+ spin_unlock(&vfc_dev_lock);
+
+ vfc_lock_device(dev);
+
+ vfc_csr_init(dev);
+ vfc_pcf8584_init(dev);
+ vfc_init_i2c_bus(dev);
+ vfc_saa9051_init(dev);
+ vfc_memptr_reset(dev);
+ vfc_captstat_reset(dev);
+
+ vfc_unlock_device(dev);
+ return 0;
+}
+
+static int vfc_release(struct inode *inode,struct file *file)
+{
+ struct vfc_dev *dev;
+
+ spin_lock(&vfc_dev_lock);
+ dev = vfc_get_dev_ptr(iminor(inode));
+ if (!dev || !dev->busy) {
+ spin_unlock(&vfc_dev_lock);
+ return -EINVAL;
+ }
+ dev->busy = 0;
+ spin_unlock(&vfc_dev_lock);
+ return 0;
+}
+
+static int vfc_debug(struct vfc_dev *dev, int cmd, void __user *argp)
+{
+ struct vfc_debug_inout inout;
+ unsigned char *buffer;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ switch(cmd) {
+ case VFC_I2C_SEND:
+ if(copy_from_user(&inout, argp, sizeof(inout)))
+ return -EFAULT;
+
+ buffer = kmalloc(inout.len, GFP_KERNEL);
+ if (buffer == NULL)
+ return -ENOMEM;
+
+ if(copy_from_user(buffer, inout.buffer, inout.len)) {
+ kfree(buffer);
+ return -EFAULT;
+ }
+
+
+ vfc_lock_device(dev);
+ inout.ret=
+ vfc_i2c_sendbuf(dev,inout.addr & 0xff,
+ buffer,inout.len);
+
+ if (copy_to_user(argp,&inout,sizeof(inout))) {
+ kfree(buffer);
+ return -EFAULT;
+ }
+ vfc_unlock_device(dev);
+
+ break;
+ case VFC_I2C_RECV:
+ if (copy_from_user(&inout, argp, sizeof(inout)))
+ return -EFAULT;
+
+ buffer = kmalloc(inout.len, GFP_KERNEL);
+ if (buffer == NULL)
+ return -ENOMEM;
+
+ memset(buffer,0,inout.len);
+ vfc_lock_device(dev);
+ inout.ret=
+ vfc_i2c_recvbuf(dev,inout.addr & 0xff
+ ,buffer,inout.len);
+ vfc_unlock_device(dev);
+
+ if (copy_to_user(inout.buffer, buffer, inout.len)) {
+ kfree(buffer);
+ return -EFAULT;
+ }
+ if (copy_to_user(argp,&inout,sizeof(inout))) {
+ kfree(buffer);
+ return -EFAULT;
+ }
+ kfree(buffer);
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+int vfc_capture_start(struct vfc_dev *dev)
+{
+ vfc_captstat_reset(dev);
+ dev->control_reg = sbus_readl(&dev->regs->control);
+ if((dev->control_reg & VFC_STATUS_CAPTURE)) {
+ printk(KERN_ERR "vfc%d: vfc capture status not reset\n",
+ dev->instance);
+ return -EIO;
+ }
+
+ vfc_lock_device(dev);
+ dev->control_reg &= ~VFC_CONTROL_CAPTURE;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ dev->control_reg |= VFC_CONTROL_CAPTURE;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ dev->control_reg &= ~VFC_CONTROL_CAPTURE;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ vfc_unlock_device(dev);
+
+ return 0;
+}
+
+int vfc_capture_poll(struct vfc_dev *dev)
+{
+ int timeout = 1000;
+
+ while (!timeout--) {
+ if (dev->regs->control & VFC_STATUS_CAPTURE)
+ break;
+ vfc_i2c_delay_no_busy(dev, 100);
+ }
+ if(!timeout) {
+ printk(KERN_WARNING "vfc%d: capture timed out\n",
+ dev->instance);
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
+
+
+
+static int vfc_set_control_ioctl(struct inode *inode, struct file *file,
+ struct vfc_dev *dev, unsigned long arg)
+{
+ int setcmd, ret = 0;
+
+ if (copy_from_user(&setcmd,(void __user *)arg,sizeof(unsigned int)))
+ return -EFAULT;
+
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCSCTRL) arg=0x%x\n",
+ dev->instance,setcmd));
+
+ switch(setcmd) {
+ case MEMPRST:
+ vfc_lock_device(dev);
+ vfc_memptr_reset(dev);
+ vfc_unlock_device(dev);
+ ret=0;
+ break;
+ case CAPTRCMD:
+ vfc_capture_start(dev);
+ vfc_capture_poll(dev);
+ break;
+ case DIAGMODE:
+ if(capable(CAP_SYS_ADMIN)) {
+ vfc_lock_device(dev);
+ dev->control_reg |= VFC_CONTROL_DIAGMODE;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ vfc_unlock_device(dev);
+ ret = 0;
+ } else {
+ ret = -EPERM;
+ }
+ break;
+ case NORMMODE:
+ vfc_lock_device(dev);
+ dev->control_reg &= ~VFC_CONTROL_DIAGMODE;
+ sbus_writel(dev->control_reg, &dev->regs->control);
+ vfc_unlock_device(dev);
+ ret = 0;
+ break;
+ case CAPTRSTR:
+ vfc_capture_start(dev);
+ ret = 0;
+ break;
+ case CAPTRWAIT:
+ vfc_capture_poll(dev);
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ };
+
+ return ret;
+}
+
+
+int vfc_port_change_ioctl(struct inode *inode, struct file *file,
+ struct vfc_dev *dev, unsigned long arg)
+{
+ int ret = 0;
+ int cmd;
+
+ if(copy_from_user(&cmd, (void __user *)arg, sizeof(unsigned int))) {
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer to "
+ "vfc_port_change_ioctl\n",
+ dev->instance));
+ return -EFAULT;
+ }
+
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCPORTCHG) arg=0x%x\n",
+ dev->instance, cmd));
+
+ switch(cmd) {
+ case 1:
+ case 2:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_START) = 0x72;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_STOP) = 0x52;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HC_START) = 0x36;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HC_STOP) = 0x18;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) = VFC_SAA9051_BP2;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C3) = VFC_SAA9051_CT | VFC_SAA9051_SS3;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_SECAM_DELAY) = 0x3e;
+ break;
+ case 3:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_START) = 0x3a;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_STOP) = 0x17;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HC_START) = 0xfa;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HC_STOP) = 0xde;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) =
+ VFC_SAA9051_BY | VFC_SAA9051_PF | VFC_SAA9051_BP2;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C3) = VFC_SAA9051_YC;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_SECAM_DELAY) = 0;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C2) &=
+ ~(VFC_SAA9051_SS0 | VFC_SAA9051_SS1);
+ break;
+ default:
+ ret = -EINVAL;
+ return ret;
+ break;
+ }
+
+ switch(cmd) {
+ case 1:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C2) |=
+ (VFC_SAA9051_SS0 | VFC_SAA9051_SS1);
+ break;
+ case 2:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C2) &=
+ ~(VFC_SAA9051_SS0 | VFC_SAA9051_SS1);
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C2) |= VFC_SAA9051_SS0;
+ break;
+ case 3:
+ break;
+ default:
+ ret = -EINVAL;
+ return ret;
+ break;
+ }
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C3) &= ~(VFC_SAA9051_SS2);
+ ret=vfc_update_saa9051(dev);
+ udelay(500);
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C3) |= (VFC_SAA9051_SS2);
+ ret=vfc_update_saa9051(dev);
+ return ret;
+}
+
+int vfc_set_video_ioctl(struct inode *inode, struct file *file,
+ struct vfc_dev *dev, unsigned long arg)
+{
+ int ret = 0;
+ int cmd;
+
+ if(copy_from_user(&cmd, (void __user *)arg, sizeof(unsigned int))) {
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer to "
+ "vfc_set_video_ioctl\n",
+ dev->instance));
+ return ret;
+ }
+
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCSVID) arg=0x%x\n",
+ dev->instance, cmd));
+ switch(cmd) {
+ case STD_NTSC:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C1) &= ~VFC_SAA9051_ALT;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C1) |= VFC_SAA9051_YPN |
+ VFC_SAA9051_CCFR0 | VFC_SAA9051_CCFR1 | VFC_SAA9051_FS;
+ ret = vfc_update_saa9051(dev);
+ break;
+ case STD_PAL:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C1) &= ~(VFC_SAA9051_YPN |
+ VFC_SAA9051_CCFR1 |
+ VFC_SAA9051_CCFR0 |
+ VFC_SAA9051_FS);
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C1) |= VFC_SAA9051_ALT;
+ ret = vfc_update_saa9051(dev);
+ break;
+
+ case COLOR_ON:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C1) |= VFC_SAA9051_CO;
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) &=
+ ~(VFC_SAA9051_BY | VFC_SAA9051_PF);
+ ret = vfc_update_saa9051(dev);
+ break;
+ case MONO:
+ VFC_SAA9051_SA(dev,VFC_SAA9051_C1) &= ~(VFC_SAA9051_CO);
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) |=
+ (VFC_SAA9051_BY | VFC_SAA9051_PF);
+ ret = vfc_update_saa9051(dev);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ };
+
+ return ret;
+}
+
+int vfc_get_video_ioctl(struct inode *inode, struct file *file,
+ struct vfc_dev *dev, unsigned long arg)
+{
+ int ret = 0;
+ unsigned int status = NO_LOCK;
+ unsigned char buf[1];
+
+ if(vfc_i2c_recvbuf(dev, VFC_SAA9051_ADDR, buf, 1)) {
+ printk(KERN_ERR "vfc%d: Unable to get status\n",
+ dev->instance);
+ return -EIO;
+ }
+
+ if(buf[0] & VFC_SAA9051_HLOCK) {
+ status = NO_LOCK;
+ } else if(buf[0] & VFC_SAA9051_FD) {
+ if(buf[0] & VFC_SAA9051_CD)
+ status = NTSC_COLOR;
+ else
+ status = NTSC_NOCOLOR;
+ } else {
+ if(buf[0] & VFC_SAA9051_CD)
+ status = PAL_COLOR;
+ else
+ status = PAL_NOCOLOR;
+ }
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCGVID) returning status 0x%x; "
+ "buf[0]=%x\n", dev->instance, status, buf[0]));
+
+ if (copy_to_user((void __user *)arg,&status,sizeof(unsigned int))) {
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer to "
+ "vfc_get_video_ioctl\n",
+ dev->instance));
+ return ret;
+ }
+ return ret;
+}
+
+static int vfc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = 0;
+ unsigned int tmp;
+ struct vfc_dev *dev;
+ void __user *argp = (void __user *)arg;
+
+ dev = vfc_get_dev_ptr(iminor(inode));
+ if(dev == NULL)
+ return -ENODEV;
+
+ switch(cmd & 0x0000ffff) {
+ case VFCGCTRL:
+#if 0
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCGCTRL)\n", dev->instance));
+#endif
+ tmp = sbus_readl(&dev->regs->control);
+ if(copy_to_user(argp, &tmp, sizeof(unsigned int))) {
+ ret = -EFAULT;
+ break;
+ }
+ ret = 0;
+ break;
+ case VFCSCTRL:
+ ret = vfc_set_control_ioctl(inode, file, dev, arg);
+ break;
+ case VFCGVID:
+ ret = vfc_get_video_ioctl(inode, file, dev, arg);
+ break;
+ case VFCSVID:
+ ret = vfc_set_video_ioctl(inode, file, dev, arg);
+ break;
+ case VFCHUE:
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCHUE)\n", dev->instance));
+ if(copy_from_user(&tmp,argp,sizeof(unsigned int))) {
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer "
+ "to IOCTL(VFCHUE)", dev->instance));
+ ret = -EFAULT;
+ } else {
+ VFC_SAA9051_SA(dev,VFC_SAA9051_HUE) = tmp;
+ vfc_update_saa9051(dev);
+ ret = 0;
+ }
+ break;
+ case VFCPORTCHG:
+ ret = vfc_port_change_ioctl(inode, file, dev, arg);
+ break;
+ case VFCRDINFO:
+ ret = -EINVAL;
+ VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCRDINFO)\n", dev->instance));
+ break;
+ default:
+ ret = vfc_debug(vfc_get_dev_ptr(iminor(inode)), cmd, argp);
+ break;
+ };
+
+ return ret;
+}
+
+static int vfc_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ unsigned int map_size, ret, map_offset;
+ struct vfc_dev *dev;
+
+ dev = vfc_get_dev_ptr(iminor(file->f_dentry->d_inode));
+ if(dev == NULL)
+ return -ENODEV;
+
+ map_size = vma->vm_end - vma->vm_start;
+ if(map_size > sizeof(struct vfc_regs))
+ map_size = sizeof(struct vfc_regs);
+
+ vma->vm_flags |=
+ (VM_SHM | VM_LOCKED | VM_IO | VM_MAYREAD | VM_MAYWRITE | VM_MAYSHARE);
+ map_offset = (unsigned int) (long)dev->phys_regs;
+ ret = io_remap_pfn_range(vma, vma->vm_start,
+ MK_IOSPACE_PFN(dev->which_io,
+ map_offset >> PAGE_SHIFT),
+ map_size, vma->vm_page_prot);
+
+ if(ret)
+ return -EAGAIN;
+
+ return 0;
+}
+
+
+static struct file_operations vfc_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .ioctl = vfc_ioctl,
+ .mmap = vfc_mmap,
+ .open = vfc_open,
+ .release = vfc_release,
+};
+
+static int vfc_probe(void)
+{
+ struct sbus_bus *sbus;
+ struct sbus_dev *sdev = NULL;
+ int ret;
+ int instance = 0, cards = 0;
+
+ for_all_sbusdev(sdev, sbus) {
+ if (strcmp(sdev->prom_name, "vfc") == 0) {
+ cards++;
+ continue;
+ }
+ }
+
+ if (!cards)
+ return -ENODEV;
+
+ vfc_dev_lst = (struct vfc_dev **)kmalloc(sizeof(struct vfc_dev *) *
+ (cards+1),
+ GFP_KERNEL);
+ if (vfc_dev_lst == NULL)
+ return -ENOMEM;
+ memset(vfc_dev_lst, 0, sizeof(struct vfc_dev *) * (cards + 1));
+ vfc_dev_lst[cards] = NULL;
+
+ ret = register_chrdev(VFC_MAJOR, vfcstr, &vfc_fops);
+ if(ret) {
+ printk(KERN_ERR "Unable to get major number %d\n", VFC_MAJOR);
+ kfree(vfc_dev_lst);
+ return -EIO;
+ }
+ devfs_mk_dir("vfc");
+ instance = 0;
+ for_all_sbusdev(sdev, sbus) {
+ if (strcmp(sdev->prom_name, "vfc") == 0) {
+ vfc_dev_lst[instance]=(struct vfc_dev *)
+ kmalloc(sizeof(struct vfc_dev), GFP_KERNEL);
+ if (vfc_dev_lst[instance] == NULL)
+ return -ENOMEM;
+ ret = init_vfc_device(sdev,
+ vfc_dev_lst[instance],
+ instance);
+ if(ret) {
+ printk(KERN_ERR "Unable to initialize"
+ " vfc%d device\n",
+ instance);
+ } else {
+ }
+
+ instance++;
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+#else
+int vfc_init(void)
+#endif
+{
+ return vfc_probe();
+}
+
+#ifdef MODULE
+static void deinit_vfc_device(struct vfc_dev *dev)
+{
+ if(dev == NULL)
+ return;
+ devfs_remove("vfc/%d", dev->instance);
+ sbus_iounmap((unsigned long)dev->regs, sizeof(struct vfc_regs));
+ kfree(dev);
+}
+
+void cleanup_module(void)
+{
+ struct vfc_dev **devp;
+
+ unregister_chrdev(VFC_MAJOR,vfcstr);
+
+ for (devp = vfc_dev_lst; *devp; devp++)
+ deinit_vfc_device(*devp);
+
+ devfs_remove("vfc");
+ kfree(vfc_dev_lst);
+ return;
+}
+#endif
+
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/sbus/char/vfc_i2c.c b/drivers/sbus/char/vfc_i2c.c
new file mode 100644
index 00000000000..95e3cebf792
--- /dev/null
+++ b/drivers/sbus/char/vfc_i2c.c
@@ -0,0 +1,347 @@
+/*
+ * drivers/sbus/char/vfc_i2c.c
+ *
+ * Driver for the Videopix Frame Grabber.
+ *
+ * Functions that support the Phillips i2c(I squared C) bus on the vfc
+ * Documentation for the Phillips I2C bus can be found on the
+ * phillips home page
+ *
+ * Copyright (C) 1996 Manish Vachharajani (mvachhar@noc.rutgers.edu)
+ *
+ */
+
+/* NOTE: It seems to me that the documentation regarding the
+pcd8584t/pcf8584 does not show the correct way to address the i2c bus.
+Based on the information on the I2C bus itself and the remainder of
+the Phillips docs the following algorithims apper to be correct. I am
+fairly certain that the flowcharts in the phillips docs are wrong. */
+
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/sbus.h>
+
+#if 0
+#define VFC_I2C_DEBUG
+#endif
+
+#include "vfc.h"
+#include "vfc_i2c.h"
+
+#define WRITE_S1(__val) \
+ sbus_writel(__val, &dev->regs->i2c_s1)
+#define WRITE_REG(__val) \
+ sbus_writel(__val, &dev->regs->i2c_reg)
+
+#define VFC_I2C_READ (0x1)
+#define VFC_I2C_WRITE (0x0)
+
+/******
+ The i2c bus controller chip on the VFC is a pcd8584t, but
+ phillips claims it doesn't exist. As far as I can tell it is
+ identical to the PCF8584 so I treat it like it is the pcf8584.
+
+ NOTE: The pcf8584 only cares
+ about the msb of the word you feed it
+*****/
+
+int vfc_pcf8584_init(struct vfc_dev *dev)
+{
+ /* This will also choose register S0_OWN so we can set it. */
+ WRITE_S1(RESET);
+
+ /* The pcf8584 shifts this value left one bit and uses
+ * it as its i2c bus address.
+ */
+ WRITE_REG(0x55000000);
+
+ /* This will set the i2c bus at the same speed sun uses,
+ * and set another magic bit.
+ */
+ WRITE_S1(SELECT(S2));
+ WRITE_REG(0x14000000);
+
+ /* Enable the serial port, idle the i2c bus and set
+ * the data reg to s0.
+ */
+ WRITE_S1(CLEAR_I2C_BUS);
+ udelay(100);
+ return 0;
+}
+
+void vfc_i2c_delay_wakeup(struct vfc_dev *dev)
+{
+ /* Used to profile code and eliminate too many delays */
+ VFC_I2C_DEBUG_PRINTK(("vfc%d: Delaying\n", dev->instance));
+ wake_up(&dev->poll_wait);
+}
+
+void vfc_i2c_delay_no_busy(struct vfc_dev *dev, unsigned long usecs)
+{
+ init_timer(&dev->poll_timer);
+ dev->poll_timer.expires = jiffies +
+ ((unsigned long)usecs*(HZ))/1000000;
+ dev->poll_timer.data=(unsigned long)dev;
+ dev->poll_timer.function=(void *)(unsigned long)vfc_i2c_delay_wakeup;
+ add_timer(&dev->poll_timer);
+ sleep_on(&dev->poll_wait);
+ del_timer(&dev->poll_timer);
+}
+
+void inline vfc_i2c_delay(struct vfc_dev *dev)
+{
+ vfc_i2c_delay_no_busy(dev, 100);
+}
+
+int vfc_init_i2c_bus(struct vfc_dev *dev)
+{
+ WRITE_S1(ENABLE_SERIAL | SELECT(S0) | ACK);
+ vfc_i2c_reset_bus(dev);
+ return 0;
+}
+
+int vfc_i2c_reset_bus(struct vfc_dev *dev)
+{
+ VFC_I2C_DEBUG_PRINTK((KERN_DEBUG "vfc%d: Resetting the i2c bus\n",
+ dev->instance));
+ if(dev == NULL)
+ return -EINVAL;
+ if(dev->regs == NULL)
+ return -EINVAL;
+ WRITE_S1(SEND_I2C_STOP);
+ WRITE_S1(SEND_I2C_STOP | ACK);
+ vfc_i2c_delay(dev);
+ WRITE_S1(CLEAR_I2C_BUS);
+ VFC_I2C_DEBUG_PRINTK((KERN_DEBUG "vfc%d: I2C status %x\n",
+ dev->instance,
+ sbus_readl(&dev->regs->i2c_s1)));
+ return 0;
+}
+
+int vfc_i2c_wait_for_bus(struct vfc_dev *dev)
+{
+ int timeout = 1000;
+
+ while(!(sbus_readl(&dev->regs->i2c_s1) & BB)) {
+ if(!(timeout--))
+ return -ETIMEDOUT;
+ vfc_i2c_delay(dev);
+ }
+ return 0;
+}
+
+int vfc_i2c_wait_for_pin(struct vfc_dev *dev, int ack)
+{
+ int timeout = 1000;
+ int s1;
+
+ while ((s1 = sbus_readl(&dev->regs->i2c_s1)) & PIN) {
+ if (!(timeout--))
+ return -ETIMEDOUT;
+ vfc_i2c_delay(dev);
+ }
+ if (ack == VFC_I2C_ACK_CHECK) {
+ if(s1 & LRB)
+ return -EIO;
+ }
+ return 0;
+}
+
+#define SHIFT(a) ((a) << 24)
+int vfc_i2c_xmit_addr(struct vfc_dev *dev, unsigned char addr, char mode)
+{
+ int ret, raddr;
+#if 1
+ WRITE_S1(SEND_I2C_STOP | ACK);
+ WRITE_S1(SELECT(S0) | ENABLE_SERIAL);
+ vfc_i2c_delay(dev);
+#endif
+
+ switch(mode) {
+ case VFC_I2C_READ:
+ raddr = SHIFT(((unsigned int)addr | 0x1));
+ WRITE_REG(raddr);
+ VFC_I2C_DEBUG_PRINTK(("vfc%d: receiving from i2c addr 0x%x\n",
+ dev->instance, addr | 0x1));
+ break;
+ case VFC_I2C_WRITE:
+ raddr = SHIFT((unsigned int)addr & ~0x1);
+ WRITE_REG(raddr);
+ VFC_I2C_DEBUG_PRINTK(("vfc%d: sending to i2c addr 0x%x\n",
+ dev->instance, addr & ~0x1));
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ WRITE_S1(SEND_I2C_START);
+ vfc_i2c_delay(dev);
+ ret = vfc_i2c_wait_for_pin(dev,VFC_I2C_ACK_CHECK); /* We wait
+ for the
+ i2c send
+ to finish
+ here but
+ Sun
+ doesn't,
+ hmm */
+ if (ret) {
+ printk(KERN_ERR "vfc%d: VFC xmit addr timed out or no ack\n",
+ dev->instance);
+ return ret;
+ } else if (mode == VFC_I2C_READ) {
+ if ((ret = sbus_readl(&dev->regs->i2c_reg) & 0xff000000) != raddr) {
+ printk(KERN_WARNING
+ "vfc%d: returned slave address "
+ "mismatch(%x,%x)\n",
+ dev->instance, raddr, ret);
+ }
+ }
+ return 0;
+}
+
+int vfc_i2c_xmit_byte(struct vfc_dev *dev,unsigned char *byte)
+{
+ int ret;
+ u32 val = SHIFT((unsigned int)*byte);
+
+ WRITE_REG(val);
+
+ ret = vfc_i2c_wait_for_pin(dev, VFC_I2C_ACK_CHECK);
+ switch(ret) {
+ case -ETIMEDOUT:
+ printk(KERN_ERR "vfc%d: VFC xmit byte timed out or no ack\n",
+ dev->instance);
+ break;
+ case -EIO:
+ ret = XMIT_LAST_BYTE;
+ break;
+ default:
+ break;
+ };
+
+ return ret;
+}
+
+int vfc_i2c_recv_byte(struct vfc_dev *dev, unsigned char *byte, int last)
+{
+ int ret;
+
+ if (last) {
+ WRITE_REG(NEGATIVE_ACK);
+ VFC_I2C_DEBUG_PRINTK(("vfc%d: sending negative ack\n",
+ dev->instance));
+ } else {
+ WRITE_S1(ACK);
+ }
+
+ ret = vfc_i2c_wait_for_pin(dev, VFC_I2C_NO_ACK_CHECK);
+ if(ret) {
+ printk(KERN_ERR "vfc%d: "
+ "VFC recv byte timed out\n",
+ dev->instance);
+ }
+ *byte = (sbus_readl(&dev->regs->i2c_reg)) >> 24;
+ return ret;
+}
+
+int vfc_i2c_recvbuf(struct vfc_dev *dev, unsigned char addr,
+ char *buf, int count)
+{
+ int ret, last;
+
+ if(!(count && buf && dev && dev->regs) )
+ return -EINVAL;
+
+ if ((ret = vfc_i2c_wait_for_bus(dev))) {
+ printk(KERN_ERR "vfc%d: VFC I2C bus busy\n", dev->instance);
+ return ret;
+ }
+
+ if ((ret = vfc_i2c_xmit_addr(dev, addr, VFC_I2C_READ))) {
+ WRITE_S1(SEND_I2C_STOP);
+ vfc_i2c_delay(dev);
+ return ret;
+ }
+
+ last = 0;
+ while (count--) {
+ if (!count)
+ last = 1;
+ if ((ret = vfc_i2c_recv_byte(dev, buf, last))) {
+ printk(KERN_ERR "vfc%d: "
+ "VFC error while receiving byte\n",
+ dev->instance);
+ WRITE_S1(SEND_I2C_STOP);
+ ret = -EINVAL;
+ }
+ buf++;
+ }
+ WRITE_S1(SEND_I2C_STOP | ACK);
+ vfc_i2c_delay(dev);
+ return ret;
+}
+
+int vfc_i2c_sendbuf(struct vfc_dev *dev, unsigned char addr,
+ char *buf, int count)
+{
+ int ret;
+
+ if (!(buf && dev && dev->regs))
+ return -EINVAL;
+
+ if ((ret = vfc_i2c_wait_for_bus(dev))) {
+ printk(KERN_ERR "vfc%d: VFC I2C bus busy\n", dev->instance);
+ return ret;
+ }
+
+ if ((ret = vfc_i2c_xmit_addr(dev, addr, VFC_I2C_WRITE))) {
+ WRITE_S1(SEND_I2C_STOP);
+ vfc_i2c_delay(dev);
+ return ret;
+ }
+
+ while(count--) {
+ ret = vfc_i2c_xmit_byte(dev, buf);
+ switch(ret) {
+ case XMIT_LAST_BYTE:
+ VFC_I2C_DEBUG_PRINTK(("vfc%d: "
+ "Receiver ended transmission with "
+ " %d bytes remaining\n",
+ dev->instance, count));
+ ret = 0;
+ goto done;
+ break;
+ case 0:
+ break;
+ default:
+ printk(KERN_ERR "vfc%d: "
+ "VFC error while sending byte\n", dev->instance);
+ break;
+ };
+
+ buf++;
+ }
+done:
+ WRITE_S1(SEND_I2C_STOP | ACK);
+ vfc_i2c_delay(dev);
+ return ret;
+}
+
+
+
+
+
+
+
+
+
diff --git a/drivers/sbus/char/vfc_i2c.h b/drivers/sbus/char/vfc_i2c.h
new file mode 100644
index 00000000000..a2e6973209d
--- /dev/null
+++ b/drivers/sbus/char/vfc_i2c.h
@@ -0,0 +1,44 @@
+#ifndef _LINUX_VFC_I2C_H_
+#define _LINUX_VFC_I2C_H_
+
+/* control bits */
+#define PIN (0x80000000)
+#define ESO (0x40000000)
+#define ES1 (0x20000000)
+#define ES2 (0x10000000)
+#define ENI (0x08000000)
+#define STA (0x04000000)
+#define STO (0x02000000)
+#define ACK (0x01000000)
+
+/* status bits */
+#define STS (0x20000000)
+#define BER (0x10000000)
+#define LRB (0x08000000)
+#define AAS (0x04000000)
+#define LAB (0x02000000)
+#define BB (0x01000000)
+
+#define SEND_I2C_START (PIN | ESO | STA)
+#define SEND_I2C_STOP (PIN | ESO | STO)
+#define CLEAR_I2C_BUS (PIN | ESO | ACK)
+#define NEGATIVE_ACK ((ESO) & ~ACK)
+
+#define SELECT(a) (a)
+#define S0 (PIN | ESO | ES1)
+#define S0_OWN (PIN)
+#define S2 (PIN | ES1)
+#define S3 (PIN | ES2)
+
+#define ENABLE_SERIAL (PIN | ESO)
+#define DISABLE_SERIAL (PIN)
+#define RESET (PIN)
+
+#define XMIT_LAST_BYTE (1)
+#define VFC_I2C_ACK_CHECK (1)
+#define VFC_I2C_NO_ACK_CHECK (0)
+
+#endif /* _LINUX_VFC_I2C_H_ */
+
+
+
diff --git a/drivers/sbus/dvma.c b/drivers/sbus/dvma.c
new file mode 100644
index 00000000000..378a1d68024
--- /dev/null
+++ b/drivers/sbus/dvma.c
@@ -0,0 +1,137 @@
+/* dvma.c: Routines that are used to access DMA on the Sparc SBus.
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ */
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/sbus.h>
+
+struct sbus_dma *dma_chain;
+
+void __init init_one_dvma(struct sbus_dma *dma, int num_dma)
+{
+ printk("dma%d: ", num_dma);
+
+ dma->next = NULL;
+ dma->running = 0; /* No transfers going on as of yet */
+ dma->allocated = 0; /* No one has allocated us yet */
+ switch(sbus_readl(dma->regs + DMA_CSR)&DMA_DEVICE_ID) {
+ case DMA_VERS0:
+ dma->revision = dvmarev0;
+ printk("Revision 0 ");
+ break;
+ case DMA_ESCV1:
+ dma->revision = dvmaesc1;
+ printk("ESC Revision 1 ");
+ break;
+ case DMA_VERS1:
+ dma->revision = dvmarev1;
+ printk("Revision 1 ");
+ break;
+ case DMA_VERS2:
+ dma->revision = dvmarev2;
+ printk("Revision 2 ");
+ break;
+ case DMA_VERHME:
+ dma->revision = dvmahme;
+ printk("HME DVMA gate array ");
+ break;
+ case DMA_VERSPLUS:
+ dma->revision = dvmarevplus;
+ printk("Revision 1 PLUS ");
+ break;
+ default:
+ printk("unknown dma version %08x",
+ sbus_readl(dma->regs + DMA_CSR) & DMA_DEVICE_ID);
+ dma->allocated = 1;
+ break;
+ }
+ printk("\n");
+}
+
+/* Probe this SBus DMA module(s) */
+void __init dvma_init(struct sbus_bus *sbus)
+{
+ struct sbus_dev *this_dev;
+ struct sbus_dma *dma;
+ struct sbus_dma *dchain;
+ static int num_dma = 0;
+
+ for_each_sbusdev(this_dev, sbus) {
+ char *name = this_dev->prom_name;
+ int hme = 0;
+
+ if(!strcmp(name, "SUNW,fas"))
+ hme = 1;
+ else if(strcmp(name, "dma") &&
+ strcmp(name, "ledma") &&
+ strcmp(name, "espdma"))
+ continue;
+
+ /* Found one... */
+ dma = kmalloc(sizeof(struct sbus_dma), GFP_ATOMIC);
+
+ dma->sdev = this_dev;
+
+ /* Put at end of dma chain */
+ dchain = dma_chain;
+ if(dchain) {
+ while(dchain->next)
+ dchain = dchain->next;
+ dchain->next = dma;
+ } else {
+ /* We're the first in line */
+ dma_chain = dma;
+ }
+
+ dma->regs = sbus_ioremap(&dma->sdev->resource[0], 0,
+ dma->sdev->resource[0].end - dma->sdev->resource[0].start + 1,
+ "dma");
+
+ dma->node = dma->sdev->prom_node;
+
+ init_one_dvma(dma, num_dma++);
+ }
+}
+
+#ifdef CONFIG_SUN4
+
+#include <asm/sun4paddr.h>
+
+void __init sun4_dvma_init(void)
+{
+ struct sbus_dma *dma;
+ struct resource r;
+
+ if(sun4_dma_physaddr) {
+ dma = kmalloc(sizeof(struct sbus_dma), GFP_ATOMIC);
+
+ /* No SBUS */
+ dma->sdev = NULL;
+
+ /* Only one DMA device */
+ dma_chain = dma;
+
+ memset(&r, 0, sizeof(r));
+ r.start = sun4_dma_physaddr;
+ dma->regs = sbus_ioremap(&r, 0, PAGE_SIZE, "dma");
+
+ /* No prom node */
+ dma->node = 0x0;
+
+ init_one_dvma(dma, 0);
+ } else {
+ dma_chain = NULL;
+ }
+}
+
+#endif
diff --git a/drivers/sbus/sbus.c b/drivers/sbus/sbus.c
new file mode 100644
index 00000000000..5d30a3ebfcc
--- /dev/null
+++ b/drivers/sbus/sbus.c
@@ -0,0 +1,564 @@
+/* $Id: sbus.c,v 1.100 2002/01/24 15:36:24 davem Exp $
+ * sbus.c: SBus support routines.
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+
+#include <asm/system.h>
+#include <asm/sbus.h>
+#include <asm/dma.h>
+#include <asm/oplib.h>
+#include <asm/bpp.h>
+#include <asm/irq.h>
+
+struct sbus_bus *sbus_root = NULL;
+
+static struct linux_prom_irqs irqs[PROMINTR_MAX] __initdata = { { 0 } };
+#ifdef CONFIG_SPARC32
+static int interrupts[PROMINTR_MAX] __initdata = { 0 };
+#endif
+
+#ifdef CONFIG_PCI
+extern int pcic_present(void);
+#endif
+
+/* Perhaps when I figure out more about the iommu we'll put a
+ * device registration routine here that probe_sbus() calls to
+ * setup the iommu for each Sbus.
+ */
+
+/* We call this for each SBus device, and fill the structure based
+ * upon the prom device tree. We return the start of memory after
+ * the things we have allocated.
+ */
+
+/* #define DEBUG_FILL */
+
+static void __init fill_sbus_device(int prom_node, struct sbus_dev *sdev)
+{
+ unsigned long address, base;
+ int len;
+
+ sdev->prom_node = prom_node;
+ prom_getstring(prom_node, "name",
+ sdev->prom_name, sizeof(sdev->prom_name));
+ address = prom_getint(prom_node, "address");
+ len = prom_getproperty(prom_node, "reg",
+ (char *) sdev->reg_addrs,
+ sizeof(sdev->reg_addrs));
+ if (len == -1) {
+ sdev->num_registers = 0;
+ goto no_regs;
+ }
+
+ if (len % sizeof(struct linux_prom_registers)) {
+ prom_printf("fill_sbus_device: proplen for regs of %s "
+ " was %d, need multiple of %d\n",
+ sdev->prom_name, len,
+ (int) sizeof(struct linux_prom_registers));
+ prom_halt();
+ }
+ if (len > (sizeof(struct linux_prom_registers) * PROMREG_MAX)) {
+ prom_printf("fill_sbus_device: Too many register properties "
+ "for device %s, len=%d\n",
+ sdev->prom_name, len);
+ prom_halt();
+ }
+ sdev->num_registers = len / sizeof(struct linux_prom_registers);
+ sdev->ranges_applied = 0;
+
+ base = (unsigned long) sdev->reg_addrs[0].phys_addr;
+
+ /* Compute the slot number. */
+ if (base >= SUN_SBUS_BVADDR && sparc_cpu_model == sun4m) {
+ sdev->slot = sbus_dev_slot(base);
+ } else {
+ sdev->slot = sdev->reg_addrs[0].which_io;
+ }
+
+no_regs:
+ len = prom_getproperty(prom_node, "ranges",
+ (char *)sdev->device_ranges,
+ sizeof(sdev->device_ranges));
+ if (len == -1) {
+ sdev->num_device_ranges = 0;
+ goto no_ranges;
+ }
+ if (len % sizeof(struct linux_prom_ranges)) {
+ prom_printf("fill_sbus_device: proplen for ranges of %s "
+ " was %d, need multiple of %d\n",
+ sdev->prom_name, len,
+ (int) sizeof(struct linux_prom_ranges));
+ prom_halt();
+ }
+ if (len > (sizeof(struct linux_prom_ranges) * PROMREG_MAX)) {
+ prom_printf("fill_sbus_device: Too many range properties "
+ "for device %s, len=%d\n",
+ sdev->prom_name, len);
+ prom_halt();
+ }
+ sdev->num_device_ranges =
+ len / sizeof(struct linux_prom_ranges);
+
+no_ranges:
+ /* XXX Unfortunately, IRQ issues are very arch specific.
+ * XXX Pull this crud out into an arch specific area
+ * XXX at some point. -DaveM
+ */
+#ifdef CONFIG_SPARC64
+ len = prom_getproperty(prom_node, "interrupts",
+ (char *) irqs, sizeof(irqs));
+ if (len == -1 || len == 0) {
+ sdev->irqs[0] = 0;
+ sdev->num_irqs = 0;
+ } else {
+ unsigned int pri = irqs[0].pri;
+
+ sdev->num_irqs = 1;
+ if (pri < 0x20)
+ pri += sdev->slot * 8;
+
+ sdev->irqs[0] = sbus_build_irq(sdev->bus, pri);
+ }
+#endif /* CONFIG_SPARC64 */
+
+#ifdef CONFIG_SPARC32
+ len = prom_getproperty(prom_node, "intr",
+ (char *)irqs, sizeof(irqs));
+ if (len != -1) {
+ sdev->num_irqs = len / 8;
+ if (sdev->num_irqs == 0) {
+ sdev->irqs[0] = 0;
+ } else if (sparc_cpu_model == sun4d) {
+ extern unsigned int sun4d_build_irq(struct sbus_dev *sdev, int irq);
+
+ for (len = 0; len < sdev->num_irqs; len++)
+ sdev->irqs[len] = sun4d_build_irq(sdev, irqs[len].pri);
+ } else {
+ for (len = 0; len < sdev->num_irqs; len++)
+ sdev->irqs[len] = irqs[len].pri;
+ }
+ } else {
+ /* No "intr" node found-- check for "interrupts" node.
+ * This node contains SBus interrupt levels, not IPLs
+ * as in "intr", and no vector values. We convert
+ * SBus interrupt levels to PILs (platform specific).
+ */
+ len = prom_getproperty(prom_node, "interrupts",
+ (char *)interrupts, sizeof(interrupts));
+ if (len == -1) {
+ sdev->irqs[0] = 0;
+ sdev->num_irqs = 0;
+ } else {
+ sdev->num_irqs = len / sizeof(int);
+ for (len = 0; len < sdev->num_irqs; len++) {
+ sdev->irqs[len] = sbint_to_irq(sdev, interrupts[len]);
+ }
+ }
+ }
+#endif /* CONFIG_SPARC32 */
+}
+
+/* This routine gets called from whoever needs the sbus first, to scan
+ * the SBus device tree. Currently it just prints out the devices
+ * found on the bus and builds trees of SBUS structs and attached
+ * devices.
+ */
+
+extern void iommu_init(int iommu_node, struct sbus_bus *sbus);
+extern void iounit_init(int sbi_node, int iounit_node, struct sbus_bus *sbus);
+void sun4_init(void);
+#ifdef CONFIG_SUN_AUXIO
+extern void auxio_probe(void);
+#endif
+
+static void __init sbus_do_child_siblings(int start_node,
+ struct sbus_dev *child,
+ struct sbus_dev *parent,
+ struct sbus_bus *sbus)
+{
+ struct sbus_dev *this_dev = child;
+ int this_node = start_node;
+
+ /* Child already filled in, just need to traverse siblings. */
+ child->child = NULL;
+ child->parent = parent;
+ while((this_node = prom_getsibling(this_node)) != 0) {
+ this_dev->next = kmalloc(sizeof(struct sbus_dev), GFP_ATOMIC);
+ this_dev = this_dev->next;
+ this_dev->next = NULL;
+ this_dev->parent = parent;
+
+ this_dev->bus = sbus;
+ fill_sbus_device(this_node, this_dev);
+
+ if(prom_getchild(this_node)) {
+ this_dev->child = kmalloc(sizeof(struct sbus_dev),
+ GFP_ATOMIC);
+ this_dev->child->bus = sbus;
+ this_dev->child->next = NULL;
+ fill_sbus_device(prom_getchild(this_node), this_dev->child);
+ sbus_do_child_siblings(prom_getchild(this_node),
+ this_dev->child, this_dev, sbus);
+ } else {
+ this_dev->child = NULL;
+ }
+ }
+}
+
+/*
+ * XXX This functions appears to be a distorted version of
+ * prom_sbus_ranges_init(), with all sun4d stuff cut away.
+ * Ask DaveM what is going on here, how is sun4d supposed to work... XXX
+ */
+/* added back sun4d patch from Thomas Bogendoerfer - should be OK (crn) */
+
+static void __init sbus_bus_ranges_init(int parent_node, struct sbus_bus *sbus)
+{
+ int len;
+
+ len = prom_getproperty(sbus->prom_node, "ranges",
+ (char *) sbus->sbus_ranges,
+ sizeof(sbus->sbus_ranges));
+ if (len == -1 || len == 0) {
+ sbus->num_sbus_ranges = 0;
+ return;
+ }
+ sbus->num_sbus_ranges = len / sizeof(struct linux_prom_ranges);
+#ifdef CONFIG_SPARC32
+ if (sparc_cpu_model == sun4d) {
+ struct linux_prom_ranges iounit_ranges[PROMREG_MAX];
+ int num_iounit_ranges;
+
+ len = prom_getproperty(parent_node, "ranges",
+ (char *) iounit_ranges,
+ sizeof (iounit_ranges));
+ if (len != -1) {
+ num_iounit_ranges = (len/sizeof(struct linux_prom_ranges));
+ prom_adjust_ranges (sbus->sbus_ranges, sbus->num_sbus_ranges, iounit_ranges, num_iounit_ranges);
+ }
+ }
+#endif
+}
+
+static void __init __apply_ranges_to_regs(struct linux_prom_ranges *ranges,
+ int num_ranges,
+ struct linux_prom_registers *regs,
+ int num_regs)
+{
+ if (num_ranges) {
+ int regnum;
+
+ for (regnum = 0; regnum < num_regs; regnum++) {
+ int rngnum;
+
+ for (rngnum = 0; rngnum < num_ranges; rngnum++) {
+ if (regs[regnum].which_io == ranges[rngnum].ot_child_space)
+ break;
+ }
+ if (rngnum == num_ranges) {
+ /* We used to flag this as an error. Actually
+ * some devices do not report the regs as we expect.
+ * For example, see SUNW,pln device. In that case
+ * the reg property is in a format internal to that
+ * node, ie. it is not in the SBUS register space
+ * per se. -DaveM
+ */
+ return;
+ }
+ regs[regnum].which_io = ranges[rngnum].ot_parent_space;
+ regs[regnum].phys_addr -= ranges[rngnum].ot_child_base;
+ regs[regnum].phys_addr += ranges[rngnum].ot_parent_base;
+ }
+ }
+}
+
+static void __init __fixup_regs_sdev(struct sbus_dev *sdev)
+{
+ if (sdev->num_registers != 0) {
+ struct sbus_dev *parent = sdev->parent;
+ int i;
+
+ while (parent != NULL) {
+ __apply_ranges_to_regs(parent->device_ranges,
+ parent->num_device_ranges,
+ sdev->reg_addrs,
+ sdev->num_registers);
+
+ parent = parent->parent;
+ }
+
+ __apply_ranges_to_regs(sdev->bus->sbus_ranges,
+ sdev->bus->num_sbus_ranges,
+ sdev->reg_addrs,
+ sdev->num_registers);
+
+ for (i = 0; i < sdev->num_registers; i++) {
+ struct resource *res = &sdev->resource[i];
+
+ res->start = sdev->reg_addrs[i].phys_addr;
+ res->end = (res->start +
+ (unsigned long)sdev->reg_addrs[i].reg_size - 1UL);
+ res->flags = IORESOURCE_IO |
+ (sdev->reg_addrs[i].which_io & 0xff);
+ }
+ }
+}
+
+static void __init sbus_fixup_all_regs(struct sbus_dev *first_sdev)
+{
+ struct sbus_dev *sdev;
+
+ for (sdev = first_sdev; sdev; sdev = sdev->next) {
+ if (sdev->child)
+ sbus_fixup_all_regs(sdev->child);
+ __fixup_regs_sdev(sdev);
+ }
+}
+
+extern void register_proc_sparc_ioport(void);
+extern void firetruck_init(void);
+
+#ifdef CONFIG_SUN4
+extern void sun4_dvma_init(void);
+#endif
+
+static int __init sbus_init(void)
+{
+ int nd, this_sbus, sbus_devs, topnd, iommund;
+ unsigned int sbus_clock;
+ struct sbus_bus *sbus;
+ struct sbus_dev *this_dev;
+ int num_sbus = 0; /* How many did we find? */
+
+#ifdef CONFIG_SPARC32
+ register_proc_sparc_ioport();
+#endif
+
+#ifdef CONFIG_SUN4
+ sun4_dvma_init();
+ return 0;
+#endif
+
+ topnd = prom_getchild(prom_root_node);
+
+ /* Finding the first sbus is a special case... */
+ iommund = 0;
+ if(sparc_cpu_model == sun4u) {
+ nd = prom_searchsiblings(topnd, "sbus");
+ if(nd == 0) {
+#ifdef CONFIG_PCI
+ if (!pcic_present()) {
+ prom_printf("Neither SBUS nor PCI found.\n");
+ prom_halt();
+ } else {
+#ifdef CONFIG_SPARC64
+ firetruck_init();
+#endif
+ }
+ return 0;
+#else
+ prom_printf("YEEE, UltraSparc sbus not found\n");
+ prom_halt();
+#endif
+ }
+ } else if(sparc_cpu_model == sun4d) {
+ if((iommund = prom_searchsiblings(topnd, "io-unit")) == 0 ||
+ (nd = prom_getchild(iommund)) == 0 ||
+ (nd = prom_searchsiblings(nd, "sbi")) == 0) {
+ panic("sbi not found");
+ }
+ } else if((nd = prom_searchsiblings(topnd, "sbus")) == 0) {
+ if((iommund = prom_searchsiblings(topnd, "iommu")) == 0 ||
+ (nd = prom_getchild(iommund)) == 0 ||
+ (nd = prom_searchsiblings(nd, "sbus")) == 0) {
+#ifdef CONFIG_PCI
+ if (!pcic_present()) {
+ prom_printf("Neither SBUS nor PCI found.\n");
+ prom_halt();
+ }
+ return 0;
+#else
+ /* No reason to run further - the data access trap will occur. */
+ panic("sbus not found");
+#endif
+ }
+ }
+
+ /* Ok, we've found the first one, allocate first SBus struct
+ * and place in chain.
+ */
+ sbus = sbus_root = kmalloc(sizeof(struct sbus_bus), GFP_ATOMIC);
+ sbus->next = NULL;
+ sbus->prom_node = nd;
+ this_sbus = nd;
+
+ if(iommund && sparc_cpu_model != sun4u && sparc_cpu_model != sun4d)
+ iommu_init(iommund, sbus);
+
+ /* Loop until we find no more SBUS's */
+ while(this_sbus) {
+#ifdef CONFIG_SPARC64
+ /* IOMMU hides inside SBUS/SYSIO prom node on Ultra. */
+ if(sparc_cpu_model == sun4u) {
+ extern void sbus_iommu_init(int prom_node, struct sbus_bus *sbus);
+
+ sbus_iommu_init(this_sbus, sbus);
+ }
+#endif /* CONFIG_SPARC64 */
+
+#ifdef CONFIG_SPARC32
+ if (sparc_cpu_model == sun4d)
+ iounit_init(this_sbus, iommund, sbus);
+#endif /* CONFIG_SPARC32 */
+ printk("sbus%d: ", num_sbus);
+ sbus_clock = prom_getint(this_sbus, "clock-frequency");
+ if(sbus_clock == -1)
+ sbus_clock = (25*1000*1000);
+ printk("Clock %d.%d MHz\n", (int) ((sbus_clock/1000)/1000),
+ (int) (((sbus_clock/1000)%1000 != 0) ?
+ (((sbus_clock/1000)%1000) + 1000) : 0));
+
+ prom_getstring(this_sbus, "name",
+ sbus->prom_name, sizeof(sbus->prom_name));
+ sbus->clock_freq = sbus_clock;
+#ifdef CONFIG_SPARC32
+ if (sparc_cpu_model == sun4d) {
+ sbus->devid = prom_getint(iommund, "device-id");
+ sbus->board = prom_getint(iommund, "board#");
+ }
+#endif
+
+ sbus_bus_ranges_init(iommund, sbus);
+
+ sbus_devs = prom_getchild(this_sbus);
+ if (!sbus_devs) {
+ sbus->devices = NULL;
+ goto next_bus;
+ }
+
+ sbus->devices = kmalloc(sizeof(struct sbus_dev), GFP_ATOMIC);
+
+ this_dev = sbus->devices;
+ this_dev->next = NULL;
+
+ this_dev->bus = sbus;
+ this_dev->parent = NULL;
+ fill_sbus_device(sbus_devs, this_dev);
+
+ /* Should we traverse for children? */
+ if(prom_getchild(sbus_devs)) {
+ /* Allocate device node */
+ this_dev->child = kmalloc(sizeof(struct sbus_dev),
+ GFP_ATOMIC);
+ /* Fill it */
+ this_dev->child->bus = sbus;
+ this_dev->child->next = NULL;
+ fill_sbus_device(prom_getchild(sbus_devs),
+ this_dev->child);
+ sbus_do_child_siblings(prom_getchild(sbus_devs),
+ this_dev->child,
+ this_dev,
+ sbus);
+ } else {
+ this_dev->child = NULL;
+ }
+
+ while((sbus_devs = prom_getsibling(sbus_devs)) != 0) {
+ /* Allocate device node */
+ this_dev->next = kmalloc(sizeof(struct sbus_dev),
+ GFP_ATOMIC);
+ this_dev = this_dev->next;
+ this_dev->next = NULL;
+
+ /* Fill it */
+ this_dev->bus = sbus;
+ this_dev->parent = NULL;
+ fill_sbus_device(sbus_devs, this_dev);
+
+ /* Is there a child node hanging off of us? */
+ if(prom_getchild(sbus_devs)) {
+ /* Get new device struct */
+ this_dev->child = kmalloc(sizeof(struct sbus_dev),
+ GFP_ATOMIC);
+ /* Fill it */
+ this_dev->child->bus = sbus;
+ this_dev->child->next = NULL;
+ fill_sbus_device(prom_getchild(sbus_devs),
+ this_dev->child);
+ sbus_do_child_siblings(prom_getchild(sbus_devs),
+ this_dev->child,
+ this_dev,
+ sbus);
+ } else {
+ this_dev->child = NULL;
+ }
+ }
+
+ /* Walk all devices and apply parent ranges. */
+ sbus_fixup_all_regs(sbus->devices);
+
+ dvma_init(sbus);
+ next_bus:
+ num_sbus++;
+ if(sparc_cpu_model == sun4u) {
+ this_sbus = prom_getsibling(this_sbus);
+ if(!this_sbus)
+ break;
+ this_sbus = prom_searchsiblings(this_sbus, "sbus");
+ } else if(sparc_cpu_model == sun4d) {
+ iommund = prom_getsibling(iommund);
+ if(!iommund)
+ break;
+ iommund = prom_searchsiblings(iommund, "io-unit");
+ if(!iommund)
+ break;
+ this_sbus = prom_searchsiblings(prom_getchild(iommund), "sbi");
+ } else {
+ this_sbus = prom_getsibling(this_sbus);
+ if(!this_sbus)
+ break;
+ this_sbus = prom_searchsiblings(this_sbus, "sbus");
+ }
+ if(this_sbus) {
+ sbus->next = kmalloc(sizeof(struct sbus_bus), GFP_ATOMIC);
+ sbus = sbus->next;
+ sbus->next = NULL;
+ sbus->prom_node = this_sbus;
+ } else {
+ break;
+ }
+ } /* while(this_sbus) */
+
+ if (sparc_cpu_model == sun4d) {
+ extern void sun4d_init_sbi_irq(void);
+ sun4d_init_sbi_irq();
+ }
+
+#ifdef CONFIG_SPARC64
+ if (sparc_cpu_model == sun4u) {
+ firetruck_init();
+ }
+#endif
+#ifdef CONFIG_SUN_AUXIO
+ if (sparc_cpu_model == sun4u)
+ auxio_probe ();
+#endif
+#ifdef CONFIG_SPARC64
+ if (sparc_cpu_model == sun4u) {
+ extern void clock_probe(void);
+
+ clock_probe();
+ }
+#endif
+
+ return 0;
+}
+
+subsys_initcall(sbus_init);