M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
#
# Makefile for the linux kernel.
#
obj-$(CONFIG_SBUSCHAR) += char/

View File

@@ -0,0 +1,75 @@
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 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 TADPOLE_TS102_UCTRL
tristate "Tadpole TS102 Microcontroller support (EXPERIMENTAL)"
depends on EXPERIMENTAL
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

View File

@@ -0,0 +1,18 @@
#
# 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.
#
bbc-objs := bbc_i2c.o bbc_envctrl.o
obj-$(CONFIG_ENVCTRL) += envctrl.o
obj-$(CONFIG_DISPLAY7SEG) += display7seg.o
obj-$(CONFIG_OBP_FLASH) += flash.o
obj-$(CONFIG_SUN_OPENPROMIO) += openprom.o
obj-$(CONFIG_TADPOLE_TS102_UCTRL) += uctrl.o
obj-$(CONFIG_SUN_JSFLASH) += jsflash.o
obj-$(CONFIG_BBC_I2C) += bbc.o

View File

@@ -0,0 +1,595 @@
/* bbc_envctrl.c: UltraSPARC-III environment control driver.
*
* Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net)
*/
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/kmod.h>
#include <linux/reboot.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <asm/oplib.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 },
};
static LIST_HEAD(all_temps);
static LIST_HEAD(all_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;
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 (orderly_poweroff(true) < 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.
*/
list_for_each_entry(tp, &all_temps, glob_list) {
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.
*/
list_for_each_entry(fp, &all_fans, glob_list) {
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;
list_for_each_entry(tp, &all_temps, glob_list) {
get_current_temps(tp);
analyze_temps(tp, &last_warning_jiffies);
}
list_for_each_entry(fp, &all_fans, glob_list)
maybe_new_fan_speeds(fp);
}
printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n");
fans_full_blast();
return 0;
}
static void attach_one_temp(struct bbc_i2c_bus *bp, struct platform_device *op,
int temp_idx)
{
struct bbc_cpu_temperature *tp;
tp = kzalloc(sizeof(*tp), GFP_KERNEL);
if (!tp)
return;
tp->client = bbc_i2c_attach(bp, op);
if (!tp->client) {
kfree(tp);
return;
}
tp->index = temp_idx;
list_add(&tp->glob_list, &all_temps);
list_add(&tp->bp_list, &bp->temps);
/* 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 bbc_i2c_bus *bp, struct platform_device *op,
int fan_idx)
{
struct bbc_fan_control *fp;
fp = kzalloc(sizeof(*fp), GFP_KERNEL);
if (!fp)
return;
fp->client = bbc_i2c_attach(bp, op);
if (!fp->client) {
kfree(fp);
return;
}
fp->index = fan_idx;
list_add(&fp->glob_list, &all_fans);
list_add(&fp->bp_list, &bp->fans);
/* 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);
}
static void destroy_one_temp(struct bbc_cpu_temperature *tp)
{
bbc_i2c_detach(tp->client);
kfree(tp);
}
static void destroy_all_temps(struct bbc_i2c_bus *bp)
{
struct bbc_cpu_temperature *tp, *tpos;
list_for_each_entry_safe(tp, tpos, &bp->temps, bp_list) {
list_del(&tp->bp_list);
list_del(&tp->glob_list);
destroy_one_temp(tp);
}
}
static void destroy_one_fan(struct bbc_fan_control *fp)
{
bbc_i2c_detach(fp->client);
kfree(fp);
}
static void destroy_all_fans(struct bbc_i2c_bus *bp)
{
struct bbc_fan_control *fp, *fpos;
list_for_each_entry_safe(fp, fpos, &bp->fans, bp_list) {
list_del(&fp->bp_list);
list_del(&fp->glob_list);
destroy_one_fan(fp);
}
}
int bbc_envctrl_init(struct bbc_i2c_bus *bp)
{
struct platform_device *op;
int temp_index = 0;
int fan_index = 0;
int devidx = 0;
while ((op = bbc_i2c_getdev(bp, devidx++)) != NULL) {
if (!strcmp(op->dev.of_node->name, "temperature"))
attach_one_temp(bp, op, temp_index++);
if (!strcmp(op->dev.of_node->name, "fan-control"))
attach_one_fan(bp, op, fan_index++);
}
if (temp_index != 0 && fan_index != 0) {
kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
if (IS_ERR(kenvctrld_task)) {
int err = PTR_ERR(kenvctrld_task);
kenvctrld_task = NULL;
destroy_all_temps(bp);
destroy_all_fans(bp);
return err;
}
}
return 0;
}
void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp)
{
if (kenvctrld_task)
kthread_stop(kenvctrld_task);
destroy_all_temps(bp);
destroy_all_fans(bp);
}

View File

@@ -0,0 +1,421 @@
/* bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III
* platforms.
*
* Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net)
*/
#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 <linux/of.h>
#include <linux/of_device.h>
#include <asm/bbc.h>
#include <asm/io.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.
*/
static void set_device_claimage(struct bbc_i2c_bus *bp, struct platform_device *op, int val)
{
int i;
for (i = 0; i < NUM_CHILDREN; i++) {
if (bp->devs[i].device == op) {
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)
struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *bp, int index)
{
struct platform_device *op = NULL;
int curidx = 0, i;
for (i = 0; i < NUM_CHILDREN; i++) {
if (!(op = bp->devs[i].device))
break;
if (curidx == index)
goto out;
op = NULL;
curidx++;
}
out:
if (curidx == index)
return op;
return NULL;
}
struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *op)
{
struct bbc_i2c_client *client;
const u32 *reg;
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client)
return NULL;
client->bp = bp;
client->op = op;
reg = of_get_property(op->dev.of_node, "reg", NULL);
if (!reg) {
kfree(client);
return NULL;
}
client->bus = reg[0];
client->address = reg[1];
claim_device(bp, op);
return client;
}
void bbc_i2c_detach(struct bbc_i2c_client *client)
{
struct bbc_i2c_bus *bp = client->bp;
struct platform_device *op = client->op;
release_device(bp, op);
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) {
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) {
ret = bbc_i2c_writeb(client, *buf, off);
if (ret < 0)
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) {
ret = bbc_i2c_readb(client, buf, off);
if (ret < 0)
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 struct bbc_i2c_bus * __init attach_one_i2c(struct platform_device *op, int index)
{
struct bbc_i2c_bus *bp;
struct device_node *dp;
int entry;
bp = kzalloc(sizeof(*bp), GFP_KERNEL);
if (!bp)
return NULL;
bp->i2c_control_regs = of_ioremap(&op->resource[0], 0, 0x2, "bbc_i2c_regs");
if (!bp->i2c_control_regs)
goto fail;
bp->i2c_bussel_reg = of_ioremap(&op->resource[1], 0, 0x1, "bbc_i2c_bussel");
if (!bp->i2c_bussel_reg)
goto fail;
bp->waiting = 0;
init_waitqueue_head(&bp->wq);
if (request_irq(op->archdata.irqs[0], bbc_i2c_interrupt,
IRQF_SHARED, "bbc_i2c", bp))
goto fail;
bp->index = index;
bp->op = op;
spin_lock_init(&bp->lock);
entry = 0;
for (dp = op->dev.of_node->child;
dp && entry < 8;
dp = dp->sibling, entry++) {
struct platform_device *child_op;
child_op = of_find_device_by_node(dp);
bp->devs[entry].device = child_op;
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 bp;
fail:
if (bp->i2c_bussel_reg)
of_iounmap(&op->resource[1], bp->i2c_bussel_reg, 1);
if (bp->i2c_control_regs)
of_iounmap(&op->resource[0], bp->i2c_control_regs, 2);
kfree(bp);
return NULL;
}
extern int bbc_envctrl_init(struct bbc_i2c_bus *bp);
extern void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp);
static int __devinit bbc_i2c_probe(struct platform_device *op)
{
struct bbc_i2c_bus *bp;
int err, index = 0;
bp = attach_one_i2c(op, index);
if (!bp)
return -EINVAL;
err = bbc_envctrl_init(bp);
if (err) {
free_irq(op->archdata.irqs[0], bp);
if (bp->i2c_bussel_reg)
of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1);
if (bp->i2c_control_regs)
of_iounmap(&op->resource[1], bp->i2c_control_regs, 2);
kfree(bp);
} else {
dev_set_drvdata(&op->dev, bp);
}
return err;
}
static int __devexit bbc_i2c_remove(struct platform_device *op)
{
struct bbc_i2c_bus *bp = dev_get_drvdata(&op->dev);
bbc_envctrl_cleanup(bp);
free_irq(op->archdata.irqs[0], bp);
if (bp->i2c_bussel_reg)
of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1);
if (bp->i2c_control_regs)
of_iounmap(&op->resource[1], bp->i2c_control_regs, 2);
kfree(bp);
return 0;
}
static const struct of_device_id bbc_i2c_match[] = {
{
.name = "i2c",
.compatible = "SUNW,bbc-i2c",
},
{},
};
MODULE_DEVICE_TABLE(of, bbc_i2c_match);
static struct platform_driver bbc_i2c_driver = {
.driver = {
.name = "bbc_i2c",
.owner = THIS_MODULE,
.of_match_table = bbc_i2c_match,
},
.probe = bbc_i2c_probe,
.remove = __devexit_p(bbc_i2c_remove),
};
module_platform_driver(bbc_i2c_driver);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,85 @@
#ifndef _BBC_I2C_H
#define _BBC_I2C_H
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/list.h>
struct bbc_i2c_client {
struct bbc_i2c_bus *bp;
struct platform_device *op;
int bus;
int address;
};
enum fan_action { FAN_SLOWER, FAN_SAME, FAN_FASTER, FAN_FULLBLAST, FAN_STATE_MAX };
struct bbc_cpu_temperature {
struct list_head bp_list;
struct list_head glob_list;
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_fan_control {
struct list_head bp_list;
struct list_head glob_list;
struct bbc_i2c_client *client;
int index;
int psupply_fan_on;
int cpu_fan_speed;
int system_fan_speed;
};
#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 list_head temps;
struct list_head fans;
struct platform_device *op;
struct {
struct platform_device *device;
int client_claimed;
} devs[NUM_CHILDREN];
};
/* Probing and attachment. */
extern struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *, int);
extern struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *);
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 */

View File

@@ -0,0 +1,278 @@
/* display7seg.c - 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/slab.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/atomic.h>
#include <asm/uaccess.h> /* put_/get_user */
#include <asm/io.h>
#include <asm/display7seg.h>
#define D7S_MINOR 193
#define DRIVER_NAME "d7s"
#define PFX DRIVER_NAME ": "
static DEFINE_MUTEX(d7s_mutex);
static int sol_compat = 0; /* Solaris compatibility mode */
/* 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");
struct d7s {
void __iomem *regs;
bool flipped;
};
struct d7s *d7s_device;
/*
* 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 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) {
struct d7s *p = d7s_device;
u8 regval = 0;
regval = readb(p->regs);
if (p->flipped)
regval |= D7S_FLIP;
else
regval &= ~D7S_FLIP;
writeb(regval, p->regs);
}
return 0;
}
static long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct d7s *p = d7s_device;
u8 regs = readb(p->regs);
int error = 0;
u8 ireg = 0;
if (D7S_MINOR != iminor(file->f_path.dentry->d_inode))
return -ENODEV;
mutex_lock(&d7s_mutex);
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 (sol_compat) {
if (regs & D7S_FLIP)
ireg |= D7S_FLIP;
else
ireg &= ~D7S_FLIP;
}
writeb(ireg, p->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 */
if (regs & D7S_FLIP)
regs &= ~D7S_FLIP;
else
regs |= D7S_FLIP;
writeb(regs, p->regs);
break;
};
mutex_unlock(&d7s_mutex);
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,
.llseek = noop_llseek,
};
static struct miscdevice d7s_miscdev = {
.minor = D7S_MINOR,
.name = DRIVER_NAME,
.fops = &d7s_fops
};
static int __devinit d7s_probe(struct platform_device *op)
{
struct device_node *opts;
int err = -EINVAL;
struct d7s *p;
u8 regs;
if (d7s_device)
goto out;
p = kzalloc(sizeof(*p), GFP_KERNEL);
err = -ENOMEM;
if (!p)
goto out;
p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s");
if (!p->regs) {
printk(KERN_ERR PFX "Cannot map chip registers\n");
goto out_free;
}
err = misc_register(&d7s_miscdev);
if (err) {
printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n",
D7S_MINOR);
goto out_iounmap;
}
/* OBP option "d7s-flipped?" is honored as default for the
* device, and reset default when detached
*/
regs = readb(p->regs);
opts = of_find_node_by_path("/options");
if (opts &&
of_get_property(opts, "d7s-flipped?", NULL))
p->flipped = true;
if (p->flipped)
regs |= D7S_FLIP;
else
regs &= ~D7S_FLIP;
writeb(regs, p->regs);
printk(KERN_INFO PFX "7-Segment Display%s at [%s:0x%llx] %s\n",
op->dev.of_node->full_name,
(regs & D7S_FLIP) ? " (FLIPPED)" : "",
op->resource[0].start,
sol_compat ? "in sol_compat mode" : "");
dev_set_drvdata(&op->dev, p);
d7s_device = p;
err = 0;
out:
return err;
out_iounmap:
of_iounmap(&op->resource[0], p->regs, sizeof(u8));
out_free:
kfree(p);
goto out;
}
static int __devexit d7s_remove(struct platform_device *op)
{
struct d7s *p = dev_get_drvdata(&op->dev);
u8 regs = readb(p->regs);
/* Honor OBP d7s-flipped? unless operating in solaris-compat mode */
if (sol_compat) {
if (p->flipped)
regs |= D7S_FLIP;
else
regs &= ~D7S_FLIP;
writeb(regs, p->regs);
}
misc_deregister(&d7s_miscdev);
of_iounmap(&op->resource[0], p->regs, sizeof(u8));
kfree(p);
return 0;
}
static const struct of_device_id d7s_match[] = {
{
.name = "display7seg",
},
{},
};
MODULE_DEVICE_TABLE(of, d7s_match);
static struct platform_driver d7s_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = d7s_match,
},
.probe = d7s_probe,
.remove = __devexit_p(d7s_remove),
};
module_platform_driver(d7s_driver);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
/* 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/fcntl.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
#include <asm/io.h>
#include <asm/upa.h>
static DEFINE_MUTEX(flash_mutex);
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)
{
mutex_lock(&flash_mutex);
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:
mutex_unlock(&flash_mutex);
return -EINVAL;
}
mutex_unlock(&flash_mutex);
return file->f_pos;
}
static ssize_t
flash_read(struct file * file, char __user * buf,
size_t count, loff_t *ppos)
{
loff_t p = *ppos;
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++;
}
*ppos += count;
return count;
}
static int
flash_open(struct inode *inode, struct file *file)
{
mutex_lock(&flash_mutex);
if (test_and_set_bit(0, (void *)&flash.busy) != 0) {
mutex_unlock(&flash_mutex);
return -EBUSY;
}
mutex_unlock(&flash_mutex);
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 __devinit flash_probe(struct platform_device *op)
{
struct device_node *dp = op->dev.of_node;
struct device_node *parent;
parent = dp->parent;
if (strcmp(parent->name, "sbus") &&
strcmp(parent->name, "sbi") &&
strcmp(parent->name, "ebus"))
return -ENODEV;
flash.read_base = op->resource[0].start;
flash.read_size = resource_size(&op->resource[0]);
if (op->resource[1].flags) {
flash.write_base = op->resource[1].start;
flash.write_size = resource_size(&op->resource[1]);
} else {
flash.write_base = op->resource[0].start;
flash.write_size = resource_size(&op->resource[0]);
}
flash.busy = 0;
printk(KERN_INFO "%s: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n",
op->dev.of_node->full_name,
flash.read_base, flash.read_size,
flash.write_base, flash.write_size);
return misc_register(&flash_dev);
}
static int __devexit flash_remove(struct platform_device *op)
{
misc_deregister(&flash_dev);
return 0;
}
static const struct of_device_id flash_match[] = {
{
.name = "flashprom",
},
{},
};
MODULE_DEVICE_TABLE(of, flash_match);
static struct platform_driver flash_driver = {
.driver = {
.name = "flash",
.owner = THIS_MODULE,
.of_match_table = flash_match,
},
.probe = flash_probe,
.remove = __devexit_p(flash_remove),
};
module_platform_driver(flash_driver);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,636 @@
/*
* 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 exceedingly 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/mutex.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#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 */
static DEFINE_MUTEX(jsf_mutex);
/*
* 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(struct request_queue *q)
{
struct request *req;
req = blk_fetch_request(q);
while (req) {
struct jsfd_part *jdp = req->rq_disk->private_data;
unsigned long offset = blk_rq_pos(req) << 9;
size_t len = blk_rq_cur_bytes(req);
int err = -EIO;
if ((offset + len) > jdp->dsize)
goto end;
if (rq_data_dir(req) != READ) {
printk(KERN_ERR "jsfd: write\n");
goto end;
}
if ((jdp->dbase & 0xff000000) != 0x20000000) {
printk(KERN_ERR "jsfd: bad base %x\n", (int)jdp->dbase);
goto end;
}
jsfd_read(req->buffer, jdp->dbase + offset, len);
err = 0;
end:
if (!__blk_end_request_cur(req, err))
req = blk_fetch_request(q);
}
}
/*
* 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;
mutex_lock(&jsf_mutex);
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;
}
mutex_unlock(&jsf_mutex);
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 long jsf_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
mutex_lock(&jsf_mutex);
int error = -ENOTTY;
void __user *argp = (void __user *)arg;
if (!capable(CAP_SYS_ADMIN)) {
mutex_unlock(&jsf_mutex);
return -EPERM;
}
switch (cmd) {
case JSFLASH_IDENT:
if (copy_to_user(argp, &jsf0.id, JSFIDSZ)) {
mutex_unlock(&jsf_mutex);
return -EFAULT;
}
break;
case JSFLASH_ERASE:
error = jsf_ioctl_erase(arg);
break;
case JSFLASH_PROGRAM:
error = jsf_ioctl_program(argp);
break;
}
mutex_unlock(&jsf_mutex);
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)
{
mutex_lock(&jsf_mutex);
if (jsf0.base == 0) {
mutex_unlock(&jsf_mutex);
return -ENXIO;
}
if (test_and_set_bit(0, (void *)&jsf0.busy) != 0) {
mutex_unlock(&jsf_mutex);
return -EBUSY;
}
mutex_unlock(&jsf_mutex);
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,
.unlocked_ioctl = jsf_ioctl,
.mmap = jsf_mmap,
.open = jsf_open,
.release = jsf_release,
};
static struct miscdevice jsf_dev = { JSF_MINOR, "jsflash", &jsf_fops };
static const struct block_device_operations jsfd_fops = {
.owner = THIS_MODULE,
};
static int jsflash_init(void)
{
int rc;
struct jsflash *jsf;
phandle node;
char banner[128];
struct linux_prom_registers reg0;
node = prom_getchild(prom_root_node);
node = prom_searchsiblings(node, "flash-memory");
if (node != 0 && (s32)node != -1) {
if (prom_getproperty(node, "reg",
(char *)&reg0, sizeof(reg0)) == -1) {
printk("jsflash: no \"reg\" property\n");
return -ENXIO;
}
if (reg0.which_io != 0) {
printk("jsflash: bus number nonzero: 0x%x:%x\n",
reg0.which_io, reg0.phys_addr);
return -ENXIO;
}
/*
* Flash may be somewhere else, for instance on Ebus.
* So, don't do the following check for IIep flash space.
*/
#if 0
if ((reg0.phys_addr >> 24) != 0x20) {
printk("jsflash: suspicious address: 0x%x:%x\n",
reg0.which_io, reg0.phys_addr);
return -ENXIO;
}
#endif
if ((int)reg0.reg_size <= 0) {
printk("jsflash: bad size 0x%x\n", (int)reg0.reg_size);
return -ENXIO;
}
} else {
/* XXX Remove this code once PROLL ID12 got widespread */
printk("jsflash: no /flash-memory node, use PROLL >= 12\n");
prom_getproperty(prom_root_node, "banner-name", banner, 128);
if (strcmp (banner, "JavaStation-NC") != 0 &&
strcmp (banner, "JavaStation-E") != 0) {
return -ENXIO;
}
reg0.which_io = 0;
reg0.phys_addr = 0x20400000;
reg0.reg_size = 0x00800000;
}
/* Let us be really paranoid for modifications to probing code. */
/* extern enum sparc_cpu sparc_cpu_model; */ /* in <asm/system.h> */
if (sparc_cpu_model != sun4m) {
/* We must be on sun4m because we use MMU Bypass ASI. */
return -ENXIO;
}
if (jsf0.base == 0) {
jsf = &jsf0;
jsf->base = reg0.phys_addr;
jsf->size = reg0.reg_size;
/* XXX Redo the userland interface. */
jsf->id.off = JSF_BASE_ALL;
jsf->id.size = 0x01000000; /* 16M - all segments */
strcpy(jsf->id.name, "Krups_all");
jsf->dv[0].dbase = jsf->base;
jsf->dv[0].dsize = jsf->size;
jsf->dv[1].dbase = jsf->base + 1024;
jsf->dv[1].dsize = jsf->size - 1024;
jsf->dv[2].dbase = JSF_BASE_ALL;
jsf->dv[2].dsize = 0x01000000;
printk("Espresso Flash @0x%lx [%d MB]\n", jsf->base,
(int) (jsf->size / (1024*1024)));
}
if ((rc = misc_register(&jsf_dev)) != 0) {
printk(KERN_ERR "jsf: unable to get misc minor %d\n",
JSF_MINOR);
jsf0.base = 0;
return rc;
}
return 0;
}
static struct request_queue *jsf_queue;
static int jsfd_init(void)
{
static DEFINE_SPINLOCK(lock);
struct jsflash *jsf;
struct jsfd_part *jdp;
int err;
int i;
if (jsf0.base == 0)
return -ENXIO;
err = -ENOMEM;
for (i = 0; i < JSF_MAX; i++) {
struct gendisk *disk = alloc_disk(1);
if (!disk)
goto out;
jsfd_disk[i] = disk;
}
if (register_blkdev(JSFD_MAJOR, "jsfd")) {
err = -EIO;
goto out;
}
jsf_queue = blk_init_queue(jsfd_do_request, &lock);
if (!jsf_queue) {
err = -ENOMEM;
unregister_blkdev(JSFD_MAJOR, "jsfd");
goto out;
}
for (i = 0; i < JSF_MAX; i++) {
struct gendisk *disk = jsfd_disk[i];
if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
jsf = &jsf0; /* actually, &jsfv[i >> JSF_PART_BITS] */
jdp = &jsf->dv[i&JSF_PART_MASK];
disk->major = JSFD_MAJOR;
disk->first_minor = i;
sprintf(disk->disk_name, "jsfd%d", i);
disk->fops = &jsfd_fops;
set_capacity(disk, jdp->dsize >> 9);
disk->private_data = jdp;
disk->queue = jsf_queue;
add_disk(disk);
set_disk_ro(disk, 1);
}
return 0;
out:
while (i--)
put_disk(jsfd_disk[i]);
return err;
}
MODULE_LICENSE("GPL");
static int __init jsflash_init_module(void) {
int rc;
if ((rc = jsflash_init()) == 0) {
jsfd_init();
return 0;
}
return rc;
}
static void __exit jsflash_cleanup_module(void)
{
int i;
for (i = 0; i < JSF_MAX; i++) {
if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
del_gendisk(jsfd_disk[i]);
put_disk(jsfd_disk[i]);
}
if (jsf0.busy)
printk("jsf0: cleaning busy unit\n");
jsf0.base = 0;
jsf0.busy = 0;
misc_deregister(&jsf_dev);
unregister_blkdev(JSFD_MAJOR, "jsfd");
blk_cleanup_queue(jsf_queue);
}
module_init(jsflash_init_module);
module_exit(jsflash_cleanup_module);

View File

@@ -0,0 +1,27 @@
/* $Id: max1617.h,v 1.1 2001/04/02 09:59:08 davem Exp $ */
#ifndef _MAX1617_H
#define _MAX1617_H
#define MAX1617_AMB_TEMP 0x00 /* Ambient temp in C */
#define MAX1617_CPU_TEMP 0x01 /* Processor die temp in C */
#define MAX1617_STATUS 0x02 /* Chip status bits */
/* Read-only versions of changeable registers. */
#define MAX1617_RD_CFG_BYTE 0x03 /* Config register */
#define MAX1617_RD_CVRATE_BYTE 0x04 /* Temp conversion rate */
#define MAX1617_RD_AMB_HIGHLIM 0x05 /* Ambient high limit */
#define MAX1617_RD_AMB_LOWLIM 0x06 /* Ambient low limit */
#define MAX1617_RD_CPU_HIGHLIM 0x07 /* Processor high limit */
#define MAX1617_RD_CPU_LOWLIM 0x08 /* Processor low limit */
/* Write-only versions of the same. */
#define MAX1617_WR_CFG_BYTE 0x09
#define MAX1617_WR_CVRATE_BYTE 0x0a
#define MAX1617_WR_AMB_HIGHLIM 0x0b
#define MAX1617_WR_AMB_LOWLIM 0x0c
#define MAX1617_WR_CPU_HIGHLIM 0x0d
#define MAX1617_WR_CPU_LOWLIM 0x0e
#define MAX1617_ONESHOT 0x0f
#endif /* _MAX1617_H */

View File

@@ -0,0 +1,762 @@
/*
* 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/mutex.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/uaccess.h>
#include <asm/openpromio.h>
#ifdef CONFIG_PCI
#include <linux/pci.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");
MODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR);
/* 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 DEFINE_MUTEX(openprom_mutex);
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)
{
const 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->phandle;
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 device_node *dp;
pdev = pci_get_bus_and_slot (((int *) op->oprom_array)[0],
((int *) op->oprom_array)[1]);
dp = pci_device_to_OF_node(pdev);
data->current_node = dp;
*((int *)op->oprom_array) = dp->phandle;
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->phandle;
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 long openprom_sunos_ioctl(struct file * file,
unsigned int cmd, unsigned long arg,
struct device_node *dp)
{
DATA *data = file->private_data;
struct openpromio *opp = NULL;
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;
mutex_lock(&openprom_mutex);
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);
mutex_unlock(&openprom_mutex);
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;
const 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->phandle;
if (copy_to_user(argp, &nd, sizeof(phandle)))
return -EFAULT;
return 0;
}
static int openprom_bsd_ioctl(struct file * file,
unsigned int cmd, unsigned long arg)
{
DATA *data = file->private_data;
void __user *argp = (void __user *)arg;
int err;
mutex_lock(&openprom_mutex);
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));
err = 0;
if (copy_to_user(argp, &options_node->phandle, sizeof(phandle)))
err = -EFAULT;
break;
case OPIOCGETNEXT:
case OPIOCGETCHILD:
err = opiocgetnext(cmd, argp);
break;
default:
err = -EINVAL;
break;
};
mutex_unlock(&openprom_mutex);
return err;
}
/*
* Handoff control to the correct ioctl handler.
*/
static long openprom_ioctl(struct file * file,
unsigned int cmd, unsigned long arg)
{
DATA *data = file->private_data;
switch (cmd) {
case OPROMGETOPT:
case OPROMNXTOPT:
if ((file->f_mode & FMODE_READ) == 0)
return -EPERM;
return openprom_sunos_ioctl(file, cmd, arg,
options_node);
case OPROMSETOPT:
case OPROMSETOPT2:
if ((file->f_mode & FMODE_WRITE) == 0)
return -EPERM;
return openprom_sunos_ioctl(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(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(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(file,cmd,arg);
case OPIOCSET:
if ((file->f_mode & FMODE_WRITE) == 0)
return -EBADF;
return openprom_bsd_ioctl(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, 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;
mutex_lock(&openprom_mutex);
data->current_node = of_find_node_by_path("/");
data->lastnode = data->current_node;
file->private_data = (void *) data;
mutex_unlock(&openprom_mutex);
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,
.unlocked_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);

View File

@@ -0,0 +1,439 @@
/* uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3
*
* Copyright 1999 Derrick J Brashear (shadow@dementia.org)
* Copyright 2008 David S. Miller (davem@davemloft.net)
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <asm/openprom.h>
#include <asm/oplib.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#define UCTRL_MINOR 174
#define DEBUG 1
#ifdef DEBUG
#define dprintk(x) printk x
#else
#define dprintk(x)
#endif
struct uctrl_regs {
u32 uctrl_intr;
u32 uctrl_data;
u32 uctrl_stat;
u32 uctrl_xxx[5];
};
struct ts102_regs {
u32 card_a_intr;
u32 card_a_stat;
u32 card_a_ctrl;
u32 card_a_xxx;
u32 card_b_intr;
u32 card_b_stat;
u32 card_b_ctrl;
u32 card_b_xxx;
u32 uctrl_intr;
u32 uctrl_data;
u32 uctrl_stat;
u32 uctrl_xxx;
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 DEFINE_MUTEX(uctrl_mutex);
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,
};
static struct uctrl_driver {
struct uctrl_regs __iomem *regs;
int irq;
int pending;
struct uctrl_status status;
} *global_driver;
static void uctrl_get_event_status(struct uctrl_driver *);
static void uctrl_get_external_status(struct uctrl_driver *);
static long
uctrl_ioctl(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)
{
mutex_lock(&uctrl_mutex);
uctrl_get_event_status(global_driver);
uctrl_get_external_status(global_driver);
mutex_unlock(&uctrl_mutex);
return 0;
}
static irqreturn_t uctrl_interrupt(int irq, void *dev_id)
{
return IRQ_HANDLED;
}
static const struct file_operations uctrl_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_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 & sbus_readl(&driver->regs->uctrl_stat)) \
break; \
} \
dprintk(("write data 0x%02x\n", value)); \
sbus_writel(value, &driver->regs->uctrl_data); \
}
/* 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 & sbus_readl(&driver->regs->uctrl_stat)) == 0) \
break; \
udelay(1); \
} \
value = sbus_readl(&driver->regs->uctrl_data); \
dprintk(("read data 0x%02x\n", value)); \
sbus_writel(UCTRL_STAT_RXNE_STA, &driver->regs->uctrl_stat); \
}
static void uctrl_do_txn(struct uctrl_driver *driver, struct uctrl_txn *txn)
{
int stat, incnt, outcnt, bytecnt, intr;
u32 byte;
stat = sbus_readl(&driver->regs->uctrl_stat);
intr = sbus_readl(&driver->regs->uctrl_intr);
sbus_writel(stat, &driver->regs->uctrl_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++;
}
}
static void uctrl_get_event_status(struct uctrl_driver *driver)
{
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(driver, &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));
}
static void uctrl_get_external_status(struct uctrl_driver *driver)
{
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(driver, &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 __devinit uctrl_probe(struct platform_device *op)
{
struct uctrl_driver *p;
int err = -ENOMEM;
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p) {
printk(KERN_ERR "uctrl: Unable to allocate device struct.\n");
goto out;
}
p->regs = of_ioremap(&op->resource[0], 0,
resource_size(&op->resource[0]),
"uctrl");
if (!p->regs) {
printk(KERN_ERR "uctrl: Unable to map registers.\n");
goto out_free;
}
p->irq = op->archdata.irqs[0];
err = request_irq(p->irq, uctrl_interrupt, 0, "uctrl", p);
if (err) {
printk(KERN_ERR "uctrl: Unable to register irq.\n");
goto out_iounmap;
}
err = misc_register(&uctrl_dev);
if (err) {
printk(KERN_ERR "uctrl: Unable to register misc device.\n");
goto out_free_irq;
}
sbus_writel(UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK, &p->regs->uctrl_intr);
printk(KERN_INFO "%s: uctrl regs[0x%p] (irq %d)\n",
op->dev.of_node->full_name, p->regs, p->irq);
uctrl_get_event_status(p);
uctrl_get_external_status(p);
dev_set_drvdata(&op->dev, p);
global_driver = p;
out:
return err;
out_free_irq:
free_irq(p->irq, p);
out_iounmap:
of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0]));
out_free:
kfree(p);
goto out;
}
static int __devexit uctrl_remove(struct platform_device *op)
{
struct uctrl_driver *p = dev_get_drvdata(&op->dev);
if (p) {
misc_deregister(&uctrl_dev);
free_irq(p->irq, p);
of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0]));
kfree(p);
}
return 0;
}
static const struct of_device_id uctrl_match[] = {
{
.name = "uctrl",
},
{},
};
MODULE_DEVICE_TABLE(of, uctrl_match);
static struct platform_driver uctrl_driver = {
.driver = {
.name = "uctrl",
.owner = THIS_MODULE,
.of_match_table = uctrl_match,
},
.probe = uctrl_probe,
.remove = __devexit_p(uctrl_remove),
};
module_platform_driver(uctrl_driver);
MODULE_LICENSE("GPL");