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!
This commit is contained in:
Linus Torvalds 2005-04-16 15:20:36 -07:00
commit 1da177e4c3
17291 changed files with 6718755 additions and 0 deletions

9
drivers/sbus/Makefile Normal file
View file

@ -0,0 +1,9 @@
#
# Makefile for the linux kernel.
#
ifneq ($(ARCH),m68k)
obj-y := sbus.o dvma.o
endif
obj-$(CONFIG_SBUSCHAR) += char/

93
drivers/sbus/char/Kconfig Normal file
View file

@ -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

View file

@ -0,0 +1,25 @@
#
# Makefile for the kernel miscellaneous SPARC device drivers.
#
# Dave Redman Frame Buffer tuning support.
#
# 7 October 2000, Bartlomiej Zolnierkiewicz <[email protected]>
# 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

2372
drivers/sbus/char/aurora.c Normal file

File diff suppressed because it is too large Load diff

276
drivers/sbus/char/aurora.h Normal file
View file

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

View file

@ -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;
}

488
drivers/sbus/char/bbc_i2c.c Normal file
View file

@ -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);

View file

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

1079
drivers/sbus/char/bpp.c Normal file

File diff suppressed because it is too large Load diff

240
drivers/sbus/char/cd180.h Normal file
View file

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

View file

@ -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 <[email protected]>");
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);

View file

@ -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 <[email protected]>");
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);

1181
drivers/sbus/char/envctrl.c Normal file

File diff suppressed because it is too large Load diff

255
drivers/sbus/char/flash.c Normal file
View file

@ -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");

627
drivers/sbus/char/jsflash.c Normal file
View file

@ -0,0 +1,627 @@
/*
* drivers/sbus/char/jsflash.c
*
* Copyright (C) 1991, 1992 Linus Torvalds (drivers/char/mem.c)
* Copyright (C) 1997 Eddie C. Dost (drivers/sbus/char/flash.c)
* Copyright (C) 1997-2000 Pavel Machek <pavel@ucw.cz> (drivers/block/nbd.c)
* Copyright (C) 1999-2000 Pete Zaitcev
*
* This driver is used to program OS into a Flash SIMM on
* Krups and Espresso platforms.
*
* TODO: do not allow erase/programming if file systems are mounted.
* TODO: Erase/program both banks of a 8MB SIMM.
*
* It is anticipated that programming an OS Flash will be a routine
* procedure. In the same time it is exeedingly dangerous because
* a user can program its OBP flash with OS image and effectively
* kill the machine.
*
* This driver uses an interface different from Eddie's flash.c
* as a silly safeguard.
*
* XXX The flash.c manipulates page caching characteristics in a certain
* dubious way; also it assumes that remap_pfn_range() can remap
* PCI bus locations, which may be false. ioremap() must be used
* instead. We should discuss this.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/smp_lock.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#define MAJOR_NR JSFD_MAJOR
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/io.h>
#include <asm/pcic.h>
#include <asm/oplib.h>
#include <asm/jsflash.h> /* ioctl arguments. <linux/> ?? */
#define JSFIDSZ (sizeof(struct jsflash_ident_arg))
#define JSFPRGSZ (sizeof(struct jsflash_program_arg))
/*
* Our device numbers have no business in system headers.
* The only thing a user knows is the device name /dev/jsflash.
*
* Block devices are laid out like this:
* minor+0 - Bootstrap, for 8MB SIMM 0x20400000[0x800000]
* minor+1 - Filesystem to mount, normally 0x20400400[0x7ffc00]
* minor+2 - Whole flash area for any case... 0x20000000[0x01000000]
* Total 3 minors per flash device.
*
* It is easier to have static size vectors, so we define
* a total minor range JSF_MAX, which must cover all minors.
*/
/* character device */
#define JSF_MINOR 178 /* 178 is registered with hpa */
/* block device */
#define JSF_MAX 3 /* 3 minors wasted total so far. */
#define JSF_NPART 3 /* 3 minors per flash device */
#define JSF_PART_BITS 2 /* 2 bits of minors to cover JSF_NPART */
#define JSF_PART_MASK 0x3 /* 2 bits mask */
/*
* Access functions.
* We could ioremap(), but it's easier this way.
*/
static unsigned int jsf_inl(unsigned long addr)
{
unsigned long retval;
__asm__ __volatile__("lda [%1] %2, %0\n\t" :
"=r" (retval) :
"r" (addr), "i" (ASI_M_BYPASS));
return retval;
}
static void jsf_outl(unsigned long addr, __u32 data)
{
__asm__ __volatile__("sta %0, [%1] %2\n\t" : :
"r" (data), "r" (addr), "i" (ASI_M_BYPASS) :
"memory");
}
/*
* soft carrier
*/
struct jsfd_part {
unsigned long dbase;
unsigned long dsize;
};
struct jsflash {
unsigned long base;
unsigned long size;
unsigned long busy; /* In use? */
struct jsflash_ident_arg id;
/* int mbase; */ /* Minor base, typically zero */
struct jsfd_part dv[JSF_NPART];
};
/*
* We do not map normal memory or obio as a safety precaution.
* But offsets are real, for ease of userland programming.
*/
#define JSF_BASE_TOP 0x30000000
#define JSF_BASE_ALL 0x20000000
#define JSF_BASE_JK 0x20400000
/*
*/
static struct gendisk *jsfd_disk[JSF_MAX];
/*
* Let's pretend we may have several of these...
*/
static struct jsflash jsf0;
/*
* Wait for AMD to finish its embedded algorithm.
* We use the Toggle bit DQ6 (0x40) because it does not
* depend on the data value as /DATA bit DQ7 does.
*
* XXX Do we need any timeout here? So far it never hanged, beware broken hw.
*/
static void jsf_wait(unsigned long p) {
unsigned int x1, x2;
for (;;) {
x1 = jsf_inl(p);
x2 = jsf_inl(p);
if ((x1 & 0x40404040) == (x2 & 0x40404040)) return;
}
}
/*
* Programming will only work if Flash is clean,
* we leave it to the programmer application.
*
* AMD must be programmed one byte at a time;
* thus, Simple Tech SIMM must be written 4 bytes at a time.
*
* Write waits for the chip to become ready after the write
* was finished. This is done so that application would read
* consistent data after the write is done.
*/
static void jsf_write4(unsigned long fa, u32 data) {
jsf_outl(fa, 0xAAAAAAAA); /* Unlock 1 Write 1 */
jsf_outl(fa, 0x55555555); /* Unlock 1 Write 2 */
jsf_outl(fa, 0xA0A0A0A0); /* Byte Program */
jsf_outl(fa, data);
jsf_wait(fa);
}
/*
*/
static void jsfd_read(char *buf, unsigned long p, size_t togo) {
union byte4 {
char s[4];
unsigned int n;
} b;
while (togo >= 4) {
togo -= 4;
b.n = jsf_inl(p);
memcpy(buf, b.s, 4);
p += 4;
buf += 4;
}
}
static void jsfd_do_request(request_queue_t *q)
{
struct request *req;
while ((req = elv_next_request(q)) != NULL) {
struct jsfd_part *jdp = req->rq_disk->private_data;
unsigned long offset = req->sector << 9;
size_t len = req->current_nr_sectors << 9;
if ((offset + len) > jdp->dsize) {
end_request(req, 0);
continue;
}
if (rq_data_dir(req) != READ) {
printk(KERN_ERR "jsfd: write\n");
end_request(req, 0);
continue;
}
if ((jdp->dbase & 0xff000000) != 0x20000000) {
printk(KERN_ERR "jsfd: bad base %x\n", (int)jdp->dbase);
end_request(req, 0);
continue;
}
jsfd_read(req->buffer, jdp->dbase + offset, len);
end_request(req, 1);
}
}
/*
* The memory devices use the full 32/64 bits of the offset, and so we cannot
* check against negative addresses: they are ok. The return value is weird,
* though, in that case (0).
*
* also note that seeking relative to the "end of file" isn't supported:
* it has no meaning, so it returns -EINVAL.
*/
static loff_t jsf_lseek(struct file * file, loff_t offset, int orig)
{
loff_t ret;
lock_kernel();
switch (orig) {
case 0:
file->f_pos = offset;
ret = file->f_pos;
break;
case 1:
file->f_pos += offset;
ret = file->f_pos;
break;
default:
ret = -EINVAL;
}
unlock_kernel();
return ret;
}
/*
* OS SIMM Cannot be read in other size but a 32bits word.
*/
static ssize_t jsf_read(struct file * file, char * buf,
size_t togo, loff_t *ppos)
{
unsigned long p = *ppos;
char *tmp = buf;
union byte4 {
char s[4];
unsigned int n;
} b;
if (p < JSF_BASE_ALL || p >= JSF_BASE_TOP) {
return 0;
}
if ((p + togo) < p /* wrap */
|| (p + togo) >= JSF_BASE_TOP) {
togo = JSF_BASE_TOP - p;
}
if (p < JSF_BASE_ALL && togo != 0) {
#if 0 /* __bzero XXX */
size_t x = JSF_BASE_ALL - p;
if (x > togo) x = togo;
clear_user(tmp, x);
tmp += x;
p += x;
togo -= x;
#else
/*
* Implementation of clear_user() calls __bzero
* without regard to modversions,
* so we cannot build a module.
*/
return 0;
#endif
}
while (togo >= 4) {
togo -= 4;
b.n = jsf_inl(p);
if (copy_to_user(tmp, b.s, 4))
return -EFAULT;
tmp += 4;
p += 4;
}
/*
* XXX Small togo may remain if 1 byte is ordered.
* It would be nice if we did a word size read and unpacked it.
*/
*ppos = p;
return tmp-buf;
}
static ssize_t jsf_write(struct file * file, const char * buf,
size_t count, loff_t *ppos)
{
return -ENOSPC;
}
/*
*/
static int jsf_ioctl_erase(unsigned long arg)
{
unsigned long p;
/* p = jsf0.base; hits wrong bank */
p = 0x20400000;
jsf_outl(p, 0xAAAAAAAA); /* Unlock 1 Write 1 */
jsf_outl(p, 0x55555555); /* Unlock 1 Write 2 */
jsf_outl(p, 0x80808080); /* Erase setup */
jsf_outl(p, 0xAAAAAAAA); /* Unlock 2 Write 1 */
jsf_outl(p, 0x55555555); /* Unlock 2 Write 2 */
jsf_outl(p, 0x10101010); /* Chip erase */
#if 0
/*
* This code is ok, except that counter based timeout
* has no place in this world. Let's just drop timeouts...
*/
{
int i;
__u32 x;
for (i = 0; i < 1000000; i++) {
x = jsf_inl(p);
if ((x & 0x80808080) == 0x80808080) break;
}
if ((x & 0x80808080) != 0x80808080) {
printk("jsf0: erase timeout with 0x%08x\n", x);
} else {
printk("jsf0: erase done with 0x%08x\n", x);
}
}
#else
jsf_wait(p);
#endif
return 0;
}
/*
* Program a block of flash.
* Very simple because we can do it byte by byte anyway.
*/
static int jsf_ioctl_program(unsigned long arg)
{
struct jsflash_program_arg abuf;
char *uptr;
unsigned long p;
unsigned int togo;
union {
unsigned int n;
char s[4];
} b;
if (copy_from_user(&abuf, (char *)arg, JSFPRGSZ))
return -EFAULT;
p = abuf.off;
togo = abuf.size;
if ((togo & 3) || (p & 3)) return -EINVAL;
uptr = (char *) (unsigned long) abuf.data;
while (togo != 0) {
togo -= 4;
if (copy_from_user(&b.s[0], uptr, 4))
return -EFAULT;
jsf_write4(p, b.n);
p += 4;
uptr += 4;
}
return 0;
}
static int jsf_ioctl(struct inode *inode, struct file *f, unsigned int cmd,
unsigned long arg)
{
int error = -ENOTTY;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
switch (cmd) {
case JSFLASH_IDENT:
if (copy_to_user((void *)arg, &jsf0.id, JSFIDSZ))
return -EFAULT;
break;
case JSFLASH_ERASE:
error = jsf_ioctl_erase(arg);
break;
case JSFLASH_PROGRAM:
error = jsf_ioctl_program(arg);
break;
}
return error;
}
static int jsf_mmap(struct file * file, struct vm_area_struct * vma)
{
return -ENXIO;
}
static int jsf_open(struct inode * inode, struct file * filp)
{
if (jsf0.base == 0) return -ENXIO;
if (test_and_set_bit(0, (void *)&jsf0.busy) != 0)
return -EBUSY;
return 0; /* XXX What security? */
}
static int jsf_release(struct inode *inode, struct file *file)
{
jsf0.busy = 0;
return 0;
}
static struct file_operations jsf_fops = {
.owner = THIS_MODULE,
.llseek = jsf_lseek,
.read = jsf_read,
.write = jsf_write,
.ioctl = jsf_ioctl,
.mmap = jsf_mmap,
.open = jsf_open,
.release = jsf_release,
};
static struct miscdevice jsf_dev = { JSF_MINOR, "jsflash", &jsf_fops };
static struct block_device_operations jsfd_fops = {
.owner = THIS_MODULE,
};
static int jsflash_init(void)
{
int rc;
struct jsflash *jsf;
int node;
char banner[128];
struct linux_prom_registers reg0;
node = prom_getchild(prom_root_node);
node = prom_searchsiblings(node, "flash-memory");
if (node != 0 && node != -1) {
if (prom_getproperty(node, "reg",
(char *)&reg0, sizeof(reg0)) == -1) {
printk("jsflash: no \"reg\" property\n");
return -ENXIO;
}
if (reg0.which_io != 0) {
printk("jsflash: bus number nonzero: 0x%x:%x\n",
reg0.which_io, reg0.phys_addr);
return -ENXIO;
}
/*
* Flash may be somewhere else, for instance on Ebus.
* So, don't do the following check for IIep flash space.
*/
#if 0
if ((reg0.phys_addr >> 24) != 0x20) {
printk("jsflash: suspicious address: 0x%x:%x\n",
reg0.which_io, reg0.phys_addr);
return -ENXIO;
}
#endif
if ((int)reg0.reg_size <= 0) {
printk("jsflash: bad size 0x%x\n", (int)reg0.reg_size);
return -ENXIO;
}
} else {
/* XXX Remove this code once PROLL ID12 got widespread */
printk("jsflash: no /flash-memory node, use PROLL >= 12\n");
prom_getproperty(prom_root_node, "banner-name", banner, 128);
if (strcmp (banner, "JavaStation-NC") != 0 &&
strcmp (banner, "JavaStation-E") != 0) {
return -ENXIO;
}
reg0.which_io = 0;
reg0.phys_addr = 0x20400000;
reg0.reg_size = 0x00800000;
}
/* Let us be really paranoid for modifications to probing code. */
/* extern enum sparc_cpu sparc_cpu_model; */ /* in <asm/system.h> */
if (sparc_cpu_model != sun4m) {
/* We must be on sun4m because we use MMU Bypass ASI. */
return -ENXIO;
}
if (jsf0.base == 0) {
jsf = &jsf0;
jsf->base = reg0.phys_addr;
jsf->size = reg0.reg_size;
/* XXX Redo the userland interface. */
jsf->id.off = JSF_BASE_ALL;
jsf->id.size = 0x01000000; /* 16M - all segments */
strcpy(jsf->id.name, "Krups_all");
jsf->dv[0].dbase = jsf->base;
jsf->dv[0].dsize = jsf->size;
jsf->dv[1].dbase = jsf->base + 1024;
jsf->dv[1].dsize = jsf->size - 1024;
jsf->dv[2].dbase = JSF_BASE_ALL;
jsf->dv[2].dsize = 0x01000000;
printk("Espresso Flash @0x%lx [%d MB]\n", jsf->base,
(int) (jsf->size / (1024*1024)));
}
if ((rc = misc_register(&jsf_dev)) != 0) {
printk(KERN_ERR "jsf: unable to get misc minor %d\n",
JSF_MINOR);
jsf0.base = 0;
return rc;
}
return 0;
}
static struct request_queue *jsf_queue;
static int jsfd_init(void)
{
static DEFINE_SPINLOCK(lock);
struct jsflash *jsf;
struct jsfd_part *jdp;
int err;
int i;
if (jsf0.base == 0)
return -ENXIO;
err = -ENOMEM;
for (i = 0; i < JSF_MAX; i++) {
struct gendisk *disk = alloc_disk(1);
if (!disk)
goto out;
jsfd_disk[i] = disk;
}
if (register_blkdev(JSFD_MAJOR, "jsfd")) {
err = -EIO;
goto out;
}
jsf_queue = blk_init_queue(jsfd_do_request, &lock);
if (!jsf_queue) {
err = -ENOMEM;
unregister_blkdev(JSFD_MAJOR, "jsfd");
goto out;
}
for (i = 0; i < JSF_MAX; i++) {
struct gendisk *disk = jsfd_disk[i];
if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
jsf = &jsf0; /* actually, &jsfv[i >> JSF_PART_BITS] */
jdp = &jsf->dv[i&JSF_PART_MASK];
disk->major = JSFD_MAJOR;
disk->first_minor = i;
sprintf(disk->disk_name, "jsfd%d", i);
disk->fops = &jsfd_fops;
set_capacity(disk, jdp->dsize >> 9);
disk->private_data = jdp;
disk->queue = jsf_queue;
add_disk(disk);
set_disk_ro(disk, 1);
}
return 0;
out:
while (i--)
put_disk(jsfd_disk[i]);
return err;
}
MODULE_LICENSE("GPL");
static int __init jsflash_init_module(void) {
int rc;
if ((rc = jsflash_init()) == 0) {
jsfd_init();
return 0;
}
return rc;
}
static void __exit jsflash_cleanup_module(void)
{
int i;
for (i = 0; i < JSF_MAX; i++) {
if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
del_gendisk(jsfd_disk[i]);
put_disk(jsfd_disk[i]);
}
if (jsf0.busy)
printk("jsf0: cleaning busy unit\n");
jsf0.base = 0;
jsf0.busy = 0;
misc_deregister(&jsf_dev);
if (unregister_blkdev(JSFD_MAJOR, "jsfd") != 0)
printk("jsfd: cleanup_module failed\n");
blk_cleanup_queue(jsf_queue);
}
module_init(jsflash_init_module);
module_exit(jsflash_cleanup_module);

View file

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

View file

@ -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");

View file

@ -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 <[email protected]>");
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);

178
drivers/sbus/char/rtc.c Normal file
View file

@ -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");

422
drivers/sbus/char/uctrl.c Normal file
View file

@ -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");

179
drivers/sbus/char/vfc.h Normal file
View file

@ -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_ */

742
drivers/sbus/char/vfc_dev.c Normal file
View file

@ -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");

347
drivers/sbus/char/vfc_i2c.c Normal file
View file

@ -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;
}

View file

@ -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_ */

137
drivers/sbus/dvma.c Normal file
View file

@ -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

564
drivers/sbus/sbus.c Normal file
View file

@ -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);