Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
101
drivers/sbus/char/Kconfig
Normal file
101
drivers/sbus/char/Kconfig
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
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 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.
|
||||
|
||||
config BBC_I2C
|
||||
tristate "UltraSPARC-III bootbus i2c controller driver"
|
||||
depends on PCI && SPARC64
|
||||
help
|
||||
The BBC devices on the UltraSPARC III have two I2C controllers. The
|
||||
first I2C controller connects mainly to configuration PROMs (NVRAM,
|
||||
CPU configuration, DIMM types, etc.). 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. Say Y to enable support for these.
|
||||
|
||||
config ENVCTRL
|
||||
tristate "SUNW, envctrl support"
|
||||
depends on PCI && SPARC64
|
||||
help
|
||||
Kernel support for temperature and fan monitoring on Sun SME
|
||||
machines.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called envctrl.
|
||||
|
||||
config DISPLAY7SEG
|
||||
tristate "7-Segment Display support"
|
||||
depends on PCI && SPARC64
|
||||
---help---
|
||||
This is the driver for the 7-segment display and LED present on
|
||||
Sun Microsystems CompactPCI models CP1400 and CP1500.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called display7seg.
|
||||
|
||||
If you do not have a CompactPCI model CP1400 or CP1500, or
|
||||
another UltraSPARC-IIi-cEngine boardset with a 7-segment display,
|
||||
you should say N to this option.
|
||||
|
||||
endmenu
|
||||
|
||||
24
drivers/sbus/char/Makefile
Normal file
24
drivers/sbus/char/Makefile
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Makefile for the kernel miscellaneous SPARC device drivers.
|
||||
#
|
||||
# Dave Redman Frame Buffer tuning support.
|
||||
#
|
||||
# 7 October 2000, Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
|
||||
# Rewritten to use lists instead of if-statements.
|
||||
#
|
||||
|
||||
vfc-objs := vfc_dev.o vfc_i2c.o
|
||||
bbc-objs := bbc_i2c.o bbc_envctrl.o
|
||||
|
||||
obj-$(CONFIG_ENVCTRL) += envctrl.o
|
||||
obj-$(CONFIG_DISPLAY7SEG) += display7seg.o
|
||||
obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwatchdog.o
|
||||
obj-$(CONFIG_WATCHDOG_RIO) += riowatchdog.o
|
||||
obj-$(CONFIG_OBP_FLASH) += flash.o
|
||||
obj-$(CONFIG_SUN_OPENPROMIO) += openprom.o
|
||||
obj-$(CONFIG_SUN_MOSTEK_RTC) += rtc.o
|
||||
obj-$(CONFIG_SUN_BPP) += bpp.o
|
||||
obj-$(CONFIG_SUN_VIDEOPIX) += vfc.o
|
||||
obj-$(CONFIG_TADPOLE_TS102_UCTRL) += uctrl.o
|
||||
obj-$(CONFIG_SUN_JSFLASH) += jsflash.o
|
||||
obj-$(CONFIG_BBC_I2C) += bbc.o
|
||||
621
drivers/sbus/char/bbc_envctrl.c
Normal file
621
drivers/sbus/char/bbc_envctrl.c
Normal file
@@ -0,0 +1,621 @@
|
||||
/* $Id: bbc_envctrl.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
* bbc_envctrl.c: UltraSPARC-III environment control driver.
|
||||
*
|
||||
* Copyright (C) 2001 David S. Miller (davem@redhat.com)
|
||||
*/
|
||||
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <asm/oplib.h>
|
||||
#include <asm/ebus.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 (call_usermodehelper("/sbin/shutdown", argv, envp, 0) < 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)
|
||||
{
|
||||
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 (kthread_should_stop())
|
||||
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;
|
||||
|
||||
while ((echild = bbc_i2c_getdev(devidx++)) != NULL) {
|
||||
if (!strcmp(echild->prom_node->name, "temperature"))
|
||||
attach_one_temp(echild, temp_index++);
|
||||
if (!strcmp(echild->prom_node->name, "fan-control"))
|
||||
attach_one_fan(echild, fan_index++);
|
||||
}
|
||||
if (temp_index != 0 && fan_index != 0) {
|
||||
kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
|
||||
if (IS_ERR(kenvctrld_task))
|
||||
return PTR_ERR(kenvctrld_task);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
kthread_stop(kenvctrld_task);
|
||||
|
||||
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;
|
||||
}
|
||||
491
drivers/sbus/char/bbc_i2c.c
Normal file
491
drivers/sbus/char/bbc_i2c.c
Normal file
@@ -0,0 +1,491 @@
|
||||
/* $Id: bbc_i2c.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu 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) {
|
||||
unsigned long val;
|
||||
|
||||
val = wait_event_interruptible_timeout(
|
||||
bp->wq,
|
||||
(((*status = readb(bp->i2c_control_regs + 0))
|
||||
& I2C_PCF_PIN) == 0),
|
||||
msecs_to_jiffies(250));
|
||||
if (val > 0) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
remove_wait_queue(&bp->wq, &wait);
|
||||
bp->waiting = 0;
|
||||
|
||||
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 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_interruptible(&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,
|
||||
IRQF_SHARED, "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_node->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 && tlb_type != cheetah_plus) ||
|
||||
!bbc_present())
|
||||
return -ENODEV;
|
||||
|
||||
for_each_ebus(ebus) {
|
||||
for_each_ebusdev(edev, ebus) {
|
||||
if (!strcmp(edev->prom_node->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);
|
||||
MODULE_LICENSE("GPL");
|
||||
20
drivers/sbus/char/bbc_i2c.h
Normal file
20
drivers/sbus/char/bbc_i2c.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/* $Id: bbc_i2c.h,v 1.1.1.1 2007/06/12 07:27:09 eyryu 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 */
|
||||
1053
drivers/sbus/char/bpp.c
Normal file
1053
drivers/sbus/char/bpp.c
Normal file
File diff suppressed because it is too large
Load Diff
853
drivers/sbus/char/cpwatchdog.c
Normal file
853
drivers/sbus/char/cpwatchdog.c
Normal file
@@ -0,0 +1,853 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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/interrupt.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/ebus.h>
|
||||
#include <asm/oplib.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include <asm/watchdog.h>
|
||||
|
||||
#define WD_OBPNAME "watchdog"
|
||||
#define WD_BADMODEL "SUNW,501-5336"
|
||||
#define WD_BTIMEOUT (jiffies + (HZ * 1000))
|
||||
#define WD_BLIMIT 0xFFFF
|
||||
|
||||
#define WD0_DEVNAME "watchdog0"
|
||||
#define WD1_DEVNAME "watchdog1"
|
||||
#define WD2_DEVNAME "watchdog2"
|
||||
|
||||
#define WD0_MINOR 212
|
||||
#define WD1_MINOR 213
|
||||
#define WD2_MINOR 214
|
||||
|
||||
|
||||
/* Internal driver definitions
|
||||
*/
|
||||
#define WD0_ID 0 /* Watchdog0 */
|
||||
#define WD1_ID 1 /* Watchdog1 */
|
||||
#define WD2_ID 2 /* Watchdog2 */
|
||||
#define WD_NUMDEVS 3 /* Device contains 3 timers */
|
||||
|
||||
#define WD_INTR_OFF 0 /* Interrupt disable value */
|
||||
#define WD_INTR_ON 1 /* Interrupt enable value */
|
||||
|
||||
#define WD_STAT_INIT 0x01 /* Watchdog timer is initialized */
|
||||
#define WD_STAT_BSTOP 0x02 /* Watchdog timer is brokenstopped */
|
||||
#define WD_STAT_SVCD 0x04 /* Watchdog interrupt occurred */
|
||||
|
||||
/* Register value definitions
|
||||
*/
|
||||
#define WD0_INTR_MASK 0x01 /* Watchdog device interrupt masks */
|
||||
#define WD1_INTR_MASK 0x02
|
||||
#define WD2_INTR_MASK 0x04
|
||||
|
||||
#define WD_S_RUNNING 0x01 /* Watchdog device status running */
|
||||
#define WD_S_EXPIRED 0x02 /* Watchdog device status expired */
|
||||
|
||||
/* Sun uses Altera PLD EPF8820ATC144-4
|
||||
* providing three hardware watchdogs:
|
||||
*
|
||||
* 1) RIC - sends an interrupt when triggered
|
||||
* 2) XIR - asserts XIR_B_RESET when triggered, resets CPU
|
||||
* 3) POR - asserts POR_B_RESET when triggered, resets CPU, backplane, board
|
||||
*
|
||||
*** Timer register block definition (struct wd_timer_regblk)
|
||||
*
|
||||
* dcntr and limit registers (halfword access):
|
||||
* -------------------
|
||||
* | 15 | ...| 1 | 0 |
|
||||
* -------------------
|
||||
* |- counter val -|
|
||||
* -------------------
|
||||
* dcntr - Current 16-bit downcounter value.
|
||||
* When downcounter reaches '0' watchdog expires.
|
||||
* Reading this register resets downcounter with 'limit' value.
|
||||
* limit - 16-bit countdown value in 1/10th second increments.
|
||||
* Writing this register begins countdown with input value.
|
||||
* Reading from this register does not affect counter.
|
||||
* NOTES: After watchdog reset, dcntr and limit contain '1'
|
||||
*
|
||||
* status register (byte access):
|
||||
* ---------------------------
|
||||
* | 7 | ... | 2 | 1 | 0 |
|
||||
* --------------+------------
|
||||
* |- UNUSED -| EXP | RUN |
|
||||
* ---------------------------
|
||||
* status- Bit 0 - Watchdog is running
|
||||
* Bit 1 - Watchdog has expired
|
||||
*
|
||||
*** PLD register block definition (struct wd_pld_regblk)
|
||||
*
|
||||
* intr_mask register (byte access):
|
||||
* ---------------------------------
|
||||
* | 7 | ... | 3 | 2 | 1 | 0 |
|
||||
* +-------------+------------------
|
||||
* |- UNUSED -| WD3 | WD2 | WD1 |
|
||||
* ---------------------------------
|
||||
* WD3 - 1 == Interrupt disabled for watchdog 3
|
||||
* WD2 - 1 == Interrupt disabled for watchdog 2
|
||||
* WD1 - 1 == Interrupt disabled for watchdog 1
|
||||
*
|
||||
* pld_status register (byte access):
|
||||
* UNKNOWN, MAGICAL MYSTERY REGISTER
|
||||
*
|
||||
*/
|
||||
#define WD_TIMER_REGSZ 16
|
||||
#define WD0_OFF 0
|
||||
#define WD1_OFF (WD_TIMER_REGSZ * 1)
|
||||
#define WD2_OFF (WD_TIMER_REGSZ * 2)
|
||||
#define PLD_OFF (WD_TIMER_REGSZ * 3)
|
||||
|
||||
#define WD_DCNTR 0x00
|
||||
#define WD_LIMIT 0x04
|
||||
#define WD_STATUS 0x08
|
||||
|
||||
#define PLD_IMASK (PLD_OFF + 0x00)
|
||||
#define PLD_STATUS (PLD_OFF + 0x04)
|
||||
|
||||
/* Individual timer structure
|
||||
*/
|
||||
struct wd_timer {
|
||||
__u16 timeout;
|
||||
__u8 intr_mask;
|
||||
unsigned char runstatus;
|
||||
void __iomem *regs;
|
||||
};
|
||||
|
||||
/* Device structure
|
||||
*/
|
||||
struct wd_device {
|
||||
int irq;
|
||||
spinlock_t lock;
|
||||
unsigned char isbaddoggie; /* defective PLD */
|
||||
unsigned char opt_enable;
|
||||
unsigned char opt_reboot;
|
||||
unsigned short opt_timeout;
|
||||
unsigned char initialized;
|
||||
struct wd_timer watchdog[WD_NUMDEVS];
|
||||
void __iomem *regs;
|
||||
};
|
||||
|
||||
static struct wd_device wd_dev = {
|
||||
0, SPIN_LOCK_UNLOCKED, 0, 0, 0, 0,
|
||||
};
|
||||
|
||||
static struct timer_list wd_timer;
|
||||
|
||||
static int wd0_timeout = 0;
|
||||
static int wd1_timeout = 0;
|
||||
static int wd2_timeout = 0;
|
||||
|
||||
#ifdef MODULE
|
||||
module_param (wd0_timeout, int, 0);
|
||||
MODULE_PARM_DESC(wd0_timeout, "Default watchdog0 timeout in 1/10secs");
|
||||
module_param (wd1_timeout, int, 0);
|
||||
MODULE_PARM_DESC(wd1_timeout, "Default watchdog1 timeout in 1/10secs");
|
||||
module_param (wd2_timeout, int, 0);
|
||||
MODULE_PARM_DESC(wd2_timeout, "Default watchdog2 timeout in 1/10secs");
|
||||
|
||||
MODULE_AUTHOR
|
||||
("Eric Brower <ebrower@usa.net>");
|
||||
MODULE_DESCRIPTION
|
||||
("Hardware watchdog driver for Sun Microsystems CP1400/1500");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_SUPPORTED_DEVICE
|
||||
("watchdog");
|
||||
#endif /* ifdef MODULE */
|
||||
|
||||
/* Forward declarations of internal methods
|
||||
*/
|
||||
#ifdef WD_DEBUG
|
||||
static void wd_dumpregs(void);
|
||||
#endif
|
||||
static irqreturn_t wd_interrupt(int irq, void *dev_id);
|
||||
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,
|
||||
IRQF_SHARED,
|
||||
WD_OBPNAME,
|
||||
(void *)wd_dev.regs)) {
|
||||
printk("%s: Cannot register IRQ %d\n",
|
||||
WD_OBPNAME, 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 long wd_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int rval = -ENOIOCTLCMD;
|
||||
|
||||
switch (cmd) {
|
||||
/* solaris ioctls are specific to this driver */
|
||||
case WIOCSTART:
|
||||
case WIOCSTOP:
|
||||
case WIOCGSTAT:
|
||||
lock_kernel();
|
||||
rval = wd_ioctl(file->f_path.dentry->d_inode, file, cmd, arg);
|
||||
unlock_kernel();
|
||||
break;
|
||||
/* everything else is handled by the generic compat layer */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
/* 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 const struct file_operations wd_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.ioctl = wd_ioctl,
|
||||
.compat_ioctl = wd_compat_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->ofdev.node->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);
|
||||
242
drivers/sbus/char/display7seg.c
Normal file
242
drivers/sbus/char/display7seg.c
Normal file
@@ -0,0 +1,242 @@
|
||||
/* $Id: display7seg.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu 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 */
|
||||
#include <linux/smp_lock.h>
|
||||
#include <asm/atomic.h>
|
||||
#include <asm/ebus.h> /* EBus device */
|
||||
#include <asm/oplib.h> /* OpenProm Library */
|
||||
#include <asm/uaccess.h> /* put_/get_user */
|
||||
|
||||
#include <asm/display7seg.h>
|
||||
|
||||
#define D7S_MINOR 193
|
||||
#define D7S_OBPNAME "display7seg"
|
||||
#define D7S_DEVNAME "d7s"
|
||||
|
||||
static int sol_compat = 0; /* Solaris compatibility mode */
|
||||
|
||||
#ifdef MODULE
|
||||
|
||||
/* Solaris compatibility flag -
|
||||
* The Solaris implementation omits support for several
|
||||
* documented driver features (ref Sun doc 806-0180-03).
|
||||
* By default, this module supports the documented driver
|
||||
* abilities, rather than the Solaris implementation:
|
||||
*
|
||||
* 1) Device ALWAYS reverts to OBP-specified FLIPPED mode
|
||||
* upon closure of device or module unload.
|
||||
* 2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of
|
||||
* FLIP bit
|
||||
*
|
||||
* If you wish the device to operate as under Solaris,
|
||||
* omitting above features, set this parameter to non-zero.
|
||||
*/
|
||||
module_param
|
||||
(sol_compat, int, 0);
|
||||
MODULE_PARM_DESC
|
||||
(sol_compat,
|
||||
"Disables documented functionality omitted from Solaris driver");
|
||||
|
||||
MODULE_AUTHOR
|
||||
("Eric Brower <ebrower@usa.net>");
|
||||
MODULE_DESCRIPTION
|
||||
("7-Segment Display driver for Sun Microsystems CP1400/1500");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_SUPPORTED_DEVICE
|
||||
("d7s");
|
||||
#endif /* ifdef MODULE */
|
||||
|
||||
/*
|
||||
* Register block address- see header for details
|
||||
* -----------------------------------------
|
||||
* | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 |
|
||||
* -----------------------------------------
|
||||
*
|
||||
* DP - Toggles decimal point on/off
|
||||
* ALARM - Toggles "Alarm" LED green/red
|
||||
* FLIP - Inverts display for upside-down mounted board
|
||||
* bits 0-4 - 7-segment display contents
|
||||
*/
|
||||
static void __iomem* d7s_regs;
|
||||
|
||||
static inline void d7s_free(void)
|
||||
{
|
||||
iounmap(d7s_regs);
|
||||
}
|
||||
|
||||
static inline int d7s_obpflipped(void)
|
||||
{
|
||||
int opt_node;
|
||||
|
||||
opt_node = prom_getchild(prom_root_node);
|
||||
opt_node = prom_searchsiblings(opt_node, "options");
|
||||
return ((-1 != prom_getintdefault(opt_node, "d7s-flipped?", -1)) ? 0 : 1);
|
||||
}
|
||||
|
||||
static atomic_t d7s_users = ATOMIC_INIT(0);
|
||||
|
||||
static int d7s_open(struct inode *inode, struct file *f)
|
||||
{
|
||||
if (D7S_MINOR != iminor(inode))
|
||||
return -ENODEV;
|
||||
atomic_inc(&d7s_users);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int d7s_release(struct inode *inode, struct file *f)
|
||||
{
|
||||
/* Reset flipped state to OBP default only if
|
||||
* no other users have the device open and we
|
||||
* are not operating in solaris-compat mode
|
||||
*/
|
||||
if (atomic_dec_and_test(&d7s_users) && !sol_compat) {
|
||||
int regval = 0;
|
||||
|
||||
regval = readb(d7s_regs);
|
||||
(0 == d7s_obpflipped()) ?
|
||||
writeb(regval |= D7S_FLIP, d7s_regs):
|
||||
writeb(regval &= ~D7S_FLIP, d7s_regs);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
__u8 regs = readb(d7s_regs);
|
||||
__u8 ireg = 0;
|
||||
int error = 0;
|
||||
|
||||
if (D7S_MINOR != iminor(file->f_path.dentry->d_inode))
|
||||
return -ENODEV;
|
||||
|
||||
lock_kernel();
|
||||
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)) {
|
||||
error = -EFAULT;
|
||||
break;
|
||||
}
|
||||
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)) {
|
||||
error = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case D7SIOCTM:
|
||||
/* toggle device mode-- flip display orientation */
|
||||
(regs & D7S_FLIP) ?
|
||||
(regs &= ~D7S_FLIP) : (regs |= D7S_FLIP);
|
||||
writeb(regs, d7s_regs);
|
||||
break;
|
||||
};
|
||||
unlock_kernel();
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static const struct file_operations d7s_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = d7s_ioctl,
|
||||
.compat_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_node->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);
|
||||
1140
drivers/sbus/char/envctrl.c
Normal file
1140
drivers/sbus/char/envctrl.c
Normal file
File diff suppressed because it is too large
Load Diff
254
drivers/sbus/char/flash.c
Normal file
254
drivers/sbus/char/flash.c
Normal file
@@ -0,0 +1,254 @@
|
||||
/* $Id: flash.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
* flash.c: Allow mmap access to the OBP Flash, for OBP updates.
|
||||
*
|
||||
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
|
||||
*/
|
||||
|
||||
#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));
|
||||
|
||||
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
|
||||
|
||||
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 const 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
|
||||
struct linux_prom_registers *ebus_regs;
|
||||
|
||||
for_each_ebus(ebus) {
|
||||
for_each_ebusdev(edev, ebus) {
|
||||
if (!strcmp(edev->prom_node->name, "flashprom"))
|
||||
goto ebus_done;
|
||||
}
|
||||
}
|
||||
ebus_done:
|
||||
if (!edev)
|
||||
return -ENODEV;
|
||||
|
||||
ebus_regs = of_get_property(edev->prom_node, "reg", &len);
|
||||
if (!ebus_regs || (len % sizeof(regs[0])) != 0) {
|
||||
printk("flash: Strange reg property size %d\n", len);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
nregs = len / sizeof(ebus_regs[0]);
|
||||
|
||||
flash.read_base = edev->resource[0].start;
|
||||
flash.read_size = ebus_regs[0].reg_size;
|
||||
|
||||
if (nregs == 1) {
|
||||
flash.write_base = edev->resource[0].start;
|
||||
flash.write_size = ebus_regs[0].reg_size;
|
||||
} else if (nregs == 2) {
|
||||
flash.write_base = edev->resource[1].start;
|
||||
flash.write_size = ebus_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");
|
||||
628
drivers/sbus/char/jsflash.c
Normal file
628
drivers/sbus/char/jsflash.c
Normal file
@@ -0,0 +1,628 @@
|
||||
/*
|
||||
* 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 __user * buf,
|
||||
size_t togo, loff_t *ppos)
|
||||
{
|
||||
unsigned long p = *ppos;
|
||||
char __user *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 __user * 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(void __user *arg)
|
||||
{
|
||||
struct jsflash_program_arg abuf;
|
||||
char __user *uptr;
|
||||
unsigned long p;
|
||||
unsigned int togo;
|
||||
union {
|
||||
unsigned int n;
|
||||
char s[4];
|
||||
} b;
|
||||
|
||||
if (copy_from_user(&abuf, arg, JSFPRGSZ))
|
||||
return -EFAULT;
|
||||
p = abuf.off;
|
||||
togo = abuf.size;
|
||||
if ((togo & 3) || (p & 3)) return -EINVAL;
|
||||
|
||||
uptr = (char __user *) (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;
|
||||
void __user *argp = (void __user *)arg;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
switch (cmd) {
|
||||
case JSFLASH_IDENT:
|
||||
if (copy_to_user(argp, &jsf0.id, JSFIDSZ))
|
||||
return -EFAULT;
|
||||
break;
|
||||
case JSFLASH_ERASE:
|
||||
error = jsf_ioctl_erase(arg);
|
||||
break;
|
||||
case JSFLASH_PROGRAM:
|
||||
error = jsf_ioctl_program(argp);
|
||||
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 const struct file_operations jsf_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = jsf_lseek,
|
||||
.read = jsf_read,
|
||||
.write = jsf_write,
|
||||
.ioctl = jsf_ioctl,
|
||||
.mmap = jsf_mmap,
|
||||
.open = jsf_open,
|
||||
.release = jsf_release,
|
||||
};
|
||||
|
||||
static struct miscdevice jsf_dev = { JSF_MINOR, "jsflash", &jsf_fops };
|
||||
|
||||
static struct block_device_operations jsfd_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int jsflash_init(void)
|
||||
{
|
||||
int rc;
|
||||
struct jsflash *jsf;
|
||||
int node;
|
||||
char banner[128];
|
||||
struct linux_prom_registers reg0;
|
||||
|
||||
node = prom_getchild(prom_root_node);
|
||||
node = prom_searchsiblings(node, "flash-memory");
|
||||
if (node != 0 && node != -1) {
|
||||
if (prom_getproperty(node, "reg",
|
||||
(char *)®0, sizeof(reg0)) == -1) {
|
||||
printk("jsflash: no \"reg\" property\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
if (reg0.which_io != 0) {
|
||||
printk("jsflash: bus number nonzero: 0x%x:%x\n",
|
||||
reg0.which_io, reg0.phys_addr);
|
||||
return -ENXIO;
|
||||
}
|
||||
/*
|
||||
* Flash may be somewhere else, for instance on Ebus.
|
||||
* So, don't do the following check for IIep flash space.
|
||||
*/
|
||||
#if 0
|
||||
if ((reg0.phys_addr >> 24) != 0x20) {
|
||||
printk("jsflash: suspicious address: 0x%x:%x\n",
|
||||
reg0.which_io, reg0.phys_addr);
|
||||
return -ENXIO;
|
||||
}
|
||||
#endif
|
||||
if ((int)reg0.reg_size <= 0) {
|
||||
printk("jsflash: bad size 0x%x\n", (int)reg0.reg_size);
|
||||
return -ENXIO;
|
||||
}
|
||||
} else {
|
||||
/* XXX Remove this code once PROLL ID12 got widespread */
|
||||
printk("jsflash: no /flash-memory node, use PROLL >= 12\n");
|
||||
prom_getproperty(prom_root_node, "banner-name", banner, 128);
|
||||
if (strcmp (banner, "JavaStation-NC") != 0 &&
|
||||
strcmp (banner, "JavaStation-E") != 0) {
|
||||
return -ENXIO;
|
||||
}
|
||||
reg0.which_io = 0;
|
||||
reg0.phys_addr = 0x20400000;
|
||||
reg0.reg_size = 0x00800000;
|
||||
}
|
||||
|
||||
/* Let us be really paranoid for modifications to probing code. */
|
||||
/* extern enum sparc_cpu sparc_cpu_model; */ /* in <asm/system.h> */
|
||||
if (sparc_cpu_model != sun4m) {
|
||||
/* We must be on sun4m because we use MMU Bypass ASI. */
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
if (jsf0.base == 0) {
|
||||
jsf = &jsf0;
|
||||
|
||||
jsf->base = reg0.phys_addr;
|
||||
jsf->size = reg0.reg_size;
|
||||
|
||||
/* XXX Redo the userland interface. */
|
||||
jsf->id.off = JSF_BASE_ALL;
|
||||
jsf->id.size = 0x01000000; /* 16M - all segments */
|
||||
strcpy(jsf->id.name, "Krups_all");
|
||||
|
||||
jsf->dv[0].dbase = jsf->base;
|
||||
jsf->dv[0].dsize = jsf->size;
|
||||
jsf->dv[1].dbase = jsf->base + 1024;
|
||||
jsf->dv[1].dsize = jsf->size - 1024;
|
||||
jsf->dv[2].dbase = JSF_BASE_ALL;
|
||||
jsf->dv[2].dsize = 0x01000000;
|
||||
|
||||
printk("Espresso Flash @0x%lx [%d MB]\n", jsf->base,
|
||||
(int) (jsf->size / (1024*1024)));
|
||||
}
|
||||
|
||||
if ((rc = misc_register(&jsf_dev)) != 0) {
|
||||
printk(KERN_ERR "jsf: unable to get misc minor %d\n",
|
||||
JSF_MINOR);
|
||||
jsf0.base = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct request_queue *jsf_queue;
|
||||
|
||||
static int jsfd_init(void)
|
||||
{
|
||||
static DEFINE_SPINLOCK(lock);
|
||||
struct jsflash *jsf;
|
||||
struct jsfd_part *jdp;
|
||||
int err;
|
||||
int i;
|
||||
|
||||
if (jsf0.base == 0)
|
||||
return -ENXIO;
|
||||
|
||||
err = -ENOMEM;
|
||||
for (i = 0; i < JSF_MAX; i++) {
|
||||
struct gendisk *disk = alloc_disk(1);
|
||||
if (!disk)
|
||||
goto out;
|
||||
jsfd_disk[i] = disk;
|
||||
}
|
||||
|
||||
if (register_blkdev(JSFD_MAJOR, "jsfd")) {
|
||||
err = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
jsf_queue = blk_init_queue(jsfd_do_request, &lock);
|
||||
if (!jsf_queue) {
|
||||
err = -ENOMEM;
|
||||
unregister_blkdev(JSFD_MAJOR, "jsfd");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < JSF_MAX; i++) {
|
||||
struct gendisk *disk = jsfd_disk[i];
|
||||
if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
|
||||
jsf = &jsf0; /* actually, &jsfv[i >> JSF_PART_BITS] */
|
||||
jdp = &jsf->dv[i&JSF_PART_MASK];
|
||||
|
||||
disk->major = JSFD_MAJOR;
|
||||
disk->first_minor = i;
|
||||
sprintf(disk->disk_name, "jsfd%d", i);
|
||||
disk->fops = &jsfd_fops;
|
||||
set_capacity(disk, jdp->dsize >> 9);
|
||||
disk->private_data = jdp;
|
||||
disk->queue = jsf_queue;
|
||||
add_disk(disk);
|
||||
set_disk_ro(disk, 1);
|
||||
}
|
||||
return 0;
|
||||
out:
|
||||
while (i--)
|
||||
put_disk(jsfd_disk[i]);
|
||||
return err;
|
||||
}
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static int __init jsflash_init_module(void) {
|
||||
int rc;
|
||||
|
||||
if ((rc = jsflash_init()) == 0) {
|
||||
jsfd_init();
|
||||
return 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __exit jsflash_cleanup_module(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < JSF_MAX; i++) {
|
||||
if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
|
||||
del_gendisk(jsfd_disk[i]);
|
||||
put_disk(jsfd_disk[i]);
|
||||
}
|
||||
if (jsf0.busy)
|
||||
printk("jsf0: cleaning busy unit\n");
|
||||
jsf0.base = 0;
|
||||
jsf0.busy = 0;
|
||||
|
||||
misc_deregister(&jsf_dev);
|
||||
if (unregister_blkdev(JSFD_MAJOR, "jsfd") != 0)
|
||||
printk("jsfd: cleanup_module failed\n");
|
||||
blk_cleanup_queue(jsf_queue);
|
||||
}
|
||||
|
||||
module_init(jsflash_init_module);
|
||||
module_exit(jsflash_cleanup_module);
|
||||
27
drivers/sbus/char/max1617.h
Normal file
27
drivers/sbus/char/max1617.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/* $Id: max1617.h,v 1.1.1.1 2007/06/12 07:27:09 eyryu 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 */
|
||||
754
drivers/sbus/char/openprom.c
Normal file
754
drivers/sbus/char/openprom.c
Normal file
@@ -0,0 +1,754 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.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/prom.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
|
||||
|
||||
MODULE_AUTHOR("Thomas K. Dyas (tdyas@noc.rutgers.edu) and Eddie C. Dost (ecd@skynet.be)");
|
||||
MODULE_DESCRIPTION("OPENPROM Configuration Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("1.0");
|
||||
|
||||
/* Private data kept by the driver for each descriptor. */
|
||||
typedef struct openprom_private_data
|
||||
{
|
||||
struct device_node *current_node; /* Current node for SunOS ioctls. */
|
||||
struct device_node *lastnode; /* Last valid node used by BSD ioctls. */
|
||||
} DATA;
|
||||
|
||||
/* ID of the PROM node containing all of the EEPROM options. */
|
||||
static struct device_node *options_node;
|
||||
|
||||
/*
|
||||
* 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 = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
|
||||
return -ENOMEM;
|
||||
|
||||
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 = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
|
||||
return -ENOMEM;
|
||||
|
||||
(*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;
|
||||
}
|
||||
|
||||
static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
|
||||
{
|
||||
void *pval;
|
||||
int len;
|
||||
|
||||
if (!dp ||
|
||||
!(pval = of_get_property(dp, op->oprom_array, &len)) ||
|
||||
len <= 0 || len > bufsize)
|
||||
return copyout(argp, op, sizeof(int));
|
||||
|
||||
memcpy(op->oprom_array, pval, len);
|
||||
op->oprom_array[len] = '\0';
|
||||
op->oprom_size = len;
|
||||
|
||||
return copyout(argp, op, sizeof(int) + bufsize);
|
||||
}
|
||||
|
||||
static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
|
||||
{
|
||||
struct property *prop;
|
||||
int len;
|
||||
|
||||
if (!dp)
|
||||
return copyout(argp, op, sizeof(int));
|
||||
if (op->oprom_array[0] == '\0') {
|
||||
prop = dp->properties;
|
||||
if (!prop)
|
||||
return copyout(argp, op, sizeof(int));
|
||||
len = strlen(prop->name);
|
||||
} else {
|
||||
prop = of_find_property(dp, op->oprom_array, NULL);
|
||||
|
||||
if (!prop ||
|
||||
!prop->next ||
|
||||
(len = strlen(prop->next->name)) + 1 > bufsize)
|
||||
return copyout(argp, op, sizeof(int));
|
||||
|
||||
prop = prop->next;
|
||||
}
|
||||
|
||||
memcpy(op->oprom_array, prop->name, len);
|
||||
op->oprom_array[len] = '\0';
|
||||
op->oprom_size = ++len;
|
||||
|
||||
return copyout(argp, op, sizeof(int) + bufsize);
|
||||
}
|
||||
|
||||
static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize)
|
||||
{
|
||||
char *buf = op->oprom_array + strlen(op->oprom_array) + 1;
|
||||
int len = op->oprom_array + bufsize - buf;
|
||||
|
||||
return of_set_property(options_node, op->oprom_array, buf, len);
|
||||
}
|
||||
|
||||
static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
|
||||
{
|
||||
phandle ph;
|
||||
|
||||
BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
|
||||
|
||||
if (bufsize < sizeof(phandle))
|
||||
return -EINVAL;
|
||||
|
||||
ph = *((int *) op->oprom_array);
|
||||
if (ph) {
|
||||
dp = of_find_node_by_phandle(ph);
|
||||
if (!dp)
|
||||
return -EINVAL;
|
||||
|
||||
switch (cmd) {
|
||||
case OPROMNEXT:
|
||||
dp = dp->sibling;
|
||||
break;
|
||||
|
||||
case OPROMCHILD:
|
||||
dp = dp->child;
|
||||
break;
|
||||
|
||||
case OPROMSETCUR:
|
||||
default:
|
||||
break;
|
||||
};
|
||||
} else {
|
||||
/* Sibling of node zero is the root node. */
|
||||
if (cmd != OPROMNEXT)
|
||||
return -EINVAL;
|
||||
|
||||
dp = of_find_node_by_path("/");
|
||||
}
|
||||
|
||||
ph = 0;
|
||||
if (dp)
|
||||
ph = dp->node;
|
||||
|
||||
data->current_node = dp;
|
||||
*((int *) op->oprom_array) = ph;
|
||||
op->oprom_size = sizeof(phandle);
|
||||
|
||||
return copyout(argp, op, bufsize + sizeof(int));
|
||||
}
|
||||
|
||||
static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
|
||||
{
|
||||
int err = -EINVAL;
|
||||
|
||||
if (bufsize >= 2*sizeof(int)) {
|
||||
#ifdef CONFIG_PCI
|
||||
struct pci_dev *pdev;
|
||||
struct pcidev_cookie *pcp;
|
||||
pdev = pci_get_bus_and_slot (((int *) op->oprom_array)[0],
|
||||
((int *) op->oprom_array)[1]);
|
||||
|
||||
pcp = pdev->sysdata;
|
||||
if (pcp != NULL) {
|
||||
dp = pcp->prom_node;
|
||||
data->current_node = dp;
|
||||
*((int *)op->oprom_array) = dp->node;
|
||||
op->oprom_size = sizeof(int);
|
||||
err = copyout(argp, op, bufsize + sizeof(int));
|
||||
}
|
||||
pci_dev_put(pdev);
|
||||
#endif
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
|
||||
{
|
||||
phandle ph = 0;
|
||||
|
||||
dp = of_find_node_by_path(op->oprom_array);
|
||||
if (dp)
|
||||
ph = dp->node;
|
||||
data->current_node = dp;
|
||||
*((int *)op->oprom_array) = ph;
|
||||
op->oprom_size = sizeof(int);
|
||||
|
||||
return copyout(argp, op, bufsize + sizeof(int));
|
||||
}
|
||||
|
||||
static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize)
|
||||
{
|
||||
char *buf = saved_command_line;
|
||||
int len = strlen(buf);
|
||||
|
||||
if (len > bufsize)
|
||||
return -EINVAL;
|
||||
|
||||
strcpy(op->oprom_array, buf);
|
||||
op->oprom_size = len;
|
||||
|
||||
return copyout(argp, op, bufsize + sizeof(int));
|
||||
}
|
||||
|
||||
/*
|
||||
* SunOS and Solaris /dev/openprom ioctl calls.
|
||||
*/
|
||||
static int openprom_sunos_ioctl(struct inode * inode, struct file * file,
|
||||
unsigned int cmd, unsigned long arg,
|
||||
struct device_node *dp)
|
||||
{
|
||||
DATA *data = file->private_data;
|
||||
struct openpromio *opp;
|
||||
int bufsize, 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:
|
||||
error = opromgetprop(argp, dp, opp, bufsize);
|
||||
break;
|
||||
|
||||
case OPROMNXTOPT:
|
||||
case OPROMNXTPROP:
|
||||
error = opromnxtprop(argp, dp, opp, bufsize);
|
||||
break;
|
||||
|
||||
case OPROMSETOPT:
|
||||
case OPROMSETOPT2:
|
||||
error = opromsetopt(dp, opp, bufsize);
|
||||
break;
|
||||
|
||||
case OPROMNEXT:
|
||||
case OPROMCHILD:
|
||||
case OPROMSETCUR:
|
||||
error = opromnext(argp, cmd, dp, opp, bufsize, data);
|
||||
break;
|
||||
|
||||
case OPROMPCI2NODE:
|
||||
error = oprompci2node(argp, dp, opp, bufsize, data);
|
||||
break;
|
||||
|
||||
case OPROMPATH2NODE:
|
||||
error = oprompath2node(argp, dp, opp, bufsize, data);
|
||||
break;
|
||||
|
||||
case OPROMGETBOOTARGS:
|
||||
error = opromgetbootargs(argp, opp, bufsize);
|
||||
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;
|
||||
}
|
||||
|
||||
static struct device_node *get_node(phandle n, DATA *data)
|
||||
{
|
||||
struct device_node *dp = of_find_node_by_phandle(n);
|
||||
|
||||
if (dp)
|
||||
data->lastnode = dp;
|
||||
|
||||
return dp;
|
||||
}
|
||||
|
||||
/* 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 opiocget(void __user *argp, DATA *data)
|
||||
{
|
||||
struct opiocdesc op;
|
||||
struct device_node *dp;
|
||||
char *str;
|
||||
void *pval;
|
||||
int err, len;
|
||||
|
||||
if (copy_from_user(&op, argp, sizeof(op)))
|
||||
return -EFAULT;
|
||||
|
||||
dp = get_node(op.op_nodeid, data);
|
||||
|
||||
err = copyin_string(op.op_name, op.op_namelen, &str);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
pval = of_get_property(dp, str, &len);
|
||||
err = 0;
|
||||
if (!pval || len > op.op_buflen) {
|
||||
err = -EINVAL;
|
||||
} else {
|
||||
op.op_buflen = len;
|
||||
if (copy_to_user(argp, &op, sizeof(op)) ||
|
||||
copy_to_user(op.op_buf, pval, len))
|
||||
err = -EFAULT;
|
||||
}
|
||||
kfree(str);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int opiocnextprop(void __user *argp, DATA *data)
|
||||
{
|
||||
struct opiocdesc op;
|
||||
struct device_node *dp;
|
||||
struct property *prop;
|
||||
char *str;
|
||||
int err, len;
|
||||
|
||||
if (copy_from_user(&op, argp, sizeof(op)))
|
||||
return -EFAULT;
|
||||
|
||||
dp = get_node(op.op_nodeid, data);
|
||||
if (!dp)
|
||||
return -EINVAL;
|
||||
|
||||
err = copyin_string(op.op_name, op.op_namelen, &str);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (str[0] == '\0') {
|
||||
prop = dp->properties;
|
||||
} else {
|
||||
prop = of_find_property(dp, str, NULL);
|
||||
if (prop)
|
||||
prop = prop->next;
|
||||
}
|
||||
kfree(str);
|
||||
|
||||
if (!prop)
|
||||
len = 0;
|
||||
else
|
||||
len = prop->length;
|
||||
|
||||
if (len > op.op_buflen)
|
||||
len = op.op_buflen;
|
||||
|
||||
if (copy_to_user(argp, &op, sizeof(op)))
|
||||
return -EFAULT;
|
||||
|
||||
if (len &&
|
||||
copy_to_user(op.op_buf, prop->value, len))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int opiocset(void __user *argp, DATA *data)
|
||||
{
|
||||
struct opiocdesc op;
|
||||
struct device_node *dp;
|
||||
char *str, *tmp;
|
||||
int err;
|
||||
|
||||
if (copy_from_user(&op, argp, sizeof(op)))
|
||||
return -EFAULT;
|
||||
|
||||
dp = get_node(op.op_nodeid, data);
|
||||
if (!dp)
|
||||
return -EINVAL;
|
||||
|
||||
err = copyin_string(op.op_name, op.op_namelen, &str);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = copyin_string(op.op_buf, op.op_buflen, &tmp);
|
||||
if (err) {
|
||||
kfree(str);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = of_set_property(dp, str, tmp, op.op_buflen);
|
||||
|
||||
kfree(str);
|
||||
kfree(tmp);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int opiocgetnext(unsigned int cmd, void __user *argp)
|
||||
{
|
||||
struct device_node *dp;
|
||||
phandle nd;
|
||||
|
||||
BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
|
||||
|
||||
if (copy_from_user(&nd, argp, sizeof(phandle)))
|
||||
return -EFAULT;
|
||||
|
||||
if (nd == 0) {
|
||||
if (cmd != OPIOCGETNEXT)
|
||||
return -EINVAL;
|
||||
dp = of_find_node_by_path("/");
|
||||
} else {
|
||||
dp = of_find_node_by_phandle(nd);
|
||||
nd = 0;
|
||||
if (dp) {
|
||||
if (cmd == OPIOCGETNEXT)
|
||||
dp = dp->sibling;
|
||||
else
|
||||
dp = dp->child;
|
||||
}
|
||||
}
|
||||
if (dp)
|
||||
nd = dp->node;
|
||||
if (copy_to_user(argp, &nd, sizeof(phandle)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
int err;
|
||||
|
||||
switch (cmd) {
|
||||
case OPIOCGET:
|
||||
err = opiocget(argp, data);
|
||||
break;
|
||||
|
||||
case OPIOCNEXTPROP:
|
||||
err = opiocnextprop(argp, data);
|
||||
break;
|
||||
|
||||
case OPIOCSET:
|
||||
err = opiocset(argp, data);
|
||||
break;
|
||||
|
||||
case OPIOCGETOPTNODE:
|
||||
BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
|
||||
|
||||
if (copy_to_user(argp, &options_node->node, sizeof(phandle)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
|
||||
case OPIOCGETNEXT:
|
||||
case OPIOCGETCHILD:
|
||||
err = opiocgetnext(cmd, argp);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
||||
};
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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, NULL);
|
||||
|
||||
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:
|
||||
return -EINVAL;
|
||||
};
|
||||
}
|
||||
|
||||
static long openprom_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
long rval = -ENOTTY;
|
||||
|
||||
/*
|
||||
* SunOS/Solaris only, the NetBSD one's have embedded pointers in
|
||||
* the arg which we'd need to clean up...
|
||||
*/
|
||||
switch (cmd) {
|
||||
case OPROMGETOPT:
|
||||
case OPROMSETOPT:
|
||||
case OPROMNXTOPT:
|
||||
case OPROMSETOPT2:
|
||||
case OPROMNEXT:
|
||||
case OPROMCHILD:
|
||||
case OPROMGETPROP:
|
||||
case OPROMNXTPROP:
|
||||
case OPROMU2P:
|
||||
case OPROMGETCONS:
|
||||
case OPROMGETFBNAME:
|
||||
case OPROMGETBOOTARGS:
|
||||
case OPROMSETCUR:
|
||||
case OPROMPCI2NODE:
|
||||
case OPROMPATH2NODE:
|
||||
rval = openprom_ioctl(file->f_path.dentry->d_inode, file, cmd, arg);
|
||||
break;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
static int openprom_open(struct inode * inode, struct file * file)
|
||||
{
|
||||
DATA *data;
|
||||
|
||||
data = kmalloc(sizeof(DATA), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->current_node = of_find_node_by_path("/");
|
||||
data->lastnode = data->current_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 const struct file_operations openprom_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.ioctl = openprom_ioctl,
|
||||
.compat_ioctl = openprom_compat_ioctl,
|
||||
.open = openprom_open,
|
||||
.release = openprom_release,
|
||||
};
|
||||
|
||||
static struct miscdevice openprom_dev = {
|
||||
.minor = SUN_OPENPROM_MINOR,
|
||||
.name = "openprom",
|
||||
.fops = &openprom_fops,
|
||||
};
|
||||
|
||||
static int __init openprom_init(void)
|
||||
{
|
||||
struct device_node *dp;
|
||||
int err;
|
||||
|
||||
err = misc_register(&openprom_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dp = of_find_node_by_path("/");
|
||||
dp = dp->child;
|
||||
while (dp) {
|
||||
if (!strcmp(dp->name, "options"))
|
||||
break;
|
||||
dp = dp->sibling;
|
||||
}
|
||||
options_node = dp;
|
||||
|
||||
if (!options_node) {
|
||||
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);
|
||||
293
drivers/sbus/char/riowatchdog.c
Normal file
293
drivers/sbus/char/riowatchdog.c
Normal file
@@ -0,0 +1,293 @@
|
||||
/* $Id: riowatchdog.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
* riowatchdog.c - driver for hw watchdog inside Super I/O of RIO
|
||||
*
|
||||
* Copyright (C) 2001 David S. Miller (davem@redhat.com)
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/miscdevice.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/ebus.h>
|
||||
#include <asm/bbc.h>
|
||||
#include <asm/oplib.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include <asm/watchdog.h>
|
||||
|
||||
/* RIO uses the NatSemi Super I/O power management logical device
|
||||
* as its' watchdog.
|
||||
*
|
||||
* When the watchdog triggers, it asserts a line to the BBC (Boot Bus
|
||||
* Controller) of the machine. The BBC can only be configured to
|
||||
* trigger a power-on reset when the signal is asserted. The BBC
|
||||
* can be configured to ignore the signal entirely as well.
|
||||
*
|
||||
* The only Super I/O device register we care about is at index
|
||||
* 0x05 (WDTO_INDEX) which is the watchdog time-out in minutes (1-255).
|
||||
* If set to zero, this disables the watchdog. When set, the system
|
||||
* must periodically (before watchdog expires) clear (set to zero) and
|
||||
* re-set the watchdog else it will trigger.
|
||||
*
|
||||
* There are two other indexed watchdog registers inside this Super I/O
|
||||
* logical device, but they are unused. The first, at index 0x06 is
|
||||
* the watchdog control and can be used to make the watchdog timer re-set
|
||||
* when the PS/2 mouse or serial lines show activity. The second, at
|
||||
* index 0x07 is merely a sampling of the line from the watchdog to the
|
||||
* BBC.
|
||||
*
|
||||
* The watchdog device generates no interrupts.
|
||||
*/
|
||||
|
||||
MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
|
||||
MODULE_DESCRIPTION("Hardware watchdog driver for Sun RIO");
|
||||
MODULE_SUPPORTED_DEVICE("watchdog");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define RIOWD_NAME "pmc"
|
||||
#define RIOWD_MINOR 215
|
||||
|
||||
static DEFINE_SPINLOCK(riowd_lock);
|
||||
|
||||
static void __iomem *bbc_regs;
|
||||
static void __iomem *riowd_regs;
|
||||
#define WDTO_INDEX 0x05
|
||||
|
||||
static int riowd_timeout = 1; /* in minutes */
|
||||
module_param(riowd_timeout, int, 0);
|
||||
MODULE_PARM_DESC(riowd_timeout, "Watchdog timeout in minutes");
|
||||
|
||||
#if 0 /* Currently unused. */
|
||||
static u8 riowd_readreg(int index)
|
||||
{
|
||||
unsigned long flags;
|
||||
u8 ret;
|
||||
|
||||
spin_lock_irqsave(&riowd_lock, flags);
|
||||
writeb(index, riowd_regs + 0);
|
||||
ret = readb(riowd_regs + 1);
|
||||
spin_unlock_irqrestore(&riowd_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void riowd_writereg(u8 val, int index)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&riowd_lock, flags);
|
||||
writeb(index, riowd_regs + 0);
|
||||
writeb(val, riowd_regs + 1);
|
||||
spin_unlock_irqrestore(&riowd_lock, flags);
|
||||
}
|
||||
|
||||
static void riowd_pingtimer(void)
|
||||
{
|
||||
riowd_writereg(riowd_timeout, WDTO_INDEX);
|
||||
}
|
||||
|
||||
static void riowd_stoptimer(void)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
riowd_writereg(0, WDTO_INDEX);
|
||||
|
||||
val = readb(bbc_regs + BBC_WDACTION);
|
||||
val &= ~BBC_WDACTION_RST;
|
||||
writeb(val, bbc_regs + BBC_WDACTION);
|
||||
}
|
||||
|
||||
static void riowd_starttimer(void)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
riowd_writereg(riowd_timeout, WDTO_INDEX);
|
||||
|
||||
val = readb(bbc_regs + BBC_WDACTION);
|
||||
val |= BBC_WDACTION_RST;
|
||||
writeb(val, bbc_regs + BBC_WDACTION);
|
||||
}
|
||||
|
||||
static int riowd_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
nonseekable_open(inode, filp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int riowd_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int riowd_ioctl(struct inode *inode, struct file *filp,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
static struct watchdog_info info = {
|
||||
WDIOF_SETTIMEOUT, 0, "Natl. Semiconductor PC97317"
|
||||
};
|
||||
void __user *argp = (void __user *)arg;
|
||||
unsigned int options;
|
||||
int new_margin;
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
if (copy_to_user(argp, &info, sizeof(info)))
|
||||
return -EFAULT;
|
||||
break;
|
||||
|
||||
case WDIOC_GETSTATUS:
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
if (put_user(0, (int __user *)argp))
|
||||
return -EFAULT;
|
||||
break;
|
||||
|
||||
case WDIOC_KEEPALIVE:
|
||||
riowd_pingtimer();
|
||||
break;
|
||||
|
||||
case WDIOC_SETOPTIONS:
|
||||
if (copy_from_user(&options, argp, sizeof(options)))
|
||||
return -EFAULT;
|
||||
|
||||
if (options & WDIOS_DISABLECARD)
|
||||
riowd_stoptimer();
|
||||
else if (options & WDIOS_ENABLECARD)
|
||||
riowd_starttimer();
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
break;
|
||||
|
||||
case WDIOC_SETTIMEOUT:
|
||||
if (get_user(new_margin, (int __user *)argp))
|
||||
return -EFAULT;
|
||||
if ((new_margin < 60) || (new_margin > (255 * 60)))
|
||||
return -EINVAL;
|
||||
riowd_timeout = (new_margin + 59) / 60;
|
||||
riowd_pingtimer();
|
||||
/* Fall */
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(riowd_timeout * 60, (int __user *)argp);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t riowd_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
if (count) {
|
||||
riowd_pingtimer();
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const 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->ofdev.node->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->ofdev.node->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);
|
||||
273
drivers/sbus/char/rtc.c
Normal file
273
drivers/sbus/char/rtc.c
Normal file
@@ -0,0 +1,273 @@
|
||||
/* $Id: rtc.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu 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;
|
||||
|
||||
/* This is the structure layout used by drivers/char/rtc.c, we
|
||||
* support that driver's ioctls so that things are less messy in
|
||||
* userspace.
|
||||
*/
|
||||
struct rtc_time_generic {
|
||||
int tm_sec;
|
||||
int tm_min;
|
||||
int tm_hour;
|
||||
int tm_mday;
|
||||
int tm_mon;
|
||||
int tm_year;
|
||||
int tm_wday;
|
||||
int tm_yday;
|
||||
int tm_isdst;
|
||||
};
|
||||
#define RTC_AIE_ON _IO('p', 0x01) /* Alarm int. enable on */
|
||||
#define RTC_AIE_OFF _IO('p', 0x02) /* ... off */
|
||||
#define RTC_UIE_ON _IO('p', 0x03) /* Update int. enable on */
|
||||
#define RTC_UIE_OFF _IO('p', 0x04) /* ... off */
|
||||
#define RTC_PIE_ON _IO('p', 0x05) /* Periodic int. enable on */
|
||||
#define RTC_PIE_OFF _IO('p', 0x06) /* ... off */
|
||||
#define RTC_WIE_ON _IO('p', 0x0f) /* Watchdog int. enable on */
|
||||
#define RTC_WIE_OFF _IO('p', 0x10) /* ... off */
|
||||
#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time_generic) /* Read RTC time */
|
||||
#define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time_generic) /* Set RTC time */
|
||||
#define RTC_ALM_SET _IOW('p', 0x07, struct rtc_time) /* Set alarm time */
|
||||
#define RTC_ALM_READ _IOR('p', 0x08, struct rtc_time) /* Read alarm time */
|
||||
#define RTC_IRQP_READ _IOR('p', 0x0b, unsigned long) /* Read IRQ rate */
|
||||
#define RTC_IRQP_SET _IOW('p', 0x0c, unsigned long) /* Set IRQ rate */
|
||||
#define RTC_EPOCH_READ _IOR('p', 0x0d, unsigned long) /* Read epoch */
|
||||
#define RTC_EPOCH_SET _IOW('p', 0x0e, unsigned long) /* Set epoch */
|
||||
#define RTC_WKALM_SET _IOW('p', 0x0f, struct rtc_wkalrm)/* Set wakeup alarm*/
|
||||
#define RTC_WKALM_RD _IOR('p', 0x10, struct rtc_wkalrm)/* Get wakeup alarm*/
|
||||
#define RTC_PLL_GET _IOR('p', 0x11, struct rtc_pll_info) /* Get PLL correction */
|
||||
#define RTC_PLL_SET _IOW('p', 0x12, struct rtc_pll_info) /* Set PLL correction */
|
||||
|
||||
/* 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 put_rtc_time_generic(void __user *argp, struct rtc_time *tm)
|
||||
{
|
||||
struct rtc_time_generic __user *utm = argp;
|
||||
|
||||
if (__put_user(tm->sec, &utm->tm_sec) ||
|
||||
__put_user(tm->min, &utm->tm_min) ||
|
||||
__put_user(tm->hour, &utm->tm_hour) ||
|
||||
__put_user(tm->dom, &utm->tm_mday) ||
|
||||
__put_user(tm->month, &utm->tm_mon) ||
|
||||
__put_user(tm->year, &utm->tm_year) ||
|
||||
__put_user(tm->dow, &utm->tm_wday) ||
|
||||
__put_user(0, &utm->tm_yday) ||
|
||||
__put_user(0, &utm->tm_isdst))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_rtc_time_generic(struct rtc_time *tm, void __user *argp)
|
||||
{
|
||||
struct rtc_time_generic __user *utm = argp;
|
||||
|
||||
if (__get_user(tm->sec, &utm->tm_sec) ||
|
||||
__get_user(tm->min, &utm->tm_min) ||
|
||||
__get_user(tm->hour, &utm->tm_hour) ||
|
||||
__get_user(tm->dom, &utm->tm_mday) ||
|
||||
__get_user(tm->month, &utm->tm_mon) ||
|
||||
__get_user(tm->year, &utm->tm_year) ||
|
||||
__get_user(tm->dow, &utm->tm_wday))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
/* No interrupt support, return an error
|
||||
* compatible with drivers/char/rtc.c
|
||||
*/
|
||||
case RTC_AIE_OFF:
|
||||
case RTC_AIE_ON:
|
||||
case RTC_PIE_OFF:
|
||||
case RTC_PIE_ON:
|
||||
case RTC_UIE_OFF:
|
||||
case RTC_UIE_ON:
|
||||
case RTC_IRQP_READ:
|
||||
case RTC_IRQP_SET:
|
||||
case RTC_EPOCH_SET:
|
||||
case RTC_EPOCH_READ:
|
||||
return -EINVAL;
|
||||
|
||||
case RTCGET:
|
||||
case RTC_RD_TIME:
|
||||
memset(&rtc_tm, 0, sizeof(struct rtc_time));
|
||||
get_rtc_time(&rtc_tm);
|
||||
|
||||
if (cmd == RTCGET) {
|
||||
if (copy_to_user(argp, &rtc_tm,
|
||||
sizeof(struct rtc_time)))
|
||||
return -EFAULT;
|
||||
} else if (put_rtc_time_generic(argp, &rtc_tm))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
case RTCSET:
|
||||
case RTC_SET_TIME:
|
||||
if (!capable(CAP_SYS_TIME))
|
||||
return -EPERM;
|
||||
|
||||
if (cmd == RTCSET) {
|
||||
if (copy_from_user(&rtc_tm, argp,
|
||||
sizeof(struct rtc_time)))
|
||||
return -EFAULT;
|
||||
} else if (get_rtc_time_generic(&rtc_tm, argp))
|
||||
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 const 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)
|
||||
return -ENODEV;
|
||||
|
||||
error = misc_register(&rtc_dev);
|
||||
if (error) {
|
||||
printk(KERN_ERR "rtc: unable to get misc minor for Mostek\n");
|
||||
return error;
|
||||
}
|
||||
printk("rtc_sun_init: Registered Mostek RTC driver.\n");
|
||||
|
||||
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");
|
||||
427
drivers/sbus/char/uctrl.c
Normal file
427
drivers/sbus/char/uctrl.c
Normal file
@@ -0,0 +1,427 @@
|
||||
/* $Id: uctrl.c,v 1.1.1.1 2007/06/12 07:27:09 eyryu Exp $
|
||||
* uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3
|
||||
*
|
||||
* Copyright 1999 Derrick J Brashear (shadow@dementia.org)
|
||||
*/
|
||||
|
||||
#include <linux/module.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 uctrl_driver *driver = (struct uctrl_driver *)dev_id;
|
||||
printk("in uctrl_interrupt\n");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const 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(void)
|
||||
{
|
||||
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 = NULL;
|
||||
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(void)
|
||||
{
|
||||
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 = NULL;
|
||||
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);
|
||||
int err;
|
||||
|
||||
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;
|
||||
|
||||
err = request_irq(driver->irq, uctrl_interrupt, 0, "uctrl", driver);
|
||||
if (err) {
|
||||
printk("%s: unable to register irq %d\n",
|
||||
__FUNCTION__, driver->irq);
|
||||
return err;
|
||||
}
|
||||
|
||||
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%p (irq %d)\n", driver->regs, 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 = NULL;
|
||||
}
|
||||
|
||||
module_init(ts102_uctrl_init);
|
||||
module_exit(ts102_uctrl_cleanup);
|
||||
MODULE_LICENSE("GPL");
|
||||
175
drivers/sbus/char/vfc.h
Normal file
175
drivers/sbus/char/vfc.h
Normal file
@@ -0,0 +1,175 @@
|
||||
#ifndef _LINUX_VFC_H_
|
||||
#define _LINUX_VFC_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 __iomem *regs;
|
||||
struct vfc_regs *phys_regs;
|
||||
unsigned int control_reg;
|
||||
struct semaphore device_lock_sem;
|
||||
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_ */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
732
drivers/sbus/char/vfc_dev.c
Normal file
732
drivers/sbus/char/vfc_dev.c
Normal file
@@ -0,0 +1,732 @@
|
||||
/*
|
||||
* 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/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 const 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;
|
||||
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 __iomem *)
|
||||
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;
|
||||
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 = kzalloc(inout.len, GFP_KERNEL);
|
||||
if (buffer == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
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 (sbus_readl(&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_path.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_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 const 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 = 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;
|
||||
}
|
||||
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;
|
||||
sbus_iounmap(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);
|
||||
|
||||
kfree(vfc_dev_lst);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
333
drivers/sbus/char/vfc_i2c.c
Normal file
333
drivers/sbus/char/vfc_i2c.c
Normal file
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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 algorithms appear 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_no_busy(struct vfc_dev *dev, unsigned long usecs)
|
||||
{
|
||||
schedule_timeout_uninterruptible(usecs_to_jiffies(usecs));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
44
drivers/sbus/char/vfc_i2c.h
Normal file
44
drivers/sbus/char/vfc_i2c.h
Normal 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_ */
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user