diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/sbus |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/sbus')
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 *)®0, 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); |