add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
menuconfig MACINTOSH_DRIVERS
bool "Macintosh device drivers"
depends on PPC || MAC || X86
default y if (PPC_PMAC || MAC)
---help---
Say Y here to get to see options for devices used with Macintosh
computers. This option alone does not add any kernel code.
If you say N, all options in this submenu will be skipped and disabled.
if MACINTOSH_DRIVERS
config ADB
bool "Apple Desktop Bus (ADB) support"
depends on MAC || (PPC_PMAC && PPC32)
help
Apple Desktop Bus (ADB) support is for support of devices which
are connected to an ADB port. ADB devices tend to have 4 pins.
If you have an Apple Macintosh prior to the iMac, an iBook or
PowerBook, or a "Blue and White G3", you probably want to say Y
here. Otherwise say N.
config ADB_MACII
bool "Include Mac II ADB driver"
depends on ADB && MAC
help
Say Y here if want your kernel to support Macintosh systems that use
the Mac II style ADB. This includes the II, IIx, IIcx, SE/30, IIci,
Quadra 610, Quadra 650, Quadra 700, Quadra 800, Centris 610 and
Centris 650.
config ADB_MACIISI
bool "Include Mac IIsi ADB driver"
depends on ADB && MAC
help
Say Y here if want your kernel to support Macintosh systems that use
the Mac IIsi style ADB. This includes the IIsi, IIvi, IIvx, Classic
II, LC, LC II, LC III, Performa 460, and the Performa 600.
config ADB_IOP
bool "Include IOP (IIfx/Quadra 9x0) ADB driver"
depends on ADB && MAC
help
The I/O Processor (IOP) is an Apple custom IC designed to provide
intelligent support for I/O controllers. It is described at
<http://www.angelfire.com/ca2/dev68k/iopdesc.html> to enable direct
support for it, say 'Y' here.
config ADB_PMU68K
bool "Include PMU (Powerbook) ADB driver"
depends on ADB && MAC
help
Say Y here if want your kernel to support the m68k based Powerbooks.
This includes the PowerBook 140, PowerBook 145, PowerBook 150,
PowerBook 160, PowerBook 165, PowerBook 165c, PowerBook 170,
PowerBook 180, PowerBook, 180c, PowerBook 190cs, PowerBook 520,
PowerBook Duo 210, PowerBook Duo 230, PowerBook Duo 250,
PowerBook Duo 270c, PowerBook Duo 280 and PowerBook Duo 280c.
# we want to change this to something like CONFIG_SYSCTRL_CUDA/PMU
config ADB_CUDA
bool "Support for CUDA based Macs and PowerMacs"
depends on (ADB || PPC_PMAC) && !PPC_PMAC64
help
This provides support for CUDA based Macintosh and Power Macintosh
systems. This includes many m68k based Macs (Color Classic, Mac TV,
Performa 475, Performa 520, Performa 550, Performa 575,
Performa 588, Quadra 605, Quadra 630, Quadra/Centris 660AV, and
Quadra 840AV), most OldWorld PowerMacs, the first generation iMacs,
the Blue&White G3 and the "Yikes" G4 (PCI Graphics). All later
models should use CONFIG_ADB_PMU instead. It is safe to say Y here
even if your machine doesn't have a CUDA.
If unsure say Y.
config ADB_PMU
bool "Support for PMU based PowerMacs"
depends on PPC_PMAC
help
On PowerBooks, iBooks, and recent iMacs and Power Macintoshes, the
PMU is an embedded microprocessor whose primary function is to
control system power, and battery charging on the portable models.
The PMU also controls the ADB (Apple Desktop Bus) which connects to
the keyboard and mouse on some machines, as well as the non-volatile
RAM and the RTC (real time clock) chip. Say Y to enable support for
this device; you should do so if your machine is one of those
mentioned above.
config ADB_PMU_LED
bool "Support for the Power/iBook front LED"
depends on ADB_PMU
select NEW_LEDS
select LEDS_CLASS
help
Support the front LED on Power/iBooks as a generic LED that can
be triggered by any of the supported triggers. To get the
behaviour of the old CONFIG_BLK_DEV_IDE_PMAC_BLINK, select this
and the ide-disk LED trigger and configure appropriately through
sysfs.
config ADB_PMU_LED_IDE
bool "Use front LED as IDE LED by default"
depends on ADB_PMU_LED
select LEDS_TRIGGERS
select LEDS_TRIGGER_IDE_DISK
help
This option makes the front LED default to the IDE trigger
so that it blinks on IDE activity.
config PMAC_SMU
bool "Support for SMU based PowerMacs"
depends on PPC_PMAC64
help
This option adds support for the newer G5 iMacs and PowerMacs based
on the "SMU" system control chip which replaces the old PMU.
If you don't know, say Y.
config PMAC_APM_EMU
tristate "APM emulation"
select APM_EMULATION
depends on ADB_PMU && PM && PPC32
config PMAC_MEDIABAY
bool "Support PowerBook hotswap media bay"
depends on PPC_PMAC && PPC32 && BLOCK
help
This option adds support for older PowerBook's hotswap media bay
that can contains batteries, floppy drives, or IDE devices. PCI
devices are not fully supported in the bay as I never had one to
try with
config PMAC_BACKLIGHT
bool "Backlight control for LCD screens"
depends on ADB_PMU && FB = y && (BROKEN || !PPC64)
select FB_BACKLIGHT
help
Say Y here to enable Macintosh specific extensions of the generic
backlight code. With this enabled, the brightness keys on older
PowerBooks will be enabled so you can change the screen brightness.
Newer models should use a userspace daemon like pbbuttonsd.
config PMAC_BACKLIGHT_LEGACY
bool "Provide legacy ioctl's on /dev/pmu for the backlight"
depends on PMAC_BACKLIGHT && (BROKEN || !PPC64)
help
Say Y if you want to enable legacy ioctl's on /dev/pmu. This is for
programs which use this old interface. New and updated programs
should use the backlight classes in sysfs.
config ADB_MACIO
bool "Include MacIO (CHRP) ADB driver"
depends on ADB && PPC_CHRP && !PPC_PMAC64
help
Say Y here to include direct support for the ADB controller in the
Hydra chip used on PowerPC Macintoshes of the CHRP type. (The Hydra
also includes a MESH II SCSI controller, DBDMA controller, VIA chip,
OpenPIC controller and two RS422/Geoports.)
config INPUT_ADBHID
bool "Support for ADB input devices (keyboard, mice, ...)"
depends on ADB && INPUT=y
help
Say Y here if you want to have ADB (Apple Desktop Bus) HID devices
such as keyboards, mice, joysticks, trackpads or graphic tablets
handled by the input layer. If you say Y here, make sure to say Y to
the corresponding drivers "Keyboard support" (CONFIG_INPUT_KEYBDEV),
"Mouse Support" (CONFIG_INPUT_MOUSEDEV) and "Event interface
support" (CONFIG_INPUT_EVDEV) as well.
If unsure, say Y.
config MAC_EMUMOUSEBTN
bool "Support for mouse button 2+3 emulation"
select INPUT
help
This provides generic support for emulating the 2nd and 3rd mouse
button with keypresses. If you say Y here, the emulation is still
disabled by default. The emulation is controlled by these sysctl
entries:
/proc/sys/dev/mac_hid/mouse_button_emulation
/proc/sys/dev/mac_hid/mouse_button2_keycode
/proc/sys/dev/mac_hid/mouse_button3_keycode
If you have an Apple machine with a 1-button mouse, say Y here.
config THERM_WINDTUNNEL
tristate "Support for thermal management on Windtunnel G4s"
depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64
help
This driver provides some thermostat and fan control for the desktop
G4 "Windtunnel"
config THERM_ADT746X
tristate "Support for thermal mgmnt on laptops with ADT 746x chipset"
depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64
help
This driver provides some thermostat and fan control for the
iBook G4, and the ATI based aluminium PowerBooks, allowing slightly
better fan behaviour by default, and some manual control.
config THERM_PM72
tristate "Support for thermal management on PowerMac G5"
depends on I2C && I2C_POWERMAC && PPC_PMAC64
help
This driver provides thermostat and fan control for the desktop
G5 machines.
config WINDFARM
tristate "New PowerMac thermal control infrastructure"
depends on PPC
config WINDFARM_PM81
tristate "Support for thermal management on iMac G5"
depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
select I2C_POWERMAC
help
This driver provides thermal control for the iMacG5
config WINDFARM_PM91
tristate "Support for thermal management on PowerMac9,1"
depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU
select I2C_POWERMAC
help
This driver provides thermal control for the PowerMac9,1
which is the recent (SMU based) single CPU desktop G5
config WINDFARM_PM112
tristate "Support for thermal management on PowerMac11,2"
depends on WINDFARM && I2C && PMAC_SMU
select I2C_POWERMAC
help
This driver provides thermal control for the PowerMac11,2
which are the recent dual and quad G5 machines using the
970MP dual-core processor.
config WINDFARM_PM121
tristate "Support for thermal management on PowerMac12,1"
depends on WINDFARM && I2C && PMAC_SMU
select I2C_POWERMAC
help
This driver provides thermal control for the PowerMac12,1
which is the iMac G5 (iSight).
config ANSLCD
tristate "Support for ANS LCD display"
depends on ADB_CUDA && PPC_PMAC
config PMAC_RACKMETER
tristate "Support for Apple XServe front panel LEDs"
depends on PPC_PMAC
help
This driver provides some support to control the front panel
blue LEDs "vu-meter" of the XServer macs.
endif # MACINTOSH_DRIVERS

View File

@@ -0,0 +1,50 @@
#
# Makefile for the Macintosh-specific device drivers.
#
# Each configuration option enables a list of files.
obj-$(CONFIG_PPC_PMAC) += macio_asic.o macio_sysfs.o
obj-$(CONFIG_PMAC_MEDIABAY) += mediabay.o
obj-$(CONFIG_MAC_EMUMOUSEBTN) += mac_hid.o
obj-$(CONFIG_INPUT_ADBHID) += adbhid.o
obj-$(CONFIG_ANSLCD) += ans-lcd.o
obj-$(CONFIG_ADB_PMU) += via-pmu.o via-pmu-event.o
obj-$(CONFIG_ADB_PMU_LED) += via-pmu-led.o
obj-$(CONFIG_PMAC_BACKLIGHT) += via-pmu-backlight.o
obj-$(CONFIG_ADB_CUDA) += via-cuda.o
obj-$(CONFIG_PMAC_APM_EMU) += apm_emu.o
obj-$(CONFIG_PMAC_SMU) += smu.o
obj-$(CONFIG_ADB) += adb.o
obj-$(CONFIG_ADB_MACII) += via-macii.o
obj-$(CONFIG_ADB_MACIISI) += via-maciisi.o
obj-$(CONFIG_ADB_IOP) += adb-iop.o
obj-$(CONFIG_ADB_PMU68K) += via-pmu68k.o
obj-$(CONFIG_ADB_MACIO) += macio-adb.o
obj-$(CONFIG_THERM_PM72) += therm_pm72.o
obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o
obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o
obj-$(CONFIG_WINDFARM) += windfarm_core.o
obj-$(CONFIG_WINDFARM_PM81) += windfarm_smu_controls.o \
windfarm_smu_sensors.o \
windfarm_lm75_sensor.o windfarm_pid.o \
windfarm_cpufreq_clamp.o windfarm_pm81.o
obj-$(CONFIG_WINDFARM_PM91) += windfarm_smu_controls.o \
windfarm_smu_sensors.o \
windfarm_lm75_sensor.o windfarm_pid.o \
windfarm_cpufreq_clamp.o windfarm_pm91.o
obj-$(CONFIG_WINDFARM_PM112) += windfarm_pm112.o windfarm_smu_sat.o \
windfarm_smu_controls.o \
windfarm_smu_sensors.o \
windfarm_max6690_sensor.o \
windfarm_lm75_sensor.o windfarm_pid.o
obj-$(CONFIG_WINDFARM_PM121) += windfarm_pm121.o windfarm_smu_sat.o \
windfarm_smu_controls.o \
windfarm_smu_sensors.o \
windfarm_max6690_sensor.o \
windfarm_lm75_sensor.o windfarm_pid.o
obj-$(CONFIG_PMAC_RACKMETER) += rack-meter.o

View File

@@ -0,0 +1,286 @@
/*
* I/O Processor (IOP) ADB Driver
* Written and (C) 1999 by Joshua M. Thompson (funaho@jurai.org)
* Based on via-cuda.c by Paul Mackerras.
*
* 1999-07-01 (jmt) - First implementation for new driver architecture.
*
* 1999-07-31 (jmt) - First working version.
*
* TODO:
*
* o Implement SRQ handling.
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/macintosh.h>
#include <asm/macints.h>
#include <asm/mac_iop.h>
#include <asm/mac_oss.h>
#include <asm/adb_iop.h>
#include <linux/adb.h>
/*#define DEBUG_ADB_IOP*/
extern void iop_ism_irq(int, void *);
static struct adb_request *current_req;
static struct adb_request *last_req;
#if 0
static unsigned char reply_buff[16];
static unsigned char *reply_ptr;
#endif
static enum adb_iop_state {
idle,
sending,
awaiting_reply
} adb_iop_state;
static void adb_iop_start(void);
static int adb_iop_probe(void);
static int adb_iop_init(void);
static int adb_iop_send_request(struct adb_request *, int);
static int adb_iop_write(struct adb_request *);
static int adb_iop_autopoll(int);
static void adb_iop_poll(void);
static int adb_iop_reset_bus(void);
struct adb_driver adb_iop_driver = {
"ISM IOP",
adb_iop_probe,
adb_iop_init,
adb_iop_send_request,
adb_iop_autopoll,
adb_iop_poll,
adb_iop_reset_bus
};
static void adb_iop_end_req(struct adb_request *req, int state)
{
req->complete = 1;
current_req = req->next;
if (req->done) (*req->done)(req);
adb_iop_state = state;
}
/*
* Completion routine for ADB commands sent to the IOP.
*
* This will be called when a packet has been successfully sent.
*/
static void adb_iop_complete(struct iop_msg *msg)
{
struct adb_request *req;
uint flags;
local_irq_save(flags);
req = current_req;
if ((adb_iop_state == sending) && req && req->reply_expected) {
adb_iop_state = awaiting_reply;
}
local_irq_restore(flags);
}
/*
* Listen for ADB messages from the IOP.
*
* This will be called when unsolicited messages (usually replies to TALK
* commands or autopoll packets) are received.
*/
static void adb_iop_listen(struct iop_msg *msg)
{
struct adb_iopmsg *amsg = (struct adb_iopmsg *) msg->message;
struct adb_request *req;
uint flags;
#ifdef DEBUG_ADB_IOP
int i;
#endif
local_irq_save(flags);
req = current_req;
#ifdef DEBUG_ADB_IOP
printk("adb_iop_listen %p: rcvd packet, %d bytes: %02X %02X", req,
(uint) amsg->count + 2, (uint) amsg->flags, (uint) amsg->cmd);
for (i = 0; i < amsg->count; i++)
printk(" %02X", (uint) amsg->data[i]);
printk("\n");
#endif
/* Handle a timeout. Timeout packets seem to occur even after */
/* we've gotten a valid reply to a TALK, so I'm assuming that */
/* a "timeout" is actually more like an "end-of-data" signal. */
/* We need to send back a timeout packet to the IOP to shut */
/* it up, plus complete the current request, if any. */
if (amsg->flags & ADB_IOP_TIMEOUT) {
msg->reply[0] = ADB_IOP_TIMEOUT | ADB_IOP_AUTOPOLL;
msg->reply[1] = 0;
msg->reply[2] = 0;
if (req && (adb_iop_state != idle)) {
adb_iop_end_req(req, idle);
}
} else {
/* TODO: is it possible for more than one chunk of data */
/* to arrive before the timeout? If so we need to */
/* use reply_ptr here like the other drivers do. */
if ((adb_iop_state == awaiting_reply) &&
(amsg->flags & ADB_IOP_EXPLICIT)) {
req->reply_len = amsg->count + 1;
memcpy(req->reply, &amsg->cmd, req->reply_len);
} else {
adb_input(&amsg->cmd, amsg->count + 1,
amsg->flags & ADB_IOP_AUTOPOLL);
}
memcpy(msg->reply, msg->message, IOP_MSG_LEN);
}
iop_complete_message(msg);
local_irq_restore(flags);
}
/*
* Start sending an ADB packet, IOP style
*
* There isn't much to do other than hand the packet over to the IOP
* after encapsulating it in an adb_iopmsg.
*/
static void adb_iop_start(void)
{
unsigned long flags;
struct adb_request *req;
struct adb_iopmsg amsg;
#ifdef DEBUG_ADB_IOP
int i;
#endif
/* get the packet to send */
req = current_req;
if (!req) return;
local_irq_save(flags);
#ifdef DEBUG_ADB_IOP
printk("adb_iop_start %p: sending packet, %d bytes:", req, req->nbytes);
for (i = 0 ; i < req->nbytes ; i++)
printk(" %02X", (uint) req->data[i]);
printk("\n");
#endif
/* The IOP takes MacII-style packets, so */
/* strip the initial ADB_PACKET byte. */
amsg.flags = ADB_IOP_EXPLICIT;
amsg.count = req->nbytes - 2;
/* amsg.data immediately follows amsg.cmd, effectively making */
/* amsg.cmd a pointer to the beginning of a full ADB packet. */
memcpy(&amsg.cmd, req->data + 1, req->nbytes - 1);
req->sent = 1;
adb_iop_state = sending;
local_irq_restore(flags);
/* Now send it. The IOP manager will call adb_iop_complete */
/* when the packet has been sent. */
iop_send_message(ADB_IOP, ADB_CHAN, req,
sizeof(amsg), (__u8 *) &amsg, adb_iop_complete);
}
int adb_iop_probe(void)
{
if (!iop_ism_present) return -ENODEV;
return 0;
}
int adb_iop_init(void)
{
printk("adb: IOP ISM driver v0.4 for Unified ADB.\n");
iop_listen(ADB_IOP, ADB_CHAN, adb_iop_listen, "ADB");
return 0;
}
int adb_iop_send_request(struct adb_request *req, int sync)
{
int err;
err = adb_iop_write(req);
if (err) return err;
if (sync) {
while (!req->complete) adb_iop_poll();
}
return 0;
}
static int adb_iop_write(struct adb_request *req)
{
unsigned long flags;
if ((req->nbytes < 2) || (req->data[0] != ADB_PACKET)) {
req->complete = 1;
return -EINVAL;
}
local_irq_save(flags);
req->next = NULL;
req->sent = 0;
req->complete = 0;
req->reply_len = 0;
if (current_req != 0) {
last_req->next = req;
last_req = req;
} else {
current_req = req;
last_req = req;
}
local_irq_restore(flags);
if (adb_iop_state == idle) adb_iop_start();
return 0;
}
int adb_iop_autopoll(int devs)
{
/* TODO: how do we enable/disable autopoll? */
return 0;
}
void adb_iop_poll(void)
{
if (adb_iop_state == idle) adb_iop_start();
iop_ism_irq(0, (void *) ADB_IOP);
}
int adb_iop_reset_bus(void)
{
struct adb_request req = {
.reply_expected = 0,
.nbytes = 2,
.data = { ADB_PACKET, 0 },
};
adb_iop_write(&req);
while (!req.complete) {
adb_iop_poll();
schedule();
}
return 0;
}

View File

@@ -0,0 +1,869 @@
/*
* Device driver for the Apple Desktop Bus
* and the /dev/adb device on macintoshes.
*
* Copyright (C) 1996 Paul Mackerras.
*
* Modified to declare controllers as structures, added
* client notification of bus reset and handles PowerBook
* sleep, by Benjamin Herrenschmidt.
*
* To do:
*
* - /sys/bus/adb to list the devices and infos
* - more /dev/adb to allow userland to receive the
* flow of auto-polling datas from a given device.
* - move bus probe to a kernel thread
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/adb.h>
#include <linux/cuda.h>
#include <linux/pmu.h>
#include <linux/notifier.h>
#include <linux/wait.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/kthread.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
#ifdef CONFIG_PPC
#include <asm/prom.h>
#include <asm/machdep.h>
#endif
EXPORT_SYMBOL(adb_client_list);
extern struct adb_driver via_macii_driver;
extern struct adb_driver via_maciisi_driver;
extern struct adb_driver via_cuda_driver;
extern struct adb_driver adb_iop_driver;
extern struct adb_driver via_pmu_driver;
extern struct adb_driver macio_adb_driver;
static struct adb_driver *adb_driver_list[] = {
#ifdef CONFIG_ADB_MACII
&via_macii_driver,
#endif
#ifdef CONFIG_ADB_MACIISI
&via_maciisi_driver,
#endif
#ifdef CONFIG_ADB_CUDA
&via_cuda_driver,
#endif
#ifdef CONFIG_ADB_IOP
&adb_iop_driver,
#endif
#if defined(CONFIG_ADB_PMU) || defined(CONFIG_ADB_PMU68K)
&via_pmu_driver,
#endif
#ifdef CONFIG_ADB_MACIO
&macio_adb_driver,
#endif
NULL
};
static struct class *adb_dev_class;
static struct adb_driver *adb_controller;
BLOCKING_NOTIFIER_HEAD(adb_client_list);
static int adb_got_sleep;
static int adb_inited;
static DECLARE_MUTEX(adb_probe_mutex);
static int sleepy_trackpad;
static int autopoll_devs;
int __adb_probe_sync;
static int adb_scan_bus(void);
static int do_adb_reset_bus(void);
static void adbdev_init(void);
static int try_handler_change(int, int);
static struct adb_handler {
void (*handler)(unsigned char *, int, int);
int original_address;
int handler_id;
int busy;
} adb_handler[16];
/*
* The adb_handler_mutex mutex protects all accesses to the original_address
* and handler_id fields of adb_handler[i] for all i, and changes to the
* handler field.
* Accesses to the handler field are protected by the adb_handler_lock
* rwlock. It is held across all calls to any handler, so that by the
* time adb_unregister returns, we know that the old handler isn't being
* called.
*/
static DEFINE_MUTEX(adb_handler_mutex);
static DEFINE_RWLOCK(adb_handler_lock);
#if 0
static void printADBreply(struct adb_request *req)
{
int i;
printk("adb reply (%d)", req->reply_len);
for(i = 0; i < req->reply_len; i++)
printk(" %x", req->reply[i]);
printk("\n");
}
#endif
static int adb_scan_bus(void)
{
int i, highFree=0, noMovement;
int devmask = 0;
struct adb_request req;
/* assumes adb_handler[] is all zeroes at this point */
for (i = 1; i < 16; i++) {
/* see if there is anything at address i */
adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
(i << 4) | 0xf);
if (req.reply_len > 1)
/* one or more devices at this address */
adb_handler[i].original_address = i;
else if (i > highFree)
highFree = i;
}
/* Note we reset noMovement to 0 each time we move a device */
for (noMovement = 1; noMovement < 2 && highFree > 0; noMovement++) {
for (i = 1; i < 16; i++) {
if (adb_handler[i].original_address == 0)
continue;
/*
* Send a "talk register 3" command to address i
* to provoke a collision if there is more than
* one device at this address.
*/
adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
(i << 4) | 0xf);
/*
* Move the device(s) which didn't detect a
* collision to address `highFree'. Hopefully
* this only moves one device.
*/
adb_request(&req, NULL, ADBREQ_SYNC, 3,
(i<< 4) | 0xb, (highFree | 0x60), 0xfe);
/*
* See if anybody actually moved. This is suggested
* by HW TechNote 01:
*
* http://developer.apple.com/technotes/hw/hw_01.html
*/
adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
(highFree << 4) | 0xf);
if (req.reply_len <= 1) continue;
/*
* Test whether there are any device(s) left
* at address i.
*/
adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
(i << 4) | 0xf);
if (req.reply_len > 1) {
/*
* There are still one or more devices
* left at address i. Register the one(s)
* we moved to `highFree', and find a new
* value for highFree.
*/
adb_handler[highFree].original_address =
adb_handler[i].original_address;
while (highFree > 0 &&
adb_handler[highFree].original_address)
highFree--;
if (highFree <= 0)
break;
noMovement = 0;
}
else {
/*
* No devices left at address i; move the
* one(s) we moved to `highFree' back to i.
*/
adb_request(&req, NULL, ADBREQ_SYNC, 3,
(highFree << 4) | 0xb,
(i | 0x60), 0xfe);
}
}
}
/* Now fill in the handler_id field of the adb_handler entries. */
printk(KERN_DEBUG "adb devices:");
for (i = 1; i < 16; i++) {
if (adb_handler[i].original_address == 0)
continue;
adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
(i << 4) | 0xf);
adb_handler[i].handler_id = req.reply[2];
printk(" [%d]: %d %x", i, adb_handler[i].original_address,
adb_handler[i].handler_id);
devmask |= 1 << i;
}
printk("\n");
return devmask;
}
/*
* This kernel task handles ADB probing. It dies once probing is
* completed.
*/
static int
adb_probe_task(void *x)
{
printk(KERN_INFO "adb: starting probe task...\n");
do_adb_reset_bus();
printk(KERN_INFO "adb: finished probe task...\n");
up(&adb_probe_mutex);
return 0;
}
static void
__adb_probe_task(struct work_struct *bullshit)
{
kthread_run(adb_probe_task, NULL, "kadbprobe");
}
static DECLARE_WORK(adb_reset_work, __adb_probe_task);
int
adb_reset_bus(void)
{
if (__adb_probe_sync) {
do_adb_reset_bus();
return 0;
}
down(&adb_probe_mutex);
schedule_work(&adb_reset_work);
return 0;
}
#ifdef CONFIG_PM
/*
* notify clients before sleep
*/
static int adb_suspend(struct platform_device *dev, pm_message_t state)
{
adb_got_sleep = 1;
/* We need to get a lock on the probe thread */
down(&adb_probe_mutex);
/* Stop autopoll */
if (adb_controller->autopoll)
adb_controller->autopoll(0);
blocking_notifier_call_chain(&adb_client_list, ADB_MSG_POWERDOWN, NULL);
return 0;
}
/*
* reset bus after sleep
*/
static int adb_resume(struct platform_device *dev)
{
adb_got_sleep = 0;
up(&adb_probe_mutex);
adb_reset_bus();
return 0;
}
#endif /* CONFIG_PM */
static int __init adb_init(void)
{
struct adb_driver *driver;
int i;
#ifdef CONFIG_PPC32
if (!machine_is(chrp) && !machine_is(powermac))
return 0;
#endif
#ifdef CONFIG_MAC
if (!MACH_IS_MAC)
return 0;
#endif
/* xmon may do early-init */
if (adb_inited)
return 0;
adb_inited = 1;
adb_controller = NULL;
i = 0;
while ((driver = adb_driver_list[i++]) != NULL) {
if (!driver->probe()) {
adb_controller = driver;
break;
}
}
if ((adb_controller == NULL) || adb_controller->init()) {
printk(KERN_WARNING "Warning: no ADB interface detected\n");
adb_controller = NULL;
} else {
#ifdef CONFIG_PPC
if (machine_is_compatible("AAPL,PowerBook1998") ||
machine_is_compatible("PowerBook1,1"))
sleepy_trackpad = 1;
#endif /* CONFIG_PPC */
adbdev_init();
adb_reset_bus();
}
return 0;
}
device_initcall(adb_init);
static int
do_adb_reset_bus(void)
{
int ret;
if (adb_controller == NULL)
return -ENXIO;
if (adb_controller->autopoll)
adb_controller->autopoll(0);
blocking_notifier_call_chain(&adb_client_list,
ADB_MSG_PRE_RESET, NULL);
if (sleepy_trackpad) {
/* Let the trackpad settle down */
msleep(500);
}
mutex_lock(&adb_handler_mutex);
write_lock_irq(&adb_handler_lock);
memset(adb_handler, 0, sizeof(adb_handler));
write_unlock_irq(&adb_handler_lock);
/* That one is still a bit synchronous, oh well... */
if (adb_controller->reset_bus)
ret = adb_controller->reset_bus();
else
ret = 0;
if (sleepy_trackpad) {
/* Let the trackpad settle down */
msleep(1500);
}
if (!ret) {
autopoll_devs = adb_scan_bus();
if (adb_controller->autopoll)
adb_controller->autopoll(autopoll_devs);
}
mutex_unlock(&adb_handler_mutex);
blocking_notifier_call_chain(&adb_client_list,
ADB_MSG_POST_RESET, NULL);
return ret;
}
void
adb_poll(void)
{
if ((adb_controller == NULL)||(adb_controller->poll == NULL))
return;
adb_controller->poll();
}
static void adb_sync_req_done(struct adb_request *req)
{
struct completion *comp = req->arg;
complete(comp);
}
int
adb_request(struct adb_request *req, void (*done)(struct adb_request *),
int flags, int nbytes, ...)
{
va_list list;
int i;
int rc;
struct completion comp;
if ((adb_controller == NULL) || (adb_controller->send_request == NULL))
return -ENXIO;
if (nbytes < 1)
return -EINVAL;
req->nbytes = nbytes+1;
req->done = done;
req->reply_expected = flags & ADBREQ_REPLY;
req->data[0] = ADB_PACKET;
va_start(list, nbytes);
for (i = 0; i < nbytes; ++i)
req->data[i+1] = va_arg(list, int);
va_end(list);
if (flags & ADBREQ_NOSEND)
return 0;
/* Synchronous requests block using an on-stack completion */
if (flags & ADBREQ_SYNC) {
WARN_ON(done);
req->done = adb_sync_req_done;
req->arg = &comp;
init_completion(&comp);
}
rc = adb_controller->send_request(req, 0);
if ((flags & ADBREQ_SYNC) && !rc && !req->complete)
wait_for_completion(&comp);
return rc;
}
/* Ultimately this should return the number of devices with
the given default id.
And it does it now ! Note: changed behaviour: This function
will now register if default_id _and_ handler_id both match
but handler_id can be left to 0 to match with default_id only.
When handler_id is set, this function will try to adjust
the handler_id id it doesn't match. */
int
adb_register(int default_id, int handler_id, struct adb_ids *ids,
void (*handler)(unsigned char *, int, int))
{
int i;
mutex_lock(&adb_handler_mutex);
ids->nids = 0;
for (i = 1; i < 16; i++) {
if ((adb_handler[i].original_address == default_id) &&
(!handler_id || (handler_id == adb_handler[i].handler_id) ||
try_handler_change(i, handler_id))) {
if (adb_handler[i].handler != 0) {
printk(KERN_ERR
"Two handlers for ADB device %d\n",
default_id);
continue;
}
write_lock_irq(&adb_handler_lock);
adb_handler[i].handler = handler;
write_unlock_irq(&adb_handler_lock);
ids->id[ids->nids++] = i;
}
}
mutex_unlock(&adb_handler_mutex);
return ids->nids;
}
int
adb_unregister(int index)
{
int ret = -ENODEV;
mutex_lock(&adb_handler_mutex);
write_lock_irq(&adb_handler_lock);
if (adb_handler[index].handler) {
while(adb_handler[index].busy) {
write_unlock_irq(&adb_handler_lock);
yield();
write_lock_irq(&adb_handler_lock);
}
ret = 0;
adb_handler[index].handler = NULL;
}
write_unlock_irq(&adb_handler_lock);
mutex_unlock(&adb_handler_mutex);
return ret;
}
void
adb_input(unsigned char *buf, int nb, int autopoll)
{
int i, id;
static int dump_adb_input = 0;
unsigned long flags;
void (*handler)(unsigned char *, int, int);
/* We skip keystrokes and mouse moves when the sleep process
* has been started. We stop autopoll, but this is another security
*/
if (adb_got_sleep)
return;
id = buf[0] >> 4;
if (dump_adb_input) {
printk(KERN_INFO "adb packet: ");
for (i = 0; i < nb; ++i)
printk(" %x", buf[i]);
printk(", id = %d\n", id);
}
write_lock_irqsave(&adb_handler_lock, flags);
handler = adb_handler[id].handler;
if (handler != NULL)
adb_handler[id].busy = 1;
write_unlock_irqrestore(&adb_handler_lock, flags);
if (handler != NULL) {
(*handler)(buf, nb, autopoll);
wmb();
adb_handler[id].busy = 0;
}
}
/* Try to change handler to new_id. Will return 1 if successful. */
static int try_handler_change(int address, int new_id)
{
struct adb_request req;
if (adb_handler[address].handler_id == new_id)
return 1;
adb_request(&req, NULL, ADBREQ_SYNC, 3,
ADB_WRITEREG(address, 3), address | 0x20, new_id);
adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1,
ADB_READREG(address, 3));
if (req.reply_len < 2)
return 0;
if (req.reply[2] != new_id)
return 0;
adb_handler[address].handler_id = req.reply[2];
return 1;
}
int
adb_try_handler_change(int address, int new_id)
{
int ret;
mutex_lock(&adb_handler_mutex);
ret = try_handler_change(address, new_id);
mutex_unlock(&adb_handler_mutex);
return ret;
}
int
adb_get_infos(int address, int *original_address, int *handler_id)
{
mutex_lock(&adb_handler_mutex);
*original_address = adb_handler[address].original_address;
*handler_id = adb_handler[address].handler_id;
mutex_unlock(&adb_handler_mutex);
return (*original_address != 0);
}
/*
* /dev/adb device driver.
*/
#define ADB_MAJOR 56 /* major number for /dev/adb */
struct adbdev_state {
spinlock_t lock;
atomic_t n_pending;
struct adb_request *completed;
wait_queue_head_t wait_queue;
int inuse;
};
static void adb_write_done(struct adb_request *req)
{
struct adbdev_state *state = (struct adbdev_state *) req->arg;
unsigned long flags;
if (!req->complete) {
req->reply_len = 0;
req->complete = 1;
}
spin_lock_irqsave(&state->lock, flags);
atomic_dec(&state->n_pending);
if (!state->inuse) {
kfree(req);
if (atomic_read(&state->n_pending) == 0) {
spin_unlock_irqrestore(&state->lock, flags);
kfree(state);
return;
}
} else {
struct adb_request **ap = &state->completed;
while (*ap != NULL)
ap = &(*ap)->next;
req->next = NULL;
*ap = req;
wake_up_interruptible(&state->wait_queue);
}
spin_unlock_irqrestore(&state->lock, flags);
}
static int
do_adb_query(struct adb_request *req)
{
int ret = -EINVAL;
switch(req->data[1])
{
case ADB_QUERY_GETDEVINFO:
if (req->nbytes < 3)
break;
mutex_lock(&adb_handler_mutex);
req->reply[0] = adb_handler[req->data[2]].original_address;
req->reply[1] = adb_handler[req->data[2]].handler_id;
mutex_unlock(&adb_handler_mutex);
req->complete = 1;
req->reply_len = 2;
adb_write_done(req);
ret = 0;
break;
}
return ret;
}
static int adb_open(struct inode *inode, struct file *file)
{
struct adbdev_state *state;
int ret = 0;
lock_kernel();
if (iminor(inode) > 0 || adb_controller == NULL) {
ret = -ENXIO;
goto out;
}
state = kmalloc(sizeof(struct adbdev_state), GFP_KERNEL);
if (state == 0) {
ret = -ENOMEM;
goto out;
}
file->private_data = state;
spin_lock_init(&state->lock);
atomic_set(&state->n_pending, 0);
state->completed = NULL;
init_waitqueue_head(&state->wait_queue);
state->inuse = 1;
out:
unlock_kernel();
return ret;
}
static int adb_release(struct inode *inode, struct file *file)
{
struct adbdev_state *state = file->private_data;
unsigned long flags;
lock_kernel();
if (state) {
file->private_data = NULL;
spin_lock_irqsave(&state->lock, flags);
if (atomic_read(&state->n_pending) == 0
&& state->completed == NULL) {
spin_unlock_irqrestore(&state->lock, flags);
kfree(state);
} else {
state->inuse = 0;
spin_unlock_irqrestore(&state->lock, flags);
}
}
unlock_kernel();
return 0;
}
static ssize_t adb_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int ret = 0;
struct adbdev_state *state = file->private_data;
struct adb_request *req;
wait_queue_t wait = __WAITQUEUE_INITIALIZER(wait,current);
unsigned long flags;
if (count < 2)
return -EINVAL;
if (count > sizeof(req->reply))
count = sizeof(req->reply);
if (!access_ok(VERIFY_WRITE, buf, count))
return -EFAULT;
req = NULL;
spin_lock_irqsave(&state->lock, flags);
add_wait_queue(&state->wait_queue, &wait);
current->state = TASK_INTERRUPTIBLE;
for (;;) {
req = state->completed;
if (req != NULL)
state->completed = req->next;
else if (atomic_read(&state->n_pending) == 0)
ret = -EIO;
if (req != NULL || ret != 0)
break;
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
break;
}
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
spin_unlock_irqrestore(&state->lock, flags);
schedule();
spin_lock_irqsave(&state->lock, flags);
}
current->state = TASK_RUNNING;
remove_wait_queue(&state->wait_queue, &wait);
spin_unlock_irqrestore(&state->lock, flags);
if (ret)
return ret;
ret = req->reply_len;
if (ret > count)
ret = count;
if (ret > 0 && copy_to_user(buf, req->reply, ret))
ret = -EFAULT;
kfree(req);
return ret;
}
static ssize_t adb_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
int ret/*, i*/;
struct adbdev_state *state = file->private_data;
struct adb_request *req;
if (count < 2 || count > sizeof(req->data))
return -EINVAL;
if (adb_controller == NULL)
return -ENXIO;
if (!access_ok(VERIFY_READ, buf, count))
return -EFAULT;
req = kmalloc(sizeof(struct adb_request),
GFP_KERNEL);
if (req == NULL)
return -ENOMEM;
req->nbytes = count;
req->done = adb_write_done;
req->arg = (void *) state;
req->complete = 0;
ret = -EFAULT;
if (copy_from_user(req->data, buf, count))
goto out;
atomic_inc(&state->n_pending);
/* If a probe is in progress or we are sleeping, wait for it to complete */
down(&adb_probe_mutex);
/* Queries are special requests sent to the ADB driver itself */
if (req->data[0] == ADB_QUERY) {
if (count > 1)
ret = do_adb_query(req);
else
ret = -EINVAL;
up(&adb_probe_mutex);
}
/* Special case for ADB_BUSRESET request, all others are sent to
the controller */
else if ((req->data[0] == ADB_PACKET)&&(count > 1)
&&(req->data[1] == ADB_BUSRESET)) {
ret = do_adb_reset_bus();
up(&adb_probe_mutex);
atomic_dec(&state->n_pending);
if (ret == 0)
ret = count;
goto out;
} else {
req->reply_expected = ((req->data[1] & 0xc) == 0xc);
if (adb_controller && adb_controller->send_request)
ret = adb_controller->send_request(req, 0);
else
ret = -ENXIO;
up(&adb_probe_mutex);
}
if (ret != 0) {
atomic_dec(&state->n_pending);
goto out;
}
return count;
out:
kfree(req);
return ret;
}
static const struct file_operations adb_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = adb_read,
.write = adb_write,
.open = adb_open,
.release = adb_release,
};
static struct platform_driver adb_pfdrv = {
.driver = {
.name = "adb",
},
#ifdef CONFIG_PM
.suspend = adb_suspend,
.resume = adb_resume,
#endif
};
static struct platform_device adb_pfdev = {
.name = "adb",
};
static int __init
adb_dummy_probe(struct platform_device *dev)
{
if (dev == &adb_pfdev)
return 0;
return -ENODEV;
}
static void __init
adbdev_init(void)
{
if (register_chrdev(ADB_MAJOR, "adb", &adb_fops)) {
printk(KERN_ERR "adb: unable to get major %d\n", ADB_MAJOR);
return;
}
adb_dev_class = class_create(THIS_MODULE, "adb");
if (IS_ERR(adb_dev_class))
return;
device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb");
platform_device_register(&adb_pfdev);
platform_driver_probe(&adb_pfdrv, adb_dummy_probe);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
/*
* /dev/lcd driver for Apple Network Servers.
*/
#include <linux/types.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <asm/sections.h>
#include <asm/prom.h>
#include <asm/io.h>
#include "ans-lcd.h"
#define ANSLCD_ADDR 0xf301c000
#define ANSLCD_CTRL_IX 0x00
#define ANSLCD_DATA_IX 0x10
static unsigned long anslcd_short_delay = 80;
static unsigned long anslcd_long_delay = 3280;
static volatile unsigned char __iomem *anslcd_ptr;
#undef DEBUG
static void
anslcd_write_byte_ctrl ( unsigned char c )
{
#ifdef DEBUG
printk(KERN_DEBUG "LCD: CTRL byte: %02x\n",c);
#endif
out_8(anslcd_ptr + ANSLCD_CTRL_IX, c);
switch(c) {
case 1:
case 2:
case 3:
udelay(anslcd_long_delay); break;
default: udelay(anslcd_short_delay);
}
}
static void
anslcd_write_byte_data ( unsigned char c )
{
out_8(anslcd_ptr + ANSLCD_DATA_IX, c);
udelay(anslcd_short_delay);
}
static ssize_t
anslcd_write( struct file * file, const char __user * buf,
size_t count, loff_t *ppos )
{
const char __user *p = buf;
int i;
#ifdef DEBUG
printk(KERN_DEBUG "LCD: write\n");
#endif
if (!access_ok(VERIFY_READ, buf, count))
return -EFAULT;
for ( i = *ppos; count > 0; ++i, ++p, --count )
{
char c;
__get_user(c, p);
anslcd_write_byte_data( c );
}
*ppos = i;
return p - buf;
}
static int
anslcd_ioctl( struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg )
{
char ch, __user *temp;
#ifdef DEBUG
printk(KERN_DEBUG "LCD: ioctl(%d,%d)\n",cmd,arg);
#endif
switch ( cmd )
{
case ANSLCD_CLEAR:
anslcd_write_byte_ctrl ( 0x38 );
anslcd_write_byte_ctrl ( 0x0f );
anslcd_write_byte_ctrl ( 0x06 );
anslcd_write_byte_ctrl ( 0x01 );
anslcd_write_byte_ctrl ( 0x02 );
return 0;
case ANSLCD_SENDCTRL:
temp = (char __user *) arg;
__get_user(ch, temp);
for (; ch; temp++) { /* FIXME: This is ugly, but should work, as a \0 byte is not a valid command code */
anslcd_write_byte_ctrl ( ch );
__get_user(ch, temp);
}
return 0;
case ANSLCD_SETSHORTDELAY:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
anslcd_short_delay=arg;
return 0;
case ANSLCD_SETLONGDELAY:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
anslcd_long_delay=arg;
return 0;
default:
return -EINVAL;
}
}
static int
anslcd_open( struct inode * inode, struct file * file )
{
cycle_kernel_lock();
return 0;
}
const struct file_operations anslcd_fops = {
.write = anslcd_write,
.ioctl = anslcd_ioctl,
.open = anslcd_open,
};
static struct miscdevice anslcd_dev = {
ANSLCD_MINOR,
"anslcd",
&anslcd_fops
};
const char anslcd_logo[] = "********************" /* Line #1 */
"* LINUX! *" /* Line #3 */
"* Welcome to *" /* Line #2 */
"********************"; /* Line #4 */
static int __init
anslcd_init(void)
{
int a;
int retval;
struct device_node* node;
node = of_find_node_by_name(NULL, "lcd");
if (!node || !node->parent || strcmp(node->parent->name, "gc")) {
of_node_put(node);
return -ENODEV;
}
of_node_put(node);
anslcd_ptr = ioremap(ANSLCD_ADDR, 0x20);
retval = misc_register(&anslcd_dev);
if(retval < 0){
printk(KERN_INFO "LCD: misc_register failed\n");
iounmap(anslcd_ptr);
return retval;
}
#ifdef DEBUG
printk(KERN_DEBUG "LCD: init\n");
#endif
anslcd_write_byte_ctrl ( 0x38 );
anslcd_write_byte_ctrl ( 0x0c );
anslcd_write_byte_ctrl ( 0x06 );
anslcd_write_byte_ctrl ( 0x01 );
anslcd_write_byte_ctrl ( 0x02 );
for(a=0;a<80;a++) {
anslcd_write_byte_data(anslcd_logo[a]);
}
return 0;
}
static void __exit
anslcd_exit(void)
{
misc_deregister(&anslcd_dev);
iounmap(anslcd_ptr);
}
module_init(anslcd_init);
module_exit(anslcd_exit);

View File

@@ -0,0 +1,11 @@
#ifndef _PPC_ANS_LCD_H
#define _PPC_ANS_LCD_H
#define ANSLCD_MINOR 156
#define ANSLCD_CLEAR 0x01
#define ANSLCD_SENDCTRL 0x02
#define ANSLCD_SETSHORTDELAY 0x03
#define ANSLCD_SETLONGDELAY 0x04
#endif

View File

@@ -0,0 +1,119 @@
/*
* APM emulation for PMU-based machines
*
* Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
*
* 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, 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.
*
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/apm-emulation.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#define APM_CRITICAL 10
#define APM_LOW 30
static void pmu_apm_get_power_status(struct apm_power_info *info)
{
int percentage = -1;
int batteries = 0;
int time_units = -1;
int real_count = 0;
int i;
char charging = 0;
long charge = -1;
long amperage = 0;
unsigned long btype = 0;
info->battery_status = APM_BATTERY_STATUS_UNKNOWN;
info->battery_flag = APM_BATTERY_FLAG_UNKNOWN;
info->units = APM_UNITS_MINS;
if (pmu_power_flags & PMU_PWR_AC_PRESENT)
info->ac_line_status = APM_AC_ONLINE;
else
info->ac_line_status = APM_AC_OFFLINE;
for (i=0; i<pmu_battery_count; i++) {
if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
batteries++;
if (percentage < 0)
percentage = 0;
if (charge < 0)
charge = 0;
percentage += (pmu_batteries[i].charge * 100) /
pmu_batteries[i].max_charge;
charge += pmu_batteries[i].charge;
amperage += pmu_batteries[i].amperage;
if (btype == 0)
btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK);
real_count++;
if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
charging++;
}
}
if (batteries == 0)
info->ac_line_status = APM_AC_ONLINE;
if (real_count) {
if (amperage < 0) {
if (btype == PMU_BATT_TYPE_SMART)
time_units = (charge * 59) / (amperage * -1);
else
time_units = (charge * 16440) / (amperage * -60);
}
percentage /= real_count;
if (charging > 0) {
info->battery_status = APM_BATTERY_STATUS_CHARGING;
info->battery_flag = APM_BATTERY_FLAG_CHARGING;
} else if (percentage <= APM_CRITICAL) {
info->battery_status = APM_BATTERY_STATUS_CRITICAL;
info->battery_flag = APM_BATTERY_FLAG_CRITICAL;
} else if (percentage <= APM_LOW) {
info->battery_status = APM_BATTERY_STATUS_LOW;
info->battery_flag = APM_BATTERY_FLAG_LOW;
} else {
info->battery_status = APM_BATTERY_STATUS_HIGH;
info->battery_flag = APM_BATTERY_FLAG_HIGH;
}
}
info->battery_life = percentage;
info->time = time_units;
}
static int __init apm_emu_init(void)
{
apm_get_power_status = pmu_apm_get_power_status;
printk(KERN_INFO "apm_emu: PMU APM Emulation initialized.\n");
return 0;
}
static void __exit apm_emu_exit(void)
{
if (apm_get_power_status == pmu_apm_get_power_status)
apm_get_power_status = NULL;
printk(KERN_INFO "apm_emu: PMU APM Emulation removed.\n");
}
module_init(apm_emu_init);
module_exit(apm_emu_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt");
MODULE_DESCRIPTION("APM emulation for PowerMac");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,153 @@
/*
* drivers/macintosh/mac_hid.c
*
* HID support stuff for Macintosh computers.
*
* Copyright (C) 2000 Franz Sirl.
*
* This file will soon be removed in favor of an uinput userspace tool.
*/
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/kbd_kern.h>
static struct input_dev *emumousebtn;
static int emumousebtn_input_register(void);
static int mouse_emulate_buttons;
static int mouse_button2_keycode = KEY_RIGHTCTRL; /* right control key */
static int mouse_button3_keycode = KEY_RIGHTALT; /* right option key */
static int mouse_last_keycode;
#if defined(CONFIG_SYSCTL)
/* file(s) in /proc/sys/dev/mac_hid */
static ctl_table mac_hid_files[] = {
{
.ctl_name = DEV_MAC_HID_MOUSE_BUTTON_EMULATION,
.procname = "mouse_button_emulation",
.data = &mouse_emulate_buttons,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = DEV_MAC_HID_MOUSE_BUTTON2_KEYCODE,
.procname = "mouse_button2_keycode",
.data = &mouse_button2_keycode,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{
.ctl_name = DEV_MAC_HID_MOUSE_BUTTON3_KEYCODE,
.procname = "mouse_button3_keycode",
.data = &mouse_button3_keycode,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec,
},
{ .ctl_name = 0 }
};
/* dir in /proc/sys/dev */
static ctl_table mac_hid_dir[] = {
{
.ctl_name = DEV_MAC_HID,
.procname = "mac_hid",
.maxlen = 0,
.mode = 0555,
.child = mac_hid_files,
},
{ .ctl_name = 0 }
};
/* /proc/sys/dev itself, in case that is not there yet */
static ctl_table mac_hid_root_dir[] = {
{
.ctl_name = CTL_DEV,
.procname = "dev",
.maxlen = 0,
.mode = 0555,
.child = mac_hid_dir,
},
{ .ctl_name = 0 }
};
static struct ctl_table_header *mac_hid_sysctl_header;
#endif /* endif CONFIG_SYSCTL */
int mac_hid_mouse_emulate_buttons(int caller, unsigned int keycode, int down)
{
switch (caller) {
case 1:
/* Called from keyboard.c */
if (mouse_emulate_buttons
&& (keycode == mouse_button2_keycode
|| keycode == mouse_button3_keycode)) {
if (mouse_emulate_buttons == 1) {
input_report_key(emumousebtn,
keycode == mouse_button2_keycode ? BTN_MIDDLE : BTN_RIGHT,
down);
input_sync(emumousebtn);
return 1;
}
mouse_last_keycode = down ? keycode : 0;
}
break;
}
return 0;
}
static struct lock_class_key emumousebtn_event_class;
static struct lock_class_key emumousebtn_mutex_class;
static int emumousebtn_input_register(void)
{
int ret;
emumousebtn = input_allocate_device();
if (!emumousebtn)
return -ENOMEM;
lockdep_set_class(&emumousebtn->event_lock, &emumousebtn_event_class);
lockdep_set_class(&emumousebtn->mutex, &emumousebtn_mutex_class);
emumousebtn->name = "Macintosh mouse button emulation";
emumousebtn->id.bustype = BUS_ADB;
emumousebtn->id.vendor = 0x0001;
emumousebtn->id.product = 0x0001;
emumousebtn->id.version = 0x0100;
emumousebtn->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
emumousebtn->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT);
emumousebtn->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
ret = input_register_device(emumousebtn);
if (ret)
input_free_device(emumousebtn);
return ret;
}
static int __init mac_hid_init(void)
{
int err;
err = emumousebtn_input_register();
if (err)
return err;
#if defined(CONFIG_SYSCTL)
mac_hid_sysctl_header = register_sysctl_table(mac_hid_root_dir);
#endif /* CONFIG_SYSCTL */
return 0;
}
device_initcall(mac_hid_init);

View File

@@ -0,0 +1,284 @@
/*
* Driver for the ADB controller in the Mac I/O (Hydra) chip.
*/
#include <stdarg.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <asm/prom.h>
#include <linux/adb.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/hydra.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <linux/init.h>
#include <linux/ioport.h>
struct preg {
unsigned char r;
char pad[15];
};
struct adb_regs {
struct preg intr;
struct preg data[9];
struct preg intr_enb;
struct preg dcount;
struct preg error;
struct preg ctrl;
struct preg autopoll;
struct preg active_hi;
struct preg active_lo;
struct preg test;
};
/* Bits in intr and intr_enb registers */
#define DFB 1 /* data from bus */
#define TAG 2 /* transfer access grant */
/* Bits in dcount register */
#define HMB 0x0f /* how many bytes */
#define APD 0x10 /* auto-poll data */
/* Bits in error register */
#define NRE 1 /* no response error */
#define DLE 2 /* data lost error */
/* Bits in ctrl register */
#define TAR 1 /* transfer access request */
#define DTB 2 /* data to bus */
#define CRE 4 /* command response expected */
#define ADB_RST 8 /* ADB reset */
/* Bits in autopoll register */
#define APE 1 /* autopoll enable */
static volatile struct adb_regs __iomem *adb;
static struct adb_request *current_req, *last_req;
static DEFINE_SPINLOCK(macio_lock);
static int macio_probe(void);
static int macio_init(void);
static irqreturn_t macio_adb_interrupt(int irq, void *arg);
static int macio_send_request(struct adb_request *req, int sync);
static int macio_adb_autopoll(int devs);
static void macio_adb_poll(void);
static int macio_adb_reset_bus(void);
struct adb_driver macio_adb_driver = {
"MACIO",
macio_probe,
macio_init,
macio_send_request,
/*macio_write,*/
macio_adb_autopoll,
macio_adb_poll,
macio_adb_reset_bus
};
int macio_probe(void)
{
struct device_node *np;
np = of_find_compatible_node(NULL, "adb", "chrp,adb0");
if (np) {
of_node_put(np);
return 0;
}
return -ENODEV;
}
int macio_init(void)
{
struct device_node *adbs;
struct resource r;
unsigned int irq;
adbs = of_find_compatible_node(NULL, "adb", "chrp,adb0");
if (adbs == 0)
return -ENXIO;
if (of_address_to_resource(adbs, 0, &r)) {
of_node_put(adbs);
return -ENXIO;
}
adb = ioremap(r.start, sizeof(struct adb_regs));
out_8(&adb->ctrl.r, 0);
out_8(&adb->intr.r, 0);
out_8(&adb->error.r, 0);
out_8(&adb->active_hi.r, 0xff); /* for now, set all devices active */
out_8(&adb->active_lo.r, 0xff);
out_8(&adb->autopoll.r, APE);
irq = irq_of_parse_and_map(adbs, 0);
of_node_put(adbs);
if (request_irq(irq, macio_adb_interrupt, 0, "ADB", (void *)0)) {
printk(KERN_ERR "ADB: can't get irq %d\n", irq);
return -EAGAIN;
}
out_8(&adb->intr_enb.r, DFB | TAG);
printk("adb: mac-io driver 1.0 for unified ADB\n");
return 0;
}
static int macio_adb_autopoll(int devs)
{
unsigned long flags;
spin_lock_irqsave(&macio_lock, flags);
out_8(&adb->active_hi.r, devs >> 8);
out_8(&adb->active_lo.r, devs);
out_8(&adb->autopoll.r, devs? APE: 0);
spin_unlock_irqrestore(&macio_lock, flags);
return 0;
}
static int macio_adb_reset_bus(void)
{
unsigned long flags;
int timeout = 1000000;
/* Hrm... we may want to not lock interrupts for so
* long ... oh well, who uses that chip anyway ? :)
* That function will be seldomly used during boot
* on rare machines, so...
*/
spin_lock_irqsave(&macio_lock, flags);
out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | ADB_RST);
while ((in_8(&adb->ctrl.r) & ADB_RST) != 0) {
if (--timeout == 0) {
out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) & ~ADB_RST);
return -1;
}
}
spin_unlock_irqrestore(&macio_lock, flags);
return 0;
}
/* Send an ADB command */
static int macio_send_request(struct adb_request *req, int sync)
{
unsigned long flags;
int i;
if (req->data[0] != ADB_PACKET)
return -EINVAL;
for (i = 0; i < req->nbytes - 1; ++i)
req->data[i] = req->data[i+1];
--req->nbytes;
req->next = NULL;
req->sent = 0;
req->complete = 0;
req->reply_len = 0;
spin_lock_irqsave(&macio_lock, flags);
if (current_req != 0) {
last_req->next = req;
last_req = req;
} else {
current_req = last_req = req;
out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
}
spin_unlock_irqrestore(&macio_lock, flags);
if (sync) {
while (!req->complete)
macio_adb_poll();
}
return 0;
}
static irqreturn_t macio_adb_interrupt(int irq, void *arg)
{
int i, n, err;
struct adb_request *req = NULL;
unsigned char ibuf[16];
int ibuf_len = 0;
int complete = 0;
int autopoll = 0;
int handled = 0;
spin_lock(&macio_lock);
if (in_8(&adb->intr.r) & TAG) {
handled = 1;
if ((req = current_req) != 0) {
/* put the current request in */
for (i = 0; i < req->nbytes; ++i)
out_8(&adb->data[i].r, req->data[i]);
out_8(&adb->dcount.r, req->nbytes & HMB);
req->sent = 1;
if (req->reply_expected) {
out_8(&adb->ctrl.r, DTB + CRE);
} else {
out_8(&adb->ctrl.r, DTB);
current_req = req->next;
complete = 1;
if (current_req)
out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
}
}
out_8(&adb->intr.r, 0);
}
if (in_8(&adb->intr.r) & DFB) {
handled = 1;
err = in_8(&adb->error.r);
if (current_req && current_req->sent) {
/* this is the response to a command */
req = current_req;
if (err == 0) {
req->reply_len = in_8(&adb->dcount.r) & HMB;
for (i = 0; i < req->reply_len; ++i)
req->reply[i] = in_8(&adb->data[i].r);
}
current_req = req->next;
complete = 1;
if (current_req)
out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR);
} else if (err == 0) {
/* autopoll data */
n = in_8(&adb->dcount.r) & HMB;
for (i = 0; i < n; ++i)
ibuf[i] = in_8(&adb->data[i].r);
ibuf_len = n;
autopoll = (in_8(&adb->dcount.r) & APD) != 0;
}
out_8(&adb->error.r, 0);
out_8(&adb->intr.r, 0);
}
spin_unlock(&macio_lock);
if (complete && req) {
void (*done)(struct adb_request *) = req->done;
mb();
req->complete = 1;
/* Here, we assume that if the request has a done member, the
* struct request will survive to setting req->complete to 1
*/
if (done)
(*done)(req);
}
if (ibuf_len)
adb_input(ibuf, ibuf_len, autopoll);
return IRQ_RETVAL(handled);
}
static void macio_adb_poll(void)
{
unsigned long flags;
local_irq_save(flags);
if (in_8(&adb->intr.r) != 0)
macio_adb_interrupt(0, NULL);
local_irq_restore(flags);
}

View File

@@ -0,0 +1,746 @@
/*
* Bus & driver management routines for devices within
* a MacIO ASIC. Interface to new driver model mostly
* stolen from the PCI version.
*
* Copyright (C) 2005 Ben. Herrenschmidt (benh@kernel.crashing.org)
*
* 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.
*
* TODO:
*
* - Don't probe below media bay by default, but instead provide
* some hooks for media bay to dynamically add/remove it's own
* sub-devices.
*/
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <asm/machdep.h>
#include <asm/macio.h>
#include <asm/pmac_feature.h>
#include <asm/prom.h>
#include <asm/pci-bridge.h>
#undef DEBUG
#define MAX_NODE_NAME_SIZE (20 - 12)
static struct macio_chip *macio_on_hold;
static int macio_bus_match(struct device *dev, struct device_driver *drv)
{
struct macio_dev * macio_dev = to_macio_device(dev);
struct macio_driver * macio_drv = to_macio_driver(drv);
const struct of_device_id * matches = macio_drv->match_table;
if (!matches)
return 0;
return of_match_device(matches, &macio_dev->ofdev) != NULL;
}
struct macio_dev *macio_dev_get(struct macio_dev *dev)
{
struct device *tmp;
if (!dev)
return NULL;
tmp = get_device(&dev->ofdev.dev);
if (tmp)
return to_macio_device(tmp);
else
return NULL;
}
void macio_dev_put(struct macio_dev *dev)
{
if (dev)
put_device(&dev->ofdev.dev);
}
static int macio_device_probe(struct device *dev)
{
int error = -ENODEV;
struct macio_driver *drv;
struct macio_dev *macio_dev;
const struct of_device_id *match;
drv = to_macio_driver(dev->driver);
macio_dev = to_macio_device(dev);
if (!drv->probe)
return error;
macio_dev_get(macio_dev);
match = of_match_device(drv->match_table, &macio_dev->ofdev);
if (match)
error = drv->probe(macio_dev, match);
if (error)
macio_dev_put(macio_dev);
return error;
}
static int macio_device_remove(struct device *dev)
{
struct macio_dev * macio_dev = to_macio_device(dev);
struct macio_driver * drv = to_macio_driver(dev->driver);
if (dev->driver && drv->remove)
drv->remove(macio_dev);
macio_dev_put(macio_dev);
return 0;
}
static void macio_device_shutdown(struct device *dev)
{
struct macio_dev * macio_dev = to_macio_device(dev);
struct macio_driver * drv = to_macio_driver(dev->driver);
if (dev->driver && drv->shutdown)
drv->shutdown(macio_dev);
}
static int macio_device_suspend(struct device *dev, pm_message_t state)
{
struct macio_dev * macio_dev = to_macio_device(dev);
struct macio_driver * drv = to_macio_driver(dev->driver);
if (dev->driver && drv->suspend)
return drv->suspend(macio_dev, state);
return 0;
}
static int macio_device_resume(struct device * dev)
{
struct macio_dev * macio_dev = to_macio_device(dev);
struct macio_driver * drv = to_macio_driver(dev->driver);
if (dev->driver && drv->resume)
return drv->resume(macio_dev);
return 0;
}
extern struct device_attribute macio_dev_attrs[];
struct bus_type macio_bus_type = {
.name = "macio",
.match = macio_bus_match,
.uevent = of_device_uevent,
.probe = macio_device_probe,
.remove = macio_device_remove,
.shutdown = macio_device_shutdown,
.suspend = macio_device_suspend,
.resume = macio_device_resume,
.dev_attrs = macio_dev_attrs,
};
static int __init macio_bus_driver_init(void)
{
return bus_register(&macio_bus_type);
}
postcore_initcall(macio_bus_driver_init);
/**
* macio_release_dev - free a macio device structure when all users of it are
* finished.
* @dev: device that's been disconnected
*
* Will be called only by the device core when all users of this macio device
* are done. This currently means never as we don't hot remove any macio
* device yet, though that will happen with mediabay based devices in a later
* implementation.
*/
static void macio_release_dev(struct device *dev)
{
struct macio_dev *mdev;
mdev = to_macio_device(dev);
kfree(mdev);
}
/**
* macio_resource_quirks - tweak or skip some resources for a device
* @np: pointer to the device node
* @res: resulting resource
* @index: index of resource in node
*
* If this routine returns non-null, then the resource is completely
* skipped.
*/
static int macio_resource_quirks(struct device_node *np, struct resource *res,
int index)
{
/* Only quirks for memory resources for now */
if ((res->flags & IORESOURCE_MEM) == 0)
return 0;
/* Grand Central has too large resource 0 on some machines */
if (index == 0 && !strcmp(np->name, "gc"))
res->end = res->start + 0x1ffff;
/* Airport has bogus resource 2 */
if (index >= 2 && !strcmp(np->name, "radio"))
return 1;
#ifndef CONFIG_PPC64
/* DBDMAs may have bogus sizes */
if ((res->start & 0x0001f000) == 0x00008000)
res->end = res->start + 0xff;
#endif /* CONFIG_PPC64 */
/* ESCC parent eats child resources. We could have added a
* level of hierarchy, but I don't really feel the need
* for it
*/
if (!strcmp(np->name, "escc"))
return 1;
/* ESCC has bogus resources >= 3 */
if (index >= 3 && !(strcmp(np->name, "ch-a") &&
strcmp(np->name, "ch-b")))
return 1;
/* Media bay has too many resources, keep only first one */
if (index > 0 && !strcmp(np->name, "media-bay"))
return 1;
/* Some older IDE resources have bogus sizes */
if (!(strcmp(np->name, "IDE") && strcmp(np->name, "ATA") &&
strcmp(np->type, "ide") && strcmp(np->type, "ata"))) {
if (index == 0 && (res->end - res->start) > 0xfff)
res->end = res->start + 0xfff;
if (index == 1 && (res->end - res->start) > 0xff)
res->end = res->start + 0xff;
}
return 0;
}
static void macio_create_fixup_irq(struct macio_dev *dev, int index,
unsigned int line)
{
unsigned int irq;
irq = irq_create_mapping(NULL, line);
if (irq != NO_IRQ) {
dev->interrupt[index].start = irq;
dev->interrupt[index].flags = IORESOURCE_IRQ;
dev->interrupt[index].name = dev_name(&dev->ofdev.dev);
}
if (dev->n_interrupts <= index)
dev->n_interrupts = index + 1;
}
static void macio_add_missing_resources(struct macio_dev *dev)
{
struct device_node *np = dev->ofdev.node;
unsigned int irq_base;
/* Gatwick has some missing interrupts on child nodes */
if (dev->bus->chip->type != macio_gatwick)
return;
/* irq_base is always 64 on gatwick. I have no cleaner way to get
* that value from here at this point
*/
irq_base = 64;
/* Fix SCC */
if (strcmp(np->name, "ch-a") == 0) {
macio_create_fixup_irq(dev, 0, 15 + irq_base);
macio_create_fixup_irq(dev, 1, 4 + irq_base);
macio_create_fixup_irq(dev, 2, 5 + irq_base);
printk(KERN_INFO "macio: fixed SCC irqs on gatwick\n");
}
/* Fix media-bay */
if (strcmp(np->name, "media-bay") == 0) {
macio_create_fixup_irq(dev, 0, 29 + irq_base);
printk(KERN_INFO "macio: fixed media-bay irq on gatwick\n");
}
/* Fix left media bay childs */
if (dev->media_bay != NULL && strcmp(np->name, "floppy") == 0) {
macio_create_fixup_irq(dev, 0, 19 + irq_base);
macio_create_fixup_irq(dev, 1, 1 + irq_base);
printk(KERN_INFO "macio: fixed left floppy irqs\n");
}
if (dev->media_bay != NULL && strcasecmp(np->name, "ata4") == 0) {
macio_create_fixup_irq(dev, 0, 14 + irq_base);
macio_create_fixup_irq(dev, 0, 3 + irq_base);
printk(KERN_INFO "macio: fixed left ide irqs\n");
}
}
static void macio_setup_interrupts(struct macio_dev *dev)
{
struct device_node *np = dev->ofdev.node;
unsigned int irq;
int i = 0, j = 0;
for (;;) {
struct resource *res;
if (j >= MACIO_DEV_COUNT_IRQS)
break;
res = &dev->interrupt[j];
irq = irq_of_parse_and_map(np, i++);
if (irq == NO_IRQ)
break;
res->start = irq;
res->flags = IORESOURCE_IRQ;
res->name = dev_name(&dev->ofdev.dev);
if (macio_resource_quirks(np, res, i - 1)) {
memset(res, 0, sizeof(struct resource));
continue;
} else
j++;
}
dev->n_interrupts = j;
}
static void macio_setup_resources(struct macio_dev *dev,
struct resource *parent_res)
{
struct device_node *np = dev->ofdev.node;
struct resource r;
int index;
for (index = 0; of_address_to_resource(np, index, &r) == 0; index++) {
struct resource *res;
if (index >= MACIO_DEV_COUNT_RESOURCES)
break;
res = &dev->resource[index];
*res = r;
res->name = dev_name(&dev->ofdev.dev);
if (macio_resource_quirks(np, res, index)) {
memset(res, 0, sizeof(struct resource));
continue;
}
/* Currently, we consider failure as harmless, this may
* change in the future, once I've found all the device
* tree bugs in older machines & worked around them
*/
if (insert_resource(parent_res, res)) {
printk(KERN_WARNING "Can't request resource "
"%d for MacIO device %s\n",
index, dev_name(&dev->ofdev.dev));
}
}
dev->n_resources = index;
}
/**
* macio_add_one_device - Add one device from OF node to the device tree
* @chip: pointer to the macio_chip holding the device
* @np: pointer to the device node in the OF tree
* @in_bay: set to 1 if device is part of a media-bay
*
* When media-bay is changed to hotswap drivers, this function will
* be exposed to the bay driver some way...
*/
static struct macio_dev * macio_add_one_device(struct macio_chip *chip,
struct device *parent,
struct device_node *np,
struct macio_dev *in_bay,
struct resource *parent_res)
{
struct macio_dev *dev;
const u32 *reg;
if (np == NULL)
return NULL;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return NULL;
dev->bus = &chip->lbus;
dev->media_bay = in_bay;
dev->ofdev.node = np;
dev->ofdev.dma_mask = 0xffffffffUL;
dev->ofdev.dev.dma_mask = &dev->ofdev.dma_mask;
dev->ofdev.dev.parent = parent;
dev->ofdev.dev.bus = &macio_bus_type;
dev->ofdev.dev.release = macio_release_dev;
#ifdef CONFIG_PCI
/* Set the DMA ops to the ones from the PCI device, this could be
* fishy if we didn't know that on PowerMac it's always direct ops
* or iommu ops that will work fine
*/
dev->ofdev.dev.archdata.dma_ops =
chip->lbus.pdev->dev.archdata.dma_ops;
dev->ofdev.dev.archdata.dma_data =
chip->lbus.pdev->dev.archdata.dma_data;
#endif /* CONFIG_PCI */
#ifdef DEBUG
printk("preparing mdev @%p, ofdev @%p, dev @%p, kobj @%p\n",
dev, &dev->ofdev, &dev->ofdev.dev, &dev->ofdev.dev.kobj);
#endif
/* MacIO itself has a different reg, we use it's PCI base */
if (np == chip->of_node) {
dev_set_name(&dev->ofdev.dev, "%1d.%08x:%.*s",
chip->lbus.index,
#ifdef CONFIG_PCI
(unsigned int)pci_resource_start(chip->lbus.pdev, 0),
#else
0, /* NuBus may want to do something better here */
#endif
MAX_NODE_NAME_SIZE, np->name);
} else {
reg = of_get_property(np, "reg", NULL);
dev_set_name(&dev->ofdev.dev, "%1d.%08x:%.*s",
chip->lbus.index,
reg ? *reg : 0, MAX_NODE_NAME_SIZE, np->name);
}
/* Setup interrupts & resources */
macio_setup_interrupts(dev);
macio_setup_resources(dev, parent_res);
macio_add_missing_resources(dev);
/* Register with core */
if (of_device_register(&dev->ofdev) != 0) {
printk(KERN_DEBUG"macio: device registration error for %s!\n",
dev_name(&dev->ofdev.dev));
kfree(dev);
return NULL;
}
return dev;
}
static int macio_skip_device(struct device_node *np)
{
if (strncmp(np->name, "battery", 7) == 0)
return 1;
if (strncmp(np->name, "escc-legacy", 11) == 0)
return 1;
return 0;
}
/**
* macio_pci_add_devices - Adds sub-devices of mac-io to the device tree
* @chip: pointer to the macio_chip holding the devices
*
* This function will do the job of extracting devices from the
* Open Firmware device tree, build macio_dev structures and add
* them to the Linux device tree.
*
* For now, childs of media-bay are added now as well. This will
* change rsn though.
*/
static void macio_pci_add_devices(struct macio_chip *chip)
{
struct device_node *np, *pnode;
struct macio_dev *rdev, *mdev, *mbdev = NULL, *sdev = NULL;
struct device *parent = NULL;
struct resource *root_res = &iomem_resource;
/* Add a node for the macio bus itself */
#ifdef CONFIG_PCI
if (chip->lbus.pdev) {
parent = &chip->lbus.pdev->dev;
root_res = &chip->lbus.pdev->resource[0];
}
#endif
pnode = of_node_get(chip->of_node);
if (pnode == NULL)
return;
/* Add macio itself to hierarchy */
rdev = macio_add_one_device(chip, parent, pnode, NULL, root_res);
if (rdev == NULL)
return;
root_res = &rdev->resource[0];
/* First scan 1st level */
for (np = NULL; (np = of_get_next_child(pnode, np)) != NULL;) {
if (macio_skip_device(np))
continue;
of_node_get(np);
mdev = macio_add_one_device(chip, &rdev->ofdev.dev, np, NULL,
root_res);
if (mdev == NULL)
of_node_put(np);
else if (strncmp(np->name, "media-bay", 9) == 0)
mbdev = mdev;
else if (strncmp(np->name, "escc", 4) == 0)
sdev = mdev;
}
/* Add media bay devices if any */
if (mbdev)
for (np = NULL; (np = of_get_next_child(mbdev->ofdev.node, np))
!= NULL;) {
if (macio_skip_device(np))
continue;
of_node_get(np);
if (macio_add_one_device(chip, &mbdev->ofdev.dev, np,
mbdev, root_res) == NULL)
of_node_put(np);
}
/* Add serial ports if any */
if (sdev) {
for (np = NULL; (np = of_get_next_child(sdev->ofdev.node, np))
!= NULL;) {
if (macio_skip_device(np))
continue;
of_node_get(np);
if (macio_add_one_device(chip, &sdev->ofdev.dev, np,
NULL, root_res) == NULL)
of_node_put(np);
}
}
}
/**
* macio_register_driver - Registers a new MacIO device driver
* @drv: pointer to the driver definition structure
*/
int macio_register_driver(struct macio_driver *drv)
{
/* initialize common driver fields */
drv->driver.name = drv->name;
drv->driver.bus = &macio_bus_type;
/* register with core */
return driver_register(&drv->driver);
}
/**
* macio_unregister_driver - Unregisters a new MacIO device driver
* @drv: pointer to the driver definition structure
*/
void macio_unregister_driver(struct macio_driver *drv)
{
driver_unregister(&drv->driver);
}
/**
* macio_request_resource - Request an MMIO resource
* @dev: pointer to the device holding the resource
* @resource_no: resource number to request
* @name: resource name
*
* Mark memory region number @resource_no associated with MacIO
* device @dev as being reserved by owner @name. Do not access
* any address inside the memory regions unless this call returns
* successfully.
*
* Returns 0 on success, or %EBUSY on error. A warning
* message is also printed on failure.
*/
int macio_request_resource(struct macio_dev *dev, int resource_no,
const char *name)
{
if (macio_resource_len(dev, resource_no) == 0)
return 0;
if (!request_mem_region(macio_resource_start(dev, resource_no),
macio_resource_len(dev, resource_no),
name))
goto err_out;
return 0;
err_out:
printk (KERN_WARNING "MacIO: Unable to reserve resource #%d:%lx@%lx"
" for device %s\n",
resource_no,
macio_resource_len(dev, resource_no),
macio_resource_start(dev, resource_no),
dev_name(&dev->ofdev.dev));
return -EBUSY;
}
/**
* macio_release_resource - Release an MMIO resource
* @dev: pointer to the device holding the resource
* @resource_no: resource number to release
*/
void macio_release_resource(struct macio_dev *dev, int resource_no)
{
if (macio_resource_len(dev, resource_no) == 0)
return;
release_mem_region(macio_resource_start(dev, resource_no),
macio_resource_len(dev, resource_no));
}
/**
* macio_request_resources - Reserve all memory resources
* @dev: MacIO device whose resources are to be reserved
* @name: Name to be associated with resource.
*
* Mark all memory regions associated with MacIO device @dev as
* being reserved by owner @name. Do not access any address inside
* the memory regions unless this call returns successfully.
*
* Returns 0 on success, or %EBUSY on error. A warning
* message is also printed on failure.
*/
int macio_request_resources(struct macio_dev *dev, const char *name)
{
int i;
for (i = 0; i < dev->n_resources; i++)
if (macio_request_resource(dev, i, name))
goto err_out;
return 0;
err_out:
while(--i >= 0)
macio_release_resource(dev, i);
return -EBUSY;
}
/**
* macio_release_resources - Release reserved memory resources
* @dev: MacIO device whose resources were previously reserved
*/
void macio_release_resources(struct macio_dev *dev)
{
int i;
for (i = 0; i < dev->n_resources; i++)
macio_release_resource(dev, i);
}
#ifdef CONFIG_PCI
static int __devinit macio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct device_node* np;
struct macio_chip* chip;
if (ent->vendor != PCI_VENDOR_ID_APPLE)
return -ENODEV;
/* Note regarding refcounting: We assume pci_device_to_OF_node() is
* ported to new OF APIs and returns a node with refcount incremented.
*/
np = pci_device_to_OF_node(pdev);
if (np == NULL)
return -ENODEV;
/* The above assumption is wrong !!!
* fix that here for now until I fix the arch code
*/
of_node_get(np);
/* We also assume that pmac_feature will have done a get() on nodes
* stored in the macio chips array
*/
chip = macio_find(np, macio_unknown);
of_node_put(np);
if (chip == NULL)
return -ENODEV;
/* XXX Need locking ??? */
if (chip->lbus.pdev == NULL) {
chip->lbus.pdev = pdev;
chip->lbus.chip = chip;
pci_set_drvdata(pdev, &chip->lbus);
pci_set_master(pdev);
}
printk(KERN_INFO "MacIO PCI driver attached to %s chipset\n",
chip->name);
/*
* HACK ALERT: The WallStreet PowerBook and some OHare based machines
* have 2 macio ASICs. I must probe the "main" one first or IDE
* ordering will be incorrect. So I put on "hold" the second one since
* it seem to appear first on PCI
*/
if (chip->type == macio_gatwick || chip->type == macio_ohareII)
if (macio_chips[0].lbus.pdev == NULL) {
macio_on_hold = chip;
return 0;
}
macio_pci_add_devices(chip);
if (macio_on_hold && macio_chips[0].lbus.pdev != NULL) {
macio_pci_add_devices(macio_on_hold);
macio_on_hold = NULL;
}
return 0;
}
static void __devexit macio_pci_remove(struct pci_dev* pdev)
{
panic("removing of macio-asic not supported !\n");
}
/*
* MacIO is matched against any Apple ID, it's probe() function
* will then decide wether it applies or not
*/
static const struct pci_device_id __devinitdata pci_ids [] = { {
.vendor = PCI_VENDOR_ID_APPLE,
.device = PCI_ANY_ID,
.subvendor = PCI_ANY_ID,
.subdevice = PCI_ANY_ID,
}, { /* end: all zeroes */ }
};
MODULE_DEVICE_TABLE (pci, pci_ids);
/* pci driver glue; this is a "new style" PCI driver module */
static struct pci_driver macio_pci_driver = {
.name = (char *) "macio",
.id_table = pci_ids,
.probe = macio_pci_probe,
.remove = macio_pci_remove,
};
#endif /* CONFIG_PCI */
static int __init macio_module_init (void)
{
#ifdef CONFIG_PCI
int rc;
rc = pci_register_driver(&macio_pci_driver);
if (rc)
return rc;
#endif /* CONFIG_PCI */
return 0;
}
module_init(macio_module_init);
EXPORT_SYMBOL(macio_register_driver);
EXPORT_SYMBOL(macio_unregister_driver);
EXPORT_SYMBOL(macio_dev_get);
EXPORT_SYMBOL(macio_dev_put);
EXPORT_SYMBOL(macio_request_resource);
EXPORT_SYMBOL(macio_release_resource);
EXPORT_SYMBOL(macio_request_resources);
EXPORT_SYMBOL(macio_release_resources);

View File

@@ -0,0 +1,74 @@
#include <linux/kernel.h>
#include <linux/stat.h>
#include <asm/macio.h>
#define macio_config_of_attr(field, format_string) \
static ssize_t \
field##_show (struct device *dev, struct device_attribute *attr, \
char *buf) \
{ \
struct macio_dev *mdev = to_macio_device (dev); \
return sprintf (buf, format_string, mdev->ofdev.node->field); \
}
static ssize_t
compatible_show (struct device *dev, struct device_attribute *attr, char *buf)
{
struct of_device *of;
const char *compat;
int cplen;
int length = 0;
of = &to_macio_device (dev)->ofdev;
compat = of_get_property(of->node, "compatible", &cplen);
if (!compat) {
*buf = '\0';
return 0;
}
while (cplen > 0) {
int l;
length += sprintf (buf, "%s\n", compat);
buf += length;
l = strlen (compat) + 1;
compat += l;
cplen -= l;
}
return length;
}
static ssize_t modalias_show (struct device *dev, struct device_attribute *attr,
char *buf)
{
struct of_device *ofdev = to_of_device(dev);
int len;
len = of_device_get_modalias(ofdev, buf, PAGE_SIZE - 2);
buf[len] = '\n';
buf[len+1] = 0;
return len+1;
}
static ssize_t devspec_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct of_device *ofdev;
ofdev = to_of_device(dev);
return sprintf(buf, "%s\n", ofdev->node->full_name);
}
macio_config_of_attr (name, "%s\n");
macio_config_of_attr (type, "%s\n");
struct device_attribute macio_dev_attrs[] = {
__ATTR_RO(name),
__ATTR_RO(type),
__ATTR_RO(compatible),
__ATTR_RO(modalias),
__ATTR_RO(devspec),
__ATTR_NULL
};

View File

@@ -0,0 +1,838 @@
/*
* Driver for the media bay on the PowerBook 3400 and 2400.
*
* Copyright (C) 1998 Paul Mackerras.
*
* Various evolutions by Benjamin Herrenschmidt & Henry Worth
*
* 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.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/stddef.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <asm/prom.h>
#include <asm/pgtable.h>
#include <asm/io.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <asm/mediabay.h>
#include <asm/sections.h>
#include <asm/ohare.h>
#include <asm/heathrow.h>
#include <asm/keylargo.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#define MB_DEBUG
#ifdef MB_DEBUG
#define MBDBG(fmt, arg...) printk(KERN_INFO fmt , ## arg)
#else
#define MBDBG(fmt, arg...) do { } while (0)
#endif
#define MB_FCR32(bay, r) ((bay)->base + ((r) >> 2))
#define MB_FCR8(bay, r) (((volatile u8 __iomem *)((bay)->base)) + (r))
#define MB_IN32(bay,r) (in_le32(MB_FCR32(bay,r)))
#define MB_OUT32(bay,r,v) (out_le32(MB_FCR32(bay,r), (v)))
#define MB_BIS(bay,r,v) (MB_OUT32((bay), (r), MB_IN32((bay), r) | (v)))
#define MB_BIC(bay,r,v) (MB_OUT32((bay), (r), MB_IN32((bay), r) & ~(v)))
#define MB_IN8(bay,r) (in_8(MB_FCR8(bay,r)))
#define MB_OUT8(bay,r,v) (out_8(MB_FCR8(bay,r), (v)))
struct media_bay_info;
struct mb_ops {
char* name;
void (*init)(struct media_bay_info *bay);
u8 (*content)(struct media_bay_info *bay);
void (*power)(struct media_bay_info *bay, int on_off);
int (*setup_bus)(struct media_bay_info *bay, u8 device_id);
void (*un_reset)(struct media_bay_info *bay);
void (*un_reset_ide)(struct media_bay_info *bay);
};
struct media_bay_info {
u32 __iomem *base;
int content_id;
int state;
int last_value;
int value_count;
int timer;
struct macio_dev *mdev;
struct mb_ops* ops;
int index;
int cached_gpio;
int sleeping;
struct mutex lock;
#ifdef CONFIG_BLK_DEV_IDE_PMAC
ide_hwif_t *cd_port;
void __iomem *cd_base;
int cd_irq;
int cd_retry;
#endif
#if defined(CONFIG_BLK_DEV_IDE_PMAC)
int cd_index;
#endif
};
#define MAX_BAYS 2
static struct media_bay_info media_bays[MAX_BAYS];
int media_bay_count = 0;
#ifdef CONFIG_BLK_DEV_IDE_PMAC
/* check the busy bit in the media-bay ide interface
(assumes the media-bay contains an ide device) */
#define MB_IDE_READY(i) ((readb(media_bays[i].cd_base + 0x70) & 0x80) == 0)
#endif
/*
* Wait that number of ms between each step in normal polling mode
*/
#define MB_POLL_DELAY 25
/*
* Consider the media-bay ID value stable if it is the same for
* this number of milliseconds
*/
#define MB_STABLE_DELAY 100
/* Wait after powering up the media bay this delay in ms
* timeout bumped for some powerbooks
*/
#define MB_POWER_DELAY 200
/*
* Hold the media-bay reset signal true for this many ticks
* after a device is inserted before releasing it.
*/
#define MB_RESET_DELAY 50
/*
* Wait this long after the reset signal is released and before doing
* further operations. After this delay, the IDE reset signal is released
* too for an IDE device
*/
#define MB_SETUP_DELAY 100
/*
* Wait this many ticks after an IDE device (e.g. CD-ROM) is inserted
* (or until the device is ready) before waiting for busy bit to disappear
*/
#define MB_IDE_WAIT 1000
/*
* Timeout waiting for busy bit of an IDE device to go down
*/
#define MB_IDE_TIMEOUT 5000
/*
* Max retries of the full power up/down sequence for an IDE device
*/
#define MAX_CD_RETRIES 3
/*
* States of a media bay
*/
enum {
mb_empty = 0, /* Idle */
mb_powering_up, /* power bit set, waiting MB_POWER_DELAY */
mb_enabling_bay, /* enable bits set, waiting MB_RESET_DELAY */
mb_resetting, /* reset bit unset, waiting MB_SETUP_DELAY */
mb_ide_resetting, /* IDE reset bit unser, waiting MB_IDE_WAIT */
mb_ide_waiting, /* Waiting for BUSY bit to go away until MB_IDE_TIMEOUT */
mb_up, /* Media bay full */
mb_powering_down /* Powering down (avoid too fast down/up) */
};
#define MB_POWER_SOUND 0x08
#define MB_POWER_FLOPPY 0x04
#define MB_POWER_ATA 0x02
#define MB_POWER_PCI 0x01
#define MB_POWER_OFF 0x00
/*
* Functions for polling content of media bay
*/
static u8
ohare_mb_content(struct media_bay_info *bay)
{
return (MB_IN32(bay, OHARE_MBCR) >> 12) & 7;
}
static u8
heathrow_mb_content(struct media_bay_info *bay)
{
return (MB_IN32(bay, HEATHROW_MBCR) >> 12) & 7;
}
static u8
keylargo_mb_content(struct media_bay_info *bay)
{
int new_gpio;
new_gpio = MB_IN8(bay, KL_GPIO_MEDIABAY_IRQ) & KEYLARGO_GPIO_INPUT_DATA;
if (new_gpio) {
bay->cached_gpio = new_gpio;
return MB_NO;
} else if (bay->cached_gpio != new_gpio) {
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE);
(void)MB_IN32(bay, KEYLARGO_MBCR);
udelay(5);
MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F);
(void)MB_IN32(bay, KEYLARGO_MBCR);
udelay(5);
bay->cached_gpio = new_gpio;
}
return (MB_IN32(bay, KEYLARGO_MBCR) >> 4) & 7;
}
/*
* Functions for powering up/down the bay, puts the bay device
* into reset state as well
*/
static void
ohare_mb_power(struct media_bay_info* bay, int on_off)
{
if (on_off) {
/* Power up device, assert it's reset line */
MB_BIC(bay, OHARE_FCR, OH_BAY_RESET_N);
MB_BIC(bay, OHARE_FCR, OH_BAY_POWER_N);
} else {
/* Disable all devices */
MB_BIC(bay, OHARE_FCR, OH_BAY_DEV_MASK);
MB_BIC(bay, OHARE_FCR, OH_FLOPPY_ENABLE);
/* Cut power from bay, release reset line */
MB_BIS(bay, OHARE_FCR, OH_BAY_POWER_N);
MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N);
MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N);
}
MB_BIC(bay, OHARE_MBCR, 0x00000F00);
}
static void
heathrow_mb_power(struct media_bay_info* bay, int on_off)
{
if (on_off) {
/* Power up device, assert it's reset line */
MB_BIC(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
MB_BIC(bay, HEATHROW_FCR, HRW_BAY_POWER_N);
} else {
/* Disable all devices */
MB_BIC(bay, HEATHROW_FCR, HRW_BAY_DEV_MASK);
MB_BIC(bay, HEATHROW_FCR, HRW_SWIM_ENABLE);
/* Cut power from bay, release reset line */
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_POWER_N);
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
}
MB_BIC(bay, HEATHROW_MBCR, 0x00000F00);
}
static void
keylargo_mb_power(struct media_bay_info* bay, int on_off)
{
if (on_off) {
/* Power up device, assert it's reset line */
MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER);
} else {
/* Disable all devices */
MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_MASK);
MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE);
/* Cut power from bay, release reset line */
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER);
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
}
MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F);
}
/*
* Functions for configuring the media bay for a given type of device,
* enable the related busses
*/
static int
ohare_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
{
switch(device_id) {
case MB_FD:
case MB_FD1:
MB_BIS(bay, OHARE_FCR, OH_BAY_FLOPPY_ENABLE);
MB_BIS(bay, OHARE_FCR, OH_FLOPPY_ENABLE);
return 0;
case MB_CD:
MB_BIC(bay, OHARE_FCR, OH_IDE1_RESET_N);
MB_BIS(bay, OHARE_FCR, OH_BAY_IDE_ENABLE);
return 0;
case MB_PCI:
MB_BIS(bay, OHARE_FCR, OH_BAY_PCI_ENABLE);
return 0;
}
return -ENODEV;
}
static int
heathrow_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
{
switch(device_id) {
case MB_FD:
case MB_FD1:
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_FLOPPY_ENABLE);
MB_BIS(bay, HEATHROW_FCR, HRW_SWIM_ENABLE);
return 0;
case MB_CD:
MB_BIC(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_IDE_ENABLE);
return 0;
case MB_PCI:
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_PCI_ENABLE);
return 0;
}
return -ENODEV;
}
static int
keylargo_mb_setup_bus(struct media_bay_info* bay, u8 device_id)
{
switch(device_id) {
case MB_CD:
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_IDE_ENABLE);
MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE);
return 0;
case MB_PCI:
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_PCI_ENABLE);
return 0;
case MB_SOUND:
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_SOUND_ENABLE);
return 0;
}
return -ENODEV;
}
/*
* Functions for tweaking resets
*/
static void
ohare_mb_un_reset(struct media_bay_info* bay)
{
MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N);
}
static void keylargo_mb_init(struct media_bay_info *bay)
{
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE);
}
static void heathrow_mb_un_reset(struct media_bay_info* bay)
{
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
}
static void keylargo_mb_un_reset(struct media_bay_info* bay)
{
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
}
static void ohare_mb_un_reset_ide(struct media_bay_info* bay)
{
MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N);
}
static void heathrow_mb_un_reset_ide(struct media_bay_info* bay)
{
MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
}
static void keylargo_mb_un_reset_ide(struct media_bay_info* bay)
{
MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
}
static inline void set_mb_power(struct media_bay_info* bay, int onoff)
{
/* Power up up and assert the bay reset line */
if (onoff) {
bay->ops->power(bay, 1);
bay->state = mb_powering_up;
MBDBG("mediabay%d: powering up\n", bay->index);
} else {
/* Make sure everything is powered down & disabled */
bay->ops->power(bay, 0);
bay->state = mb_powering_down;
MBDBG("mediabay%d: powering down\n", bay->index);
}
bay->timer = msecs_to_jiffies(MB_POWER_DELAY);
}
static void poll_media_bay(struct media_bay_info* bay)
{
int id = bay->ops->content(bay);
if (id == bay->last_value) {
if (id != bay->content_id) {
bay->value_count += msecs_to_jiffies(MB_POLL_DELAY);
if (bay->value_count >= msecs_to_jiffies(MB_STABLE_DELAY)) {
/* If the device type changes without going thru
* "MB_NO", we force a pass by "MB_NO" to make sure
* things are properly reset
*/
if ((id != MB_NO) && (bay->content_id != MB_NO)) {
id = MB_NO;
MBDBG("mediabay%d: forcing MB_NO\n", bay->index);
}
MBDBG("mediabay%d: switching to %d\n", bay->index, id);
set_mb_power(bay, id != MB_NO);
bay->content_id = id;
if (id == MB_NO) {
#ifdef CONFIG_BLK_DEV_IDE_PMAC
bay->cd_retry = 0;
#endif
printk(KERN_INFO "media bay %d is empty\n", bay->index);
}
}
}
} else {
bay->last_value = id;
bay->value_count = 0;
}
}
#ifdef CONFIG_BLK_DEV_IDE_PMAC
int check_media_bay(struct device_node *which_bay, int what)
{
int i;
for (i=0; i<media_bay_count; i++)
if (media_bays[i].mdev && which_bay == media_bays[i].mdev->ofdev.node) {
if ((what == media_bays[i].content_id) && media_bays[i].state == mb_up)
return 0;
media_bays[i].cd_index = -1;
return -EINVAL;
}
return -ENODEV;
}
EXPORT_SYMBOL(check_media_bay);
int check_media_bay_by_base(unsigned long base, int what)
{
int i;
for (i=0; i<media_bay_count; i++)
if (media_bays[i].mdev && base == (unsigned long) media_bays[i].cd_base) {
if ((what == media_bays[i].content_id) && media_bays[i].state == mb_up)
return 0;
media_bays[i].cd_index = -1;
return -EINVAL;
}
return -ENODEV;
}
EXPORT_SYMBOL_GPL(check_media_bay_by_base);
int media_bay_set_ide_infos(struct device_node* which_bay, unsigned long base,
int irq, ide_hwif_t *hwif)
{
int i;
for (i=0; i<media_bay_count; i++) {
struct media_bay_info* bay = &media_bays[i];
if (bay->mdev && which_bay == bay->mdev->ofdev.node) {
int timeout = 5000, index = hwif->index;
mutex_lock(&bay->lock);
bay->cd_port = hwif;
bay->cd_base = (void __iomem *) base;
bay->cd_irq = irq;
if ((MB_CD != bay->content_id) || bay->state != mb_up) {
mutex_unlock(&bay->lock);
return 0;
}
printk(KERN_DEBUG "Registered ide%d for media bay %d\n", index, i);
do {
if (MB_IDE_READY(i)) {
bay->cd_index = index;
mutex_unlock(&bay->lock);
return 0;
}
mdelay(1);
} while(--timeout);
printk(KERN_DEBUG "Timeount waiting IDE in bay %d\n", i);
mutex_unlock(&bay->lock);
return -ENODEV;
}
}
return -ENODEV;
}
EXPORT_SYMBOL_GPL(media_bay_set_ide_infos);
#endif /* CONFIG_BLK_DEV_IDE_PMAC */
static void media_bay_step(int i)
{
struct media_bay_info* bay = &media_bays[i];
/* We don't poll when powering down */
if (bay->state != mb_powering_down)
poll_media_bay(bay);
/* If timer expired or polling IDE busy, run state machine */
if ((bay->state != mb_ide_waiting) && (bay->timer != 0)) {
bay->timer -= msecs_to_jiffies(MB_POLL_DELAY);
if (bay->timer > 0)
return;
bay->timer = 0;
}
switch(bay->state) {
case mb_powering_up:
if (bay->ops->setup_bus(bay, bay->last_value) < 0) {
MBDBG("mediabay%d: device not supported (kind:%d)\n", i, bay->content_id);
set_mb_power(bay, 0);
break;
}
bay->timer = msecs_to_jiffies(MB_RESET_DELAY);
bay->state = mb_enabling_bay;
MBDBG("mediabay%d: enabling (kind:%d)\n", i, bay->content_id);
break;
case mb_enabling_bay:
bay->ops->un_reset(bay);
bay->timer = msecs_to_jiffies(MB_SETUP_DELAY);
bay->state = mb_resetting;
MBDBG("mediabay%d: waiting reset (kind:%d)\n", i, bay->content_id);
break;
case mb_resetting:
if (bay->content_id != MB_CD) {
MBDBG("mediabay%d: bay is up (kind:%d)\n", i, bay->content_id);
bay->state = mb_up;
break;
}
#ifdef CONFIG_BLK_DEV_IDE_PMAC
MBDBG("mediabay%d: waiting IDE reset (kind:%d)\n", i, bay->content_id);
bay->ops->un_reset_ide(bay);
bay->timer = msecs_to_jiffies(MB_IDE_WAIT);
bay->state = mb_ide_resetting;
#else
printk(KERN_DEBUG "media-bay %d is ide (not compiled in kernel)\n", i);
set_mb_power(bay, 0);
#endif /* CONFIG_BLK_DEV_IDE_PMAC */
break;
#ifdef CONFIG_BLK_DEV_IDE_PMAC
case mb_ide_resetting:
bay->timer = msecs_to_jiffies(MB_IDE_TIMEOUT);
bay->state = mb_ide_waiting;
MBDBG("mediabay%d: waiting IDE ready (kind:%d)\n", i, bay->content_id);
break;
case mb_ide_waiting:
if (bay->cd_base == NULL) {
bay->timer = 0;
bay->state = mb_up;
MBDBG("mediabay%d: up before IDE init\n", i);
break;
} else if (MB_IDE_READY(i)) {
bay->timer = 0;
bay->state = mb_up;
if (bay->cd_index < 0) {
printk("mediabay %d, registering IDE...\n", i);
pmu_suspend();
ide_port_scan(bay->cd_port);
if (bay->cd_port->present)
bay->cd_index = bay->cd_port->index;
pmu_resume();
}
if (bay->cd_index == -1) {
/* We eventually do a retry */
bay->cd_retry++;
printk("IDE register error\n");
set_mb_power(bay, 0);
} else {
printk(KERN_DEBUG "media-bay %d is ide%d\n", i, bay->cd_index);
MBDBG("mediabay %d IDE ready\n", i);
}
break;
} else if (bay->timer > 0)
bay->timer -= msecs_to_jiffies(MB_POLL_DELAY);
if (bay->timer <= 0) {
printk("\nIDE Timeout in bay %d !, IDE state is: 0x%02x\n",
i, readb(bay->cd_base + 0x70));
MBDBG("mediabay%d: nIDE Timeout !\n", i);
set_mb_power(bay, 0);
bay->timer = 0;
}
break;
#endif /* CONFIG_BLK_DEV_IDE_PMAC */
case mb_powering_down:
bay->state = mb_empty;
#ifdef CONFIG_BLK_DEV_IDE_PMAC
if (bay->cd_index >= 0) {
printk(KERN_DEBUG "Unregistering mb %d ide, index:%d\n", i,
bay->cd_index);
ide_port_unregister_devices(bay->cd_port);
bay->cd_index = -1;
}
if (bay->cd_retry) {
if (bay->cd_retry > MAX_CD_RETRIES) {
/* Should add an error sound (sort of beep in dmasound) */
printk("\nmedia-bay %d, IDE device badly inserted or unrecognised\n", i);
} else {
/* Force a new power down/up sequence */
bay->content_id = MB_NO;
}
}
#endif /* CONFIG_BLK_DEV_IDE_PMAC */
MBDBG("mediabay%d: end of power down\n", i);
break;
}
}
/*
* This procedure runs as a kernel thread to poll the media bay
* once each tick and register and unregister the IDE interface
* with the IDE driver. It needs to be a thread because
* ide_register can't be called from interrupt context.
*/
static int media_bay_task(void *x)
{
int i;
while (!kthread_should_stop()) {
for (i = 0; i < media_bay_count; ++i) {
mutex_lock(&media_bays[i].lock);
if (!media_bays[i].sleeping)
media_bay_step(i);
mutex_unlock(&media_bays[i].lock);
}
msleep_interruptible(MB_POLL_DELAY);
}
return 0;
}
static int __devinit media_bay_attach(struct macio_dev *mdev, const struct of_device_id *match)
{
struct media_bay_info* bay;
u32 __iomem *regbase;
struct device_node *ofnode;
unsigned long base;
int i;
ofnode = mdev->ofdev.node;
if (macio_resource_count(mdev) < 1)
return -ENODEV;
if (macio_request_resources(mdev, "media-bay"))
return -EBUSY;
/* Media bay registers are located at the beginning of the
* mac-io chip, for now, we trick and align down the first
* resource passed in
*/
base = macio_resource_start(mdev, 0) & 0xffff0000u;
regbase = (u32 __iomem *)ioremap(base, 0x100);
if (regbase == NULL) {
macio_release_resources(mdev);
return -ENOMEM;
}
i = media_bay_count++;
bay = &media_bays[i];
bay->mdev = mdev;
bay->base = regbase;
bay->index = i;
bay->ops = match->data;
bay->sleeping = 0;
mutex_init(&bay->lock);
/* Init HW probing */
if (bay->ops->init)
bay->ops->init(bay);
printk(KERN_INFO "mediabay%d: Registered %s media-bay\n", i, bay->ops->name);
/* Force an immediate detect */
set_mb_power(bay, 0);
msleep(MB_POWER_DELAY);
bay->content_id = MB_NO;
bay->last_value = bay->ops->content(bay);
bay->value_count = msecs_to_jiffies(MB_STABLE_DELAY);
bay->state = mb_empty;
do {
msleep(MB_POLL_DELAY);
media_bay_step(i);
} while((bay->state != mb_empty) &&
(bay->state != mb_up));
/* Mark us ready by filling our mdev data */
macio_set_drvdata(mdev, bay);
/* Startup kernel thread */
if (i == 0)
kthread_run(media_bay_task, NULL, "media-bay");
return 0;
}
static int media_bay_suspend(struct macio_dev *mdev, pm_message_t state)
{
struct media_bay_info *bay = macio_get_drvdata(mdev);
if (state.event != mdev->ofdev.dev.power.power_state.event
&& (state.event & PM_EVENT_SLEEP)) {
mutex_lock(&bay->lock);
bay->sleeping = 1;
set_mb_power(bay, 0);
mutex_unlock(&bay->lock);
msleep(MB_POLL_DELAY);
mdev->ofdev.dev.power.power_state = state;
}
return 0;
}
static int media_bay_resume(struct macio_dev *mdev)
{
struct media_bay_info *bay = macio_get_drvdata(mdev);
if (mdev->ofdev.dev.power.power_state.event != PM_EVENT_ON) {
mdev->ofdev.dev.power.power_state = PMSG_ON;
/* We re-enable the bay using it's previous content
only if it did not change. Note those bozo timings,
they seem to help the 3400 get it right.
*/
/* Force MB power to 0 */
mutex_lock(&bay->lock);
set_mb_power(bay, 0);
msleep(MB_POWER_DELAY);
if (bay->ops->content(bay) != bay->content_id) {
printk("mediabay%d: content changed during sleep...\n", bay->index);
mutex_unlock(&bay->lock);
return 0;
}
set_mb_power(bay, 1);
bay->last_value = bay->content_id;
bay->value_count = msecs_to_jiffies(MB_STABLE_DELAY);
bay->timer = msecs_to_jiffies(MB_POWER_DELAY);
#ifdef CONFIG_BLK_DEV_IDE_PMAC
bay->cd_retry = 0;
#endif
do {
msleep(MB_POLL_DELAY);
media_bay_step(bay->index);
} while((bay->state != mb_empty) &&
(bay->state != mb_up));
bay->sleeping = 0;
mutex_unlock(&bay->lock);
}
return 0;
}
/* Definitions of "ops" structures.
*/
static struct mb_ops ohare_mb_ops = {
.name = "Ohare",
.content = ohare_mb_content,
.power = ohare_mb_power,
.setup_bus = ohare_mb_setup_bus,
.un_reset = ohare_mb_un_reset,
.un_reset_ide = ohare_mb_un_reset_ide,
};
static struct mb_ops heathrow_mb_ops = {
.name = "Heathrow",
.content = heathrow_mb_content,
.power = heathrow_mb_power,
.setup_bus = heathrow_mb_setup_bus,
.un_reset = heathrow_mb_un_reset,
.un_reset_ide = heathrow_mb_un_reset_ide,
};
static struct mb_ops keylargo_mb_ops = {
.name = "KeyLargo",
.init = keylargo_mb_init,
.content = keylargo_mb_content,
.power = keylargo_mb_power,
.setup_bus = keylargo_mb_setup_bus,
.un_reset = keylargo_mb_un_reset,
.un_reset_ide = keylargo_mb_un_reset_ide,
};
/*
* It seems that the bit for the media-bay interrupt in the IRQ_LEVEL
* register is always set when there is something in the media bay.
* This causes problems for the interrupt code if we attach an interrupt
* handler to the media-bay interrupt, because it tends to go into
* an infinite loop calling the media bay interrupt handler.
* Therefore we do it all by polling the media bay once each tick.
*/
static struct of_device_id media_bay_match[] =
{
{
.name = "media-bay",
.compatible = "keylargo-media-bay",
.data = &keylargo_mb_ops,
},
{
.name = "media-bay",
.compatible = "heathrow-media-bay",
.data = &heathrow_mb_ops,
},
{
.name = "media-bay",
.compatible = "ohare-media-bay",
.data = &ohare_mb_ops,
},
{},
};
static struct macio_driver media_bay_driver =
{
.name = "media-bay",
.match_table = media_bay_match,
.probe = media_bay_attach,
.suspend = media_bay_suspend,
.resume = media_bay_resume
};
static int __init media_bay_init(void)
{
int i;
for (i=0; i<MAX_BAYS; i++) {
memset((char *)&media_bays[i], 0, sizeof(struct media_bay_info));
media_bays[i].content_id = -1;
#ifdef CONFIG_BLK_DEV_IDE_PMAC
media_bays[i].cd_index = -1;
#endif
}
if (!machine_is(powermac))
return 0;
macio_register_driver(&media_bay_driver);
return 0;
}
device_initcall(media_bay_init);

View File

@@ -0,0 +1,131 @@
/*
* /dev/nvram driver for Power Macintosh.
*/
#define NVRAM_VERSION "1.0"
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/fcntl.h>
#include <linux/nvram.h>
#include <linux/init.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
#include <asm/nvram.h>
#define NVRAM_SIZE 8192
static loff_t nvram_llseek(struct file *file, loff_t offset, int origin)
{
lock_kernel();
switch (origin) {
case 1:
offset += file->f_pos;
break;
case 2:
offset += NVRAM_SIZE;
break;
}
if (offset < 0) {
unlock_kernel();
return -EINVAL;
}
file->f_pos = offset;
unlock_kernel();
return file->f_pos;
}
static ssize_t read_nvram(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
unsigned int i;
char __user *p = buf;
if (!access_ok(VERIFY_WRITE, buf, count))
return -EFAULT;
if (*ppos >= NVRAM_SIZE)
return 0;
for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count)
if (__put_user(nvram_read_byte(i), p))
return -EFAULT;
*ppos = i;
return p - buf;
}
static ssize_t write_nvram(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
unsigned int i;
const char __user *p = buf;
char c;
if (!access_ok(VERIFY_READ, buf, count))
return -EFAULT;
if (*ppos >= NVRAM_SIZE)
return 0;
for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count) {
if (__get_user(c, p))
return -EFAULT;
nvram_write_byte(c, i);
}
*ppos = i;
return p - buf;
}
static int nvram_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case PMAC_NVRAM_GET_OFFSET:
{
int part, offset;
if (copy_from_user(&part, (void __user*)arg, sizeof(part)) != 0)
return -EFAULT;
if (part < pmac_nvram_OF || part > pmac_nvram_NR)
return -EINVAL;
offset = pmac_get_partition(part);
if (copy_to_user((void __user*)arg, &offset, sizeof(offset)) != 0)
return -EFAULT;
break;
}
default:
return -EINVAL;
}
return 0;
}
const struct file_operations nvram_fops = {
.owner = THIS_MODULE,
.llseek = nvram_llseek,
.read = read_nvram,
.write = write_nvram,
.ioctl = nvram_ioctl,
};
static struct miscdevice nvram_dev = {
NVRAM_MINOR,
"nvram",
&nvram_fops
};
int __init nvram_init(void)
{
printk(KERN_INFO "Macintosh non-volatile memory driver v%s\n",
NVRAM_VERSION);
return misc_register(&nvram_dev);
}
void __exit nvram_cleanup(void)
{
misc_deregister( &nvram_dev );
}
module_init(nvram_init);
module_exit(nvram_cleanup);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,615 @@
/*
* RackMac vu-meter driver
*
* (c) Copyright 2006 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* Support the CPU-meter LEDs of the Xserve G5
*
* TODO: Implement PWM to do variable intensity and provide userland
* interface for fun. Also, the CPU-meter could be made nicer by being
* a bit less "immediate" but giving instead a more average load over
* time. Patches welcome :-)
*
*/
#undef DEBUG
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/kernel_stat.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <asm/dbdma.h>
#include <asm/macio.h>
#include <asm/keylargo.h>
/* Number of samples in a sample buffer */
#define SAMPLE_COUNT 256
/* CPU meter sampling rate in ms */
#define CPU_SAMPLING_RATE 250
struct rackmeter_dma {
struct dbdma_cmd cmd[4] ____cacheline_aligned;
u32 mark ____cacheline_aligned;
u32 buf1[SAMPLE_COUNT] ____cacheline_aligned;
u32 buf2[SAMPLE_COUNT] ____cacheline_aligned;
} ____cacheline_aligned;
struct rackmeter_cpu {
struct delayed_work sniffer;
struct rackmeter *rm;
cputime64_t prev_wall;
cputime64_t prev_idle;
int zero;
} ____cacheline_aligned;
struct rackmeter {
struct macio_dev *mdev;
unsigned int irq;
struct device_node *i2s;
u8 *ubuf;
struct dbdma_regs __iomem *dma_regs;
void __iomem *i2s_regs;
dma_addr_t dma_buf_p;
struct rackmeter_dma *dma_buf_v;
int stale_irq;
struct rackmeter_cpu cpu[2];
int paused;
struct mutex sem;
};
/* To be set as a tunable */
static int rackmeter_ignore_nice;
/* This GPIO is whacked by the OS X driver when initializing */
#define RACKMETER_MAGIC_GPIO 0x78
/* This is copied from cpufreq_ondemand, maybe we should put it in
* a common header somewhere
*/
static inline cputime64_t get_cpu_idle_time(unsigned int cpu)
{
cputime64_t retval;
retval = cputime64_add(kstat_cpu(cpu).cpustat.idle,
kstat_cpu(cpu).cpustat.iowait);
if (rackmeter_ignore_nice)
retval = cputime64_add(retval, kstat_cpu(cpu).cpustat.nice);
return retval;
}
static void rackmeter_setup_i2s(struct rackmeter *rm)
{
struct macio_chip *macio = rm->mdev->bus->chip;
/* First whack magic GPIO */
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, RACKMETER_MAGIC_GPIO, 5);
/* Call feature code to enable the sound channel and the proper
* clock sources
*/
pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, rm->i2s, 0, 1);
/* Power i2s and stop i2s clock. We whack MacIO FCRs directly for now.
* This is a bit racy, thus we should add new platform functions to
* handle that. snd-aoa needs that too
*/
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE);
MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT);
(void)MACIO_IN32(KEYLARGO_FCR1);
udelay(10);
/* Then setup i2s. For now, we use the same magic value that
* the OS X driver seems to use. We might want to play around
* with the clock divisors later
*/
out_le32(rm->i2s_regs + 0x10, 0x01fa0000);
(void)in_le32(rm->i2s_regs + 0x10);
udelay(10);
/* Fully restart i2s*/
MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE |
KL1_I2S0_CLK_ENABLE_BIT);
(void)MACIO_IN32(KEYLARGO_FCR1);
udelay(10);
}
static void rackmeter_set_default_pattern(struct rackmeter *rm)
{
int i;
for (i = 0; i < 16; i++) {
if (i < 8)
rm->ubuf[i] = (i & 1) * 255;
else
rm->ubuf[i] = ((~i) & 1) * 255;
}
}
static void rackmeter_do_pause(struct rackmeter *rm, int pause)
{
struct rackmeter_dma *rdma = rm->dma_buf_v;
pr_debug("rackmeter: %s\n", pause ? "paused" : "started");
rm->paused = pause;
if (pause) {
DBDMA_DO_STOP(rm->dma_regs);
return;
}
memset(rdma->buf1, 0, SAMPLE_COUNT & sizeof(u32));
memset(rdma->buf2, 0, SAMPLE_COUNT & sizeof(u32));
rm->dma_buf_v->mark = 0;
mb();
out_le32(&rm->dma_regs->cmdptr_hi, 0);
out_le32(&rm->dma_regs->cmdptr, rm->dma_buf_p);
out_le32(&rm->dma_regs->control, (RUN << 16) | RUN);
}
static void rackmeter_setup_dbdma(struct rackmeter *rm)
{
struct rackmeter_dma *db = rm->dma_buf_v;
struct dbdma_cmd *cmd = db->cmd;
/* Make sure dbdma is reset */
DBDMA_DO_RESET(rm->dma_regs);
pr_debug("rackmeter: mark offset=0x%zx\n",
offsetof(struct rackmeter_dma, mark));
pr_debug("rackmeter: buf1 offset=0x%zx\n",
offsetof(struct rackmeter_dma, buf1));
pr_debug("rackmeter: buf2 offset=0x%zx\n",
offsetof(struct rackmeter_dma, buf2));
/* Prepare 4 dbdma commands for the 2 buffers */
memset(cmd, 0, 4 * sizeof(struct dbdma_cmd));
st_le16(&cmd->req_count, 4);
st_le16(&cmd->command, STORE_WORD | INTR_ALWAYS | KEY_SYSTEM);
st_le32(&cmd->phy_addr, rm->dma_buf_p +
offsetof(struct rackmeter_dma, mark));
st_le32(&cmd->cmd_dep, 0x02000000);
cmd++;
st_le16(&cmd->req_count, SAMPLE_COUNT * 4);
st_le16(&cmd->command, OUTPUT_MORE);
st_le32(&cmd->phy_addr, rm->dma_buf_p +
offsetof(struct rackmeter_dma, buf1));
cmd++;
st_le16(&cmd->req_count, 4);
st_le16(&cmd->command, STORE_WORD | INTR_ALWAYS | KEY_SYSTEM);
st_le32(&cmd->phy_addr, rm->dma_buf_p +
offsetof(struct rackmeter_dma, mark));
st_le32(&cmd->cmd_dep, 0x01000000);
cmd++;
st_le16(&cmd->req_count, SAMPLE_COUNT * 4);
st_le16(&cmd->command, OUTPUT_MORE | BR_ALWAYS);
st_le32(&cmd->phy_addr, rm->dma_buf_p +
offsetof(struct rackmeter_dma, buf2));
st_le32(&cmd->cmd_dep, rm->dma_buf_p);
rackmeter_do_pause(rm, 0);
}
static void rackmeter_do_timer(struct work_struct *work)
{
struct rackmeter_cpu *rcpu =
container_of(work, struct rackmeter_cpu, sniffer.work);
struct rackmeter *rm = rcpu->rm;
unsigned int cpu = smp_processor_id();
cputime64_t cur_jiffies, total_idle_ticks;
unsigned int total_ticks, idle_ticks;
int i, offset, load, cumm, pause;
cur_jiffies = jiffies64_to_cputime64(get_jiffies_64());
total_ticks = (unsigned int)cputime64_sub(cur_jiffies,
rcpu->prev_wall);
rcpu->prev_wall = cur_jiffies;
total_idle_ticks = get_cpu_idle_time(cpu);
idle_ticks = (unsigned int) cputime64_sub(total_idle_ticks,
rcpu->prev_idle);
rcpu->prev_idle = total_idle_ticks;
/* We do a very dumb calculation to update the LEDs for now,
* we'll do better once we have actual PWM implemented
*/
load = (9 * (total_ticks - idle_ticks)) / total_ticks;
offset = cpu << 3;
cumm = 0;
for (i = 0; i < 8; i++) {
u8 ub = (load > i) ? 0xff : 0;
rm->ubuf[i + offset] = ub;
cumm |= ub;
}
rcpu->zero = (cumm == 0);
/* Now check if LEDs are all 0, we can stop DMA */
pause = (rm->cpu[0].zero && rm->cpu[1].zero);
if (pause != rm->paused) {
mutex_lock(&rm->sem);
pause = (rm->cpu[0].zero && rm->cpu[1].zero);
rackmeter_do_pause(rm, pause);
mutex_unlock(&rm->sem);
}
schedule_delayed_work_on(cpu, &rcpu->sniffer,
msecs_to_jiffies(CPU_SAMPLING_RATE));
}
static void __devinit rackmeter_init_cpu_sniffer(struct rackmeter *rm)
{
unsigned int cpu;
/* This driver works only with 1 or 2 CPUs numbered 0 and 1,
* but that's really all we have on Apple Xserve. It doesn't
* play very nice with CPU hotplug neither but we don't do that
* on those machines yet
*/
rm->cpu[0].rm = rm;
INIT_DELAYED_WORK(&rm->cpu[0].sniffer, rackmeter_do_timer);
rm->cpu[1].rm = rm;
INIT_DELAYED_WORK(&rm->cpu[1].sniffer, rackmeter_do_timer);
for_each_online_cpu(cpu) {
struct rackmeter_cpu *rcpu;
if (cpu > 1)
continue;
rcpu = &rm->cpu[cpu];
rcpu->prev_idle = get_cpu_idle_time(cpu);
rcpu->prev_wall = jiffies64_to_cputime64(get_jiffies_64());
schedule_delayed_work_on(cpu, &rm->cpu[cpu].sniffer,
msecs_to_jiffies(CPU_SAMPLING_RATE));
}
}
static void __devexit rackmeter_stop_cpu_sniffer(struct rackmeter *rm)
{
cancel_rearming_delayed_work(&rm->cpu[0].sniffer);
cancel_rearming_delayed_work(&rm->cpu[1].sniffer);
}
static int __devinit rackmeter_setup(struct rackmeter *rm)
{
pr_debug("rackmeter: setting up i2s..\n");
rackmeter_setup_i2s(rm);
pr_debug("rackmeter: setting up default pattern..\n");
rackmeter_set_default_pattern(rm);
pr_debug("rackmeter: setting up dbdma..\n");
rackmeter_setup_dbdma(rm);
pr_debug("rackmeter: start CPU measurements..\n");
rackmeter_init_cpu_sniffer(rm);
printk(KERN_INFO "RackMeter initialized\n");
return 0;
}
/* XXX FIXME: No PWM yet, this is 0/1 */
static u32 rackmeter_calc_sample(struct rackmeter *rm, unsigned int index)
{
int led;
u32 sample = 0;
for (led = 0; led < 16; led++) {
sample >>= 1;
sample |= ((rm->ubuf[led] >= 0x80) << 15);
}
return (sample << 17) | (sample >> 15);
}
static irqreturn_t rackmeter_irq(int irq, void *arg)
{
struct rackmeter *rm = arg;
struct rackmeter_dma *db = rm->dma_buf_v;
unsigned int mark, i;
u32 *buf;
/* Flush PCI buffers with an MMIO read. Maybe we could actually
* check the status one day ... in case things go wrong, though
* this never happened to me
*/
(void)in_le32(&rm->dma_regs->status);
/* Make sure the CPU gets us in order */
rmb();
/* Read mark */
mark = db->mark;
if (mark != 1 && mark != 2) {
printk(KERN_WARNING "rackmeter: Incorrect DMA mark 0x%08x\n",
mark);
/* We allow for 3 errors like that (stale DBDMA irqs) */
if (++rm->stale_irq > 3) {
printk(KERN_ERR "rackmeter: Too many errors,"
" stopping DMA\n");
DBDMA_DO_RESET(rm->dma_regs);
}
return IRQ_HANDLED;
}
/* Next buffer we need to fill is mark value */
buf = mark == 1 ? db->buf1 : db->buf2;
/* Fill it now. This routine converts the 8 bits depth sample array
* into the PWM bitmap for each LED.
*/
for (i = 0; i < SAMPLE_COUNT; i++)
buf[i] = rackmeter_calc_sample(rm, i);
return IRQ_HANDLED;
}
static int __devinit rackmeter_probe(struct macio_dev* mdev,
const struct of_device_id *match)
{
struct device_node *i2s = NULL, *np = NULL;
struct rackmeter *rm = NULL;
struct resource ri2s, rdma;
int rc = -ENODEV;
pr_debug("rackmeter_probe()\n");
/* Get i2s-a node */
while ((i2s = of_get_next_child(mdev->ofdev.node, i2s)) != NULL)
if (strcmp(i2s->name, "i2s-a") == 0)
break;
if (i2s == NULL) {
pr_debug(" i2s-a child not found\n");
goto bail;
}
/* Get lightshow or virtual sound */
while ((np = of_get_next_child(i2s, np)) != NULL) {
if (strcmp(np->name, "lightshow") == 0)
break;
if ((strcmp(np->name, "sound") == 0) &&
of_get_property(np, "virtual", NULL) != NULL)
break;
}
if (np == NULL) {
pr_debug(" lightshow or sound+virtual child not found\n");
goto bail;
}
/* Create and initialize our instance data */
rm = kzalloc(sizeof(struct rackmeter), GFP_KERNEL);
if (rm == NULL) {
printk(KERN_ERR "rackmeter: failed to allocate memory !\n");
rc = -ENOMEM;
goto bail_release;
}
rm->mdev = mdev;
rm->i2s = i2s;
mutex_init(&rm->sem);
dev_set_drvdata(&mdev->ofdev.dev, rm);
/* Check resources availability. We need at least resource 0 and 1 */
#if 0 /* Use that when i2s-a is finally an mdev per-se */
if (macio_resource_count(mdev) < 2 || macio_irq_count(mdev) < 2) {
printk(KERN_ERR
"rackmeter: found match but lacks resources: %s"
" (%d resources, %d interrupts)\n",
mdev->ofdev.node->full_name);
rc = -ENXIO;
goto bail_free;
}
if (macio_request_resources(mdev, "rackmeter")) {
printk(KERN_ERR
"rackmeter: failed to request resources: %s\n",
mdev->ofdev.node->full_name);
rc = -EBUSY;
goto bail_free;
}
rm->irq = macio_irq(mdev, 1);
#else
rm->irq = irq_of_parse_and_map(i2s, 1);
if (rm->irq == NO_IRQ ||
of_address_to_resource(i2s, 0, &ri2s) ||
of_address_to_resource(i2s, 1, &rdma)) {
printk(KERN_ERR
"rackmeter: found match but lacks resources: %s",
mdev->ofdev.node->full_name);
rc = -ENXIO;
goto bail_free;
}
#endif
pr_debug(" i2s @0x%08x\n", (unsigned int)ri2s.start);
pr_debug(" dma @0x%08x\n", (unsigned int)rdma.start);
pr_debug(" irq %d\n", rm->irq);
rm->ubuf = (u8 *)__get_free_page(GFP_KERNEL);
if (rm->ubuf == NULL) {
printk(KERN_ERR
"rackmeter: failed to allocate samples page !\n");
rc = -ENOMEM;
goto bail_release;
}
rm->dma_buf_v = dma_alloc_coherent(&macio_get_pci_dev(mdev)->dev,
sizeof(struct rackmeter_dma),
&rm->dma_buf_p, GFP_KERNEL);
if (rm->dma_buf_v == NULL) {
printk(KERN_ERR
"rackmeter: failed to allocate dma buffer !\n");
rc = -ENOMEM;
goto bail_free_samples;
}
#if 0
rm->i2s_regs = ioremap(macio_resource_start(mdev, 0), 0x1000);
#else
rm->i2s_regs = ioremap(ri2s.start, 0x1000);
#endif
if (rm->i2s_regs == NULL) {
printk(KERN_ERR
"rackmeter: failed to map i2s registers !\n");
rc = -ENXIO;
goto bail_free_dma;
}
#if 0
rm->dma_regs = ioremap(macio_resource_start(mdev, 1), 0x100);
#else
rm->dma_regs = ioremap(rdma.start, 0x100);
#endif
if (rm->dma_regs == NULL) {
printk(KERN_ERR
"rackmeter: failed to map dma registers !\n");
rc = -ENXIO;
goto bail_unmap_i2s;
}
rc = rackmeter_setup(rm);
if (rc) {
printk(KERN_ERR
"rackmeter: failed to initialize !\n");
rc = -ENXIO;
goto bail_unmap_dma;
}
rc = request_irq(rm->irq, rackmeter_irq, 0, "rackmeter", rm);
if (rc != 0) {
printk(KERN_ERR
"rackmeter: failed to request interrupt !\n");
goto bail_stop_dma;
}
of_node_put(np);
return 0;
bail_stop_dma:
DBDMA_DO_RESET(rm->dma_regs);
bail_unmap_dma:
iounmap(rm->dma_regs);
bail_unmap_i2s:
iounmap(rm->i2s_regs);
bail_free_dma:
dma_free_coherent(&macio_get_pci_dev(mdev)->dev,
sizeof(struct rackmeter_dma),
rm->dma_buf_v, rm->dma_buf_p);
bail_free_samples:
free_page((unsigned long)rm->ubuf);
bail_release:
#if 0
macio_release_resources(mdev);
#endif
bail_free:
kfree(rm);
bail:
of_node_put(i2s);
of_node_put(np);
dev_set_drvdata(&mdev->ofdev.dev, NULL);
return rc;
}
static int __devexit rackmeter_remove(struct macio_dev* mdev)
{
struct rackmeter *rm = dev_get_drvdata(&mdev->ofdev.dev);
/* Stop CPU sniffer timer & work queues */
rackmeter_stop_cpu_sniffer(rm);
/* Clear reference to private data */
dev_set_drvdata(&mdev->ofdev.dev, NULL);
/* Stop/reset dbdma */
DBDMA_DO_RESET(rm->dma_regs);
/* Release the IRQ */
free_irq(rm->irq, rm);
/* Unmap registers */
iounmap(rm->dma_regs);
iounmap(rm->i2s_regs);
/* Free DMA */
dma_free_coherent(&macio_get_pci_dev(mdev)->dev,
sizeof(struct rackmeter_dma),
rm->dma_buf_v, rm->dma_buf_p);
/* Free samples */
free_page((unsigned long)rm->ubuf);
#if 0
/* Release resources */
macio_release_resources(mdev);
#endif
/* Get rid of me */
kfree(rm);
return 0;
}
static int rackmeter_shutdown(struct macio_dev* mdev)
{
struct rackmeter *rm = dev_get_drvdata(&mdev->ofdev.dev);
if (rm == NULL)
return -ENODEV;
/* Stop CPU sniffer timer & work queues */
rackmeter_stop_cpu_sniffer(rm);
/* Stop/reset dbdma */
DBDMA_DO_RESET(rm->dma_regs);
return 0;
}
static struct of_device_id rackmeter_match[] = {
{ .name = "i2s" },
{ }
};
static struct macio_driver rackmeter_driver = {
.name = "rackmeter",
.owner = THIS_MODULE,
.match_table = rackmeter_match,
.probe = rackmeter_probe,
.remove = __devexit_p(rackmeter_remove),
.shutdown = rackmeter_shutdown,
};
static int __init rackmeter_init(void)
{
pr_debug("rackmeter_init()\n");
return macio_register_driver(&rackmeter_driver);
}
static void __exit rackmeter_exit(void)
{
pr_debug("rackmeter_exit()\n");
macio_unregister_driver(&rackmeter_driver);
}
module_init(rackmeter_init);
module_exit(rackmeter_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("RackMeter: Support vu-meter on XServe front panel");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,696 @@
/*
* Device driver for the i2c thermostat found on the iBook G4, Albook G4
*
* Copyright (C) 2003, 2004 Colin Leroy, Rasmus Rohde, Benjamin Herrenschmidt
*
* Documentation from
* http://www.analog.com/UploadedFiles/Data_Sheets/115254175ADT7467_pra.pdf
* http://www.analog.com/UploadedFiles/Data_Sheets/3686221171167ADT7460_b.pdf
*
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/suspend.h>
#include <linux/kthread.h>
#include <linux/moduleparam.h>
#include <linux/freezer.h>
#include <linux/of_platform.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#undef DEBUG
#define CONFIG_REG 0x40
#define MANUAL_MASK 0xe0
#define AUTO_MASK 0x20
#define INVERT_MASK 0x10
static u8 TEMP_REG[3] = {0x26, 0x25, 0x27}; /* local, sensor1, sensor2 */
static u8 LIMIT_REG[3] = {0x6b, 0x6a, 0x6c}; /* local, sensor1, sensor2 */
static u8 MANUAL_MODE[2] = {0x5c, 0x5d};
static u8 REM_CONTROL[2] = {0x00, 0x40};
static u8 FAN_SPEED[2] = {0x28, 0x2a};
static u8 FAN_SPD_SET[2] = {0x30, 0x31};
static u8 default_limits_local[3] = {70, 50, 70}; /* local, sensor1, sensor2 */
static u8 default_limits_chip[3] = {80, 65, 80}; /* local, sensor1, sensor2 */
static const char *sensor_location[3];
static int limit_adjust;
static int fan_speed = -1;
static int verbose;
MODULE_AUTHOR("Colin Leroy <colin@colino.net>");
MODULE_DESCRIPTION("Driver for ADT746x thermostat in iBook G4 and "
"Powerbook G4 Alu");
MODULE_LICENSE("GPL");
module_param(limit_adjust, int, 0644);
MODULE_PARM_DESC(limit_adjust,"Adjust maximum temperatures (50 sensor1, 70 sensor2) "
"by N degrees.");
module_param(fan_speed, int, 0644);
MODULE_PARM_DESC(fan_speed,"Specify starting fan speed (0-255) "
"(default 64)");
module_param(verbose, bool, 0);
MODULE_PARM_DESC(verbose,"Verbose log operations "
"(default 0)");
struct thermostat {
struct i2c_client *clt;
u8 temps[3];
u8 cached_temp[3];
u8 initial_limits[3];
u8 limits[3];
int last_speed[2];
int last_var[2];
int pwm_inv[2];
};
static enum {ADT7460, ADT7467} therm_type;
static int therm_bus, therm_address;
static struct of_device * of_dev;
static struct thermostat* thermostat;
static struct task_struct *thread_therm = NULL;
static void write_both_fan_speed(struct thermostat *th, int speed);
static void write_fan_speed(struct thermostat *th, int speed, int fan);
static void thermostat_create_files(void);
static void thermostat_remove_files(void);
static int
write_reg(struct thermostat* th, int reg, u8 data)
{
u8 tmp[2];
int rc;
tmp[0] = reg;
tmp[1] = data;
rc = i2c_master_send(th->clt, (const char *)tmp, 2);
if (rc < 0)
return rc;
if (rc != 2)
return -ENODEV;
return 0;
}
static int
read_reg(struct thermostat* th, int reg)
{
u8 reg_addr, data;
int rc;
reg_addr = (u8)reg;
rc = i2c_master_send(th->clt, &reg_addr, 1);
if (rc < 0)
return rc;
if (rc != 1)
return -ENODEV;
rc = i2c_master_recv(th->clt, (char *)&data, 1);
if (rc < 0)
return rc;
return data;
}
static struct i2c_driver thermostat_driver;
static int
attach_thermostat(struct i2c_adapter *adapter)
{
unsigned long bus_no;
struct i2c_board_info info;
struct i2c_client *client;
if (strncmp(adapter->name, "uni-n", 5))
return -ENODEV;
bus_no = simple_strtoul(adapter->name + 6, NULL, 10);
if (bus_no != therm_bus)
return -ENODEV;
memset(&info, 0, sizeof(struct i2c_board_info));
strlcpy(info.type, "therm_adt746x", I2C_NAME_SIZE);
info.addr = therm_address;
client = i2c_new_device(adapter, &info);
if (!client)
return -ENODEV;
/*
* Let i2c-core delete that device on driver removal.
* This is safe because i2c-core holds the core_lock mutex for us.
*/
list_add_tail(&client->detected, &thermostat_driver.clients);
return 0;
}
static int
remove_thermostat(struct i2c_client *client)
{
struct thermostat *th = i2c_get_clientdata(client);
int i;
thermostat_remove_files();
if (thread_therm != NULL) {
kthread_stop(thread_therm);
}
printk(KERN_INFO "adt746x: Putting max temperatures back from "
"%d, %d, %d to %d, %d, %d\n",
th->limits[0], th->limits[1], th->limits[2],
th->initial_limits[0], th->initial_limits[1],
th->initial_limits[2]);
for (i = 0; i < 3; i++)
write_reg(th, LIMIT_REG[i], th->initial_limits[i]);
write_both_fan_speed(th, -1);
thermostat = NULL;
kfree(th);
return 0;
}
static int read_fan_speed(struct thermostat *th, u8 addr)
{
u8 tmp[2];
u16 res;
/* should start with low byte */
tmp[1] = read_reg(th, addr);
tmp[0] = read_reg(th, addr + 1);
res = tmp[1] + (tmp[0] << 8);
/* "a value of 0xffff means that the fan has stopped" */
return (res == 0xffff ? 0 : (90000*60)/res);
}
static void write_both_fan_speed(struct thermostat *th, int speed)
{
write_fan_speed(th, speed, 0);
if (therm_type == ADT7460)
write_fan_speed(th, speed, 1);
}
static void write_fan_speed(struct thermostat *th, int speed, int fan)
{
u8 manual;
if (speed > 0xff)
speed = 0xff;
else if (speed < -1)
speed = 0;
if (therm_type == ADT7467 && fan == 1)
return;
if (th->last_speed[fan] != speed) {
if (verbose) {
if (speed == -1)
printk(KERN_DEBUG "adt746x: Setting speed to automatic "
"for %s fan.\n", sensor_location[fan+1]);
else
printk(KERN_DEBUG "adt746x: Setting speed to %d "
"for %s fan.\n", speed, sensor_location[fan+1]);
}
} else
return;
if (speed >= 0) {
manual = read_reg(th, MANUAL_MODE[fan]);
manual &= ~INVERT_MASK;
write_reg(th, MANUAL_MODE[fan],
manual | MANUAL_MASK | th->pwm_inv[fan]);
write_reg(th, FAN_SPD_SET[fan], speed);
} else {
/* back to automatic */
if(therm_type == ADT7460) {
manual = read_reg(th,
MANUAL_MODE[fan]) & (~MANUAL_MASK);
manual &= ~INVERT_MASK;
manual |= th->pwm_inv[fan];
write_reg(th,
MANUAL_MODE[fan], manual|REM_CONTROL[fan]);
} else {
manual = read_reg(th, MANUAL_MODE[fan]);
manual &= ~INVERT_MASK;
manual |= th->pwm_inv[fan];
write_reg(th, MANUAL_MODE[fan], manual&(~AUTO_MASK));
}
}
th->last_speed[fan] = speed;
}
static void read_sensors(struct thermostat *th)
{
int i = 0;
for (i = 0; i < 3; i++)
th->temps[i] = read_reg(th, TEMP_REG[i]);
}
#ifdef DEBUG
static void display_stats(struct thermostat *th)
{
if (th->temps[0] != th->cached_temp[0]
|| th->temps[1] != th->cached_temp[1]
|| th->temps[2] != th->cached_temp[2]) {
printk(KERN_INFO "adt746x: Temperature infos:"
" thermostats: %d,%d,%d;"
" limits: %d,%d,%d;"
" fan speed: %d RPM\n",
th->temps[0], th->temps[1], th->temps[2],
th->limits[0], th->limits[1], th->limits[2],
read_fan_speed(th, FAN_SPEED[0]));
}
th->cached_temp[0] = th->temps[0];
th->cached_temp[1] = th->temps[1];
th->cached_temp[2] = th->temps[2];
}
#endif
static void update_fans_speed (struct thermostat *th)
{
int lastvar = 0; /* last variation, for iBook */
int i = 0;
/* we don't care about local sensor, so we start at sensor 1 */
for (i = 1; i < 3; i++) {
int started = 0;
int fan_number = (therm_type == ADT7460 && i == 2);
int var = th->temps[i] - th->limits[i];
if (var > -1) {
int step = (255 - fan_speed) / 7;
int new_speed = 0;
/* hysteresis : change fan speed only if variation is
* more than two degrees */
if (abs(var - th->last_var[fan_number]) < 2)
continue;
started = 1;
new_speed = fan_speed + ((var-1)*step);
if (new_speed < fan_speed)
new_speed = fan_speed;
if (new_speed > 255)
new_speed = 255;
if (verbose)
printk(KERN_DEBUG "adt746x: Setting fans speed to %d "
"(limit exceeded by %d on %s) \n",
new_speed, var,
sensor_location[fan_number+1]);
write_both_fan_speed(th, new_speed);
th->last_var[fan_number] = var;
} else if (var < -2) {
/* don't stop fan if sensor2 is cold and sensor1 is not
* so cold (lastvar >= -1) */
if (i == 2 && lastvar < -1) {
if (th->last_speed[fan_number] != 0)
if (verbose)
printk(KERN_DEBUG "adt746x: Stopping "
"fans.\n");
write_both_fan_speed(th, 0);
}
}
lastvar = var;
if (started)
return; /* we don't want to re-stop the fan
* if sensor1 is heating and sensor2 is not */
}
}
static int monitor_task(void *arg)
{
struct thermostat* th = arg;
set_freezable();
while(!kthread_should_stop()) {
try_to_freeze();
msleep_interruptible(2000);
#ifndef DEBUG
if (fan_speed != -1)
read_sensors(th);
#else
read_sensors(th);
#endif
if (fan_speed != -1)
update_fans_speed(th);
#ifdef DEBUG
display_stats(th);
#endif
}
return 0;
}
static void set_limit(struct thermostat *th, int i)
{
/* Set sensor1 limit higher to avoid powerdowns */
th->limits[i] = default_limits_chip[i] + limit_adjust;
write_reg(th, LIMIT_REG[i], th->limits[i]);
/* set our limits to normal */
th->limits[i] = default_limits_local[i] + limit_adjust;
}
static int probe_thermostat(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct thermostat* th;
int rc;
int i;
if (thermostat)
return 0;
th = kzalloc(sizeof(struct thermostat), GFP_KERNEL);
if (!th)
return -ENOMEM;
i2c_set_clientdata(client, th);
th->clt = client;
rc = read_reg(th, 0);
if (rc < 0) {
dev_err(&client->dev, "Thermostat failed to read config!\n");
kfree(th);
return -ENODEV;
}
/* force manual control to start the fan quieter */
if (fan_speed == -1)
fan_speed = 64;
if(therm_type == ADT7460) {
printk(KERN_INFO "adt746x: ADT7460 initializing\n");
/* The 7460 needs to be started explicitly */
write_reg(th, CONFIG_REG, 1);
} else
printk(KERN_INFO "adt746x: ADT7467 initializing\n");
for (i = 0; i < 3; i++) {
th->initial_limits[i] = read_reg(th, LIMIT_REG[i]);
set_limit(th, i);
}
printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d"
" to %d, %d, %d\n",
th->initial_limits[0], th->initial_limits[1],
th->initial_limits[2], th->limits[0], th->limits[1],
th->limits[2]);
thermostat = th;
/* record invert bit status because fw can corrupt it after suspend */
th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK;
th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK;
/* be sure to really write fan speed the first time */
th->last_speed[0] = -2;
th->last_speed[1] = -2;
th->last_var[0] = -80;
th->last_var[1] = -80;
if (fan_speed != -1) {
/* manual mode, stop fans */
write_both_fan_speed(th, 0);
} else {
/* automatic mode */
write_both_fan_speed(th, -1);
}
thread_therm = kthread_run(monitor_task, th, "kfand");
if (thread_therm == ERR_PTR(-ENOMEM)) {
printk(KERN_INFO "adt746x: Kthread creation failed\n");
thread_therm = NULL;
return -ENOMEM;
}
thermostat_create_files();
return 0;
}
static const struct i2c_device_id therm_adt746x_id[] = {
{ "therm_adt746x", 0 },
{ }
};
static struct i2c_driver thermostat_driver = {
.driver = {
.name = "therm_adt746x",
},
.attach_adapter = attach_thermostat,
.probe = probe_thermostat,
.remove = remove_thermostat,
.id_table = therm_adt746x_id,
};
/*
* Now, unfortunately, sysfs doesn't give us a nice void * we could
* pass around to the attribute functions, so we don't really have
* choice but implement a bunch of them...
*
* FIXME, it does now...
*/
#define BUILD_SHOW_FUNC_INT(name, data) \
static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%d\n", data); \
}
#define BUILD_SHOW_FUNC_STR(name, data) \
static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%s\n", data); \
}
#define BUILD_SHOW_FUNC_FAN(name, data) \
static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%d (%d rpm)\n", \
thermostat->last_speed[data], \
read_fan_speed(thermostat, FAN_SPEED[data]) \
); \
}
#define BUILD_STORE_FUNC_DEG(name, data) \
static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \
{ \
int val; \
int i; \
val = simple_strtol(buf, NULL, 10); \
printk(KERN_INFO "Adjusting limits by %d degrees\n", val); \
limit_adjust = val; \
for (i=0; i < 3; i++) \
set_limit(thermostat, i); \
return n; \
}
#define BUILD_STORE_FUNC_INT(name, data) \
static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \
{ \
int val; \
val = simple_strtol(buf, NULL, 10); \
if (val < 0 || val > 255) \
return -EINVAL; \
printk(KERN_INFO "Setting specified fan speed to %d\n", val); \
data = val; \
return n; \
}
BUILD_SHOW_FUNC_INT(sensor1_temperature, (read_reg(thermostat, TEMP_REG[1])))
BUILD_SHOW_FUNC_INT(sensor2_temperature, (read_reg(thermostat, TEMP_REG[2])))
BUILD_SHOW_FUNC_INT(sensor1_limit, thermostat->limits[1])
BUILD_SHOW_FUNC_INT(sensor2_limit, thermostat->limits[2])
BUILD_SHOW_FUNC_STR(sensor1_location, sensor_location[1])
BUILD_SHOW_FUNC_STR(sensor2_location, sensor_location[2])
BUILD_SHOW_FUNC_INT(specified_fan_speed, fan_speed)
BUILD_SHOW_FUNC_FAN(sensor1_fan_speed, 0)
BUILD_SHOW_FUNC_FAN(sensor2_fan_speed, 1)
BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed)
BUILD_SHOW_FUNC_INT(limit_adjust, limit_adjust)
BUILD_STORE_FUNC_DEG(limit_adjust, thermostat)
static DEVICE_ATTR(sensor1_temperature, S_IRUGO,
show_sensor1_temperature,NULL);
static DEVICE_ATTR(sensor2_temperature, S_IRUGO,
show_sensor2_temperature,NULL);
static DEVICE_ATTR(sensor1_limit, S_IRUGO,
show_sensor1_limit, NULL);
static DEVICE_ATTR(sensor2_limit, S_IRUGO,
show_sensor2_limit, NULL);
static DEVICE_ATTR(sensor1_location, S_IRUGO,
show_sensor1_location, NULL);
static DEVICE_ATTR(sensor2_location, S_IRUGO,
show_sensor2_location, NULL);
static DEVICE_ATTR(specified_fan_speed, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
show_specified_fan_speed,store_specified_fan_speed);
static DEVICE_ATTR(sensor1_fan_speed, S_IRUGO,
show_sensor1_fan_speed, NULL);
static DEVICE_ATTR(sensor2_fan_speed, S_IRUGO,
show_sensor2_fan_speed, NULL);
static DEVICE_ATTR(limit_adjust, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,
show_limit_adjust, store_limit_adjust);
static int __init
thermostat_init(void)
{
struct device_node* np;
const u32 *prop;
int i = 0, offset = 0;
np = of_find_node_by_name(NULL, "fan");
if (!np)
return -ENODEV;
if (of_device_is_compatible(np, "adt7460"))
therm_type = ADT7460;
else if (of_device_is_compatible(np, "adt7467"))
therm_type = ADT7467;
else {
of_node_put(np);
return -ENODEV;
}
prop = of_get_property(np, "hwsensor-params-version", NULL);
printk(KERN_INFO "adt746x: version %d (%ssupported)\n", *prop,
(*prop == 1)?"":"un");
if (*prop != 1) {
of_node_put(np);
return -ENODEV;
}
prop = of_get_property(np, "reg", NULL);
if (!prop) {
of_node_put(np);
return -ENODEV;
}
/* look for bus either by path or using "reg" */
if (strstr(np->full_name, "/i2c-bus@") != NULL) {
const char *tmp_bus = (strstr(np->full_name, "/i2c-bus@") + 9);
therm_bus = tmp_bus[0]-'0';
} else {
therm_bus = ((*prop) >> 8) & 0x0f;
}
therm_address = ((*prop) & 0xff) >> 1;
printk(KERN_INFO "adt746x: Thermostat bus: %d, address: 0x%02x, "
"limit_adjust: %d, fan_speed: %d\n",
therm_bus, therm_address, limit_adjust, fan_speed);
if (of_get_property(np, "hwsensor-location", NULL)) {
for (i = 0; i < 3; i++) {
sensor_location[i] = of_get_property(np,
"hwsensor-location", NULL) + offset;
if (sensor_location[i] == NULL)
sensor_location[i] = "";
printk(KERN_INFO "sensor %d: %s\n", i, sensor_location[i]);
offset += strlen(sensor_location[i]) + 1;
}
} else {
sensor_location[0] = "?";
sensor_location[1] = "?";
sensor_location[2] = "?";
}
of_dev = of_platform_device_create(np, "temperatures", NULL);
of_node_put(np);
if (of_dev == NULL) {
printk(KERN_ERR "Can't register temperatures device !\n");
return -ENODEV;
}
#ifndef CONFIG_I2C_POWERMAC
request_module("i2c-powermac");
#endif
return i2c_add_driver(&thermostat_driver);
}
static void thermostat_create_files(void)
{
int err;
err = device_create_file(&of_dev->dev, &dev_attr_sensor1_temperature);
err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_temperature);
err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_limit);
err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_limit);
err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_location);
err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_location);
err |= device_create_file(&of_dev->dev, &dev_attr_limit_adjust);
err |= device_create_file(&of_dev->dev, &dev_attr_specified_fan_speed);
err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_fan_speed);
if(therm_type == ADT7460)
err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_fan_speed);
if (err)
printk(KERN_WARNING
"Failed to create tempertaure attribute file(s).\n");
}
static void thermostat_remove_files(void)
{
if (of_dev) {
device_remove_file(&of_dev->dev, &dev_attr_sensor1_temperature);
device_remove_file(&of_dev->dev, &dev_attr_sensor2_temperature);
device_remove_file(&of_dev->dev, &dev_attr_sensor1_limit);
device_remove_file(&of_dev->dev, &dev_attr_sensor2_limit);
device_remove_file(&of_dev->dev, &dev_attr_sensor1_location);
device_remove_file(&of_dev->dev, &dev_attr_sensor2_location);
device_remove_file(&of_dev->dev, &dev_attr_limit_adjust);
device_remove_file(&of_dev->dev, &dev_attr_specified_fan_speed);
device_remove_file(&of_dev->dev, &dev_attr_sensor1_fan_speed);
if(therm_type == ADT7460)
device_remove_file(&of_dev->dev,
&dev_attr_sensor2_fan_speed);
}
}
static void __exit
thermostat_exit(void)
{
i2c_del_driver(&thermostat_driver);
of_device_unregister(of_dev);
}
module_init(thermostat_init);
module_exit(thermostat_exit);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,326 @@
#ifndef __THERM_PMAC_7_2_H__
#define __THERM_PMAC_7_2_H__
typedef unsigned short fu16;
typedef int fs32;
typedef short fs16;
struct mpu_data
{
u8 signature; /* 0x00 - EEPROM sig. */
u8 bytes_used; /* 0x01 - Bytes used in eeprom (160 ?) */
u8 size; /* 0x02 - EEPROM size (256 ?) */
u8 version; /* 0x03 - EEPROM version */
u32 data_revision; /* 0x04 - Dataset revision */
u8 processor_bin_code[3]; /* 0x08 - Processor BIN code */
u8 bin_code_expansion; /* 0x0b - ??? (padding ?) */
u8 processor_num; /* 0x0c - Number of CPUs on this MPU */
u8 input_mul_bus_div; /* 0x0d - Clock input multiplier/bus divider */
u8 reserved1[2]; /* 0x0e - */
u32 input_clk_freq_high; /* 0x10 - Input clock frequency high */
u8 cpu_nb_target_cycles; /* 0x14 - ??? */
u8 cpu_statlat; /* 0x15 - ??? */
u8 cpu_snooplat; /* 0x16 - ??? */
u8 cpu_snoopacc; /* 0x17 - ??? */
u8 nb_paamwin; /* 0x18 - ??? */
u8 nb_statlat; /* 0x19 - ??? */
u8 nb_snooplat; /* 0x1a - ??? */
u8 nb_snoopwin; /* 0x1b - ??? */
u8 api_bus_mode; /* 0x1c - ??? */
u8 reserved2[3]; /* 0x1d - */
u32 input_clk_freq_low; /* 0x20 - Input clock frequency low */
u8 processor_card_slot; /* 0x24 - Processor card slot number */
u8 reserved3[2]; /* 0x25 - */
u8 padjmax; /* 0x27 - Max power adjustment (Not in OF!) */
u8 ttarget; /* 0x28 - Target temperature */
u8 tmax; /* 0x29 - Max temperature */
u8 pmaxh; /* 0x2a - Max power */
u8 tguardband; /* 0x2b - Guardband temp ??? Hist. len in OSX */
fs32 pid_gp; /* 0x2c - PID proportional gain */
fs32 pid_gr; /* 0x30 - PID reset gain */
fs32 pid_gd; /* 0x34 - PID derivative gain */
fu16 voph; /* 0x38 - Vop High */
fu16 vopl; /* 0x3a - Vop Low */
fs16 nactual_die; /* 0x3c - nActual Die */
fs16 nactual_heatsink; /* 0x3e - nActual Heatsink */
fs16 nactual_system; /* 0x40 - nActual System */
u16 calibration_flags; /* 0x42 - Calibration flags */
fu16 mdiode; /* 0x44 - Diode M value (scaling factor) */
fs16 bdiode; /* 0x46 - Diode B value (offset) */
fs32 theta_heat_sink; /* 0x48 - Theta heat sink */
u16 rminn_intake_fan; /* 0x4c - Intake fan min RPM */
u16 rmaxn_intake_fan; /* 0x4e - Intake fan max RPM */
u16 rminn_exhaust_fan; /* 0x50 - Exhaust fan min RPM */
u16 rmaxn_exhaust_fan; /* 0x52 - Exhaust fan max RPM */
u8 processor_part_num[8]; /* 0x54 - Processor part number XX pumps min/max */
u32 processor_lot_num; /* 0x5c - Processor lot number */
u8 orig_card_sernum[0x10]; /* 0x60 - Card original serial number */
u8 curr_card_sernum[0x10]; /* 0x70 - Card current serial number */
u8 mlb_sernum[0x18]; /* 0x80 - MLB serial number */
u32 checksum1; /* 0x98 - */
u32 checksum2; /* 0x9c - */
}; /* Total size = 0xa0 */
/* Display a 16.16 fixed point value */
#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16)
/*
* Maximum number of seconds to be in critical state (after a
* normal shutdown attempt). If the machine isn't down after
* this counter elapses, we force an immediate machine power
* off.
*/
#define MAX_CRITICAL_STATE 30
static char * critical_overtemp_path = "/sbin/critical_overtemp";
/*
* This option is "weird" :) Basically, if you define this to 1
* the control loop for the RPMs fans (not PWMs) will apply the
* correction factor obtained from the PID to the _actual_ RPM
* speed read from the FCU.
* If you define the below constant to 0, then it will be
* applied to the setpoint RPM speed, that is basically the
* speed we proviously "asked" for.
*
* I'm not sure which of these Apple's algorithm is supposed
* to use
*/
#define RPM_PID_USE_ACTUAL_SPEED 0
/*
* i2c IDs. Currently, we hard code those and assume that
* the FCU is on U3 bus 1 while all sensors are on U3 bus
* 0. This appear to be safe enough for this first version
* of the driver, though I would accept any clean patch
* doing a better use of the device-tree without turning the
* while i2c registration mechanism into a racy mess
*
* Note: Xserve changed this. We have some bits on the K2 bus,
* which I arbitrarily set to 0x200. Ultimately, we really want
* too lookup these in the device-tree though
*/
#define FAN_CTRLER_ID 0x15e
#define SUPPLY_MONITOR_ID 0x58
#define SUPPLY_MONITORB_ID 0x5a
#define DRIVES_DALLAS_ID 0x94
#define BACKSIDE_MAX_ID 0x98
#define XSERVE_DIMMS_LM87 0x25a
#define XSERVE_SLOTS_LM75 0x290
/*
* Some MAX6690, DS1775, LM87 register definitions
*/
#define MAX6690_INT_TEMP 0
#define MAX6690_EXT_TEMP 1
#define DS1775_TEMP 0
#define LM87_INT_TEMP 0x27
/*
* Scaling factors for the AD7417 ADC converters (except
* for the CPU diode which is obtained from the EEPROM).
* Those values are obtained from the property list of
* the darwin driver
*/
#define ADC_12V_CURRENT_SCALE 0x0320 /* _AD2 */
#define ADC_CPU_VOLTAGE_SCALE 0x00a0 /* _AD3 */
#define ADC_CPU_CURRENT_SCALE 0x1f40 /* _AD4 */
/*
* PID factors for the U3/Backside fan control loop. We have 2 sets
* of values here, one set for U3 and one set for U3H
*/
#define BACKSIDE_FAN_PWM_DEFAULT_ID 1
#define BACKSIDE_FAN_PWM_INDEX 0
#define BACKSIDE_PID_U3_G_d 0x02800000
#define BACKSIDE_PID_U3H_G_d 0x01400000
#define BACKSIDE_PID_RACK_G_d 0x00500000
#define BACKSIDE_PID_G_p 0x00500000
#define BACKSIDE_PID_RACK_G_p 0x0004cccc
#define BACKSIDE_PID_G_r 0x00000000
#define BACKSIDE_PID_U3_INPUT_TARGET 0x00410000
#define BACKSIDE_PID_U3H_INPUT_TARGET 0x004b0000
#define BACKSIDE_PID_RACK_INPUT_TARGET 0x00460000
#define BACKSIDE_PID_INTERVAL 5
#define BACKSIDE_PID_RACK_INTERVAL 1
#define BACKSIDE_PID_OUTPUT_MAX 100
#define BACKSIDE_PID_U3_OUTPUT_MIN 20
#define BACKSIDE_PID_U3H_OUTPUT_MIN 20
#define BACKSIDE_PID_HISTORY_SIZE 2
struct basckside_pid_params
{
s32 G_d;
s32 G_p;
s32 G_r;
s32 input_target;
s32 output_min;
s32 output_max;
s32 interval;
int additive;
};
struct backside_pid_state
{
int ticks;
struct i2c_client * monitor;
s32 sample_history[BACKSIDE_PID_HISTORY_SIZE];
s32 error_history[BACKSIDE_PID_HISTORY_SIZE];
int cur_sample;
s32 last_temp;
int pwm;
int first;
};
/*
* PID factors for the Drive Bay fan control loop
*/
#define DRIVES_FAN_RPM_DEFAULT_ID 2
#define DRIVES_FAN_RPM_INDEX 1
#define DRIVES_PID_G_d 0x01e00000
#define DRIVES_PID_G_p 0x00500000
#define DRIVES_PID_G_r 0x00000000
#define DRIVES_PID_INPUT_TARGET 0x00280000
#define DRIVES_PID_INTERVAL 5
#define DRIVES_PID_OUTPUT_MAX 4000
#define DRIVES_PID_OUTPUT_MIN 300
#define DRIVES_PID_HISTORY_SIZE 2
struct drives_pid_state
{
int ticks;
struct i2c_client * monitor;
s32 sample_history[BACKSIDE_PID_HISTORY_SIZE];
s32 error_history[BACKSIDE_PID_HISTORY_SIZE];
int cur_sample;
s32 last_temp;
int rpm;
int first;
};
#define SLOTS_FAN_PWM_DEFAULT_ID 2
#define SLOTS_FAN_PWM_INDEX 2
#define SLOTS_FAN_DEFAULT_PWM 40 /* Do better here ! */
/*
* PID factors for the Xserve DIMM control loop
*/
#define DIMM_PID_G_d 0
#define DIMM_PID_G_p 0
#define DIMM_PID_G_r 0x06553600
#define DIMM_PID_INPUT_TARGET 3276800
#define DIMM_PID_INTERVAL 1
#define DIMM_PID_OUTPUT_MAX 14000
#define DIMM_PID_OUTPUT_MIN 4000
#define DIMM_PID_HISTORY_SIZE 20
struct dimm_pid_state
{
int ticks;
struct i2c_client * monitor;
s32 sample_history[DIMM_PID_HISTORY_SIZE];
s32 error_history[DIMM_PID_HISTORY_SIZE];
int cur_sample;
s32 last_temp;
int first;
int output;
};
/*
* PID factors for the Xserve Slots control loop
*/
#define SLOTS_PID_G_d 0
#define SLOTS_PID_G_p 0
#define SLOTS_PID_G_r 0x00100000
#define SLOTS_PID_INPUT_TARGET 3200000
#define SLOTS_PID_INTERVAL 1
#define SLOTS_PID_OUTPUT_MAX 100
#define SLOTS_PID_OUTPUT_MIN 20
#define SLOTS_PID_HISTORY_SIZE 20
struct slots_pid_state
{
int ticks;
struct i2c_client * monitor;
s32 sample_history[SLOTS_PID_HISTORY_SIZE];
s32 error_history[SLOTS_PID_HISTORY_SIZE];
int cur_sample;
s32 last_temp;
int first;
int pwm;
};
/* Desktops */
#define CPUA_INTAKE_FAN_RPM_DEFAULT_ID 3
#define CPUA_EXHAUST_FAN_RPM_DEFAULT_ID 4
#define CPUB_INTAKE_FAN_RPM_DEFAULT_ID 5
#define CPUB_EXHAUST_FAN_RPM_DEFAULT_ID 6
#define CPUA_INTAKE_FAN_RPM_INDEX 3
#define CPUA_EXHAUST_FAN_RPM_INDEX 4
#define CPUB_INTAKE_FAN_RPM_INDEX 5
#define CPUB_EXHAUST_FAN_RPM_INDEX 6
#define CPU_INTAKE_SCALE 0x0000f852
#define CPU_TEMP_HISTORY_SIZE 2
#define CPU_POWER_HISTORY_SIZE 10
#define CPU_PID_INTERVAL 1
#define CPU_MAX_OVERTEMP 30
#define CPUA_PUMP_RPM_INDEX 7
#define CPUB_PUMP_RPM_INDEX 8
#define CPU_PUMP_OUTPUT_MAX 3200
#define CPU_PUMP_OUTPUT_MIN 1250
/* Xserve */
#define CPU_A1_FAN_RPM_INDEX 9
#define CPU_A2_FAN_RPM_INDEX 10
#define CPU_A3_FAN_RPM_INDEX 11
#define CPU_B1_FAN_RPM_INDEX 12
#define CPU_B2_FAN_RPM_INDEX 13
#define CPU_B3_FAN_RPM_INDEX 14
struct cpu_pid_state
{
int index;
struct i2c_client * monitor;
struct mpu_data mpu;
int overtemp;
s32 temp_history[CPU_TEMP_HISTORY_SIZE];
int cur_temp;
s32 power_history[CPU_POWER_HISTORY_SIZE];
s32 error_history[CPU_POWER_HISTORY_SIZE];
int cur_power;
int count_power;
int rpm;
int intake_rpm;
s32 voltage;
s32 current_a;
s32 last_temp;
s32 last_power;
int first;
u8 adc_config;
s32 pump_min;
s32 pump_max;
};
/* Tickle FCU every 10 seconds */
#define FCU_TICKLE_TICKS 10
/*
* Driver state
*/
enum {
state_detached,
state_attaching,
state_attached,
state_detaching,
};
#endif /* __THERM_PMAC_7_2_H__ */

View File

@@ -0,0 +1,528 @@
/*
* Creation Date: <2003/03/14 20:54:13 samuel>
* Time-stamp: <2004/03/20 14:20:59 samuel>
*
* <therm_windtunnel.c>
*
* The G4 "windtunnel" has a single fan controlled by an
* ADM1030 fan controller and a DS1775 thermostat.
*
* The fan controller is equipped with a temperature sensor
* which measures the case temperature. The DS1775 sensor
* measures the CPU temperature. This driver tunes the
* behavior of the fan. It is based upon empirical observations
* of the 'AppleFan' driver under Mac OS X.
*
* WARNING: This driver has only been testen on Apple's
* 1.25 MHz Dual G4 (March 03). It is tuned for a CPU
* temperatur around 57 C.
*
* Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se)
*
* Loosely based upon 'thermostat.c' written by Benjamin Herrenschmidt
*
* 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
*
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/of_platform.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/macio.h>
#define LOG_TEMP 0 /* continously log temperature */
static struct {
volatile int running;
struct task_struct *poll_task;
struct mutex lock;
struct of_device *of_dev;
struct i2c_client *thermostat;
struct i2c_client *fan;
int overheat_temp; /* 100% fan at this temp */
int overheat_hyst;
int temp;
int casetemp;
int fan_level; /* active fan_table setting */
int downind;
int upind;
int r0, r1, r20, r23, r25; /* saved register */
} x;
#define T(x,y) (((x)<<8) | (y)*0x100/10 )
static struct {
int fan_down_setting;
int temp;
int fan_up_setting;
} fan_table[] = {
{ 11, T(0,0), 11 }, /* min fan */
{ 11, T(55,0), 11 },
{ 6, T(55,3), 11 },
{ 7, T(56,0), 11 },
{ 8, T(57,0), 8 },
{ 7, T(58,3), 7 },
{ 6, T(58,8), 6 },
{ 5, T(59,2), 5 },
{ 4, T(59,6), 4 },
{ 3, T(59,9), 3 },
{ 2, T(60,1), 2 },
{ 1, 0xfffff, 1 } /* on fire */
};
static void
print_temp( const char *s, int temp )
{
printk("%s%d.%d C", s ? s : "", temp>>8, (temp & 255)*10/256 );
}
static ssize_t
show_cpu_temperature( struct device *dev, struct device_attribute *attr, char *buf )
{
return sprintf(buf, "%d.%d\n", x.temp>>8, (x.temp & 255)*10/256 );
}
static ssize_t
show_case_temperature( struct device *dev, struct device_attribute *attr, char *buf )
{
return sprintf(buf, "%d.%d\n", x.casetemp>>8, (x.casetemp & 255)*10/256 );
}
static DEVICE_ATTR(cpu_temperature, S_IRUGO, show_cpu_temperature, NULL );
static DEVICE_ATTR(case_temperature, S_IRUGO, show_case_temperature, NULL );
/************************************************************************/
/* controller thread */
/************************************************************************/
static int
write_reg( struct i2c_client *cl, int reg, int data, int len )
{
u8 tmp[3];
if( len < 1 || len > 2 || data < 0 )
return -EINVAL;
tmp[0] = reg;
tmp[1] = (len == 1) ? data : (data >> 8);
tmp[2] = data;
len++;
if( i2c_master_send(cl, tmp, len) != len )
return -ENODEV;
return 0;
}
static int
read_reg( struct i2c_client *cl, int reg, int len )
{
u8 buf[2];
if( len != 1 && len != 2 )
return -EINVAL;
buf[0] = reg;
if( i2c_master_send(cl, buf, 1) != 1 )
return -ENODEV;
if( i2c_master_recv(cl, buf, len) != len )
return -ENODEV;
return (len == 2)? ((unsigned int)buf[0] << 8) | buf[1] : buf[0];
}
static void
tune_fan( int fan_setting )
{
int val = (fan_setting << 3) | 7;
/* write_reg( x.fan, 0x24, val, 1 ); */
write_reg( x.fan, 0x25, val, 1 );
write_reg( x.fan, 0x20, 0, 1 );
print_temp("CPU-temp: ", x.temp );
if( x.casetemp )
print_temp(", Case: ", x.casetemp );
printk(", Fan: %d (tuned %+d)\n", 11-fan_setting, x.fan_level-fan_setting );
x.fan_level = fan_setting;
}
static void
poll_temp( void )
{
int temp, i, level, casetemp;
temp = read_reg( x.thermostat, 0, 2 );
/* this actually occurs when the computer is loaded */
if( temp < 0 )
return;
casetemp = read_reg(x.fan, 0x0b, 1) << 8;
casetemp |= (read_reg(x.fan, 0x06, 1) & 0x7) << 5;
if( LOG_TEMP && x.temp != temp ) {
print_temp("CPU-temp: ", temp );
print_temp(", Case: ", casetemp );
printk(", Fan: %d\n", 11-x.fan_level );
}
x.temp = temp;
x.casetemp = casetemp;
level = -1;
for( i=0; (temp & 0xffff) > fan_table[i].temp ; i++ )
;
if( i < x.downind )
level = fan_table[i].fan_down_setting;
x.downind = i;
for( i=0; (temp & 0xffff) >= fan_table[i+1].temp ; i++ )
;
if( x.upind < i )
level = fan_table[i].fan_up_setting;
x.upind = i;
if( level >= 0 )
tune_fan( level );
}
static void
setup_hardware( void )
{
int val;
int err;
/* save registers (if we unload the module) */
x.r0 = read_reg( x.fan, 0x00, 1 );
x.r1 = read_reg( x.fan, 0x01, 1 );
x.r20 = read_reg( x.fan, 0x20, 1 );
x.r23 = read_reg( x.fan, 0x23, 1 );
x.r25 = read_reg( x.fan, 0x25, 1 );
/* improve measurement resolution (convergence time 1.5s) */
if( (val=read_reg(x.thermostat, 1, 1)) >= 0 ) {
val |= 0x60;
if( write_reg( x.thermostat, 1, val, 1 ) )
printk("Failed writing config register\n");
}
/* disable interrupts and TAC input */
write_reg( x.fan, 0x01, 0x01, 1 );
/* enable filter */
write_reg( x.fan, 0x23, 0x91, 1 );
/* remote temp. controls fan */
write_reg( x.fan, 0x00, 0x95, 1 );
/* The thermostat (which besides measureing temperature controls
* has a THERM output which puts the fan on 100%) is usually
* set to kick in at 80 C (chip default). We reduce this a bit
* to be on the safe side (OSX doesn't)...
*/
if( x.overheat_temp == (80 << 8) ) {
x.overheat_temp = 75 << 8;
x.overheat_hyst = 70 << 8;
write_reg( x.thermostat, 2, x.overheat_hyst, 2 );
write_reg( x.thermostat, 3, x.overheat_temp, 2 );
print_temp("Reducing overheating limit to ", x.overheat_temp );
print_temp(" (Hyst: ", x.overheat_hyst );
printk(")\n");
}
/* set an initial fan setting */
x.downind = 0xffff;
x.upind = -1;
/* tune_fan( fan_up_table[x.upind].fan_setting ); */
err = device_create_file( &x.of_dev->dev, &dev_attr_cpu_temperature );
err |= device_create_file( &x.of_dev->dev, &dev_attr_case_temperature );
if (err)
printk(KERN_WARNING
"Failed to create temperature attribute file(s).\n");
}
static void
restore_regs( void )
{
device_remove_file( &x.of_dev->dev, &dev_attr_cpu_temperature );
device_remove_file( &x.of_dev->dev, &dev_attr_case_temperature );
write_reg( x.fan, 0x01, x.r1, 1 );
write_reg( x.fan, 0x20, x.r20, 1 );
write_reg( x.fan, 0x23, x.r23, 1 );
write_reg( x.fan, 0x25, x.r25, 1 );
write_reg( x.fan, 0x00, x.r0, 1 );
}
static int control_loop(void *dummy)
{
mutex_lock(&x.lock);
setup_hardware();
mutex_unlock(&x.lock);
for (;;) {
msleep_interruptible(8000);
if (kthread_should_stop())
break;
mutex_lock(&x.lock);
poll_temp();
mutex_unlock(&x.lock);
}
mutex_lock(&x.lock);
restore_regs();
mutex_unlock(&x.lock);
return 0;
}
/************************************************************************/
/* i2c probing and setup */
/************************************************************************/
static int
do_attach( struct i2c_adapter *adapter )
{
/* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */
static const unsigned short scan_ds1775[] = {
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
I2C_CLIENT_END
};
static const unsigned short scan_adm1030[] = {
0x2c, 0x2d, 0x2e, 0x2f,
I2C_CLIENT_END
};
if( strncmp(adapter->name, "uni-n", 5) )
return 0;
if( !x.running ) {
struct i2c_board_info info;
memset(&info, 0, sizeof(struct i2c_board_info));
strlcpy(info.type, "therm_ds1775", I2C_NAME_SIZE);
i2c_new_probed_device(adapter, &info, scan_ds1775);
strlcpy(info.type, "therm_adm1030", I2C_NAME_SIZE);
i2c_new_probed_device(adapter, &info, scan_adm1030);
if( x.thermostat && x.fan ) {
x.running = 1;
x.poll_task = kthread_run(control_loop, NULL, "g4fand");
}
}
return 0;
}
static int
do_remove(struct i2c_client *client)
{
if (x.running) {
x.running = 0;
kthread_stop(x.poll_task);
x.poll_task = NULL;
}
if (client == x.thermostat)
x.thermostat = NULL;
else if (client == x.fan)
x.fan = NULL;
else
printk(KERN_ERR "g4fan: bad client\n");
return 0;
}
static int
attach_fan( struct i2c_client *cl )
{
if( x.fan )
goto out;
/* check that this is an ADM1030 */
if( read_reg(cl, 0x3d, 1) != 0x30 || read_reg(cl, 0x3e, 1) != 0x41 )
goto out;
printk("ADM1030 fan controller [@%02x]\n", cl->addr );
x.fan = cl;
out:
return 0;
}
static int
attach_thermostat( struct i2c_client *cl )
{
int hyst_temp, os_temp, temp;
if( x.thermostat )
goto out;
if( (temp=read_reg(cl, 0, 2)) < 0 )
goto out;
/* temperature sanity check */
if( temp < 0x1600 || temp > 0x3c00 )
goto out;
hyst_temp = read_reg(cl, 2, 2);
os_temp = read_reg(cl, 3, 2);
if( hyst_temp < 0 || os_temp < 0 )
goto out;
printk("DS1775 digital thermometer [@%02x]\n", cl->addr );
print_temp("Temp: ", temp );
print_temp(" Hyst: ", hyst_temp );
print_temp(" OS: ", os_temp );
printk("\n");
x.temp = temp;
x.overheat_temp = os_temp;
x.overheat_hyst = hyst_temp;
x.thermostat = cl;
out:
return 0;
}
enum chip { ds1775, adm1030 };
static const struct i2c_device_id therm_windtunnel_id[] = {
{ "therm_ds1775", ds1775 },
{ "therm_adm1030", adm1030 },
{ }
};
static int
do_probe(struct i2c_client *cl, const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = cl->adapter;
if( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA
| I2C_FUNC_SMBUS_WRITE_BYTE) )
return 0;
switch (id->driver_data) {
case adm1030:
return attach_fan( cl );
case ds1775:
return attach_thermostat(cl);
}
return 0;
}
static struct i2c_driver g4fan_driver = {
.driver = {
.name = "therm_windtunnel",
},
.attach_adapter = do_attach,
.probe = do_probe,
.remove = do_remove,
.id_table = therm_windtunnel_id,
};
/************************************************************************/
/* initialization / cleanup */
/************************************************************************/
static int
therm_of_probe( struct of_device *dev, const struct of_device_id *match )
{
return i2c_add_driver( &g4fan_driver );
}
static int
therm_of_remove( struct of_device *dev )
{
i2c_del_driver( &g4fan_driver );
return 0;
}
static struct of_device_id therm_of_match[] = {{
.name = "fan",
.compatible = "adm1030"
}, {}
};
static struct of_platform_driver therm_of_driver = {
.name = "temperature",
.match_table = therm_of_match,
.probe = therm_of_probe,
.remove = therm_of_remove,
};
struct apple_thermal_info {
u8 id; /* implementation ID */
u8 fan_count; /* number of fans */
u8 thermostat_count; /* number of thermostats */
u8 unused;
};
static int __init
g4fan_init( void )
{
const struct apple_thermal_info *info;
struct device_node *np;
mutex_init(&x.lock);
if( !(np=of_find_node_by_name(NULL, "power-mgt")) )
return -ENODEV;
info = of_get_property(np, "thermal-info", NULL);
of_node_put(np);
if( !info || !machine_is_compatible("PowerMac3,6") )
return -ENODEV;
if( info->id != 3 ) {
printk(KERN_ERR "therm_windtunnel: unsupported thermal design %d\n", info->id );
return -ENODEV;
}
if( !(np=of_find_node_by_name(NULL, "fan")) )
return -ENODEV;
x.of_dev = of_platform_device_create(np, "temperature", NULL);
of_node_put( np );
if( !x.of_dev ) {
printk(KERN_ERR "Can't register fan controller!\n");
return -ENODEV;
}
of_register_platform_driver( &therm_of_driver );
return 0;
}
static void __exit
g4fan_exit( void )
{
of_unregister_platform_driver( &therm_of_driver );
if( x.of_dev )
of_device_unregister( x.of_dev );
}
module_init(g4fan_init);
module_exit(g4fan_exit);
MODULE_AUTHOR("Samuel Rydh <samuel@ibrium.se>");
MODULE_DESCRIPTION("Apple G4 (windtunnel) fan controller");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,632 @@
/*
* Device driver for the via-cuda on Apple Powermacs.
*
* The VIA (versatile interface adapter) interfaces to the CUDA,
* a 6805 microprocessor core which controls the ADB (Apple Desktop
* Bus) which connects to the keyboard and mouse. The CUDA also
* controls system power and the RTC (real time clock) chip.
*
* Copyright (C) 1996 Paul Mackerras.
*/
#include <stdarg.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/adb.h>
#include <linux/cuda.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#ifdef CONFIG_PPC
#include <asm/prom.h>
#include <asm/machdep.h>
#else
#include <asm/macintosh.h>
#include <asm/macints.h>
#include <asm/mac_via.h>
#endif
#include <asm/io.h>
#include <asm/system.h>
#include <linux/init.h>
static volatile unsigned char __iomem *via;
static DEFINE_SPINLOCK(cuda_lock);
/* VIA registers - spaced 0x200 bytes apart */
#define RS 0x200 /* skip between registers */
#define B 0 /* B-side data */
#define A RS /* A-side data */
#define DIRB (2*RS) /* B-side direction (1=output) */
#define DIRA (3*RS) /* A-side direction (1=output) */
#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */
#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */
#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */
#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */
#define T2CL (8*RS) /* Timer 2 ctr/latch (low 8 bits) */
#define T2CH (9*RS) /* Timer 2 counter (high 8 bits) */
#define SR (10*RS) /* Shift register */
#define ACR (11*RS) /* Auxiliary control register */
#define PCR (12*RS) /* Peripheral control register */
#define IFR (13*RS) /* Interrupt flag register */
#define IER (14*RS) /* Interrupt enable register */
#define ANH (15*RS) /* A-side data, no handshake */
/* Bits in B data register: all active low */
#define TREQ 0x08 /* Transfer request (input) */
#define TACK 0x10 /* Transfer acknowledge (output) */
#define TIP 0x20 /* Transfer in progress (output) */
/* Bits in ACR */
#define SR_CTRL 0x1c /* Shift register control bits */
#define SR_EXT 0x0c /* Shift on external clock */
#define SR_OUT 0x10 /* Shift out if 1 */
/* Bits in IFR and IER */
#define IER_SET 0x80 /* set bits in IER */
#define IER_CLR 0 /* clear bits in IER */
#define SR_INT 0x04 /* Shift register full/empty */
static enum cuda_state {
idle,
sent_first_byte,
sending,
reading,
read_done,
awaiting_reply
} cuda_state;
static struct adb_request *current_req;
static struct adb_request *last_req;
static unsigned char cuda_rbuf[16];
static unsigned char *reply_ptr;
static int reading_reply;
static int data_index;
static int cuda_irq;
#ifdef CONFIG_PPC
static struct device_node *vias;
#endif
static int cuda_fully_inited;
#ifdef CONFIG_ADB
static int cuda_probe(void);
static int cuda_init(void);
static int cuda_send_request(struct adb_request *req, int sync);
static int cuda_adb_autopoll(int devs);
static int cuda_reset_adb_bus(void);
#endif /* CONFIG_ADB */
static int cuda_init_via(void);
static void cuda_start(void);
static irqreturn_t cuda_interrupt(int irq, void *arg);
static void cuda_input(unsigned char *buf, int nb);
void cuda_poll(void);
static int cuda_write(struct adb_request *req);
int cuda_request(struct adb_request *req,
void (*done)(struct adb_request *), int nbytes, ...);
#ifdef CONFIG_ADB
struct adb_driver via_cuda_driver = {
"CUDA",
cuda_probe,
cuda_init,
cuda_send_request,
cuda_adb_autopoll,
cuda_poll,
cuda_reset_adb_bus
};
#endif /* CONFIG_ADB */
#ifdef CONFIG_PPC
int __init find_via_cuda(void)
{
struct adb_request req;
phys_addr_t taddr;
const u32 *reg;
int err;
if (vias != 0)
return 1;
vias = of_find_node_by_name(NULL, "via-cuda");
if (vias == 0)
return 0;
reg = of_get_property(vias, "reg", NULL);
if (reg == NULL) {
printk(KERN_ERR "via-cuda: No \"reg\" property !\n");
goto fail;
}
taddr = of_translate_address(vias, reg);
if (taddr == 0) {
printk(KERN_ERR "via-cuda: Can't translate address !\n");
goto fail;
}
via = ioremap(taddr, 0x2000);
if (via == NULL) {
printk(KERN_ERR "via-cuda: Can't map address !\n");
goto fail;
}
cuda_state = idle;
sys_ctrler = SYS_CTRLER_CUDA;
err = cuda_init_via();
if (err) {
printk(KERN_ERR "cuda_init_via() failed\n");
via = NULL;
return 0;
}
/* Clear and enable interrupts, but only on PPC. On 68K it's done */
/* for us by the main VIA driver in arch/m68k/mac/via.c */
out_8(&via[IFR], 0x7f); /* clear interrupts by writing 1s */
out_8(&via[IER], IER_SET|SR_INT); /* enable interrupt from SR */
/* enable autopoll */
cuda_request(&req, NULL, 3, CUDA_PACKET, CUDA_AUTOPOLL, 1);
while (!req.complete)
cuda_poll();
return 1;
fail:
of_node_put(vias);
vias = NULL;
return 0;
}
#endif /* CONFIG_PPC */
static int __init via_cuda_start(void)
{
if (via == NULL)
return -ENODEV;
#ifdef CONFIG_MAC
cuda_irq = IRQ_MAC_ADB;
#else /* CONFIG_MAC */
cuda_irq = irq_of_parse_and_map(vias, 0);
if (cuda_irq == NO_IRQ) {
printk(KERN_ERR "via-cuda: can't map interrupts for %s\n",
vias->full_name);
return -ENODEV;
}
#endif /* CONFIG_MAC */
if (request_irq(cuda_irq, cuda_interrupt, 0, "ADB", cuda_interrupt)) {
printk(KERN_ERR "via-cuda: can't request irq %d\n", cuda_irq);
return -EAGAIN;
}
printk("Macintosh CUDA driver v0.5 for Unified ADB.\n");
cuda_fully_inited = 1;
return 0;
}
device_initcall(via_cuda_start);
#ifdef CONFIG_ADB
static int
cuda_probe(void)
{
#ifdef CONFIG_PPC
if (sys_ctrler != SYS_CTRLER_CUDA)
return -ENODEV;
#else
if (macintosh_config->adb_type != MAC_ADB_CUDA)
return -ENODEV;
via = via1;
#endif
return 0;
}
static int __init
cuda_init(void)
{
#ifdef CONFIG_PPC
if (via == NULL)
return -ENODEV;
return 0;
#else
int err = cuda_init_via();
if (err) {
printk(KERN_ERR "cuda_init_via() failed\n");
return -ENODEV;
}
out_8(&via[IER], IER_SET|SR_INT); /* enable interrupt from SR */
return via_cuda_start();
#endif
}
#endif /* CONFIG_ADB */
#define WAIT_FOR(cond, what) \
do { \
int x; \
for (x = 1000; !(cond); --x) { \
if (x == 0) { \
printk("Timeout waiting for " what "\n"); \
return -ENXIO; \
} \
udelay(100); \
} \
} while (0)
static int
cuda_init_via(void)
{
out_8(&via[DIRB], (in_8(&via[DIRB]) | TACK | TIP) & ~TREQ); /* TACK & TIP out */
out_8(&via[B], in_8(&via[B]) | TACK | TIP); /* negate them */
out_8(&via[ACR] ,(in_8(&via[ACR]) & ~SR_CTRL) | SR_EXT); /* SR data in */
(void)in_8(&via[SR]); /* clear any left-over data */
#ifdef CONFIG_PPC
out_8(&via[IER], 0x7f); /* disable interrupts from VIA */
(void)in_8(&via[IER]);
#else
out_8(&via[IER], SR_INT); /* disable SR interrupt from VIA */
#endif
/* delay 4ms and then clear any pending interrupt */
mdelay(4);
(void)in_8(&via[SR]);
out_8(&via[IFR], SR_INT);
/* sync with the CUDA - assert TACK without TIP */
out_8(&via[B], in_8(&via[B]) & ~TACK);
/* wait for the CUDA to assert TREQ in response */
WAIT_FOR((in_8(&via[B]) & TREQ) == 0, "CUDA response to sync");
/* wait for the interrupt and then clear it */
WAIT_FOR(in_8(&via[IFR]) & SR_INT, "CUDA response to sync (2)");
(void)in_8(&via[SR]);
out_8(&via[IFR], SR_INT);
/* finish the sync by negating TACK */
out_8(&via[B], in_8(&via[B]) | TACK);
/* wait for the CUDA to negate TREQ and the corresponding interrupt */
WAIT_FOR(in_8(&via[B]) & TREQ, "CUDA response to sync (3)");
WAIT_FOR(in_8(&via[IFR]) & SR_INT, "CUDA response to sync (4)");
(void)in_8(&via[SR]);
out_8(&via[IFR], SR_INT);
out_8(&via[B], in_8(&via[B]) | TIP); /* should be unnecessary */
return 0;
}
#ifdef CONFIG_ADB
/* Send an ADB command */
static int
cuda_send_request(struct adb_request *req, int sync)
{
int i;
if ((via == NULL) || !cuda_fully_inited) {
req->complete = 1;
return -ENXIO;
}
req->reply_expected = 1;
i = cuda_write(req);
if (i)
return i;
if (sync) {
while (!req->complete)
cuda_poll();
}
return 0;
}
/* Enable/disable autopolling */
static int
cuda_adb_autopoll(int devs)
{
struct adb_request req;
if ((via == NULL) || !cuda_fully_inited)
return -ENXIO;
cuda_request(&req, NULL, 3, CUDA_PACKET, CUDA_AUTOPOLL, (devs? 1: 0));
while (!req.complete)
cuda_poll();
return 0;
}
/* Reset adb bus - how do we do this?? */
static int
cuda_reset_adb_bus(void)
{
struct adb_request req;
if ((via == NULL) || !cuda_fully_inited)
return -ENXIO;
cuda_request(&req, NULL, 2, ADB_PACKET, 0); /* maybe? */
while (!req.complete)
cuda_poll();
return 0;
}
#endif /* CONFIG_ADB */
/* Construct and send a cuda request */
int
cuda_request(struct adb_request *req, void (*done)(struct adb_request *),
int nbytes, ...)
{
va_list list;
int i;
if (via == NULL) {
req->complete = 1;
return -ENXIO;
}
req->nbytes = nbytes;
req->done = done;
va_start(list, nbytes);
for (i = 0; i < nbytes; ++i)
req->data[i] = va_arg(list, int);
va_end(list);
req->reply_expected = 1;
return cuda_write(req);
}
static int
cuda_write(struct adb_request *req)
{
unsigned long flags;
if (req->nbytes < 2 || req->data[0] > CUDA_PACKET) {
req->complete = 1;
return -EINVAL;
}
req->next = NULL;
req->sent = 0;
req->complete = 0;
req->reply_len = 0;
spin_lock_irqsave(&cuda_lock, flags);
if (current_req != 0) {
last_req->next = req;
last_req = req;
} else {
current_req = req;
last_req = req;
if (cuda_state == idle)
cuda_start();
}
spin_unlock_irqrestore(&cuda_lock, flags);
return 0;
}
static void
cuda_start(void)
{
struct adb_request *req;
/* assert cuda_state == idle */
/* get the packet to send */
req = current_req;
if (req == 0)
return;
if ((in_8(&via[B]) & TREQ) == 0)
return; /* a byte is coming in from the CUDA */
/* set the shift register to shift out and send a byte */
out_8(&via[ACR], in_8(&via[ACR]) | SR_OUT);
out_8(&via[SR], req->data[0]);
out_8(&via[B], in_8(&via[B]) & ~TIP);
cuda_state = sent_first_byte;
}
void
cuda_poll(void)
{
/* cuda_interrupt only takes a normal lock, we disable
* interrupts here to avoid re-entering and thus deadlocking.
*/
disable_irq(cuda_irq);
cuda_interrupt(0, NULL);
enable_irq(cuda_irq);
}
static irqreturn_t
cuda_interrupt(int irq, void *arg)
{
int status;
struct adb_request *req = NULL;
unsigned char ibuf[16];
int ibuf_len = 0;
int complete = 0;
spin_lock(&cuda_lock);
/* On powermacs, this handler is registered for the VIA IRQ. But it uses
* just the shift register IRQ -- other VIA interrupt sources are disabled.
* On m68k macs, the VIA IRQ sources are dispatched individually. Unless
* we are polling, the shift register IRQ flag has already been cleared.
*/
#ifdef CONFIG_MAC
if (!arg)
#endif
{
if ((in_8(&via[IFR]) & SR_INT) == 0) {
spin_unlock(&cuda_lock);
return IRQ_NONE;
} else {
out_8(&via[IFR], SR_INT);
}
}
status = (~in_8(&via[B]) & (TIP|TREQ)) | (in_8(&via[ACR]) & SR_OUT);
/* printk("cuda_interrupt: state=%d status=%x\n", cuda_state, status); */
switch (cuda_state) {
case idle:
/* CUDA has sent us the first byte of data - unsolicited */
if (status != TREQ)
printk("cuda: state=idle, status=%x\n", status);
(void)in_8(&via[SR]);
out_8(&via[B], in_8(&via[B]) & ~TIP);
cuda_state = reading;
reply_ptr = cuda_rbuf;
reading_reply = 0;
break;
case awaiting_reply:
/* CUDA has sent us the first byte of data of a reply */
if (status != TREQ)
printk("cuda: state=awaiting_reply, status=%x\n", status);
(void)in_8(&via[SR]);
out_8(&via[B], in_8(&via[B]) & ~TIP);
cuda_state = reading;
reply_ptr = current_req->reply;
reading_reply = 1;
break;
case sent_first_byte:
if (status == TREQ + TIP + SR_OUT) {
/* collision */
out_8(&via[ACR], in_8(&via[ACR]) & ~SR_OUT);
(void)in_8(&via[SR]);
out_8(&via[B], in_8(&via[B]) | TIP | TACK);
cuda_state = idle;
} else {
/* assert status == TIP + SR_OUT */
if (status != TIP + SR_OUT)
printk("cuda: state=sent_first_byte status=%x\n", status);
out_8(&via[SR], current_req->data[1]);
out_8(&via[B], in_8(&via[B]) ^ TACK);
data_index = 2;
cuda_state = sending;
}
break;
case sending:
req = current_req;
if (data_index >= req->nbytes) {
out_8(&via[ACR], in_8(&via[ACR]) & ~SR_OUT);
(void)in_8(&via[SR]);
out_8(&via[B], in_8(&via[B]) | TACK | TIP);
req->sent = 1;
if (req->reply_expected) {
cuda_state = awaiting_reply;
} else {
current_req = req->next;
complete = 1;
/* not sure about this */
cuda_state = idle;
cuda_start();
}
} else {
out_8(&via[SR], req->data[data_index++]);
out_8(&via[B], in_8(&via[B]) ^ TACK);
}
break;
case reading:
*reply_ptr++ = in_8(&via[SR]);
if (status == TIP) {
/* that's all folks */
out_8(&via[B], in_8(&via[B]) | TACK | TIP);
cuda_state = read_done;
} else {
/* assert status == TIP | TREQ */
if (status != TIP + TREQ)
printk("cuda: state=reading status=%x\n", status);
out_8(&via[B], in_8(&via[B]) ^ TACK);
}
break;
case read_done:
(void)in_8(&via[SR]);
if (reading_reply) {
req = current_req;
req->reply_len = reply_ptr - req->reply;
if (req->data[0] == ADB_PACKET) {
/* Have to adjust the reply from ADB commands */
if (req->reply_len <= 2 || (req->reply[1] & 2) != 0) {
/* the 0x2 bit indicates no response */
req->reply_len = 0;
} else {
/* leave just the command and result bytes in the reply */
req->reply_len -= 2;
memmove(req->reply, req->reply + 2, req->reply_len);
}
}
current_req = req->next;
complete = 1;
} else {
/* This is tricky. We must break the spinlock to call
* cuda_input. However, doing so means we might get
* re-entered from another CPU getting an interrupt
* or calling cuda_poll(). I ended up using the stack
* (it's only for 16 bytes) and moving the actual
* call to cuda_input to outside of the lock.
*/
ibuf_len = reply_ptr - cuda_rbuf;
memcpy(ibuf, cuda_rbuf, ibuf_len);
}
if (status == TREQ) {
out_8(&via[B], in_8(&via[B]) & ~TIP);
cuda_state = reading;
reply_ptr = cuda_rbuf;
reading_reply = 0;
} else {
cuda_state = idle;
cuda_start();
}
break;
default:
printk("cuda_interrupt: unknown cuda_state %d?\n", cuda_state);
}
spin_unlock(&cuda_lock);
if (complete && req) {
void (*done)(struct adb_request *) = req->done;
mb();
req->complete = 1;
/* Here, we assume that if the request has a done member, the
* struct request will survive to setting req->complete to 1
*/
if (done)
(*done)(req);
}
if (ibuf_len)
cuda_input(ibuf, ibuf_len);
return IRQ_HANDLED;
}
static void
cuda_input(unsigned char *buf, int nb)
{
int i;
switch (buf[0]) {
case ADB_PACKET:
#ifdef CONFIG_XMON
if (nb == 5 && buf[2] == 0x2c) {
extern int xmon_wants_key, xmon_adb_keycode;
if (xmon_wants_key) {
xmon_adb_keycode = buf[3];
return;
}
}
#endif /* CONFIG_XMON */
#ifdef CONFIG_ADB
adb_input(buf+2, nb-2, buf[1] & 0x40);
#endif /* CONFIG_ADB */
break;
default:
printk("data from cuda (%d bytes):", nb);
for (i = 0; i < nb; ++i)
printk(" %.2x", buf[i]);
printk("\n");
}
}

View File

@@ -0,0 +1,550 @@
/*
* Device driver for the via ADB on (many) Mac II-class machines
*
* Based on the original ADB keyboard handler Copyright (c) 1997 Alan Cox
* Also derived from code Copyright (C) 1996 Paul Mackerras.
*
* With various updates provided over the years by Michael Schmitz,
* Guideo Koerber and others.
*
* Rewrite for Unified ADB by Joshua M. Thompson (funaho@jurai.org)
*
* 1999-08-02 (jmt) - Initial rewrite for Unified ADB.
* 2000-03-29 Tony Mantler <tonym@mac.linux-m68k.org>
* - Big overhaul, should actually work now.
* 2006-12-31 Finn Thain <fthain@telegraphics.com.au> - Another overhaul.
*
* Suggested reading:
* Inside Macintosh, ch. 5 ADB Manager
* Guide to the Macinstosh Family Hardware, ch. 8 Apple Desktop Bus
* Rockwell R6522 VIA datasheet
*
* Apple's "ADB Analyzer" bus sniffer is invaluable:
* ftp://ftp.apple.com/developer/Tool_Chest/Devices_-_Hardware/Apple_Desktop_Bus/
*/
#include <stdarg.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/adb.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <asm/macintosh.h>
#include <asm/macints.h>
#include <asm/mac_via.h>
#include <asm/system.h>
static volatile unsigned char *via;
/* VIA registers - spaced 0x200 bytes apart */
#define RS 0x200 /* skip between registers */
#define B 0 /* B-side data */
#define A RS /* A-side data */
#define DIRB (2*RS) /* B-side direction (1=output) */
#define DIRA (3*RS) /* A-side direction (1=output) */
#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */
#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */
#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */
#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */
#define T2CL (8*RS) /* Timer 2 ctr/latch (low 8 bits) */
#define T2CH (9*RS) /* Timer 2 counter (high 8 bits) */
#define SR (10*RS) /* Shift register */
#define ACR (11*RS) /* Auxiliary control register */
#define PCR (12*RS) /* Peripheral control register */
#define IFR (13*RS) /* Interrupt flag register */
#define IER (14*RS) /* Interrupt enable register */
#define ANH (15*RS) /* A-side data, no handshake */
/* Bits in B data register: all active low */
#define CTLR_IRQ 0x08 /* Controller rcv status (input) */
#define ST_MASK 0x30 /* mask for selecting ADB state bits */
/* Bits in ACR */
#define SR_CTRL 0x1c /* Shift register control bits */
#define SR_EXT 0x0c /* Shift on external clock */
#define SR_OUT 0x10 /* Shift out if 1 */
/* Bits in IFR and IER */
#define IER_SET 0x80 /* set bits in IER */
#define IER_CLR 0 /* clear bits in IER */
#define SR_INT 0x04 /* Shift register full/empty */
/* ADB transaction states according to GMHW */
#define ST_CMD 0x00 /* ADB state: command byte */
#define ST_EVEN 0x10 /* ADB state: even data byte */
#define ST_ODD 0x20 /* ADB state: odd data byte */
#define ST_IDLE 0x30 /* ADB state: idle, nothing to send */
static int macii_init_via(void);
static void macii_start(void);
static irqreturn_t macii_interrupt(int irq, void *arg);
static void macii_queue_poll(void);
static int macii_probe(void);
static int macii_init(void);
static int macii_send_request(struct adb_request *req, int sync);
static int macii_write(struct adb_request *req);
static int macii_autopoll(int devs);
static void macii_poll(void);
static int macii_reset_bus(void);
struct adb_driver via_macii_driver = {
"Mac II",
macii_probe,
macii_init,
macii_send_request,
macii_autopoll,
macii_poll,
macii_reset_bus
};
static enum macii_state {
idle,
sending,
reading,
read_done,
} macii_state;
static struct adb_request *current_req; /* first request struct in the queue */
static struct adb_request *last_req; /* last request struct in the queue */
static unsigned char reply_buf[16]; /* storage for autopolled replies */
static unsigned char *reply_ptr; /* next byte in reply_buf or req->reply */
static int reading_reply; /* store reply in reply_buf else req->reply */
static int data_index; /* index of the next byte to send from req->data */
static int reply_len; /* number of bytes received in reply_buf or req->reply */
static int status; /* VIA's ADB status bits captured upon interrupt */
static int last_status; /* status bits as at previous interrupt */
static int srq_asserted; /* have to poll for the device that asserted it */
static int command_byte; /* the most recent command byte transmitted */
static int autopoll_devs; /* bits set are device addresses to be polled */
/* Sanity check for request queue. Doesn't check for cycles. */
static int request_is_queued(struct adb_request *req) {
struct adb_request *cur;
unsigned long flags;
local_irq_save(flags);
cur = current_req;
while (cur) {
if (cur == req) {
local_irq_restore(flags);
return 1;
}
cur = cur->next;
}
local_irq_restore(flags);
return 0;
}
/* Check for MacII style ADB */
static int macii_probe(void)
{
if (macintosh_config->adb_type != MAC_ADB_II) return -ENODEV;
via = via1;
printk("adb: Mac II ADB Driver v1.0 for Unified ADB\n");
return 0;
}
/* Initialize the driver */
int macii_init(void)
{
unsigned long flags;
int err;
local_irq_save(flags);
err = macii_init_via();
if (err) goto out;
err = request_irq(IRQ_MAC_ADB, macii_interrupt, IRQ_FLG_LOCK, "ADB",
macii_interrupt);
if (err) goto out;
macii_state = idle;
out:
local_irq_restore(flags);
return err;
}
/* initialize the hardware */
static int macii_init_via(void)
{
unsigned char x;
/* We want CTLR_IRQ as input and ST_EVEN | ST_ODD as output lines. */
via[DIRB] = (via[DIRB] | ST_EVEN | ST_ODD) & ~CTLR_IRQ;
/* Set up state: idle */
via[B] |= ST_IDLE;
last_status = via[B] & (ST_MASK|CTLR_IRQ);
/* Shift register on input */
via[ACR] = (via[ACR] & ~SR_CTRL) | SR_EXT;
/* Wipe any pending data and int */
x = via[SR];
return 0;
}
/* Send an ADB poll (Talk Register 0 command prepended to the request queue) */
static void macii_queue_poll(void)
{
/* No point polling the active device as it will never assert SRQ, so
* poll the next device in the autopoll list. This could leave us
* stuck in a polling loop if an unprobed device is asserting SRQ.
* In theory, that could only happen if a device was plugged in after
* probing started. Unplugging it again will break the cycle.
* (Simply polling the next higher device often ends up polling almost
* every device (after wrapping around), which takes too long.)
*/
int device_mask;
int next_device;
static struct adb_request req;
if (!autopoll_devs) return;
device_mask = (1 << (((command_byte & 0xF0) >> 4) + 1)) - 1;
if (autopoll_devs & ~device_mask)
next_device = ffs(autopoll_devs & ~device_mask) - 1;
else
next_device = ffs(autopoll_devs) - 1;
BUG_ON(request_is_queued(&req));
adb_request(&req, NULL, ADBREQ_NOSEND, 1,
ADB_READREG(next_device, 0));
req.sent = 0;
req.complete = 0;
req.reply_len = 0;
req.next = current_req;
if (current_req != NULL) {
current_req = &req;
} else {
current_req = &req;
last_req = &req;
}
}
/* Send an ADB request; if sync, poll out the reply 'till it's done */
static int macii_send_request(struct adb_request *req, int sync)
{
int err;
unsigned long flags;
BUG_ON(request_is_queued(req));
local_irq_save(flags);
err = macii_write(req);
local_irq_restore(flags);
if (!err && sync) {
while (!req->complete) {
macii_poll();
}
BUG_ON(request_is_queued(req));
}
return err;
}
/* Send an ADB request (append to request queue) */
static int macii_write(struct adb_request *req)
{
if (req->nbytes < 2 || req->data[0] != ADB_PACKET || req->nbytes > 15) {
req->complete = 1;
return -EINVAL;
}
req->next = NULL;
req->sent = 0;
req->complete = 0;
req->reply_len = 0;
if (current_req != NULL) {
last_req->next = req;
last_req = req;
} else {
current_req = req;
last_req = req;
if (macii_state == idle) macii_start();
}
return 0;
}
/* Start auto-polling */
static int macii_autopoll(int devs)
{
static struct adb_request req;
unsigned long flags;
int err = 0;
/* bit 1 == device 1, and so on. */
autopoll_devs = devs & 0xFFFE;
if (!autopoll_devs) return 0;
local_irq_save(flags);
if (current_req == NULL) {
/* Send a Talk Reg 0. The controller will repeatedly transmit
* this as long as it is idle.
*/
adb_request(&req, NULL, ADBREQ_NOSEND, 1,
ADB_READREG(ffs(autopoll_devs) - 1, 0));
err = macii_write(&req);
}
local_irq_restore(flags);
return err;
}
static inline int need_autopoll(void) {
/* Was the last command Talk Reg 0
* and is the target on the autopoll list?
*/
if ((command_byte & 0x0F) == 0x0C &&
((1 << ((command_byte & 0xF0) >> 4)) & autopoll_devs))
return 0;
return 1;
}
/* Prod the chip without interrupts */
static void macii_poll(void)
{
disable_irq(IRQ_MAC_ADB);
macii_interrupt(0, NULL);
enable_irq(IRQ_MAC_ADB);
}
/* Reset the bus */
static int macii_reset_bus(void)
{
static struct adb_request req;
if (request_is_queued(&req))
return 0;
/* Command = 0, Address = ignored */
adb_request(&req, NULL, 0, 1, ADB_BUSRESET);
/* Don't want any more requests during the Global Reset low time. */
udelay(3000);
return 0;
}
/* Start sending ADB packet */
static void macii_start(void)
{
struct adb_request *req;
req = current_req;
BUG_ON(req == NULL);
BUG_ON(macii_state != idle);
/* Now send it. Be careful though, that first byte of the request
* is actually ADB_PACKET; the real data begins at index 1!
* And req->nbytes is the number of bytes of real data plus one.
*/
/* store command byte */
command_byte = req->data[1];
/* Output mode */
via[ACR] |= SR_OUT;
/* Load data */
via[SR] = req->data[1];
/* set ADB state to 'command' */
via[B] = (via[B] & ~ST_MASK) | ST_CMD;
macii_state = sending;
data_index = 2;
}
/*
* The notorious ADB interrupt handler - does all of the protocol handling.
* Relies on the ADB controller sending and receiving data, thereby
* generating shift register interrupts (SR_INT) for us. This means there has
* to be activity on the ADB bus. The chip will poll to achieve this.
*
* The basic ADB state machine was left unchanged from the original MacII code
* by Alan Cox, which was based on the CUDA driver for PowerMac.
* The syntax of the ADB status lines is totally different on MacII,
* though. MacII uses the states Command -> Even -> Odd -> Even ->...-> Idle
* for sending and Idle -> Even -> Odd -> Even ->...-> Idle for receiving.
* Start and end of a receive packet are signalled by asserting /IRQ on the
* interrupt line (/IRQ means the CTLR_IRQ bit in port B; not to be confused
* with the VIA shift register interrupt. /IRQ never actually interrupts the
* processor, it's just an ordinary input.)
*/
static irqreturn_t macii_interrupt(int irq, void *arg)
{
int x;
static int entered;
struct adb_request *req;
if (!arg) {
/* Clear the SR IRQ flag when polling. */
if (via[IFR] & SR_INT)
via[IFR] = SR_INT;
else
return IRQ_NONE;
}
BUG_ON(entered++);
last_status = status;
status = via[B] & (ST_MASK|CTLR_IRQ);
switch (macii_state) {
case idle:
if (reading_reply) {
reply_ptr = current_req->reply;
} else {
BUG_ON(current_req != NULL);
reply_ptr = reply_buf;
}
x = via[SR];
if ((status & CTLR_IRQ) && (x == 0xFF)) {
/* Bus timeout without SRQ sequence:
* data is "FF" while CTLR_IRQ is "H"
*/
reply_len = 0;
srq_asserted = 0;
macii_state = read_done;
} else {
macii_state = reading;
*reply_ptr = x;
reply_len = 1;
}
/* set ADB state = even for first data byte */
via[B] = (via[B] & ~ST_MASK) | ST_EVEN;
break;
case sending:
req = current_req;
if (data_index >= req->nbytes) {
req->sent = 1;
macii_state = idle;
if (req->reply_expected) {
reading_reply = 1;
} else {
req->complete = 1;
current_req = req->next;
if (req->done) (*req->done)(req);
if (current_req)
macii_start();
else
if (need_autopoll())
macii_autopoll(autopoll_devs);
}
if (macii_state == idle) {
/* reset to shift in */
via[ACR] &= ~SR_OUT;
x = via[SR];
/* set ADB state idle - might get SRQ */
via[B] = (via[B] & ~ST_MASK) | ST_IDLE;
}
} else {
via[SR] = req->data[data_index++];
if ( (via[B] & ST_MASK) == ST_CMD ) {
/* just sent the command byte, set to EVEN */
via[B] = (via[B] & ~ST_MASK) | ST_EVEN;
} else {
/* invert state bits, toggle ODD/EVEN */
via[B] ^= ST_MASK;
}
}
break;
case reading:
x = via[SR];
BUG_ON((status & ST_MASK) == ST_CMD ||
(status & ST_MASK) == ST_IDLE);
/* Bus timeout with SRQ sequence:
* data is "XX FF" while CTLR_IRQ is "L L"
* End of packet without SRQ sequence:
* data is "XX...YY 00" while CTLR_IRQ is "L...H L"
* End of packet SRQ sequence:
* data is "XX...YY 00" while CTLR_IRQ is "L...L L"
* (where XX is the first response byte and
* YY is the last byte of valid response data.)
*/
srq_asserted = 0;
if (!(status & CTLR_IRQ)) {
if (x == 0xFF) {
if (!(last_status & CTLR_IRQ)) {
macii_state = read_done;
reply_len = 0;
srq_asserted = 1;
}
} else if (x == 0x00) {
macii_state = read_done;
if (!(last_status & CTLR_IRQ))
srq_asserted = 1;
}
}
if (macii_state == reading) {
BUG_ON(reply_len > 15);
reply_ptr++;
*reply_ptr = x;
reply_len++;
}
/* invert state bits, toggle ODD/EVEN */
via[B] ^= ST_MASK;
break;
case read_done:
x = via[SR];
if (reading_reply) {
reading_reply = 0;
req = current_req;
req->reply_len = reply_len;
req->complete = 1;
current_req = req->next;
if (req->done) (*req->done)(req);
} else if (reply_len && autopoll_devs)
adb_input(reply_buf, reply_len, 0);
macii_state = idle;
/* SRQ seen before, initiate poll now */
if (srq_asserted)
macii_queue_poll();
if (current_req)
macii_start();
else
if (need_autopoll())
macii_autopoll(autopoll_devs);
if (macii_state == idle)
via[B] = (via[B] & ~ST_MASK) | ST_IDLE;
break;
default:
break;
}
entered--;
return IRQ_HANDLED;
}

View File

@@ -0,0 +1,677 @@
/*
* Device driver for the IIsi-style ADB on some Mac LC and II-class machines
*
* Based on via-cuda.c and via-macii.c, as well as the original
* adb-bus.c, which in turn is somewhat influenced by (but uses no
* code from) the NetBSD HWDIRECT ADB code. Original IIsi driver work
* was done by Robert Thompson and integrated into the old style
* driver by Michael Schmitz.
*
* Original sources (c) Alan Cox, Paul Mackerras, and others.
*
* Rewritten for Unified ADB by David Huggins-Daines <dhd@debian.org>
*
* 7/13/2000- extensive changes by Andrew McPherson <andrew@macduff.dhs.org>
* Works about 30% of the time now.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/adb.h>
#include <linux/cuda.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/macintosh.h>
#include <asm/macints.h>
#include <asm/mac_via.h>
static volatile unsigned char *via;
/* VIA registers - spaced 0x200 bytes apart - only the ones we actually use */
#define RS 0x200 /* skip between registers */
#define B 0 /* B-side data */
#define A RS /* A-side data */
#define DIRB (2*RS) /* B-side direction (1=output) */
#define DIRA (3*RS) /* A-side direction (1=output) */
#define SR (10*RS) /* Shift register */
#define ACR (11*RS) /* Auxiliary control register */
#define IFR (13*RS) /* Interrupt flag register */
#define IER (14*RS) /* Interrupt enable register */
/* Bits in B data register: all active low */
#define TREQ 0x08 /* Transfer request (input) */
#define TACK 0x10 /* Transfer acknowledge (output) */
#define TIP 0x20 /* Transfer in progress (output) */
#define ST_MASK 0x30 /* mask for selecting ADB state bits */
/* Bits in ACR */
#define SR_CTRL 0x1c /* Shift register control bits */
#define SR_EXT 0x0c /* Shift on external clock */
#define SR_OUT 0x10 /* Shift out if 1 */
/* Bits in IFR and IER */
#define IER_SET 0x80 /* set bits in IER */
#define IER_CLR 0 /* clear bits in IER */
#define SR_INT 0x04 /* Shift register full/empty */
#define SR_DATA 0x08 /* Shift register data */
#define SR_CLOCK 0x10 /* Shift register clock */
#define ADB_DELAY 150
#undef DEBUG_MACIISI_ADB
static struct adb_request* current_req;
static struct adb_request* last_req;
static unsigned char maciisi_rbuf[16];
static unsigned char *reply_ptr;
static int data_index;
static int reading_reply;
static int reply_len;
static int tmp;
static int need_sync;
static enum maciisi_state {
idle,
sending,
reading,
} maciisi_state;
static int maciisi_probe(void);
static int maciisi_init(void);
static int maciisi_send_request(struct adb_request* req, int sync);
static void maciisi_sync(struct adb_request *req);
static int maciisi_write(struct adb_request* req);
static irqreturn_t maciisi_interrupt(int irq, void* arg);
static void maciisi_input(unsigned char *buf, int nb);
static int maciisi_init_via(void);
static void maciisi_poll(void);
static int maciisi_start(void);
struct adb_driver via_maciisi_driver = {
"Mac IIsi",
maciisi_probe,
maciisi_init,
maciisi_send_request,
NULL, /* maciisi_adb_autopoll, */
maciisi_poll,
NULL /* maciisi_reset_adb_bus */
};
static int
maciisi_probe(void)
{
if (macintosh_config->adb_type != MAC_ADB_IISI)
return -ENODEV;
via = via1;
return 0;
}
static int
maciisi_init(void)
{
int err;
if (via == NULL)
return -ENODEV;
if ((err = maciisi_init_via())) {
printk(KERN_ERR "maciisi_init: maciisi_init_via() failed, code %d\n", err);
via = NULL;
return err;
}
if (request_irq(IRQ_MAC_ADB, maciisi_interrupt, IRQ_FLG_LOCK | IRQ_FLG_FAST,
"ADB", maciisi_interrupt)) {
printk(KERN_ERR "maciisi_init: can't get irq %d\n", IRQ_MAC_ADB);
return -EAGAIN;
}
printk("adb: Mac IIsi driver v0.2 for Unified ADB.\n");
return 0;
}
/* Flush data from the ADB controller */
static void
maciisi_stfu(void)
{
int status = via[B] & (TIP|TREQ);
if (status & TREQ) {
#ifdef DEBUG_MACIISI_ADB
printk (KERN_DEBUG "maciisi_stfu called with TREQ high!\n");
#endif
return;
}
udelay(ADB_DELAY);
via[ACR] &= ~SR_OUT;
via[IER] = IER_CLR | SR_INT;
udelay(ADB_DELAY);
status = via[B] & (TIP|TREQ);
if (!(status & TREQ))
{
via[B] |= TIP;
while(1)
{
int poll_timeout = ADB_DELAY * 5;
/* Poll for SR interrupt */
while (!(via[IFR] & SR_INT) && poll_timeout-- > 0)
status = via[B] & (TIP|TREQ);
tmp = via[SR]; /* Clear shift register */
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "maciisi_stfu: status %x timeout %d data %x\n",
status, poll_timeout, tmp);
#endif
if(via[B] & TREQ)
break;
/* ACK on-off */
via[B] |= TACK;
udelay(ADB_DELAY);
via[B] &= ~TACK;
}
/* end frame */
via[B] &= ~TIP;
udelay(ADB_DELAY);
}
via[IER] = IER_SET | SR_INT;
}
/* All specifically VIA-related initialization goes here */
static int
maciisi_init_via(void)
{
int i;
/* Set the lines up. We want TREQ as input TACK|TIP as output */
via[DIRB] = (via[DIRB] | TACK | TIP) & ~TREQ;
/* Shift register on input */
via[ACR] = (via[ACR] & ~SR_CTRL) | SR_EXT;
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "maciisi_init_via: initial status %x\n", via[B] & (TIP|TREQ));
#endif
/* Wipe any pending data and int */
tmp = via[SR];
/* Enable keyboard interrupts */
via[IER] = IER_SET | SR_INT;
/* Set initial state: idle */
via[B] &= ~(TACK|TIP);
/* Clear interrupt bit */
via[IFR] = SR_INT;
for(i = 0; i < 60; i++) {
udelay(ADB_DELAY);
maciisi_stfu();
udelay(ADB_DELAY);
if(via[B] & TREQ)
break;
}
if (i == 60)
printk(KERN_ERR "maciisi_init_via: bus jam?\n");
maciisi_state = idle;
need_sync = 0;
return 0;
}
/* Send a request, possibly waiting for a reply */
static int
maciisi_send_request(struct adb_request* req, int sync)
{
int i;
#ifdef DEBUG_MACIISI_ADB
static int dump_packet = 0;
#endif
if (via == NULL) {
req->complete = 1;
return -ENXIO;
}
#ifdef DEBUG_MACIISI_ADB
if (dump_packet) {
printk(KERN_DEBUG "maciisi_send_request:");
for (i = 0; i < req->nbytes; i++) {
printk(" %.2x", req->data[i]);
}
printk(" sync %d\n", sync);
}
#endif
req->reply_expected = 1;
i = maciisi_write(req);
if (i)
{
/* Normally, if a packet requires syncing, that happens at the end of
* maciisi_send_request. But if the transfer fails, it will be restarted
* by maciisi_interrupt(). We use need_sync to tell maciisi_interrupt
* when to sync a packet that it sends out.
*
* Suggestions on a better way to do this are welcome.
*/
if(i == -EBUSY && sync)
need_sync = 1;
else
need_sync = 0;
return i;
}
if(sync)
maciisi_sync(req);
return 0;
}
/* Poll the ADB chip until the request completes */
static void maciisi_sync(struct adb_request *req)
{
int count = 0;
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "maciisi_sync called\n");
#endif
/* If for some reason the ADB chip shuts up on us, we want to avoid an endless loop. */
while (!req->complete && count++ < 50) {
maciisi_poll();
}
/* This could be BAD... when the ADB controller doesn't respond
* for this long, it's probably not coming back :-( */
if (count > 50) /* Hopefully shouldn't happen */
printk(KERN_ERR "maciisi_send_request: poll timed out!\n");
}
int
maciisi_request(struct adb_request *req, void (*done)(struct adb_request *),
int nbytes, ...)
{
va_list list;
int i;
req->nbytes = nbytes;
req->done = done;
req->reply_expected = 0;
va_start(list, nbytes);
for (i = 0; i < nbytes; i++)
req->data[i++] = va_arg(list, int);
va_end(list);
return maciisi_send_request(req, 1);
}
/* Enqueue a request, and run the queue if possible */
static int
maciisi_write(struct adb_request* req)
{
unsigned long flags;
int i;
/* We will accept CUDA packets - the VIA sends them to us, so
it figures that we should be able to send them to it */
if (req->nbytes < 2 || req->data[0] > CUDA_PACKET) {
printk(KERN_ERR "maciisi_write: packet too small or not an ADB or CUDA packet\n");
req->complete = 1;
return -EINVAL;
}
req->next = NULL;
req->sent = 0;
req->complete = 0;
req->reply_len = 0;
local_irq_save(flags);
if (current_req) {
last_req->next = req;
last_req = req;
} else {
current_req = req;
last_req = req;
}
if (maciisi_state == idle)
{
i = maciisi_start();
if(i != 0)
{
local_irq_restore(flags);
return i;
}
}
else
{
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "maciisi_write: would start, but state is %d\n", maciisi_state);
#endif
local_irq_restore(flags);
return -EBUSY;
}
local_irq_restore(flags);
return 0;
}
static int
maciisi_start(void)
{
struct adb_request* req;
int status;
#ifdef DEBUG_MACIISI_ADB
status = via[B] & (TIP | TREQ);
printk(KERN_DEBUG "maciisi_start called, state=%d, status=%x, ifr=%x\n", maciisi_state, status, via[IFR]);
#endif
if (maciisi_state != idle) {
/* shouldn't happen */
printk(KERN_ERR "maciisi_start: maciisi_start called when driver busy!\n");
return -EBUSY;
}
req = current_req;
if (req == NULL)
return -EINVAL;
status = via[B] & (TIP|TREQ);
if (!(status & TREQ)) {
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "maciisi_start: bus busy - aborting\n");
#endif
return -EBUSY;
}
/* Okay, send */
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "maciisi_start: sending\n");
#endif
/* Set state to active */
via[B] |= TIP;
/* ACK off */
via[B] &= ~TACK;
/* Delay */
udelay(ADB_DELAY);
/* Shift out and send */
via[ACR] |= SR_OUT;
via[SR] = req->data[0];
data_index = 1;
/* ACK on */
via[B] |= TACK;
maciisi_state = sending;
return 0;
}
void
maciisi_poll(void)
{
unsigned long flags;
local_irq_save(flags);
if (via[IFR] & SR_INT) {
maciisi_interrupt(0, NULL);
}
else /* avoid calling this function too quickly in a loop */
udelay(ADB_DELAY);
local_irq_restore(flags);
}
/* Shift register interrupt - this is *supposed* to mean that the
register is either full or empty. In practice, I have no idea what
it means :( */
static irqreturn_t
maciisi_interrupt(int irq, void* arg)
{
int status;
struct adb_request *req;
#ifdef DEBUG_MACIISI_ADB
static int dump_reply = 0;
#endif
int i;
unsigned long flags;
local_irq_save(flags);
status = via[B] & (TIP|TREQ);
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "state %d status %x ifr %x\n", maciisi_state, status, via[IFR]);
#endif
if (!(via[IFR] & SR_INT)) {
/* Shouldn't happen, we hope */
printk(KERN_ERR "maciisi_interrupt: called without interrupt flag set\n");
local_irq_restore(flags);
return IRQ_NONE;
}
/* Clear the interrupt */
/* via[IFR] = SR_INT; */
switch_start:
switch (maciisi_state) {
case idle:
if (status & TIP)
printk(KERN_ERR "maciisi_interrupt: state is idle but TIP asserted!\n");
if(!reading_reply)
udelay(ADB_DELAY);
/* Shift in */
via[ACR] &= ~SR_OUT;
/* Signal start of frame */
via[B] |= TIP;
/* Clear the interrupt (throw this value on the floor, it's useless) */
tmp = via[SR];
/* ACK adb chip, high-low */
via[B] |= TACK;
udelay(ADB_DELAY);
via[B] &= ~TACK;
reply_len = 0;
maciisi_state = reading;
if (reading_reply) {
reply_ptr = current_req->reply;
} else {
reply_ptr = maciisi_rbuf;
}
break;
case sending:
/* via[SR]; */
/* Set ACK off */
via[B] &= ~TACK;
req = current_req;
if (!(status & TREQ)) {
/* collision */
printk(KERN_ERR "maciisi_interrupt: send collision\n");
/* Set idle and input */
via[ACR] &= ~SR_OUT;
tmp = via[SR];
via[B] &= ~TIP;
/* Must re-send */
reading_reply = 0;
reply_len = 0;
maciisi_state = idle;
udelay(ADB_DELAY);
/* process this now, because the IFR has been cleared */
goto switch_start;
}
udelay(ADB_DELAY);
if (data_index >= req->nbytes) {
/* Sent the whole packet, put the bus back in idle state */
/* Shift in, we are about to read a reply (hopefully) */
via[ACR] &= ~SR_OUT;
tmp = via[SR];
/* End of frame */
via[B] &= ~TIP;
req->sent = 1;
maciisi_state = idle;
if (req->reply_expected) {
/* Note: only set this once we've
successfully sent the packet */
reading_reply = 1;
} else {
current_req = req->next;
if (req->done)
(*req->done)(req);
/* Do any queued requests now */
i = maciisi_start();
if(i == 0 && need_sync) {
/* Packet needs to be synced */
maciisi_sync(current_req);
}
if(i != -EBUSY)
need_sync = 0;
}
} else {
/* Sending more stuff */
/* Shift out */
via[ACR] |= SR_OUT;
/* Write */
via[SR] = req->data[data_index++];
/* Signal 'byte ready' */
via[B] |= TACK;
}
break;
case reading:
/* Shift in */
/* via[ACR] &= ~SR_OUT; */ /* Not in 2.2 */
if (reply_len++ > 16) {
printk(KERN_ERR "maciisi_interrupt: reply too long, aborting read\n");
via[B] |= TACK;
udelay(ADB_DELAY);
via[B] &= ~(TACK|TIP);
maciisi_state = idle;
i = maciisi_start();
if(i == 0 && need_sync) {
/* Packet needs to be synced */
maciisi_sync(current_req);
}
if(i != -EBUSY)
need_sync = 0;
break;
}
/* Read data */
*reply_ptr++ = via[SR];
status = via[B] & (TIP|TREQ);
/* ACK on/off */
via[B] |= TACK;
udelay(ADB_DELAY);
via[B] &= ~TACK;
if (!(status & TREQ))
break; /* more stuff to deal with */
/* end of frame */
via[B] &= ~TIP;
tmp = via[SR]; /* That's what happens in 2.2 */
udelay(ADB_DELAY); /* Give controller time to recover */
/* end of packet, deal with it */
if (reading_reply) {
req = current_req;
req->reply_len = reply_ptr - req->reply;
if (req->data[0] == ADB_PACKET) {
/* Have to adjust the reply from ADB commands */
if (req->reply_len <= 2 || (req->reply[1] & 2) != 0) {
/* the 0x2 bit indicates no response */
req->reply_len = 0;
} else {
/* leave just the command and result bytes in the reply */
req->reply_len -= 2;
memmove(req->reply, req->reply + 2, req->reply_len);
}
}
#ifdef DEBUG_MACIISI_ADB
if (dump_reply) {
int i;
printk(KERN_DEBUG "maciisi_interrupt: reply is ");
for (i = 0; i < req->reply_len; ++i)
printk(" %.2x", req->reply[i]);
printk("\n");
}
#endif
req->complete = 1;
current_req = req->next;
if (req->done)
(*req->done)(req);
/* Obviously, we got it */
reading_reply = 0;
} else {
maciisi_input(maciisi_rbuf, reply_ptr - maciisi_rbuf);
}
maciisi_state = idle;
status = via[B] & (TIP|TREQ);
if (!(status & TREQ)) {
/* Timeout?! More likely, another packet coming in already */
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "extra data after packet: status %x ifr %x\n",
status, via[IFR]);
#endif
#if 0
udelay(ADB_DELAY);
via[B] |= TIP;
maciisi_state = reading;
reading_reply = 0;
reply_ptr = maciisi_rbuf;
#else
/* Process the packet now */
reading_reply = 0;
goto switch_start;
#endif
/* We used to do this... but the controller might actually have data for us */
/* maciisi_stfu(); */
}
else {
/* Do any queued requests now if possible */
i = maciisi_start();
if(i == 0 && need_sync) {
/* Packet needs to be synced */
maciisi_sync(current_req);
}
if(i != -EBUSY)
need_sync = 0;
}
break;
default:
printk("maciisi_interrupt: unknown maciisi_state %d?\n", maciisi_state);
}
local_irq_restore(flags);
return IRQ_HANDLED;
}
static void
maciisi_input(unsigned char *buf, int nb)
{
#ifdef DEBUG_MACIISI_ADB
int i;
#endif
switch (buf[0]) {
case ADB_PACKET:
adb_input(buf+2, nb-2, buf[1] & 0x40);
break;
default:
#ifdef DEBUG_MACIISI_ADB
printk(KERN_DEBUG "data from IIsi ADB (%d bytes):", nb);
for (i = 0; i < nb; ++i)
printk(" %.2x", buf[i]);
printk("\n");
#endif
break;
}
}

View File

@@ -0,0 +1,191 @@
/*
* Backlight code for via-pmu
*
* Copyright (C) 1998 Paul Mackerras and Fabio Riccardi.
* Copyright (C) 2001-2002 Benjamin Herrenschmidt
* Copyright (C) 2006 Michael Hanselmann <linux-kernel@hansmi.ch>
*
*/
#include <asm/ptrace.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <asm/backlight.h>
#include <asm/prom.h>
#define MAX_PMU_LEVEL 0xFF
static struct backlight_ops pmu_backlight_data;
static DEFINE_SPINLOCK(pmu_backlight_lock);
static int sleeping, uses_pmu_bl;
static u8 bl_curve[FB_BACKLIGHT_LEVELS];
static void pmu_backlight_init_curve(u8 off, u8 min, u8 max)
{
int i, flat, count, range = (max - min);
bl_curve[0] = off;
for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat)
bl_curve[flat] = min;
count = FB_BACKLIGHT_LEVELS * 15 / 16;
for (i = 0; i < count; ++i)
bl_curve[flat + i] = min + (range * (i + 1) / count);
}
static int pmu_backlight_curve_lookup(int value)
{
int level = (FB_BACKLIGHT_LEVELS - 1);
int i, max = 0;
/* Look for biggest value */
for (i = 0; i < FB_BACKLIGHT_LEVELS; i++)
max = max((int)bl_curve[i], max);
/* Look for nearest value */
for (i = 0; i < FB_BACKLIGHT_LEVELS; i++) {
int diff = abs(bl_curve[i] - value);
if (diff < max) {
max = diff;
level = i;
}
}
return level;
}
static int pmu_backlight_get_level_brightness(int level)
{
int pmulevel;
/* Get and convert the value */
pmulevel = bl_curve[level] * FB_BACKLIGHT_MAX / MAX_PMU_LEVEL;
if (pmulevel < 0)
pmulevel = 0;
else if (pmulevel > MAX_PMU_LEVEL)
pmulevel = MAX_PMU_LEVEL;
return pmulevel;
}
static int __pmu_backlight_update_status(struct backlight_device *bd)
{
struct adb_request req;
int level = bd->props.brightness;
if (bd->props.power != FB_BLANK_UNBLANK ||
bd->props.fb_blank != FB_BLANK_UNBLANK)
level = 0;
if (level > 0) {
int pmulevel = pmu_backlight_get_level_brightness(level);
pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT, pmulevel);
pmu_wait_complete(&req);
pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
PMU_POW_BACKLIGHT | PMU_POW_ON);
pmu_wait_complete(&req);
} else {
pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
PMU_POW_BACKLIGHT | PMU_POW_OFF);
pmu_wait_complete(&req);
}
return 0;
}
static int pmu_backlight_update_status(struct backlight_device *bd)
{
unsigned long flags;
int rc = 0;
spin_lock_irqsave(&pmu_backlight_lock, flags);
/* Don't update brightness when sleeping */
if (!sleeping)
rc = __pmu_backlight_update_status(bd);
spin_unlock_irqrestore(&pmu_backlight_lock, flags);
return rc;
}
static int pmu_backlight_get_brightness(struct backlight_device *bd)
{
return bd->props.brightness;
}
static struct backlight_ops pmu_backlight_data = {
.get_brightness = pmu_backlight_get_brightness,
.update_status = pmu_backlight_update_status,
};
#ifdef CONFIG_PM
void pmu_backlight_set_sleep(int sleep)
{
unsigned long flags;
spin_lock_irqsave(&pmu_backlight_lock, flags);
sleeping = sleep;
if (pmac_backlight && uses_pmu_bl) {
if (sleep) {
struct adb_request req;
pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
PMU_POW_BACKLIGHT | PMU_POW_OFF);
pmu_wait_complete(&req);
} else
__pmu_backlight_update_status(pmac_backlight);
}
spin_unlock_irqrestore(&pmu_backlight_lock, flags);
}
#endif /* CONFIG_PM */
void __init pmu_backlight_init()
{
struct backlight_device *bd;
char name[10];
int level, autosave;
/* Special case for the old PowerBook since I can't test on it */
autosave =
machine_is_compatible("AAPL,3400/2400") ||
machine_is_compatible("AAPL,3500");
if (!autosave &&
!pmac_has_backlight_type("pmu") &&
!machine_is_compatible("AAPL,PowerBook1998") &&
!machine_is_compatible("PowerBook1,1"))
return;
snprintf(name, sizeof(name), "pmubl");
bd = backlight_device_register(name, NULL, NULL, &pmu_backlight_data);
if (IS_ERR(bd)) {
printk(KERN_ERR "PMU Backlight registration failed\n");
return;
}
uses_pmu_bl = 1;
bd->props.max_brightness = FB_BACKLIGHT_LEVELS - 1;
pmu_backlight_init_curve(0x7F, 0x46, 0x0E);
level = bd->props.max_brightness;
if (autosave) {
/* read autosaved value if available */
struct adb_request req;
pmu_request(&req, NULL, 2, 0xd9, 0);
pmu_wait_complete(&req);
level = pmu_backlight_curve_lookup(
(req.reply[0] >> 4) *
bd->props.max_brightness / 15);
}
bd->props.brightness = level;
bd->props.power = FB_BLANK_UNBLANK;
backlight_update_status(bd);
printk(KERN_INFO "PMU Backlight initialized (%s)\n", name);
}

View File

@@ -0,0 +1,80 @@
/*
* via-pmu event device for reporting some events that come through the PMU
*
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
*
* 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, GOOD TITLE or
* NON INFRINGEMENT. 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/input.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include "via-pmu-event.h"
static struct input_dev *pmu_input_dev;
static int __init via_pmu_event_init(void)
{
int err;
/* do other models report button/lid status? */
if (pmu_get_model() != PMU_KEYLARGO_BASED)
return -ENODEV;
pmu_input_dev = input_allocate_device();
if (!pmu_input_dev)
return -ENOMEM;
pmu_input_dev->name = "PMU";
pmu_input_dev->id.bustype = BUS_HOST;
pmu_input_dev->id.vendor = 0x0001;
pmu_input_dev->id.product = 0x0001;
pmu_input_dev->id.version = 0x0100;
set_bit(EV_KEY, pmu_input_dev->evbit);
set_bit(EV_SW, pmu_input_dev->evbit);
set_bit(KEY_POWER, pmu_input_dev->keybit);
set_bit(SW_LID, pmu_input_dev->swbit);
err = input_register_device(pmu_input_dev);
if (err)
input_free_device(pmu_input_dev);
return err;
}
void via_pmu_event(int key, int down)
{
if (unlikely(!pmu_input_dev))
return;
switch (key) {
case PMU_EVT_POWER:
input_report_key(pmu_input_dev, KEY_POWER, down);
break;
case PMU_EVT_LID:
input_report_switch(pmu_input_dev, SW_LID, down);
break;
default:
/* no such key handled */
return;
}
input_sync(pmu_input_dev);
}
late_initcall(via_pmu_event_init);

View File

@@ -0,0 +1,8 @@
#ifndef __VIA_PMU_EVENT_H
#define __VIA_PMU_EVENT_H
#define PMU_EVT_POWER 0
#define PMU_EVT_LID 1
extern void via_pmu_event(int key, int down);
#endif /* __VIA_PMU_EVENT_H */

View File

@@ -0,0 +1,115 @@
/*
* via-pmu LED class device
*
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
*
* 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, GOOD TITLE or
* NON INFRINGEMENT. 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/leds.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <asm/prom.h>
static spinlock_t pmu_blink_lock;
static struct adb_request pmu_blink_req;
/* -1: no change, 0: request off, 1: request on */
static int requested_change;
static void pmu_req_done(struct adb_request * req)
{
unsigned long flags;
spin_lock_irqsave(&pmu_blink_lock, flags);
/* if someone requested a change in the meantime
* (we only see the last one which is fine)
* then apply it now */
if (requested_change != -1 && !pmu_sys_suspended)
pmu_request(&pmu_blink_req, NULL, 4, 0xee, 4, 0, requested_change);
/* reset requested change */
requested_change = -1;
spin_unlock_irqrestore(&pmu_blink_lock, flags);
}
static void pmu_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
unsigned long flags;
spin_lock_irqsave(&pmu_blink_lock, flags);
switch (brightness) {
case LED_OFF:
requested_change = 0;
break;
case LED_FULL:
requested_change = 1;
break;
default:
goto out;
break;
}
/* if request isn't done, then don't do anything */
if (pmu_blink_req.complete && !pmu_sys_suspended)
pmu_request(&pmu_blink_req, NULL, 4, 0xee, 4, 0, requested_change);
out:
spin_unlock_irqrestore(&pmu_blink_lock, flags);
}
static struct led_classdev pmu_led = {
.name = "pmu-led::front",
#ifdef CONFIG_ADB_PMU_LED_IDE
.default_trigger = "ide-disk",
#endif
.brightness_set = pmu_led_set,
};
static int __init via_pmu_led_init(void)
{
struct device_node *dt;
const char *model;
/* only do this on keylargo based models */
if (pmu_get_model() != PMU_KEYLARGO_BASED)
return -ENODEV;
dt = of_find_node_by_path("/");
if (dt == NULL)
return -ENODEV;
model = of_get_property(dt, "model", NULL);
if (model == NULL)
return -ENODEV;
if (strncmp(model, "PowerBook", strlen("PowerBook")) != 0 &&
strncmp(model, "iBook", strlen("iBook")) != 0 &&
strcmp(model, "PowerMac7,2") != 0 &&
strcmp(model, "PowerMac7,3") != 0) {
of_node_put(dt);
/* ignore */
return -ENODEV;
}
of_node_put(dt);
spin_lock_init(&pmu_blink_lock);
/* no outstanding req */
pmu_blink_req.complete = 1;
pmu_blink_req.done = pmu_req_done;
return led_classdev_register(NULL, &pmu_led);
}
late_initcall(via_pmu_led_init);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,818 @@
/*
* Device driver for the PMU on 68K-based Apple PowerBooks
*
* The VIA (versatile interface adapter) interfaces to the PMU,
* a 6805 microprocessor core whose primary function is to control
* battery charging and system power on the PowerBooks.
* The PMU also controls the ADB (Apple Desktop Bus) which connects
* to the keyboard and mouse, as well as the non-volatile RAM
* and the RTC (real time clock) chip.
*
* Adapted for 68K PMU by Joshua M. Thompson
*
* Based largely on the PowerMac PMU code by Paul Mackerras and
* Fabio Riccardi.
*
* Also based on the PMU driver from MkLinux by Apple Computer, Inc.
* and the Open Software Foundation, Inc.
*/
#include <stdarg.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/blkdev.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <linux/cuda.h>
#include <asm/macintosh.h>
#include <asm/macints.h>
#include <asm/mac_via.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
/* Misc minor number allocated for /dev/pmu */
#define PMU_MINOR 154
/* VIA registers - spaced 0x200 bytes apart */
#define RS 0x200 /* skip between registers */
#define B 0 /* B-side data */
#define A RS /* A-side data */
#define DIRB (2*RS) /* B-side direction (1=output) */
#define DIRA (3*RS) /* A-side direction (1=output) */
#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */
#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */
#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */
#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */
#define T2CL (8*RS) /* Timer 2 ctr/latch (low 8 bits) */
#define T2CH (9*RS) /* Timer 2 counter (high 8 bits) */
#define SR (10*RS) /* Shift register */
#define ACR (11*RS) /* Auxiliary control register */
#define PCR (12*RS) /* Peripheral control register */
#define IFR (13*RS) /* Interrupt flag register */
#define IER (14*RS) /* Interrupt enable register */
#define ANH (15*RS) /* A-side data, no handshake */
/* Bits in B data register: both active low */
#define TACK 0x02 /* Transfer acknowledge (input) */
#define TREQ 0x04 /* Transfer request (output) */
/* Bits in ACR */
#define SR_CTRL 0x1c /* Shift register control bits */
#define SR_EXT 0x0c /* Shift on external clock */
#define SR_OUT 0x10 /* Shift out if 1 */
/* Bits in IFR and IER */
#define SR_INT 0x04 /* Shift register full/empty */
#define CB1_INT 0x10 /* transition on CB1 input */
static enum pmu_state {
idle,
sending,
intack,
reading,
reading_intr,
} pmu_state;
static struct adb_request *current_req;
static struct adb_request *last_req;
static struct adb_request *req_awaiting_reply;
static unsigned char interrupt_data[32];
static unsigned char *reply_ptr;
static int data_index;
static int data_len;
static int adb_int_pending;
static int pmu_adb_flags;
static int adb_dev_map;
static struct adb_request bright_req_1, bright_req_2, bright_req_3;
static int pmu_kind = PMU_UNKNOWN;
static int pmu_fully_inited;
int asleep;
static int pmu_probe(void);
static int pmu_init(void);
static void pmu_start(void);
static irqreturn_t pmu_interrupt(int irq, void *arg);
static int pmu_send_request(struct adb_request *req, int sync);
static int pmu_autopoll(int devs);
void pmu_poll(void);
static int pmu_reset_bus(void);
static void pmu_start(void);
static void send_byte(int x);
static void recv_byte(void);
static void pmu_done(struct adb_request *req);
static void pmu_handle_data(unsigned char *data, int len);
static void set_volume(int level);
static void pmu_enable_backlight(int on);
static void pmu_set_brightness(int level);
struct adb_driver via_pmu_driver = {
"68K PMU",
pmu_probe,
pmu_init,
pmu_send_request,
pmu_autopoll,
pmu_poll,
pmu_reset_bus
};
/*
* This table indicates for each PMU opcode:
* - the number of data bytes to be sent with the command, or -1
* if a length byte should be sent,
* - the number of response bytes which the PMU will return, or
* -1 if it will send a length byte.
*/
static s8 pmu_data_len[256][2] = {
/* 0 1 2 3 4 5 6 7 */
/*00*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*08*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*10*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*18*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0, 0},
/*20*/ {-1, 0},{ 0, 0},{ 2, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},
/*28*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0,-1},
/*30*/ { 4, 0},{20, 0},{-1, 0},{ 3, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*38*/ { 0, 4},{ 0,20},{ 2,-1},{ 2, 1},{ 3,-1},{-1,-1},{-1,-1},{ 4, 0},
/*40*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*48*/ { 0, 1},{ 0, 1},{-1,-1},{ 1, 0},{ 1, 0},{-1,-1},{-1,-1},{-1,-1},
/*50*/ { 1, 0},{ 0, 0},{ 2, 0},{ 2, 0},{-1, 0},{ 1, 0},{ 3, 0},{ 1, 0},
/*58*/ { 0, 1},{ 1, 0},{ 0, 2},{ 0, 2},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},
/*60*/ { 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*68*/ { 0, 3},{ 0, 3},{ 0, 2},{ 0, 8},{ 0,-1},{ 0,-1},{-1,-1},{-1,-1},
/*70*/ { 1, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*78*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{ 5, 1},{ 4, 1},{ 4, 1},
/*80*/ { 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*88*/ { 0, 5},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*90*/ { 1, 0},{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*98*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*a0*/ { 2, 0},{ 2, 0},{ 2, 0},{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},
/*a8*/ { 1, 1},{ 1, 0},{ 3, 0},{ 2, 0},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*b0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*b8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*c0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*c8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
/*d0*/ { 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*d8*/ { 1, 1},{ 1, 1},{-1,-1},{-1,-1},{ 0, 1},{ 0,-1},{-1,-1},{-1,-1},
/*e0*/ {-1, 0},{ 4, 0},{ 0, 1},{-1, 0},{-1, 0},{ 4, 0},{-1, 0},{-1, 0},
/*e8*/ { 3,-1},{-1,-1},{ 0, 1},{-1,-1},{ 0,-1},{-1,-1},{-1,-1},{ 0, 0},
/*f0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},
/*f8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},
};
int pmu_probe(void)
{
if (macintosh_config->adb_type == MAC_ADB_PB1) {
pmu_kind = PMU_68K_V1;
} else if (macintosh_config->adb_type == MAC_ADB_PB2) {
pmu_kind = PMU_68K_V2;
} else {
return -ENODEV;
}
pmu_state = idle;
return 0;
}
static int
pmu_init(void)
{
int timeout;
volatile struct adb_request req;
via2[B] |= TREQ; /* negate TREQ */
via2[DIRB] = (via2[DIRB] | TREQ) & ~TACK; /* TACK in, TREQ out */
pmu_request((struct adb_request *) &req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB);
timeout = 100000;
while (!req.complete) {
if (--timeout < 0) {
printk(KERN_ERR "pmu_init: no response from PMU\n");
return -EAGAIN;
}
udelay(10);
pmu_poll();
}
/* ack all pending interrupts */
timeout = 100000;
interrupt_data[0] = 1;
while (interrupt_data[0] || pmu_state != idle) {
if (--timeout < 0) {
printk(KERN_ERR "pmu_init: timed out acking intrs\n");
return -EAGAIN;
}
if (pmu_state == idle) {
adb_int_pending = 1;
pmu_interrupt(0, NULL);
}
pmu_poll();
udelay(10);
}
pmu_request((struct adb_request *) &req, NULL, 2, PMU_SET_INTR_MASK,
PMU_INT_ADB_AUTO|PMU_INT_SNDBRT|PMU_INT_ADB);
timeout = 100000;
while (!req.complete) {
if (--timeout < 0) {
printk(KERN_ERR "pmu_init: no response from PMU\n");
return -EAGAIN;
}
udelay(10);
pmu_poll();
}
bright_req_1.complete = 1;
bright_req_2.complete = 1;
bright_req_3.complete = 1;
if (request_irq(IRQ_MAC_ADB_SR, pmu_interrupt, 0, "pmu-shift",
pmu_interrupt)) {
printk(KERN_ERR "pmu_init: can't get irq %d\n",
IRQ_MAC_ADB_SR);
return -EAGAIN;
}
if (request_irq(IRQ_MAC_ADB_CL, pmu_interrupt, 0, "pmu-clock",
pmu_interrupt)) {
printk(KERN_ERR "pmu_init: can't get irq %d\n",
IRQ_MAC_ADB_CL);
free_irq(IRQ_MAC_ADB_SR, pmu_interrupt);
return -EAGAIN;
}
pmu_fully_inited = 1;
/* Enable backlight */
pmu_enable_backlight(1);
printk("adb: PMU 68K driver v0.5 for Unified ADB.\n");
return 0;
}
int
pmu_get_model(void)
{
return pmu_kind;
}
/* Send an ADB command */
static int
pmu_send_request(struct adb_request *req, int sync)
{
int i, ret;
if (!pmu_fully_inited)
{
req->complete = 1;
return -ENXIO;
}
ret = -EINVAL;
switch (req->data[0]) {
case PMU_PACKET:
for (i = 0; i < req->nbytes - 1; ++i)
req->data[i] = req->data[i+1];
--req->nbytes;
if (pmu_data_len[req->data[0]][1] != 0) {
req->reply[0] = ADB_RET_OK;
req->reply_len = 1;
} else
req->reply_len = 0;
ret = pmu_queue_request(req);
break;
case CUDA_PACKET:
switch (req->data[1]) {
case CUDA_GET_TIME:
if (req->nbytes != 2)
break;
req->data[0] = PMU_READ_RTC;
req->nbytes = 1;
req->reply_len = 3;
req->reply[0] = CUDA_PACKET;
req->reply[1] = 0;
req->reply[2] = CUDA_GET_TIME;
ret = pmu_queue_request(req);
break;
case CUDA_SET_TIME:
if (req->nbytes != 6)
break;
req->data[0] = PMU_SET_RTC;
req->nbytes = 5;
for (i = 1; i <= 4; ++i)
req->data[i] = req->data[i+1];
req->reply_len = 3;
req->reply[0] = CUDA_PACKET;
req->reply[1] = 0;
req->reply[2] = CUDA_SET_TIME;
ret = pmu_queue_request(req);
break;
case CUDA_GET_PRAM:
if (req->nbytes != 4)
break;
req->data[0] = PMU_READ_NVRAM;
req->data[1] = req->data[2];
req->data[2] = req->data[3];
req->nbytes = 3;
req->reply_len = 3;
req->reply[0] = CUDA_PACKET;
req->reply[1] = 0;
req->reply[2] = CUDA_GET_PRAM;
ret = pmu_queue_request(req);
break;
case CUDA_SET_PRAM:
if (req->nbytes != 5)
break;
req->data[0] = PMU_WRITE_NVRAM;
req->data[1] = req->data[2];
req->data[2] = req->data[3];
req->data[3] = req->data[4];
req->nbytes = 4;
req->reply_len = 3;
req->reply[0] = CUDA_PACKET;
req->reply[1] = 0;
req->reply[2] = CUDA_SET_PRAM;
ret = pmu_queue_request(req);
break;
}
break;
case ADB_PACKET:
for (i = req->nbytes - 1; i > 1; --i)
req->data[i+2] = req->data[i];
req->data[3] = req->nbytes - 2;
req->data[2] = pmu_adb_flags;
/*req->data[1] = req->data[1];*/
req->data[0] = PMU_ADB_CMD;
req->nbytes += 2;
req->reply_expected = 1;
req->reply_len = 0;
ret = pmu_queue_request(req);
break;
}
if (ret)
{
req->complete = 1;
return ret;
}
if (sync) {
while (!req->complete)
pmu_poll();
}
return 0;
}
/* Enable/disable autopolling */
static int
pmu_autopoll(int devs)
{
struct adb_request req;
if (!pmu_fully_inited) return -ENXIO;
if (devs) {
adb_dev_map = devs;
pmu_request(&req, NULL, 5, PMU_ADB_CMD, 0, 0x86,
adb_dev_map >> 8, adb_dev_map);
pmu_adb_flags = 2;
} else {
pmu_request(&req, NULL, 1, PMU_ADB_POLL_OFF);
pmu_adb_flags = 0;
}
while (!req.complete)
pmu_poll();
return 0;
}
/* Reset the ADB bus */
static int
pmu_reset_bus(void)
{
struct adb_request req;
long timeout;
int save_autopoll = adb_dev_map;
if (!pmu_fully_inited) return -ENXIO;
/* anyone got a better idea?? */
pmu_autopoll(0);
req.nbytes = 5;
req.done = NULL;
req.data[0] = PMU_ADB_CMD;
req.data[1] = 0;
req.data[2] = 3; /* ADB_BUSRESET ??? */
req.data[3] = 0;
req.data[4] = 0;
req.reply_len = 0;
req.reply_expected = 1;
if (pmu_queue_request(&req) != 0)
{
printk(KERN_ERR "pmu_adb_reset_bus: pmu_queue_request failed\n");
return -EIO;
}
while (!req.complete)
pmu_poll();
timeout = 100000;
while (!req.complete) {
if (--timeout < 0) {
printk(KERN_ERR "pmu_adb_reset_bus (reset): no response from PMU\n");
return -EIO;
}
udelay(10);
pmu_poll();
}
if (save_autopoll != 0)
pmu_autopoll(save_autopoll);
return 0;
}
/* Construct and send a pmu request */
int
pmu_request(struct adb_request *req, void (*done)(struct adb_request *),
int nbytes, ...)
{
va_list list;
int i;
if (nbytes < 0 || nbytes > 32) {
printk(KERN_ERR "pmu_request: bad nbytes (%d)\n", nbytes);
req->complete = 1;
return -EINVAL;
}
req->nbytes = nbytes;
req->done = done;
va_start(list, nbytes);
for (i = 0; i < nbytes; ++i)
req->data[i] = va_arg(list, int);
va_end(list);
if (pmu_data_len[req->data[0]][1] != 0) {
req->reply[0] = ADB_RET_OK;
req->reply_len = 1;
} else
req->reply_len = 0;
req->reply_expected = 0;
return pmu_queue_request(req);
}
int
pmu_queue_request(struct adb_request *req)
{
unsigned long flags;
int nsend;
if (req->nbytes <= 0) {
req->complete = 1;
return 0;
}
nsend = pmu_data_len[req->data[0]][0];
if (nsend >= 0 && req->nbytes != nsend + 1) {
req->complete = 1;
return -EINVAL;
}
req->next = NULL;
req->sent = 0;
req->complete = 0;
local_irq_save(flags);
if (current_req != 0) {
last_req->next = req;
last_req = req;
} else {
current_req = req;
last_req = req;
if (pmu_state == idle)
pmu_start();
}
local_irq_restore(flags);
return 0;
}
static void
send_byte(int x)
{
via1[ACR] |= SR_CTRL;
via1[SR] = x;
via2[B] &= ~TREQ; /* assert TREQ */
}
static void
recv_byte(void)
{
char c;
via1[ACR] = (via1[ACR] | SR_EXT) & ~SR_OUT;
c = via1[SR]; /* resets SR */
via2[B] &= ~TREQ;
}
static void
pmu_start(void)
{
unsigned long flags;
struct adb_request *req;
/* assert pmu_state == idle */
/* get the packet to send */
local_irq_save(flags);
req = current_req;
if (req == 0 || pmu_state != idle
|| (req->reply_expected && req_awaiting_reply))
goto out;
pmu_state = sending;
data_index = 1;
data_len = pmu_data_len[req->data[0]][0];
/* set the shift register to shift out and send a byte */
send_byte(req->data[0]);
out:
local_irq_restore(flags);
}
void
pmu_poll(void)
{
unsigned long flags;
local_irq_save(flags);
if (via1[IFR] & SR_INT) {
via1[IFR] = SR_INT;
pmu_interrupt(IRQ_MAC_ADB_SR, NULL);
}
if (via1[IFR] & CB1_INT) {
via1[IFR] = CB1_INT;
pmu_interrupt(IRQ_MAC_ADB_CL, NULL);
}
local_irq_restore(flags);
}
static irqreturn_t
pmu_interrupt(int irq, void *dev_id)
{
struct adb_request *req;
int timeout, bite = 0; /* to prevent compiler warning */
#if 0
printk("pmu_interrupt: irq %d state %d acr %02X, b %02X data_index %d/%d adb_int_pending %d\n",
irq, pmu_state, (uint) via1[ACR], (uint) via2[B], data_index, data_len, adb_int_pending);
#endif
if (irq == IRQ_MAC_ADB_CL) { /* CB1 interrupt */
adb_int_pending = 1;
} else if (irq == IRQ_MAC_ADB_SR) { /* SR interrupt */
if (via2[B] & TACK) {
printk(KERN_DEBUG "PMU: SR_INT but ack still high! (%x)\n", via2[B]);
}
/* if reading grab the byte */
if ((via1[ACR] & SR_OUT) == 0) bite = via1[SR];
/* reset TREQ and wait for TACK to go high */
via2[B] |= TREQ;
timeout = 3200;
while (!(via2[B] & TACK)) {
if (--timeout < 0) {
printk(KERN_ERR "PMU not responding (!ack)\n");
goto finish;
}
udelay(10);
}
switch (pmu_state) {
case sending:
req = current_req;
if (data_len < 0) {
data_len = req->nbytes - 1;
send_byte(data_len);
break;
}
if (data_index <= data_len) {
send_byte(req->data[data_index++]);
break;
}
req->sent = 1;
data_len = pmu_data_len[req->data[0]][1];
if (data_len == 0) {
pmu_state = idle;
current_req = req->next;
if (req->reply_expected)
req_awaiting_reply = req;
else
pmu_done(req);
} else {
pmu_state = reading;
data_index = 0;
reply_ptr = req->reply + req->reply_len;
recv_byte();
}
break;
case intack:
data_index = 0;
data_len = -1;
pmu_state = reading_intr;
reply_ptr = interrupt_data;
recv_byte();
break;
case reading:
case reading_intr:
if (data_len == -1) {
data_len = bite;
if (bite > 32)
printk(KERN_ERR "PMU: bad reply len %d\n",
bite);
} else {
reply_ptr[data_index++] = bite;
}
if (data_index < data_len) {
recv_byte();
break;
}
if (pmu_state == reading_intr) {
pmu_handle_data(interrupt_data, data_index);
} else {
req = current_req;
current_req = req->next;
req->reply_len += data_index;
pmu_done(req);
}
pmu_state = idle;
break;
default:
printk(KERN_ERR "pmu_interrupt: unknown state %d?\n",
pmu_state);
}
}
finish:
if (pmu_state == idle) {
if (adb_int_pending) {
pmu_state = intack;
send_byte(PMU_INT_ACK);
adb_int_pending = 0;
} else if (current_req) {
pmu_start();
}
}
#if 0
printk("pmu_interrupt: exit state %d acr %02X, b %02X data_index %d/%d adb_int_pending %d\n",
pmu_state, (uint) via1[ACR], (uint) via2[B], data_index, data_len, adb_int_pending);
#endif
return IRQ_HANDLED;
}
static void
pmu_done(struct adb_request *req)
{
req->complete = 1;
if (req->done)
(*req->done)(req);
}
/* Interrupt data could be the result data from an ADB cmd */
static void
pmu_handle_data(unsigned char *data, int len)
{
static int show_pmu_ints = 1;
asleep = 0;
if (len < 1) {
adb_int_pending = 0;
return;
}
if (data[0] & PMU_INT_ADB) {
if ((data[0] & PMU_INT_ADB_AUTO) == 0) {
struct adb_request *req = req_awaiting_reply;
if (req == 0) {
printk(KERN_ERR "PMU: extra ADB reply\n");
return;
}
req_awaiting_reply = NULL;
if (len <= 2)
req->reply_len = 0;
else {
memcpy(req->reply, data + 1, len - 1);
req->reply_len = len - 1;
}
pmu_done(req);
} else {
adb_input(data+1, len-1, 1);
}
} else {
if (data[0] == 0x08 && len == 3) {
/* sound/brightness buttons pressed */
pmu_set_brightness(data[1] >> 3);
set_volume(data[2]);
} else if (show_pmu_ints
&& !(data[0] == PMU_INT_TICK && len == 1)) {
int i;
printk(KERN_DEBUG "pmu intr");
for (i = 0; i < len; ++i)
printk(" %.2x", data[i]);
printk("\n");
}
}
}
static int backlight_level = -1;
static int backlight_enabled = 0;
#define LEVEL_TO_BRIGHT(lev) ((lev) < 1? 0x7f: 0x4a - ((lev) << 1))
static void
pmu_enable_backlight(int on)
{
struct adb_request req;
if (on) {
/* first call: get current backlight value */
if (backlight_level < 0) {
switch(pmu_kind) {
case PMU_68K_V1:
case PMU_68K_V2:
pmu_request(&req, NULL, 3, PMU_READ_NVRAM, 0x14, 0xe);
while (!req.complete)
pmu_poll();
printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", (int)req.reply[1]);
backlight_level = req.reply[1];
break;
default:
backlight_enabled = 0;
return;
}
}
pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT,
LEVEL_TO_BRIGHT(backlight_level));
while (!req.complete)
pmu_poll();
}
pmu_request(&req, NULL, 2, PMU_POWER_CTRL,
PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF));
while (!req.complete)
pmu_poll();
backlight_enabled = on;
}
static void
pmu_set_brightness(int level)
{
int bright;
backlight_level = level;
bright = LEVEL_TO_BRIGHT(level);
if (!backlight_enabled)
return;
if (bright_req_1.complete)
pmu_request(&bright_req_1, NULL, 2, PMU_BACKLIGHT_BRIGHT,
bright);
if (bright_req_2.complete)
pmu_request(&bright_req_2, NULL, 2, PMU_POWER_CTRL,
PMU_POW_BACKLIGHT | (bright < 0x7f ? PMU_POW_ON : PMU_POW_OFF));
}
void
pmu_enable_irled(int on)
{
struct adb_request req;
pmu_request(&req, NULL, 2, PMU_POWER_CTRL, PMU_POW_IRLED |
(on ? PMU_POW_ON : PMU_POW_OFF));
while (!req.complete)
pmu_poll();
}
static void
set_volume(int level)
{
}
int
pmu_present(void)
{
return (pmu_kind != PMU_UNKNOWN);
}

View File

@@ -0,0 +1,134 @@
/*
* Windfarm PowerMac thermal control.
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#ifndef __WINDFARM_H__
#define __WINDFARM_H__
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/device.h>
/* Display a 16.16 fixed point value */
#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16)
/*
* Control objects
*/
struct wf_control;
struct wf_control_ops {
int (*set_value)(struct wf_control *ct, s32 val);
int (*get_value)(struct wf_control *ct, s32 *val);
s32 (*get_min)(struct wf_control *ct);
s32 (*get_max)(struct wf_control *ct);
void (*release)(struct wf_control *ct);
struct module *owner;
};
struct wf_control {
struct list_head link;
struct wf_control_ops *ops;
char *name;
int type;
struct kref ref;
struct device_attribute attr;
};
#define WF_CONTROL_TYPE_GENERIC 0
#define WF_CONTROL_RPM_FAN 1
#define WF_CONTROL_PWM_FAN 2
/* Note about lifetime rules: wf_register_control() will initialize
* the kref and wf_unregister_control will decrement it, thus the
* object creating/disposing a given control shouldn't assume it
* still exists after wf_unregister_control has been called.
* wf_find_control will inc the refcount for you
*/
extern int wf_register_control(struct wf_control *ct);
extern void wf_unregister_control(struct wf_control *ct);
extern struct wf_control * wf_find_control(const char *name);
extern int wf_get_control(struct wf_control *ct);
extern void wf_put_control(struct wf_control *ct);
static inline int wf_control_set_max(struct wf_control *ct)
{
s32 vmax = ct->ops->get_max(ct);
return ct->ops->set_value(ct, vmax);
}
static inline int wf_control_set_min(struct wf_control *ct)
{
s32 vmin = ct->ops->get_min(ct);
return ct->ops->set_value(ct, vmin);
}
/*
* Sensor objects
*/
struct wf_sensor;
struct wf_sensor_ops {
int (*get_value)(struct wf_sensor *sr, s32 *val);
void (*release)(struct wf_sensor *sr);
struct module *owner;
};
struct wf_sensor {
struct list_head link;
struct wf_sensor_ops *ops;
char *name;
struct kref ref;
struct device_attribute attr;
};
/* Same lifetime rules as controls */
extern int wf_register_sensor(struct wf_sensor *sr);
extern void wf_unregister_sensor(struct wf_sensor *sr);
extern struct wf_sensor * wf_find_sensor(const char *name);
extern int wf_get_sensor(struct wf_sensor *sr);
extern void wf_put_sensor(struct wf_sensor *sr);
/* For use by clients. Note that we are a bit racy here since
* notifier_block doesn't have a module owner field. I may fix
* it one day ...
*
* LOCKING NOTE !
*
* All "events" except WF_EVENT_TICK are called with an internal mutex
* held which will deadlock if you call basically any core routine.
* So don't ! Just take note of the event and do your actual operations
* from the ticker.
*
*/
extern int wf_register_client(struct notifier_block *nb);
extern int wf_unregister_client(struct notifier_block *nb);
/* Overtemp conditions. Those are refcounted */
extern void wf_set_overtemp(void);
extern void wf_clear_overtemp(void);
extern int wf_is_overtemp(void);
#define WF_EVENT_NEW_CONTROL 0 /* param is wf_control * */
#define WF_EVENT_NEW_SENSOR 1 /* param is wf_sensor * */
#define WF_EVENT_OVERTEMP 2 /* no param */
#define WF_EVENT_NORMALTEMP 3 /* overtemp condition cleared */
#define WF_EVENT_TICK 4 /* 1 second tick */
/* Note: If that driver gets more broad use, we could replace the
* simplistic overtemp bits with "environmental conditions". That
* could then be used to also notify of things like fan failure,
* case open, battery conditions, ...
*/
#endif /* __WINDFARM_H__ */

View File

@@ -0,0 +1,495 @@
/*
* Windfarm PowerMac thermal control. Core
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* This core code tracks the list of sensors & controls, register
* clients, and holds the kernel thread used for control.
*
* TODO:
*
* Add some information about sensor/control type and data format to
* sensors/controls, and have the sysfs attribute stuff be moved
* generically here instead of hard coded in the platform specific
* driver as it us currently
*
* This however requires solving some annoying lifetime issues with
* sysfs which doesn't seem to have lifetime rules for struct attribute,
* I may have to create full features kobjects for every sensor/control
* instead which is a bit of an overkill imho
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
#include <linux/jiffies.h>
#include <linux/reboot.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/freezer.h>
#include <asm/prom.h>
#include "windfarm.h"
#define VERSION "0.2"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
static LIST_HEAD(wf_controls);
static LIST_HEAD(wf_sensors);
static DEFINE_MUTEX(wf_lock);
static BLOCKING_NOTIFIER_HEAD(wf_client_list);
static int wf_client_count;
static unsigned int wf_overtemp;
static unsigned int wf_overtemp_counter;
struct task_struct *wf_thread;
static struct platform_device wf_platform_device = {
.name = "windfarm",
};
/*
* Utilities & tick thread
*/
static inline void wf_notify(int event, void *param)
{
blocking_notifier_call_chain(&wf_client_list, event, param);
}
int wf_critical_overtemp(void)
{
static char * critical_overtemp_path = "/sbin/critical_overtemp";
char *argv[] = { critical_overtemp_path, NULL };
static char *envp[] = { "HOME=/",
"TERM=linux",
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
NULL };
return call_usermodehelper(critical_overtemp_path,
argv, envp, UMH_WAIT_EXEC);
}
EXPORT_SYMBOL_GPL(wf_critical_overtemp);
static int wf_thread_func(void *data)
{
unsigned long next, delay;
next = jiffies;
DBG("wf: thread started\n");
set_freezable();
while (!kthread_should_stop()) {
try_to_freeze();
if (time_after_eq(jiffies, next)) {
wf_notify(WF_EVENT_TICK, NULL);
if (wf_overtemp) {
wf_overtemp_counter++;
/* 10 seconds overtemp, notify userland */
if (wf_overtemp_counter > 10)
wf_critical_overtemp();
/* 30 seconds, shutdown */
if (wf_overtemp_counter > 30) {
printk(KERN_ERR "windfarm: Overtemp "
"for more than 30"
" seconds, shutting down\n");
machine_power_off();
}
}
next += HZ;
}
delay = next - jiffies;
if (delay <= HZ)
schedule_timeout_interruptible(delay);
}
DBG("wf: thread stopped\n");
return 0;
}
static void wf_start_thread(void)
{
wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm");
if (IS_ERR(wf_thread)) {
printk(KERN_ERR "windfarm: failed to create thread,err %ld\n",
PTR_ERR(wf_thread));
wf_thread = NULL;
}
}
static void wf_stop_thread(void)
{
if (wf_thread)
kthread_stop(wf_thread);
wf_thread = NULL;
}
/*
* Controls
*/
static void wf_control_release(struct kref *kref)
{
struct wf_control *ct = container_of(kref, struct wf_control, ref);
DBG("wf: Deleting control %s\n", ct->name);
if (ct->ops && ct->ops->release)
ct->ops->release(ct);
else
kfree(ct);
}
static ssize_t wf_show_control(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wf_control *ctrl = container_of(attr, struct wf_control, attr);
s32 val = 0;
int err;
err = ctrl->ops->get_value(ctrl, &val);
if (err < 0)
return err;
return sprintf(buf, "%d\n", val);
}
/* This is really only for debugging... */
static ssize_t wf_store_control(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct wf_control *ctrl = container_of(attr, struct wf_control, attr);
int val;
int err;
char *endp;
val = simple_strtoul(buf, &endp, 0);
while (endp < buf + count && (*endp == ' ' || *endp == '\n'))
++endp;
if (endp - buf < count)
return -EINVAL;
err = ctrl->ops->set_value(ctrl, val);
if (err < 0)
return err;
return count;
}
int wf_register_control(struct wf_control *new_ct)
{
struct wf_control *ct;
mutex_lock(&wf_lock);
list_for_each_entry(ct, &wf_controls, link) {
if (!strcmp(ct->name, new_ct->name)) {
printk(KERN_WARNING "windfarm: trying to register"
" duplicate control %s\n", ct->name);
mutex_unlock(&wf_lock);
return -EEXIST;
}
}
kref_init(&new_ct->ref);
list_add(&new_ct->link, &wf_controls);
new_ct->attr.attr.name = new_ct->name;
new_ct->attr.attr.mode = 0644;
new_ct->attr.show = wf_show_control;
new_ct->attr.store = wf_store_control;
if (device_create_file(&wf_platform_device.dev, &new_ct->attr))
printk(KERN_WARNING "windfarm: device_create_file failed"
" for %s\n", new_ct->name);
/* the subsystem still does useful work without the file */
DBG("wf: Registered control %s\n", new_ct->name);
wf_notify(WF_EVENT_NEW_CONTROL, new_ct);
mutex_unlock(&wf_lock);
return 0;
}
EXPORT_SYMBOL_GPL(wf_register_control);
void wf_unregister_control(struct wf_control *ct)
{
mutex_lock(&wf_lock);
list_del(&ct->link);
mutex_unlock(&wf_lock);
DBG("wf: Unregistered control %s\n", ct->name);
kref_put(&ct->ref, wf_control_release);
}
EXPORT_SYMBOL_GPL(wf_unregister_control);
struct wf_control * wf_find_control(const char *name)
{
struct wf_control *ct;
mutex_lock(&wf_lock);
list_for_each_entry(ct, &wf_controls, link) {
if (!strcmp(ct->name, name)) {
if (wf_get_control(ct))
ct = NULL;
mutex_unlock(&wf_lock);
return ct;
}
}
mutex_unlock(&wf_lock);
return NULL;
}
EXPORT_SYMBOL_GPL(wf_find_control);
int wf_get_control(struct wf_control *ct)
{
if (!try_module_get(ct->ops->owner))
return -ENODEV;
kref_get(&ct->ref);
return 0;
}
EXPORT_SYMBOL_GPL(wf_get_control);
void wf_put_control(struct wf_control *ct)
{
struct module *mod = ct->ops->owner;
kref_put(&ct->ref, wf_control_release);
module_put(mod);
}
EXPORT_SYMBOL_GPL(wf_put_control);
/*
* Sensors
*/
static void wf_sensor_release(struct kref *kref)
{
struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref);
DBG("wf: Deleting sensor %s\n", sr->name);
if (sr->ops && sr->ops->release)
sr->ops->release(sr);
else
kfree(sr);
}
static ssize_t wf_show_sensor(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct wf_sensor *sens = container_of(attr, struct wf_sensor, attr);
s32 val = 0;
int err;
err = sens->ops->get_value(sens, &val);
if (err < 0)
return err;
return sprintf(buf, "%d.%03d\n", FIX32TOPRINT(val));
}
int wf_register_sensor(struct wf_sensor *new_sr)
{
struct wf_sensor *sr;
mutex_lock(&wf_lock);
list_for_each_entry(sr, &wf_sensors, link) {
if (!strcmp(sr->name, new_sr->name)) {
printk(KERN_WARNING "windfarm: trying to register"
" duplicate sensor %s\n", sr->name);
mutex_unlock(&wf_lock);
return -EEXIST;
}
}
kref_init(&new_sr->ref);
list_add(&new_sr->link, &wf_sensors);
new_sr->attr.attr.name = new_sr->name;
new_sr->attr.attr.mode = 0444;
new_sr->attr.show = wf_show_sensor;
new_sr->attr.store = NULL;
if (device_create_file(&wf_platform_device.dev, &new_sr->attr))
printk(KERN_WARNING "windfarm: device_create_file failed"
" for %s\n", new_sr->name);
/* the subsystem still does useful work without the file */
DBG("wf: Registered sensor %s\n", new_sr->name);
wf_notify(WF_EVENT_NEW_SENSOR, new_sr);
mutex_unlock(&wf_lock);
return 0;
}
EXPORT_SYMBOL_GPL(wf_register_sensor);
void wf_unregister_sensor(struct wf_sensor *sr)
{
mutex_lock(&wf_lock);
list_del(&sr->link);
mutex_unlock(&wf_lock);
DBG("wf: Unregistered sensor %s\n", sr->name);
wf_put_sensor(sr);
}
EXPORT_SYMBOL_GPL(wf_unregister_sensor);
struct wf_sensor * wf_find_sensor(const char *name)
{
struct wf_sensor *sr;
mutex_lock(&wf_lock);
list_for_each_entry(sr, &wf_sensors, link) {
if (!strcmp(sr->name, name)) {
if (wf_get_sensor(sr))
sr = NULL;
mutex_unlock(&wf_lock);
return sr;
}
}
mutex_unlock(&wf_lock);
return NULL;
}
EXPORT_SYMBOL_GPL(wf_find_sensor);
int wf_get_sensor(struct wf_sensor *sr)
{
if (!try_module_get(sr->ops->owner))
return -ENODEV;
kref_get(&sr->ref);
return 0;
}
EXPORT_SYMBOL_GPL(wf_get_sensor);
void wf_put_sensor(struct wf_sensor *sr)
{
struct module *mod = sr->ops->owner;
kref_put(&sr->ref, wf_sensor_release);
module_put(mod);
}
EXPORT_SYMBOL_GPL(wf_put_sensor);
/*
* Client & notification
*/
int wf_register_client(struct notifier_block *nb)
{
int rc;
struct wf_control *ct;
struct wf_sensor *sr;
mutex_lock(&wf_lock);
rc = blocking_notifier_chain_register(&wf_client_list, nb);
if (rc != 0)
goto bail;
wf_client_count++;
list_for_each_entry(ct, &wf_controls, link)
wf_notify(WF_EVENT_NEW_CONTROL, ct);
list_for_each_entry(sr, &wf_sensors, link)
wf_notify(WF_EVENT_NEW_SENSOR, sr);
if (wf_client_count == 1)
wf_start_thread();
bail:
mutex_unlock(&wf_lock);
return rc;
}
EXPORT_SYMBOL_GPL(wf_register_client);
int wf_unregister_client(struct notifier_block *nb)
{
mutex_lock(&wf_lock);
blocking_notifier_chain_unregister(&wf_client_list, nb);
wf_client_count++;
if (wf_client_count == 0)
wf_stop_thread();
mutex_unlock(&wf_lock);
return 0;
}
EXPORT_SYMBOL_GPL(wf_unregister_client);
void wf_set_overtemp(void)
{
mutex_lock(&wf_lock);
wf_overtemp++;
if (wf_overtemp == 1) {
printk(KERN_WARNING "windfarm: Overtemp condition detected !\n");
wf_overtemp_counter = 0;
wf_notify(WF_EVENT_OVERTEMP, NULL);
}
mutex_unlock(&wf_lock);
}
EXPORT_SYMBOL_GPL(wf_set_overtemp);
void wf_clear_overtemp(void)
{
mutex_lock(&wf_lock);
WARN_ON(wf_overtemp == 0);
if (wf_overtemp == 0) {
mutex_unlock(&wf_lock);
return;
}
wf_overtemp--;
if (wf_overtemp == 0) {
printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n");
wf_notify(WF_EVENT_NORMALTEMP, NULL);
}
mutex_unlock(&wf_lock);
}
EXPORT_SYMBOL_GPL(wf_clear_overtemp);
int wf_is_overtemp(void)
{
return (wf_overtemp != 0);
}
EXPORT_SYMBOL_GPL(wf_is_overtemp);
static int __init windfarm_core_init(void)
{
DBG("wf: core loaded\n");
/* Don't register on old machines that use therm_pm72 for now */
if (machine_is_compatible("PowerMac7,2") ||
machine_is_compatible("PowerMac7,3") ||
machine_is_compatible("RackMac3,1"))
return -ENODEV;
platform_device_register(&wf_platform_device);
return 0;
}
static void __exit windfarm_core_exit(void)
{
BUG_ON(wf_client_count != 0);
DBG("wf: core unloaded\n");
platform_device_unregister(&wf_platform_device);
}
module_init(windfarm_core_init);
module_exit(windfarm_core_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Core component of PowerMac thermal control");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,112 @@
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/cpufreq.h>
#include <asm/prom.h>
#include "windfarm.h"
#define VERSION "0.3"
static int clamped;
static struct wf_control *clamp_control;
static int clamp_notifier_call(struct notifier_block *self,
unsigned long event, void *data)
{
struct cpufreq_policy *p = data;
unsigned long max_freq;
if (event != CPUFREQ_ADJUST)
return 0;
max_freq = clamped ? (p->cpuinfo.min_freq) : (p->cpuinfo.max_freq);
cpufreq_verify_within_limits(p, 0, max_freq);
return 0;
}
static struct notifier_block clamp_notifier = {
.notifier_call = clamp_notifier_call,
};
static int clamp_set(struct wf_control *ct, s32 value)
{
if (value)
printk(KERN_INFO "windfarm: Clamping CPU frequency to "
"minimum !\n");
else
printk(KERN_INFO "windfarm: CPU frequency unclamped !\n");
clamped = value;
cpufreq_update_policy(0);
return 0;
}
static int clamp_get(struct wf_control *ct, s32 *value)
{
*value = clamped;
return 0;
}
static s32 clamp_min(struct wf_control *ct)
{
return 0;
}
static s32 clamp_max(struct wf_control *ct)
{
return 1;
}
static struct wf_control_ops clamp_ops = {
.set_value = clamp_set,
.get_value = clamp_get,
.get_min = clamp_min,
.get_max = clamp_max,
.owner = THIS_MODULE,
};
static int __init wf_cpufreq_clamp_init(void)
{
struct wf_control *clamp;
/* Don't register on old machines that use therm_pm72 for now */
if (machine_is_compatible("PowerMac7,2") ||
machine_is_compatible("PowerMac7,3") ||
machine_is_compatible("RackMac3,1"))
return -ENODEV;
clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL);
if (clamp == NULL)
return -ENOMEM;
cpufreq_register_notifier(&clamp_notifier, CPUFREQ_POLICY_NOTIFIER);
clamp->ops = &clamp_ops;
clamp->name = "cpufreq-clamp";
if (wf_register_control(clamp))
goto fail;
clamp_control = clamp;
return 0;
fail:
kfree(clamp);
return -ENODEV;
}
static void __exit wf_cpufreq_clamp_exit(void)
{
if (clamp_control)
wf_unregister_control(clamp_control);
}
module_init(wf_cpufreq_clamp_init);
module_exit(wf_cpufreq_clamp_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,261 @@
/*
* Windfarm PowerMac thermal control. LM75 sensor
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/i2c.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/pmac_low_i2c.h>
#include "windfarm.h"
#define VERSION "0.2"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
struct wf_lm75_sensor {
int ds1775 : 1;
int inited : 1;
struct i2c_client *i2c;
struct wf_sensor sens;
};
#define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens)
static int wf_lm75_get(struct wf_sensor *sr, s32 *value)
{
struct wf_lm75_sensor *lm = wf_to_lm75(sr);
s32 data;
if (lm->i2c == NULL)
return -ENODEV;
/* Init chip if necessary */
if (!lm->inited) {
u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(lm->i2c, 1);
DBG("wf_lm75: Initializing %s, cfg was: %02x\n",
sr->name, cfg);
/* clear shutdown bit, keep other settings as left by
* the firmware for now
*/
cfg_new = cfg & ~0x01;
i2c_smbus_write_byte_data(lm->i2c, 1, cfg_new);
lm->inited = 1;
/* If we just powered it up, let's wait 200 ms */
msleep(200);
}
/* Read temperature register */
data = (s32)le16_to_cpu(i2c_smbus_read_word_data(lm->i2c, 0));
data <<= 8;
*value = data;
return 0;
}
static void wf_lm75_release(struct wf_sensor *sr)
{
struct wf_lm75_sensor *lm = wf_to_lm75(sr);
kfree(lm);
}
static struct wf_sensor_ops wf_lm75_ops = {
.get_value = wf_lm75_get,
.release = wf_lm75_release,
.owner = THIS_MODULE,
};
static int wf_lm75_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct wf_lm75_sensor *lm;
int rc;
lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL);
if (lm == NULL)
return -ENODEV;
lm->inited = 0;
lm->ds1775 = id->driver_data;
lm->i2c = client;
lm->sens.name = client->dev.platform_data;
lm->sens.ops = &wf_lm75_ops;
i2c_set_clientdata(client, lm);
rc = wf_register_sensor(&lm->sens);
if (rc) {
i2c_set_clientdata(client, NULL);
kfree(lm);
}
return rc;
}
static struct i2c_driver wf_lm75_driver;
static struct i2c_client *wf_lm75_create(struct i2c_adapter *adapter,
u8 addr, int ds1775,
const char *loc)
{
struct i2c_board_info info;
struct i2c_client *client;
char *name;
DBG("wf_lm75: creating %s device at address 0x%02x\n",
ds1775 ? "ds1775" : "lm75", addr);
/* Usual rant about sensor names not beeing very consistent in
* the device-tree, oh well ...
* Add more entries below as you deal with more setups
*/
if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY"))
name = "hd-temp";
else if (!strcmp(loc, "Incoming Air Temp"))
name = "incoming-air-temp";
else if (!strcmp(loc, "ODD Temp"))
name = "optical-drive-temp";
else if (!strcmp(loc, "HD Temp"))
name = "hard-drive-temp";
else
goto fail;
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = (addr >> 1) & 0x7f;
info.platform_data = name;
strlcpy(info.type, ds1775 ? "wf_ds1775" : "wf_lm75", I2C_NAME_SIZE);
client = i2c_new_device(adapter, &info);
if (client == NULL) {
printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n",
ds1775 ? "ds1775" : "lm75", name);
goto fail;
}
/*
* Let i2c-core delete that device on driver removal.
* This is safe because i2c-core holds the core_lock mutex for us.
*/
list_add_tail(&client->detected, &wf_lm75_driver.clients);
return client;
fail:
return NULL;
}
static int wf_lm75_attach(struct i2c_adapter *adapter)
{
struct device_node *busnode, *dev;
struct pmac_i2c_bus *bus;
DBG("wf_lm75: adapter %s detected\n", adapter->name);
bus = pmac_i2c_adapter_to_bus(adapter);
if (bus == NULL)
return -ENODEV;
busnode = pmac_i2c_get_bus_node(bus);
DBG("wf_lm75: bus found, looking for device...\n");
/* Now look for lm75(s) in there */
for (dev = NULL;
(dev = of_get_next_child(busnode, dev)) != NULL;) {
const char *loc =
of_get_property(dev, "hwsensor-location", NULL);
u8 addr;
/* We must re-match the adapter in order to properly check
* the channel on multibus setups
*/
if (!pmac_i2c_match_adapter(dev, adapter))
continue;
addr = pmac_i2c_get_dev_addr(dev);
if (loc == NULL || addr == 0)
continue;
/* real lm75 */
if (of_device_is_compatible(dev, "lm75"))
wf_lm75_create(adapter, addr, 0, loc);
/* ds1775 (compatible, better resolution */
else if (of_device_is_compatible(dev, "ds1775"))
wf_lm75_create(adapter, addr, 1, loc);
}
return 0;
}
static int wf_lm75_remove(struct i2c_client *client)
{
struct wf_lm75_sensor *lm = i2c_get_clientdata(client);
DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name);
/* Mark client detached */
lm->i2c = NULL;
/* release sensor */
wf_unregister_sensor(&lm->sens);
i2c_set_clientdata(client, NULL);
return 0;
}
static const struct i2c_device_id wf_lm75_id[] = {
{ "wf_lm75", 0 },
{ "wf_ds1775", 1 },
{ }
};
static struct i2c_driver wf_lm75_driver = {
.driver = {
.name = "wf_lm75",
},
.attach_adapter = wf_lm75_attach,
.probe = wf_lm75_probe,
.remove = wf_lm75_remove,
.id_table = wf_lm75_id,
};
static int __init wf_lm75_sensor_init(void)
{
/* Don't register on old machines that use therm_pm72 for now */
if (machine_is_compatible("PowerMac7,2") ||
machine_is_compatible("PowerMac7,3") ||
machine_is_compatible("RackMac3,1"))
return -ENODEV;
return i2c_add_driver(&wf_lm75_driver);
}
static void __exit wf_lm75_sensor_exit(void)
{
i2c_del_driver(&wf_lm75_driver);
}
module_init(wf_lm75_sensor_init);
module_exit(wf_lm75_sensor_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,208 @@
/*
* Windfarm PowerMac thermal control. MAX6690 sensor.
*
* Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>
*
* Use and redistribute under the terms of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <asm/prom.h>
#include <asm/pmac_low_i2c.h>
#include "windfarm.h"
#define VERSION "0.2"
/* This currently only exports the external temperature sensor,
since that's all the control loops need. */
/* Some MAX6690 register numbers */
#define MAX6690_INTERNAL_TEMP 0
#define MAX6690_EXTERNAL_TEMP 1
struct wf_6690_sensor {
struct i2c_client *i2c;
struct wf_sensor sens;
};
#define wf_to_6690(x) container_of((x), struct wf_6690_sensor, sens)
static int wf_max6690_get(struct wf_sensor *sr, s32 *value)
{
struct wf_6690_sensor *max = wf_to_6690(sr);
s32 data;
if (max->i2c == NULL)
return -ENODEV;
/* chip gets initialized by firmware */
data = i2c_smbus_read_byte_data(max->i2c, MAX6690_EXTERNAL_TEMP);
if (data < 0)
return data;
*value = data << 16;
return 0;
}
static void wf_max6690_release(struct wf_sensor *sr)
{
struct wf_6690_sensor *max = wf_to_6690(sr);
kfree(max);
}
static struct wf_sensor_ops wf_max6690_ops = {
.get_value = wf_max6690_get,
.release = wf_max6690_release,
.owner = THIS_MODULE,
};
static int wf_max6690_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct wf_6690_sensor *max;
int rc;
max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL);
if (max == NULL) {
printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor: "
"no memory\n");
return -ENOMEM;
}
max->i2c = client;
max->sens.name = client->dev.platform_data;
max->sens.ops = &wf_max6690_ops;
i2c_set_clientdata(client, max);
rc = wf_register_sensor(&max->sens);
if (rc) {
i2c_set_clientdata(client, NULL);
kfree(max);
}
return rc;
}
static struct i2c_driver wf_max6690_driver;
static struct i2c_client *wf_max6690_create(struct i2c_adapter *adapter,
u8 addr, const char *loc)
{
struct i2c_board_info info;
struct i2c_client *client;
char *name;
if (!strcmp(loc, "BACKSIDE"))
name = "backside-temp";
else if (!strcmp(loc, "NB Ambient"))
name = "north-bridge-temp";
else if (!strcmp(loc, "GPU Ambient"))
name = "gpu-temp";
else
goto fail;
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr >> 1;
info.platform_data = name;
strlcpy(info.type, "wf_max6690", I2C_NAME_SIZE);
client = i2c_new_device(adapter, &info);
if (client == NULL) {
printk(KERN_ERR "windfarm: failed to attach MAX6690 sensor\n");
goto fail;
}
/*
* Let i2c-core delete that device on driver removal.
* This is safe because i2c-core holds the core_lock mutex for us.
*/
list_add_tail(&client->detected, &wf_max6690_driver.clients);
return client;
fail:
return NULL;
}
static int wf_max6690_attach(struct i2c_adapter *adapter)
{
struct device_node *busnode, *dev = NULL;
struct pmac_i2c_bus *bus;
const char *loc;
bus = pmac_i2c_adapter_to_bus(adapter);
if (bus == NULL)
return -ENODEV;
busnode = pmac_i2c_get_bus_node(bus);
while ((dev = of_get_next_child(busnode, dev)) != NULL) {
u8 addr;
/* We must re-match the adapter in order to properly check
* the channel on multibus setups
*/
if (!pmac_i2c_match_adapter(dev, adapter))
continue;
if (!of_device_is_compatible(dev, "max6690"))
continue;
addr = pmac_i2c_get_dev_addr(dev);
loc = of_get_property(dev, "hwsensor-location", NULL);
if (loc == NULL || addr == 0)
continue;
printk("found max6690, loc=%s addr=0x%02x\n", loc, addr);
wf_max6690_create(adapter, addr, loc);
}
return 0;
}
static int wf_max6690_remove(struct i2c_client *client)
{
struct wf_6690_sensor *max = i2c_get_clientdata(client);
max->i2c = NULL;
wf_unregister_sensor(&max->sens);
return 0;
}
static const struct i2c_device_id wf_max6690_id[] = {
{ "wf_max6690", 0 },
{ }
};
static struct i2c_driver wf_max6690_driver = {
.driver = {
.name = "wf_max6690",
},
.attach_adapter = wf_max6690_attach,
.probe = wf_max6690_probe,
.remove = wf_max6690_remove,
.id_table = wf_max6690_id,
};
static int __init wf_max6690_sensor_init(void)
{
/* Don't register on old machines that use therm_pm72 for now */
if (machine_is_compatible("PowerMac7,2") ||
machine_is_compatible("PowerMac7,3") ||
machine_is_compatible("RackMac3,1"))
return -ENODEV;
return i2c_add_driver(&wf_max6690_driver);
}
static void __exit wf_max6690_sensor_exit(void)
{
i2c_del_driver(&wf_max6690_driver);
}
module_init(wf_max6690_sensor_init);
module_exit(wf_max6690_sensor_exit);
MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
MODULE_DESCRIPTION("MAX6690 sensor objects for PowerMac thermal control");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,149 @@
/*
* Windfarm PowerMac thermal control. Generic PID helpers
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/module.h>
#include "windfarm_pid.h"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param)
{
memset(st, 0, sizeof(struct wf_pid_state));
st->param = *param;
st->first = 1;
}
EXPORT_SYMBOL_GPL(wf_pid_init);
s32 wf_pid_run(struct wf_pid_state *st, s32 new_sample)
{
s64 error, integ, deriv;
s32 target;
int i, hlen = st->param.history_len;
/* Calculate error term */
error = new_sample - st->param.itarget;
/* Get samples into our history buffer */
if (st->first) {
for (i = 0; i < hlen; i++) {
st->samples[i] = new_sample;
st->errors[i] = error;
}
st->first = 0;
st->index = 0;
} else {
st->index = (st->index + 1) % hlen;
st->samples[st->index] = new_sample;
st->errors[st->index] = error;
}
/* Calculate integral term */
for (i = 0, integ = 0; i < hlen; i++)
integ += st->errors[(st->index + hlen - i) % hlen];
integ *= st->param.interval;
/* Calculate derivative term */
deriv = st->errors[st->index] -
st->errors[(st->index + hlen - 1) % hlen];
deriv /= st->param.interval;
/* Calculate target */
target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd +
error * (s64)st->param.gp) >> 36);
if (st->param.additive)
target += st->target;
target = max(target, st->param.min);
target = min(target, st->param.max);
st->target = target;
return st->target;
}
EXPORT_SYMBOL_GPL(wf_pid_run);
void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
struct wf_cpu_pid_param *param)
{
memset(st, 0, sizeof(struct wf_cpu_pid_state));
st->param = *param;
st->first = 1;
}
EXPORT_SYMBOL_GPL(wf_cpu_pid_init);
s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp)
{
s64 integ, deriv, prop;
s32 error, target, sval, adj;
int i, hlen = st->param.history_len;
/* Calculate error term */
error = st->param.pmaxadj - new_power;
/* Get samples into our history buffer */
if (st->first) {
for (i = 0; i < hlen; i++) {
st->powers[i] = new_power;
st->errors[i] = error;
}
st->temps[0] = st->temps[1] = new_temp;
st->first = 0;
st->index = st->tindex = 0;
} else {
st->index = (st->index + 1) % hlen;
st->powers[st->index] = new_power;
st->errors[st->index] = error;
st->tindex = (st->tindex + 1) % 2;
st->temps[st->tindex] = new_temp;
}
/* Calculate integral term */
for (i = 0, integ = 0; i < hlen; i++)
integ += st->errors[(st->index + hlen - i) % hlen];
integ *= st->param.interval;
integ *= st->param.gr;
sval = st->param.tmax - (s32)(integ >> 20);
adj = min(st->param.ttarget, sval);
DBG("integ: %lx, sval: %lx, adj: %lx\n", integ, sval, adj);
/* Calculate derivative term */
deriv = st->temps[st->tindex] -
st->temps[(st->tindex + 2 - 1) % 2];
deriv /= st->param.interval;
deriv *= st->param.gd;
/* Calculate proportional term */
prop = st->last_delta = (new_temp - adj);
prop *= st->param.gp;
DBG("deriv: %lx, prop: %lx\n", deriv, prop);
/* Calculate target */
target = st->target + (s32)((deriv + prop) >> 36);
target = max(target, st->param.min);
target = min(target, st->param.max);
st->target = target;
return st->target;
}
EXPORT_SYMBOL_GPL(wf_cpu_pid_run);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("PID algorithm for PowerMacs thermal control");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,85 @@
/*
* Windfarm PowerMac thermal control. Generic PID helpers
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* This is a pair of generic PID helpers that can be used by
* control loops. One is the basic PID implementation, the
* other one is more specifically tailored to the loops used
* for CPU control with 2 input sample types (temp and power)
*/
/*
* *** Simple PID ***
*/
#define WF_PID_MAX_HISTORY 32
/* This parameter array is passed to the PID algorithm. Currently,
* we don't support changing parameters on the fly as it's not needed
* but could be implemented (with necessary adjustment of the history
* buffer
*/
struct wf_pid_param {
int interval; /* Interval between samples in seconds */
int history_len; /* Size of history buffer */
int additive; /* 1: target relative to previous value */
s32 gd, gp, gr; /* PID gains */
s32 itarget; /* PID input target */
s32 min,max; /* min and max target values */
};
struct wf_pid_state {
int first; /* first run of the loop */
int index; /* index of current sample */
s32 target; /* current target value */
s32 samples[WF_PID_MAX_HISTORY]; /* samples history buffer */
s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */
struct wf_pid_param param;
};
extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param);
extern s32 wf_pid_run(struct wf_pid_state *st, s32 sample);
/*
* *** CPU PID ***
*/
#define WF_CPU_PID_MAX_HISTORY 32
/* This parameter array is passed to the CPU PID algorithm. Currently,
* we don't support changing parameters on the fly as it's not needed
* but could be implemented (with necessary adjustment of the history
* buffer
*/
struct wf_cpu_pid_param {
int interval; /* Interval between samples in seconds */
int history_len; /* Size of history buffer */
s32 gd, gp, gr; /* PID gains */
s32 pmaxadj; /* PID max power adjust */
s32 ttarget; /* PID input target */
s32 tmax; /* PID input max */
s32 min,max; /* min and max target values */
};
struct wf_cpu_pid_state {
int first; /* first run of the loop */
int index; /* index of current power */
int tindex; /* index of current temp */
s32 target; /* current target value */
s32 last_delta; /* last Tactual - Ttarget */
s32 powers[WF_PID_MAX_HISTORY]; /* power history buffer */
s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */
s32 temps[2]; /* temp. history buffer */
struct wf_cpu_pid_param param;
};
extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st,
struct wf_cpu_pid_param *param);
extern s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 power, s32 temp);

View File

@@ -0,0 +1,714 @@
/*
* Windfarm PowerMac thermal control.
* Control loops for machines with SMU and PPC970MP processors.
*
* Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>
* Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corp.
*
* Use and redistribute under the terms of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <asm/prom.h>
#include <asm/smu.h>
#include "windfarm.h"
#include "windfarm_pid.h"
#define VERSION "0.2"
#define DEBUG
#undef LOTSA_DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
#ifdef LOTSA_DEBUG
#define DBG_LOTS(args...) printk(args)
#else
#define DBG_LOTS(args...) do { } while(0)
#endif
/* define this to force CPU overtemp to 60 degree, useful for testing
* the overtemp code
*/
#undef HACKED_OVERTEMP
/* We currently only handle 2 chips, 4 cores... */
#define NR_CHIPS 2
#define NR_CORES 4
#define NR_CPU_FANS 3 * NR_CHIPS
/* Controls and sensors */
static struct wf_sensor *sens_cpu_temp[NR_CORES];
static struct wf_sensor *sens_cpu_power[NR_CORES];
static struct wf_sensor *hd_temp;
static struct wf_sensor *slots_power;
static struct wf_sensor *u4_temp;
static struct wf_control *cpu_fans[NR_CPU_FANS];
static char *cpu_fan_names[NR_CPU_FANS] = {
"cpu-rear-fan-0",
"cpu-rear-fan-1",
"cpu-front-fan-0",
"cpu-front-fan-1",
"cpu-pump-0",
"cpu-pump-1",
};
static struct wf_control *cpufreq_clamp;
/* Second pump isn't required (and isn't actually present) */
#define CPU_FANS_REQD (NR_CPU_FANS - 2)
#define FIRST_PUMP 4
#define LAST_PUMP 5
/* We keep a temperature history for average calculation of 180s */
#define CPU_TEMP_HIST_SIZE 180
/* Scale factor for fan speed, *100 */
static int cpu_fan_scale[NR_CPU_FANS] = {
100,
100,
97, /* inlet fans run at 97% of exhaust fan */
97,
100, /* updated later */
100, /* updated later */
};
static struct wf_control *backside_fan;
static struct wf_control *slots_fan;
static struct wf_control *drive_bay_fan;
/* PID loop state */
static struct wf_cpu_pid_state cpu_pid[NR_CORES];
static u32 cpu_thist[CPU_TEMP_HIST_SIZE];
static int cpu_thist_pt;
static s64 cpu_thist_total;
static s32 cpu_all_tmax = 100 << 16;
static int cpu_last_target;
static struct wf_pid_state backside_pid;
static int backside_tick;
static struct wf_pid_state slots_pid;
static int slots_started;
static struct wf_pid_state drive_bay_pid;
static int drive_bay_tick;
static int nr_cores;
static int have_all_controls;
static int have_all_sensors;
static int started;
static int failure_state;
#define FAILURE_SENSOR 1
#define FAILURE_FAN 2
#define FAILURE_PERM 4
#define FAILURE_LOW_OVERTEMP 8
#define FAILURE_HIGH_OVERTEMP 16
/* Overtemp values */
#define LOW_OVER_AVERAGE 0
#define LOW_OVER_IMMEDIATE (10 << 16)
#define LOW_OVER_CLEAR ((-10) << 16)
#define HIGH_OVER_IMMEDIATE (14 << 16)
#define HIGH_OVER_AVERAGE (10 << 16)
#define HIGH_OVER_IMMEDIATE (14 << 16)
/* Implementation... */
static int create_cpu_loop(int cpu)
{
int chip = cpu / 2;
int core = cpu & 1;
struct smu_sdbp_header *hdr;
struct smu_sdbp_cpupiddata *piddata;
struct wf_cpu_pid_param pid;
struct wf_control *main_fan = cpu_fans[0];
s32 tmax;
int fmin;
/* Get PID params from the appropriate SAT */
hdr = smu_sat_get_sdb_partition(chip, 0xC8 + core, NULL);
if (hdr == NULL) {
printk(KERN_WARNING"windfarm: can't get CPU PID fan config\n");
return -EINVAL;
}
piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
/* Get FVT params to get Tmax; if not found, assume default */
hdr = smu_sat_get_sdb_partition(chip, 0xC4 + core, NULL);
if (hdr) {
struct smu_sdbp_fvt *fvt = (struct smu_sdbp_fvt *)&hdr[1];
tmax = fvt->maxtemp << 16;
} else
tmax = 95 << 16; /* default to 95 degrees C */
/* We keep a global tmax for overtemp calculations */
if (tmax < cpu_all_tmax)
cpu_all_tmax = tmax;
/*
* Darwin has a minimum fan speed of 1000 rpm for the 4-way and
* 515 for the 2-way. That appears to be overkill, so for now,
* impose a minimum of 750 or 515.
*/
fmin = (nr_cores > 2) ? 750 : 515;
/* Initialize PID loop */
pid.interval = 1; /* seconds */
pid.history_len = piddata->history_len;
pid.gd = piddata->gd;
pid.gp = piddata->gp;
pid.gr = piddata->gr / piddata->history_len;
pid.pmaxadj = (piddata->max_power << 16) - (piddata->power_adj << 8);
pid.ttarget = tmax - (piddata->target_temp_delta << 16);
pid.tmax = tmax;
pid.min = main_fan->ops->get_min(main_fan);
pid.max = main_fan->ops->get_max(main_fan);
if (pid.min < fmin)
pid.min = fmin;
wf_cpu_pid_init(&cpu_pid[cpu], &pid);
return 0;
}
static void cpu_max_all_fans(void)
{
int i;
/* We max all CPU fans in case of a sensor error. We also do the
* cpufreq clamping now, even if it's supposedly done later by the
* generic code anyway, we do it earlier here to react faster
*/
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
for (i = 0; i < NR_CPU_FANS; ++i)
if (cpu_fans[i])
wf_control_set_max(cpu_fans[i]);
}
static int cpu_check_overtemp(s32 temp)
{
int new_state = 0;
s32 t_avg, t_old;
/* First check for immediate overtemps */
if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) {
new_state |= FAILURE_LOW_OVERTEMP;
if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
printk(KERN_ERR "windfarm: Overtemp due to immediate CPU"
" temperature !\n");
}
if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) {
new_state |= FAILURE_HIGH_OVERTEMP;
if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
printk(KERN_ERR "windfarm: Critical overtemp due to"
" immediate CPU temperature !\n");
}
/* We calculate a history of max temperatures and use that for the
* overtemp management
*/
t_old = cpu_thist[cpu_thist_pt];
cpu_thist[cpu_thist_pt] = temp;
cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE;
cpu_thist_total -= t_old;
cpu_thist_total += temp;
t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE;
DBG_LOTS("t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n",
FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp));
/* Now check for average overtemps */
if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) {
new_state |= FAILURE_LOW_OVERTEMP;
if ((failure_state & FAILURE_LOW_OVERTEMP) == 0)
printk(KERN_ERR "windfarm: Overtemp due to average CPU"
" temperature !\n");
}
if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) {
new_state |= FAILURE_HIGH_OVERTEMP;
if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0)
printk(KERN_ERR "windfarm: Critical overtemp due to"
" average CPU temperature !\n");
}
/* Now handle overtemp conditions. We don't currently use the windfarm
* overtemp handling core as it's not fully suited to the needs of those
* new machine. This will be fixed later.
*/
if (new_state) {
/* High overtemp -> immediate shutdown */
if (new_state & FAILURE_HIGH_OVERTEMP)
machine_power_off();
if ((failure_state & new_state) != new_state)
cpu_max_all_fans();
failure_state |= new_state;
} else if ((failure_state & FAILURE_LOW_OVERTEMP) &&
(temp < (cpu_all_tmax + LOW_OVER_CLEAR))) {
printk(KERN_ERR "windfarm: Overtemp condition cleared !\n");
failure_state &= ~FAILURE_LOW_OVERTEMP;
}
return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP);
}
static void cpu_fans_tick(void)
{
int err, cpu;
s32 greatest_delta = 0;
s32 temp, power, t_max = 0;
int i, t, target = 0;
struct wf_sensor *sr;
struct wf_control *ct;
struct wf_cpu_pid_state *sp;
DBG_LOTS(KERN_DEBUG);
for (cpu = 0; cpu < nr_cores; ++cpu) {
/* Get CPU core temperature */
sr = sens_cpu_temp[cpu];
err = sr->ops->get_value(sr, &temp);
if (err) {
DBG("\n");
printk(KERN_WARNING "windfarm: CPU %d temperature "
"sensor error %d\n", cpu, err);
failure_state |= FAILURE_SENSOR;
cpu_max_all_fans();
return;
}
/* Keep track of highest temp */
t_max = max(t_max, temp);
/* Get CPU power */
sr = sens_cpu_power[cpu];
err = sr->ops->get_value(sr, &power);
if (err) {
DBG("\n");
printk(KERN_WARNING "windfarm: CPU %d power "
"sensor error %d\n", cpu, err);
failure_state |= FAILURE_SENSOR;
cpu_max_all_fans();
return;
}
/* Run PID */
sp = &cpu_pid[cpu];
t = wf_cpu_pid_run(sp, power, temp);
if (cpu == 0 || sp->last_delta > greatest_delta) {
greatest_delta = sp->last_delta;
target = t;
}
DBG_LOTS("[%d] P=%d.%.3d T=%d.%.3d ",
cpu, FIX32TOPRINT(power), FIX32TOPRINT(temp));
}
DBG_LOTS("fans = %d, t_max = %d.%03d\n", target, FIX32TOPRINT(t_max));
/* Darwin limits decrease to 20 per iteration */
if (target < (cpu_last_target - 20))
target = cpu_last_target - 20;
cpu_last_target = target;
for (cpu = 0; cpu < nr_cores; ++cpu)
cpu_pid[cpu].target = target;
/* Handle possible overtemps */
if (cpu_check_overtemp(t_max))
return;
/* Set fans */
for (i = 0; i < NR_CPU_FANS; ++i) {
ct = cpu_fans[i];
if (ct == NULL)
continue;
err = ct->ops->set_value(ct, target * cpu_fan_scale[i] / 100);
if (err) {
printk(KERN_WARNING "windfarm: fan %s reports "
"error %d\n", ct->name, err);
failure_state |= FAILURE_FAN;
break;
}
}
}
/* Backside/U4 fan */
static struct wf_pid_param backside_param = {
.interval = 5,
.history_len = 2,
.gd = 48 << 20,
.gp = 5 << 20,
.gr = 0,
.itarget = 64 << 16,
.additive = 1,
};
static void backside_fan_tick(void)
{
s32 temp;
int speed;
int err;
if (!backside_fan || !u4_temp)
return;
if (!backside_tick) {
/* first time; initialize things */
printk(KERN_INFO "windfarm: Backside control loop started.\n");
backside_param.min = backside_fan->ops->get_min(backside_fan);
backside_param.max = backside_fan->ops->get_max(backside_fan);
wf_pid_init(&backside_pid, &backside_param);
backside_tick = 1;
}
if (--backside_tick > 0)
return;
backside_tick = backside_pid.param.interval;
err = u4_temp->ops->get_value(u4_temp, &temp);
if (err) {
printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n",
err);
failure_state |= FAILURE_SENSOR;
wf_control_set_max(backside_fan);
return;
}
speed = wf_pid_run(&backside_pid, temp);
DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n",
FIX32TOPRINT(temp), speed);
err = backside_fan->ops->set_value(backside_fan, speed);
if (err) {
printk(KERN_WARNING "windfarm: backside fan error %d\n", err);
failure_state |= FAILURE_FAN;
}
}
/* Drive bay fan */
static struct wf_pid_param drive_bay_prm = {
.interval = 5,
.history_len = 2,
.gd = 30 << 20,
.gp = 5 << 20,
.gr = 0,
.itarget = 40 << 16,
.additive = 1,
};
static void drive_bay_fan_tick(void)
{
s32 temp;
int speed;
int err;
if (!drive_bay_fan || !hd_temp)
return;
if (!drive_bay_tick) {
/* first time; initialize things */
printk(KERN_INFO "windfarm: Drive bay control loop started.\n");
drive_bay_prm.min = drive_bay_fan->ops->get_min(drive_bay_fan);
drive_bay_prm.max = drive_bay_fan->ops->get_max(drive_bay_fan);
wf_pid_init(&drive_bay_pid, &drive_bay_prm);
drive_bay_tick = 1;
}
if (--drive_bay_tick > 0)
return;
drive_bay_tick = drive_bay_pid.param.interval;
err = hd_temp->ops->get_value(hd_temp, &temp);
if (err) {
printk(KERN_WARNING "windfarm: drive bay temp sensor "
"error %d\n", err);
failure_state |= FAILURE_SENSOR;
wf_control_set_max(drive_bay_fan);
return;
}
speed = wf_pid_run(&drive_bay_pid, temp);
DBG_LOTS("drive_bay PID temp=%d.%.3d speed=%d\n",
FIX32TOPRINT(temp), speed);
err = drive_bay_fan->ops->set_value(drive_bay_fan, speed);
if (err) {
printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err);
failure_state |= FAILURE_FAN;
}
}
/* PCI slots area fan */
/* This makes the fan speed proportional to the power consumed */
static struct wf_pid_param slots_param = {
.interval = 1,
.history_len = 2,
.gd = 0,
.gp = 0,
.gr = 0x1277952,
.itarget = 0,
.min = 1560,
.max = 3510,
};
static void slots_fan_tick(void)
{
s32 power;
int speed;
int err;
if (!slots_fan || !slots_power)
return;
if (!slots_started) {
/* first time; initialize things */
printk(KERN_INFO "windfarm: Slots control loop started.\n");
wf_pid_init(&slots_pid, &slots_param);
slots_started = 1;
}
err = slots_power->ops->get_value(slots_power, &power);
if (err) {
printk(KERN_WARNING "windfarm: slots power sensor error %d\n",
err);
failure_state |= FAILURE_SENSOR;
wf_control_set_max(slots_fan);
return;
}
speed = wf_pid_run(&slots_pid, power);
DBG_LOTS("slots PID power=%d.%.3d speed=%d\n",
FIX32TOPRINT(power), speed);
err = slots_fan->ops->set_value(slots_fan, speed);
if (err) {
printk(KERN_WARNING "windfarm: slots fan error %d\n", err);
failure_state |= FAILURE_FAN;
}
}
static void set_fail_state(void)
{
int i;
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
for (i = 0; i < NR_CPU_FANS; ++i)
if (cpu_fans[i])
wf_control_set_max(cpu_fans[i]);
if (backside_fan)
wf_control_set_max(backside_fan);
if (slots_fan)
wf_control_set_max(slots_fan);
if (drive_bay_fan)
wf_control_set_max(drive_bay_fan);
}
static void pm112_tick(void)
{
int i, last_failure;
if (!started) {
started = 1;
printk(KERN_INFO "windfarm: CPUs control loops started.\n");
for (i = 0; i < nr_cores; ++i) {
if (create_cpu_loop(i) < 0) {
failure_state = FAILURE_PERM;
set_fail_state();
break;
}
}
DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax));
#ifdef HACKED_OVERTEMP
cpu_all_tmax = 60 << 16;
#endif
}
/* Permanent failure, bail out */
if (failure_state & FAILURE_PERM)
return;
/* Clear all failure bits except low overtemp which will be eventually
* cleared by the control loop itself
*/
last_failure = failure_state;
failure_state &= FAILURE_LOW_OVERTEMP;
cpu_fans_tick();
backside_fan_tick();
slots_fan_tick();
drive_bay_fan_tick();
DBG_LOTS("last_failure: 0x%x, failure_state: %x\n",
last_failure, failure_state);
/* Check for failures. Any failure causes cpufreq clamping */
if (failure_state && last_failure == 0 && cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (failure_state == 0 && last_failure && cpufreq_clamp)
wf_control_set_min(cpufreq_clamp);
/* That's it for now, we might want to deal with other failures
* differently in the future though
*/
}
static void pm112_new_control(struct wf_control *ct)
{
int i, max_exhaust;
if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
if (wf_get_control(ct) == 0)
cpufreq_clamp = ct;
}
for (i = 0; i < NR_CPU_FANS; ++i) {
if (!strcmp(ct->name, cpu_fan_names[i])) {
if (cpu_fans[i] == NULL && wf_get_control(ct) == 0)
cpu_fans[i] = ct;
break;
}
}
if (i >= NR_CPU_FANS) {
/* not a CPU fan, try the others */
if (!strcmp(ct->name, "backside-fan")) {
if (backside_fan == NULL && wf_get_control(ct) == 0)
backside_fan = ct;
} else if (!strcmp(ct->name, "slots-fan")) {
if (slots_fan == NULL && wf_get_control(ct) == 0)
slots_fan = ct;
} else if (!strcmp(ct->name, "drive-bay-fan")) {
if (drive_bay_fan == NULL && wf_get_control(ct) == 0)
drive_bay_fan = ct;
}
return;
}
for (i = 0; i < CPU_FANS_REQD; ++i)
if (cpu_fans[i] == NULL)
return;
/* work out pump scaling factors */
max_exhaust = cpu_fans[0]->ops->get_max(cpu_fans[0]);
for (i = FIRST_PUMP; i <= LAST_PUMP; ++i)
if ((ct = cpu_fans[i]) != NULL)
cpu_fan_scale[i] =
ct->ops->get_max(ct) * 100 / max_exhaust;
have_all_controls = 1;
}
static void pm112_new_sensor(struct wf_sensor *sr)
{
unsigned int i;
if (!strncmp(sr->name, "cpu-temp-", 9)) {
i = sr->name[9] - '0';
if (sr->name[10] == 0 && i < NR_CORES &&
sens_cpu_temp[i] == NULL && wf_get_sensor(sr) == 0)
sens_cpu_temp[i] = sr;
} else if (!strncmp(sr->name, "cpu-power-", 10)) {
i = sr->name[10] - '0';
if (sr->name[11] == 0 && i < NR_CORES &&
sens_cpu_power[i] == NULL && wf_get_sensor(sr) == 0)
sens_cpu_power[i] = sr;
} else if (!strcmp(sr->name, "hd-temp")) {
if (hd_temp == NULL && wf_get_sensor(sr) == 0)
hd_temp = sr;
} else if (!strcmp(sr->name, "slots-power")) {
if (slots_power == NULL && wf_get_sensor(sr) == 0)
slots_power = sr;
} else if (!strcmp(sr->name, "backside-temp")) {
if (u4_temp == NULL && wf_get_sensor(sr) == 0)
u4_temp = sr;
} else
return;
/* check if we have all the sensors we need */
for (i = 0; i < nr_cores; ++i)
if (sens_cpu_temp[i] == NULL || sens_cpu_power[i] == NULL)
return;
have_all_sensors = 1;
}
static int pm112_wf_notify(struct notifier_block *self,
unsigned long event, void *data)
{
switch (event) {
case WF_EVENT_NEW_SENSOR:
pm112_new_sensor(data);
break;
case WF_EVENT_NEW_CONTROL:
pm112_new_control(data);
break;
case WF_EVENT_TICK:
if (have_all_controls && have_all_sensors)
pm112_tick();
}
return 0;
}
static struct notifier_block pm112_events = {
.notifier_call = pm112_wf_notify,
};
static int wf_pm112_probe(struct platform_device *dev)
{
wf_register_client(&pm112_events);
return 0;
}
static int __devexit wf_pm112_remove(struct platform_device *dev)
{
wf_unregister_client(&pm112_events);
/* should release all sensors and controls */
return 0;
}
static struct platform_driver wf_pm112_driver = {
.probe = wf_pm112_probe,
.remove = __devexit_p(wf_pm112_remove),
.driver = {
.name = "windfarm",
.owner = THIS_MODULE,
},
};
static int __init wf_pm112_init(void)
{
struct device_node *cpu;
if (!machine_is_compatible("PowerMac11,2"))
return -ENODEV;
/* Count the number of CPU cores */
nr_cores = 0;
for (cpu = NULL; (cpu = of_find_node_by_type(cpu, "cpu")) != NULL; )
++nr_cores;
printk(KERN_INFO "windfarm: initializing for dual-core desktop G5\n");
#ifdef MODULE
request_module("windfarm_smu_controls");
request_module("windfarm_smu_sensors");
request_module("windfarm_smu_sat");
request_module("windfarm_lm75_sensor");
request_module("windfarm_max6690_sensor");
request_module("windfarm_cpufreq_clamp");
#endif /* MODULE */
platform_driver_register(&wf_pm112_driver);
return 0;
}
static void __exit wf_pm112_exit(void)
{
platform_driver_unregister(&wf_pm112_driver);
}
module_init(wf_pm112_init);
module_exit(wf_pm112_exit);
MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
MODULE_DESCRIPTION("Thermal control for PowerMac11,2");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:windfarm");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,813 @@
/*
* Windfarm PowerMac thermal control. iMac G5
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* The algorithm used is the PID control algorithm, used the same
* way the published Darwin code does, using the same values that
* are present in the Darwin 8.2 snapshot property lists (note however
* that none of the code has been re-used, it's a complete re-implementation
*
* The various control loops found in Darwin config file are:
*
* PowerMac8,1 and PowerMac8,2
* ===========================
*
* System Fans control loop. Different based on models. In addition to the
* usual PID algorithm, the control loop gets 2 additional pairs of linear
* scaling factors (scale/offsets) expressed as 4.12 fixed point values
* signed offset, unsigned scale)
*
* The targets are modified such as:
* - the linked control (second control) gets the target value as-is
* (typically the drive fan)
* - the main control (first control) gets the target value scaled with
* the first pair of factors, and is then modified as below
* - the value of the target of the CPU Fan control loop is retrieved,
* scaled with the second pair of factors, and the max of that and
* the scaled target is applied to the main control.
*
* # model_id: 2
* controls : system-fan, drive-bay-fan
* sensors : hd-temp
* PID params : G_d = 0x15400000
* G_p = 0x00200000
* G_r = 0x000002fd
* History = 2 entries
* Input target = 0x3a0000
* Interval = 5s
* linear-factors : offset = 0xff38 scale = 0x0ccd
* offset = 0x0208 scale = 0x07ae
*
* # model_id: 3
* controls : system-fan, drive-bay-fan
* sensors : hd-temp
* PID params : G_d = 0x08e00000
* G_p = 0x00566666
* G_r = 0x0000072b
* History = 2 entries
* Input target = 0x350000
* Interval = 5s
* linear-factors : offset = 0xff38 scale = 0x0ccd
* offset = 0x0000 scale = 0x0000
*
* # model_id: 5
* controls : system-fan
* sensors : hd-temp
* PID params : G_d = 0x15400000
* G_p = 0x00233333
* G_r = 0x000002fd
* History = 2 entries
* Input target = 0x3a0000
* Interval = 5s
* linear-factors : offset = 0x0000 scale = 0x1000
* offset = 0x0091 scale = 0x0bae
*
* CPU Fan control loop. The loop is identical for all models. it
* has an additional pair of scaling factor. This is used to scale the
* systems fan control loop target result (the one before it gets scaled
* by the System Fans control loop itself). Then, the max value of the
* calculated target value and system fan value is sent to the fans
*
* controls : cpu-fan
* sensors : cpu-temp cpu-power
* PID params : From SMU sdb partition
* linear-factors : offset = 0xfb50 scale = 0x1000
*
* CPU Slew control loop. Not implemented. The cpufreq driver in linux is
* completely separate for now, though we could find a way to link it, either
* as a client reacting to overtemp notifications, or directling monitoring
* the CPU temperature
*
* WARNING ! The CPU control loop requires the CPU tmax for the current
* operating point. However, we currently are completely separated from
* the cpufreq driver and thus do not know what the current operating
* point is. Fortunately, we also do not have any hardware supporting anything
* but operating point 0 at the moment, thus we just peek that value directly
* from the SDB partition. If we ever end up with actually slewing the system
* clock and thus changing operating points, we'll have to find a way to
* communicate with the CPU freq driver;
*
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/kmod.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#include "windfarm_pid.h"
#define VERSION "0.4"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/* define this to force CPU overtemp to 74 degree, useful for testing
* the overtemp code
*/
#undef HACKED_OVERTEMP
static int wf_smu_mach_model; /* machine model id */
/* Controls & sensors */
static struct wf_sensor *sensor_cpu_power;
static struct wf_sensor *sensor_cpu_temp;
static struct wf_sensor *sensor_hd_temp;
static struct wf_control *fan_cpu_main;
static struct wf_control *fan_hd;
static struct wf_control *fan_system;
static struct wf_control *cpufreq_clamp;
/* Set to kick the control loop into life */
static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
/* Failure handling.. could be nicer */
#define FAILURE_FAN 0x01
#define FAILURE_SENSOR 0x02
#define FAILURE_OVERTEMP 0x04
static unsigned int wf_smu_failure_state;
static int wf_smu_readjust, wf_smu_skipping;
/*
* ****** System Fans Control Loop ******
*
*/
/* Parameters for the System Fans control loop. Parameters
* not in this table such as interval, history size, ...
* are common to all versions and thus hard coded for now.
*/
struct wf_smu_sys_fans_param {
int model_id;
s32 itarget;
s32 gd, gp, gr;
s16 offset0;
u16 scale0;
s16 offset1;
u16 scale1;
};
#define WF_SMU_SYS_FANS_INTERVAL 5
#define WF_SMU_SYS_FANS_HISTORY_SIZE 2
/* State data used by the system fans control loop
*/
struct wf_smu_sys_fans_state {
int ticks;
s32 sys_setpoint;
s32 hd_setpoint;
s16 offset0;
u16 scale0;
s16 offset1;
u16 scale1;
struct wf_pid_state pid;
};
/*
* Configs for SMU Sytem Fan control loop
*/
static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = {
/* Model ID 2 */
{
.model_id = 2,
.itarget = 0x3a0000,
.gd = 0x15400000,
.gp = 0x00200000,
.gr = 0x000002fd,
.offset0 = 0xff38,
.scale0 = 0x0ccd,
.offset1 = 0x0208,
.scale1 = 0x07ae,
},
/* Model ID 3 */
{
.model_id = 3,
.itarget = 0x350000,
.gd = 0x08e00000,
.gp = 0x00566666,
.gr = 0x0000072b,
.offset0 = 0xff38,
.scale0 = 0x0ccd,
.offset1 = 0x0000,
.scale1 = 0x0000,
},
/* Model ID 5 */
{
.model_id = 5,
.itarget = 0x3a0000,
.gd = 0x15400000,
.gp = 0x00233333,
.gr = 0x000002fd,
.offset0 = 0x0000,
.scale0 = 0x1000,
.offset1 = 0x0091,
.scale1 = 0x0bae,
},
};
#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params)
static struct wf_smu_sys_fans_state *wf_smu_sys_fans;
/*
* ****** CPU Fans Control Loop ******
*
*/
#define WF_SMU_CPU_FANS_INTERVAL 1
#define WF_SMU_CPU_FANS_MAX_HISTORY 16
#define WF_SMU_CPU_FANS_SIBLING_SCALE 0x00001000
#define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50
/* State data used by the cpu fans control loop
*/
struct wf_smu_cpu_fans_state {
int ticks;
s32 cpu_setpoint;
s32 scale;
s32 offset;
struct wf_cpu_pid_state pid;
};
static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
/*
* ***** Implementation *****
*
*/
static void wf_smu_create_sys_fans(void)
{
struct wf_smu_sys_fans_param *param = NULL;
struct wf_pid_param pid_param;
int i;
/* First, locate the params for this model */
for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++)
if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) {
param = &wf_smu_sys_all_params[i];
break;
}
/* No params found, put fans to max */
if (param == NULL) {
printk(KERN_WARNING "windfarm: System fan config not found "
"for this machine model, max fan speed\n");
goto fail;
}
/* Alloc & initialize state */
wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state),
GFP_KERNEL);
if (wf_smu_sys_fans == NULL) {
printk(KERN_WARNING "windfarm: Memory allocation error"
" max fan speed\n");
goto fail;
}
wf_smu_sys_fans->ticks = 1;
wf_smu_sys_fans->scale0 = param->scale0;
wf_smu_sys_fans->offset0 = param->offset0;
wf_smu_sys_fans->scale1 = param->scale1;
wf_smu_sys_fans->offset1 = param->offset1;
/* Fill PID params */
pid_param.gd = param->gd;
pid_param.gp = param->gp;
pid_param.gr = param->gr;
pid_param.interval = WF_SMU_SYS_FANS_INTERVAL;
pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE;
pid_param.itarget = param->itarget;
pid_param.min = fan_system->ops->get_min(fan_system);
pid_param.max = fan_system->ops->get_max(fan_system);
if (fan_hd) {
pid_param.min =
max(pid_param.min,fan_hd->ops->get_min(fan_hd));
pid_param.max =
min(pid_param.max,fan_hd->ops->get_max(fan_hd));
}
wf_pid_init(&wf_smu_sys_fans->pid, &pid_param);
DBG("wf: System Fan control initialized.\n");
DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max);
return;
fail:
if (fan_system)
wf_control_set_max(fan_system);
if (fan_hd)
wf_control_set_max(fan_hd);
}
static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)
{
s32 new_setpoint, temp, scaled, cputarget;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = WF_SMU_SYS_FANS_INTERVAL;
rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n",
FIX32TOPRINT(temp));
if (temp > (st->pid.param.itarget + 0x50000))
wf_smu_failure_state |= FAILURE_OVERTEMP;
new_setpoint = wf_pid_run(&st->pid, temp);
DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0;
DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled);
cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0;
cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1;
scaled = max(scaled, cputarget);
scaled = max(scaled, st->pid.param.min);
scaled = min(scaled, st->pid.param.max);
DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled);
if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint)
return;
st->sys_setpoint = scaled;
st->hd_setpoint = new_setpoint;
readjust:
if (fan_system && wf_smu_failure_state == 0) {
rc = fan_system->ops->set_value(fan_system, st->sys_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: Sys fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
if (fan_hd && wf_smu_failure_state == 0) {
rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: HD fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
static void wf_smu_create_cpu_fans(void)
{
struct wf_cpu_pid_param pid_param;
const struct smu_sdbp_header *hdr;
struct smu_sdbp_cpupiddata *piddata;
struct smu_sdbp_fvt *fvt;
s32 tmax, tdelta, maxpow, powadj;
/* First, locate the PID params in SMU SBD */
hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
if (hdr == 0) {
printk(KERN_WARNING "windfarm: CPU PID fan config not found "
"max fan speed\n");
goto fail;
}
piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
/* Get the FVT params for operating point 0 (the only supported one
* for now) in order to get tmax
*/
hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
if (hdr) {
fvt = (struct smu_sdbp_fvt *)&hdr[1];
tmax = ((s32)fvt->maxtemp) << 16;
} else
tmax = 0x5e0000; /* 94 degree default */
/* Alloc & initialize state */
wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
GFP_KERNEL);
if (wf_smu_cpu_fans == NULL)
goto fail;
wf_smu_cpu_fans->ticks = 1;
wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE;
wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET;
/* Fill PID params */
pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
pid_param.history_len = piddata->history_len;
if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
printk(KERN_WARNING "windfarm: History size overflow on "
"CPU control loop (%d)\n", piddata->history_len);
pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
}
pid_param.gd = piddata->gd;
pid_param.gp = piddata->gp;
pid_param.gr = piddata->gr / pid_param.history_len;
tdelta = ((s32)piddata->target_temp_delta) << 16;
maxpow = ((s32)piddata->max_power) << 16;
powadj = ((s32)piddata->power_adj) << 16;
pid_param.tmax = tmax;
pid_param.ttarget = tmax - tdelta;
pid_param.pmaxadj = maxpow - powadj;
pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
DBG("wf: CPU Fan control initialized.\n");
DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
pid_param.min, pid_param.max);
return;
fail:
printk(KERN_WARNING "windfarm: CPU fan config not found\n"
"for this machine model, max fan speed\n");
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
}
static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
{
s32 new_setpoint, temp, power, systarget;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = WF_SMU_CPU_FANS_INTERVAL;
rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
if (rc) {
printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
FIX32TOPRINT(temp), FIX32TOPRINT(power));
#ifdef HACKED_OVERTEMP
if (temp > 0x4a0000)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#else
if (temp > st->pid.param.tmax)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#endif
new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0;
systarget = ((((s64)systarget) * (s64)st->scale) >> 12)
+ st->offset;
new_setpoint = max(new_setpoint, systarget);
new_setpoint = max(new_setpoint, st->pid.param.min);
new_setpoint = min(new_setpoint, st->pid.param.max);
DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint);
if (st->cpu_setpoint == new_setpoint)
return;
st->cpu_setpoint = new_setpoint;
readjust:
if (fan_cpu_main && wf_smu_failure_state == 0) {
rc = fan_cpu_main->ops->set_value(fan_cpu_main,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU main fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
/*
* ****** Setup / Init / Misc ... ******
*
*/
static void wf_smu_tick(void)
{
unsigned int last_failure = wf_smu_failure_state;
unsigned int new_failure;
if (!wf_smu_started) {
DBG("wf: creating control loops !\n");
wf_smu_create_sys_fans();
wf_smu_create_cpu_fans();
wf_smu_started = 1;
}
/* Skipping ticks */
if (wf_smu_skipping && --wf_smu_skipping)
return;
wf_smu_failure_state = 0;
if (wf_smu_sys_fans)
wf_smu_sys_fans_tick(wf_smu_sys_fans);
if (wf_smu_cpu_fans)
wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
wf_smu_readjust = 0;
new_failure = wf_smu_failure_state & ~last_failure;
/* If entering failure mode, clamp cpufreq and ramp all
* fans to full speed.
*/
if (wf_smu_failure_state && !last_failure) {
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_system)
wf_control_set_max(fan_system);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
if (fan_hd)
wf_control_set_max(fan_hd);
}
/* If leaving failure mode, unclamp cpufreq and readjust
* all fans on next iteration
*/
if (!wf_smu_failure_state && last_failure) {
if (cpufreq_clamp)
wf_control_set_min(cpufreq_clamp);
wf_smu_readjust = 1;
}
/* Overtemp condition detected, notify and start skipping a couple
* ticks to let the temperature go down
*/
if (new_failure & FAILURE_OVERTEMP) {
wf_set_overtemp();
wf_smu_skipping = 2;
}
/* We only clear the overtemp condition if overtemp is cleared
* _and_ no other failure is present. Since a sensor error will
* clear the overtemp condition (can't measure temperature) at
* the control loop levels, but we don't want to keep it clear
* here in this case
*/
if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
wf_clear_overtemp();
}
static void wf_smu_new_control(struct wf_control *ct)
{
if (wf_smu_all_controls_ok)
return;
if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) {
if (wf_get_control(ct) == 0)
fan_cpu_main = ct;
}
if (fan_system == NULL && !strcmp(ct->name, "system-fan")) {
if (wf_get_control(ct) == 0)
fan_system = ct;
}
if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
if (wf_get_control(ct) == 0)
cpufreq_clamp = ct;
}
/* Darwin property list says the HD fan is only for model ID
* 0, 1, 2 and 3
*/
if (wf_smu_mach_model > 3) {
if (fan_system && fan_cpu_main && cpufreq_clamp)
wf_smu_all_controls_ok = 1;
return;
}
if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
if (wf_get_control(ct) == 0)
fan_hd = ct;
}
if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp)
wf_smu_all_controls_ok = 1;
}
static void wf_smu_new_sensor(struct wf_sensor *sr)
{
if (wf_smu_all_sensors_ok)
return;
if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
if (wf_get_sensor(sr) == 0)
sensor_cpu_power = sr;
}
if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
if (wf_get_sensor(sr) == 0)
sensor_cpu_temp = sr;
}
if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
if (wf_get_sensor(sr) == 0)
sensor_hd_temp = sr;
}
if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp)
wf_smu_all_sensors_ok = 1;
}
static int wf_smu_notify(struct notifier_block *self,
unsigned long event, void *data)
{
switch(event) {
case WF_EVENT_NEW_CONTROL:
DBG("wf: new control %s detected\n",
((struct wf_control *)data)->name);
wf_smu_new_control(data);
wf_smu_readjust = 1;
break;
case WF_EVENT_NEW_SENSOR:
DBG("wf: new sensor %s detected\n",
((struct wf_sensor *)data)->name);
wf_smu_new_sensor(data);
break;
case WF_EVENT_TICK:
if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
wf_smu_tick();
}
return 0;
}
static struct notifier_block wf_smu_events = {
.notifier_call = wf_smu_notify,
};
static int wf_init_pm(void)
{
const struct smu_sdbp_header *hdr;
hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL);
if (hdr != 0) {
struct smu_sdbp_sensortree *st =
(struct smu_sdbp_sensortree *)&hdr[1];
wf_smu_mach_model = st->model_id;
}
printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n",
wf_smu_mach_model);
return 0;
}
static int wf_smu_probe(struct platform_device *ddev)
{
wf_register_client(&wf_smu_events);
return 0;
}
static int __devexit wf_smu_remove(struct platform_device *ddev)
{
wf_unregister_client(&wf_smu_events);
/* XXX We don't have yet a guarantee that our callback isn't
* in progress when returning from wf_unregister_client, so
* we add an arbitrary delay. I'll have to fix that in the core
*/
msleep(1000);
/* Release all sensors */
/* One more crappy race: I don't think we have any guarantee here
* that the attribute callback won't race with the sensor beeing
* disposed of, and I'm not 100% certain what best way to deal
* with that except by adding locks all over... I'll do that
* eventually but heh, who ever rmmod this module anyway ?
*/
if (sensor_cpu_power)
wf_put_sensor(sensor_cpu_power);
if (sensor_cpu_temp)
wf_put_sensor(sensor_cpu_temp);
if (sensor_hd_temp)
wf_put_sensor(sensor_hd_temp);
/* Release all controls */
if (fan_cpu_main)
wf_put_control(fan_cpu_main);
if (fan_hd)
wf_put_control(fan_hd);
if (fan_system)
wf_put_control(fan_system);
if (cpufreq_clamp)
wf_put_control(cpufreq_clamp);
/* Destroy control loops state structures */
if (wf_smu_sys_fans)
kfree(wf_smu_sys_fans);
if (wf_smu_cpu_fans)
kfree(wf_smu_cpu_fans);
return 0;
}
static struct platform_driver wf_smu_driver = {
.probe = wf_smu_probe,
.remove = __devexit_p(wf_smu_remove),
.driver = {
.name = "windfarm",
.owner = THIS_MODULE,
},
};
static int __init wf_smu_init(void)
{
int rc = -ENODEV;
if (machine_is_compatible("PowerMac8,1") ||
machine_is_compatible("PowerMac8,2"))
rc = wf_init_pm();
if (rc == 0) {
#ifdef MODULE
request_module("windfarm_smu_controls");
request_module("windfarm_smu_sensors");
request_module("windfarm_lm75_sensor");
request_module("windfarm_cpufreq_clamp");
#endif /* MODULE */
platform_driver_register(&wf_smu_driver);
}
return rc;
}
static void __exit wf_smu_exit(void)
{
platform_driver_unregister(&wf_smu_driver);
}
module_init(wf_smu_init);
module_exit(wf_smu_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Thermal control logic for iMac G5");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:windfarm");

View File

@@ -0,0 +1,745 @@
/*
* Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*
* The algorithm used is the PID control algorithm, used the same
* way the published Darwin code does, using the same values that
* are present in the Darwin 8.2 snapshot property lists (note however
* that none of the code has been re-used, it's a complete re-implementation
*
* The various control loops found in Darwin config file are:
*
* PowerMac9,1
* ===========
*
* Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't
* try to play with other control loops fans). Drive bay is rather basic PID
* with one sensor and one fan. Slots area is a bit different as the Darwin
* driver is supposed to be capable of working in a special "AGP" mode which
* involves the presence of an AGP sensor and an AGP fan (possibly on the
* AGP card itself). I can't deal with that special mode as I don't have
* access to those additional sensor/fans for now (though ultimately, it would
* be possible to add sensor objects for them) so I'm only implementing the
* basic PCI slot control loop
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/kmod.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#include "windfarm_pid.h"
#define VERSION "0.4"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/* define this to force CPU overtemp to 74 degree, useful for testing
* the overtemp code
*/
#undef HACKED_OVERTEMP
/* Controls & sensors */
static struct wf_sensor *sensor_cpu_power;
static struct wf_sensor *sensor_cpu_temp;
static struct wf_sensor *sensor_hd_temp;
static struct wf_sensor *sensor_slots_power;
static struct wf_control *fan_cpu_main;
static struct wf_control *fan_cpu_second;
static struct wf_control *fan_cpu_third;
static struct wf_control *fan_hd;
static struct wf_control *fan_slots;
static struct wf_control *cpufreq_clamp;
/* Set to kick the control loop into life */
static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started;
/* Failure handling.. could be nicer */
#define FAILURE_FAN 0x01
#define FAILURE_SENSOR 0x02
#define FAILURE_OVERTEMP 0x04
static unsigned int wf_smu_failure_state;
static int wf_smu_readjust, wf_smu_skipping;
/*
* ****** CPU Fans Control Loop ******
*
*/
#define WF_SMU_CPU_FANS_INTERVAL 1
#define WF_SMU_CPU_FANS_MAX_HISTORY 16
/* State data used by the cpu fans control loop
*/
struct wf_smu_cpu_fans_state {
int ticks;
s32 cpu_setpoint;
struct wf_cpu_pid_state pid;
};
static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans;
/*
* ****** Drive Fan Control Loop ******
*
*/
struct wf_smu_drive_fans_state {
int ticks;
s32 setpoint;
struct wf_pid_state pid;
};
static struct wf_smu_drive_fans_state *wf_smu_drive_fans;
/*
* ****** Slots Fan Control Loop ******
*
*/
struct wf_smu_slots_fans_state {
int ticks;
s32 setpoint;
struct wf_pid_state pid;
};
static struct wf_smu_slots_fans_state *wf_smu_slots_fans;
/*
* ***** Implementation *****
*
*/
static void wf_smu_create_cpu_fans(void)
{
struct wf_cpu_pid_param pid_param;
const struct smu_sdbp_header *hdr;
struct smu_sdbp_cpupiddata *piddata;
struct smu_sdbp_fvt *fvt;
s32 tmax, tdelta, maxpow, powadj;
/* First, locate the PID params in SMU SBD */
hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL);
if (hdr == 0) {
printk(KERN_WARNING "windfarm: CPU PID fan config not found "
"max fan speed\n");
goto fail;
}
piddata = (struct smu_sdbp_cpupiddata *)&hdr[1];
/* Get the FVT params for operating point 0 (the only supported one
* for now) in order to get tmax
*/
hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
if (hdr) {
fvt = (struct smu_sdbp_fvt *)&hdr[1];
tmax = ((s32)fvt->maxtemp) << 16;
} else
tmax = 0x5e0000; /* 94 degree default */
/* Alloc & initialize state */
wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state),
GFP_KERNEL);
if (wf_smu_cpu_fans == NULL)
goto fail;
wf_smu_cpu_fans->ticks = 1;
/* Fill PID params */
pid_param.interval = WF_SMU_CPU_FANS_INTERVAL;
pid_param.history_len = piddata->history_len;
if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) {
printk(KERN_WARNING "windfarm: History size overflow on "
"CPU control loop (%d)\n", piddata->history_len);
pid_param.history_len = WF_CPU_PID_MAX_HISTORY;
}
pid_param.gd = piddata->gd;
pid_param.gp = piddata->gp;
pid_param.gr = piddata->gr / pid_param.history_len;
tdelta = ((s32)piddata->target_temp_delta) << 16;
maxpow = ((s32)piddata->max_power) << 16;
powadj = ((s32)piddata->power_adj) << 16;
pid_param.tmax = tmax;
pid_param.ttarget = tmax - tdelta;
pid_param.pmaxadj = maxpow - powadj;
pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main);
pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main);
wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param);
DBG("wf: CPU Fan control initialized.\n");
DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax),
pid_param.min, pid_param.max);
return;
fail:
printk(KERN_WARNING "windfarm: CPU fan config not found\n"
"for this machine model, max fan speed\n");
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
}
static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)
{
s32 new_setpoint, temp, power;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = WF_SMU_CPU_FANS_INTERVAL;
rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power);
if (rc) {
printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n",
FIX32TOPRINT(temp), FIX32TOPRINT(power));
#ifdef HACKED_OVERTEMP
if (temp > 0x4a0000)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#else
if (temp > st->pid.param.tmax)
wf_smu_failure_state |= FAILURE_OVERTEMP;
#endif
new_setpoint = wf_cpu_pid_run(&st->pid, power, temp);
DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint);
if (st->cpu_setpoint == new_setpoint)
return;
st->cpu_setpoint = new_setpoint;
readjust:
if (fan_cpu_main && wf_smu_failure_state == 0) {
rc = fan_cpu_main->ops->set_value(fan_cpu_main,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU main fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
if (fan_cpu_second && wf_smu_failure_state == 0) {
rc = fan_cpu_second->ops->set_value(fan_cpu_second,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU second fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
if (fan_cpu_third && wf_smu_failure_state == 0) {
rc = fan_cpu_main->ops->set_value(fan_cpu_third,
st->cpu_setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: CPU third fan"
" error %d\n", rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
static void wf_smu_create_drive_fans(void)
{
struct wf_pid_param param = {
.interval = 5,
.history_len = 2,
.gd = 0x01e00000,
.gp = 0x00500000,
.gr = 0x00000000,
.itarget = 0x00200000,
};
/* Alloc & initialize state */
wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state),
GFP_KERNEL);
if (wf_smu_drive_fans == NULL) {
printk(KERN_WARNING "windfarm: Memory allocation error"
" max fan speed\n");
goto fail;
}
wf_smu_drive_fans->ticks = 1;
/* Fill PID params */
param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN);
param.min = fan_hd->ops->get_min(fan_hd);
param.max = fan_hd->ops->get_max(fan_hd);
wf_pid_init(&wf_smu_drive_fans->pid, &param);
DBG("wf: Drive Fan control initialized.\n");
DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(param.itarget), param.min, param.max);
return;
fail:
if (fan_hd)
wf_control_set_max(fan_hd);
}
static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)
{
s32 new_setpoint, temp;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = st->pid.param.interval;
rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp);
if (rc) {
printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n",
FIX32TOPRINT(temp));
if (temp > (st->pid.param.itarget + 0x50000))
wf_smu_failure_state |= FAILURE_OVERTEMP;
new_setpoint = wf_pid_run(&st->pid, temp);
DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
if (st->setpoint == new_setpoint)
return;
st->setpoint = new_setpoint;
readjust:
if (fan_hd && wf_smu_failure_state == 0) {
rc = fan_hd->ops->set_value(fan_hd, st->setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: HD fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
static void wf_smu_create_slots_fans(void)
{
struct wf_pid_param param = {
.interval = 1,
.history_len = 8,
.gd = 0x00000000,
.gp = 0x00000000,
.gr = 0x00020000,
.itarget = 0x00000000
};
/* Alloc & initialize state */
wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state),
GFP_KERNEL);
if (wf_smu_slots_fans == NULL) {
printk(KERN_WARNING "windfarm: Memory allocation error"
" max fan speed\n");
goto fail;
}
wf_smu_slots_fans->ticks = 1;
/* Fill PID params */
param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN);
param.min = fan_slots->ops->get_min(fan_slots);
param.max = fan_slots->ops->get_max(fan_slots);
wf_pid_init(&wf_smu_slots_fans->pid, &param);
DBG("wf: Slots Fan control initialized.\n");
DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n",
FIX32TOPRINT(param.itarget), param.min, param.max);
return;
fail:
if (fan_slots)
wf_control_set_max(fan_slots);
}
static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)
{
s32 new_setpoint, power;
int rc;
if (--st->ticks != 0) {
if (wf_smu_readjust)
goto readjust;
return;
}
st->ticks = st->pid.param.interval;
rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power);
if (rc) {
printk(KERN_WARNING "windfarm: Slots power sensor error %d\n",
rc);
wf_smu_failure_state |= FAILURE_SENSOR;
return;
}
DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n",
FIX32TOPRINT(power));
#if 0 /* Check what makes a good overtemp condition */
if (power > (st->pid.param.itarget + 0x50000))
wf_smu_failure_state |= FAILURE_OVERTEMP;
#endif
new_setpoint = wf_pid_run(&st->pid, power);
DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint);
if (st->setpoint == new_setpoint)
return;
st->setpoint = new_setpoint;
readjust:
if (fan_slots && wf_smu_failure_state == 0) {
rc = fan_slots->ops->set_value(fan_slots, st->setpoint);
if (rc) {
printk(KERN_WARNING "windfarm: Slots fan error %d\n",
rc);
wf_smu_failure_state |= FAILURE_FAN;
}
}
}
/*
* ****** Setup / Init / Misc ... ******
*
*/
static void wf_smu_tick(void)
{
unsigned int last_failure = wf_smu_failure_state;
unsigned int new_failure;
if (!wf_smu_started) {
DBG("wf: creating control loops !\n");
wf_smu_create_drive_fans();
wf_smu_create_slots_fans();
wf_smu_create_cpu_fans();
wf_smu_started = 1;
}
/* Skipping ticks */
if (wf_smu_skipping && --wf_smu_skipping)
return;
wf_smu_failure_state = 0;
if (wf_smu_drive_fans)
wf_smu_drive_fans_tick(wf_smu_drive_fans);
if (wf_smu_slots_fans)
wf_smu_slots_fans_tick(wf_smu_slots_fans);
if (wf_smu_cpu_fans)
wf_smu_cpu_fans_tick(wf_smu_cpu_fans);
wf_smu_readjust = 0;
new_failure = wf_smu_failure_state & ~last_failure;
/* If entering failure mode, clamp cpufreq and ramp all
* fans to full speed.
*/
if (wf_smu_failure_state && !last_failure) {
if (cpufreq_clamp)
wf_control_set_max(cpufreq_clamp);
if (fan_cpu_main)
wf_control_set_max(fan_cpu_main);
if (fan_cpu_second)
wf_control_set_max(fan_cpu_second);
if (fan_cpu_third)
wf_control_set_max(fan_cpu_third);
if (fan_hd)
wf_control_set_max(fan_hd);
if (fan_slots)
wf_control_set_max(fan_slots);
}
/* If leaving failure mode, unclamp cpufreq and readjust
* all fans on next iteration
*/
if (!wf_smu_failure_state && last_failure) {
if (cpufreq_clamp)
wf_control_set_min(cpufreq_clamp);
wf_smu_readjust = 1;
}
/* Overtemp condition detected, notify and start skipping a couple
* ticks to let the temperature go down
*/
if (new_failure & FAILURE_OVERTEMP) {
wf_set_overtemp();
wf_smu_skipping = 2;
}
/* We only clear the overtemp condition if overtemp is cleared
* _and_ no other failure is present. Since a sensor error will
* clear the overtemp condition (can't measure temperature) at
* the control loop levels, but we don't want to keep it clear
* here in this case
*/
if (new_failure == 0 && last_failure & FAILURE_OVERTEMP)
wf_clear_overtemp();
}
static void wf_smu_new_control(struct wf_control *ct)
{
if (wf_smu_all_controls_ok)
return;
if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) {
if (wf_get_control(ct) == 0)
fan_cpu_main = ct;
}
if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) {
if (wf_get_control(ct) == 0)
fan_cpu_second = ct;
}
if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) {
if (wf_get_control(ct) == 0)
fan_cpu_third = ct;
}
if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) {
if (wf_get_control(ct) == 0)
cpufreq_clamp = ct;
}
if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) {
if (wf_get_control(ct) == 0)
fan_hd = ct;
}
if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) {
if (wf_get_control(ct) == 0)
fan_slots = ct;
}
if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd &&
fan_slots && cpufreq_clamp)
wf_smu_all_controls_ok = 1;
}
static void wf_smu_new_sensor(struct wf_sensor *sr)
{
if (wf_smu_all_sensors_ok)
return;
if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) {
if (wf_get_sensor(sr) == 0)
sensor_cpu_power = sr;
}
if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) {
if (wf_get_sensor(sr) == 0)
sensor_cpu_temp = sr;
}
if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) {
if (wf_get_sensor(sr) == 0)
sensor_hd_temp = sr;
}
if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) {
if (wf_get_sensor(sr) == 0)
sensor_slots_power = sr;
}
if (sensor_cpu_power && sensor_cpu_temp &&
sensor_hd_temp && sensor_slots_power)
wf_smu_all_sensors_ok = 1;
}
static int wf_smu_notify(struct notifier_block *self,
unsigned long event, void *data)
{
switch(event) {
case WF_EVENT_NEW_CONTROL:
DBG("wf: new control %s detected\n",
((struct wf_control *)data)->name);
wf_smu_new_control(data);
wf_smu_readjust = 1;
break;
case WF_EVENT_NEW_SENSOR:
DBG("wf: new sensor %s detected\n",
((struct wf_sensor *)data)->name);
wf_smu_new_sensor(data);
break;
case WF_EVENT_TICK:
if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok)
wf_smu_tick();
}
return 0;
}
static struct notifier_block wf_smu_events = {
.notifier_call = wf_smu_notify,
};
static int wf_init_pm(void)
{
printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n");
return 0;
}
static int wf_smu_probe(struct platform_device *ddev)
{
wf_register_client(&wf_smu_events);
return 0;
}
static int __devexit wf_smu_remove(struct platform_device *ddev)
{
wf_unregister_client(&wf_smu_events);
/* XXX We don't have yet a guarantee that our callback isn't
* in progress when returning from wf_unregister_client, so
* we add an arbitrary delay. I'll have to fix that in the core
*/
msleep(1000);
/* Release all sensors */
/* One more crappy race: I don't think we have any guarantee here
* that the attribute callback won't race with the sensor beeing
* disposed of, and I'm not 100% certain what best way to deal
* with that except by adding locks all over... I'll do that
* eventually but heh, who ever rmmod this module anyway ?
*/
if (sensor_cpu_power)
wf_put_sensor(sensor_cpu_power);
if (sensor_cpu_temp)
wf_put_sensor(sensor_cpu_temp);
if (sensor_hd_temp)
wf_put_sensor(sensor_hd_temp);
if (sensor_slots_power)
wf_put_sensor(sensor_slots_power);
/* Release all controls */
if (fan_cpu_main)
wf_put_control(fan_cpu_main);
if (fan_cpu_second)
wf_put_control(fan_cpu_second);
if (fan_cpu_third)
wf_put_control(fan_cpu_third);
if (fan_hd)
wf_put_control(fan_hd);
if (fan_slots)
wf_put_control(fan_slots);
if (cpufreq_clamp)
wf_put_control(cpufreq_clamp);
/* Destroy control loops state structures */
if (wf_smu_slots_fans)
kfree(wf_smu_cpu_fans);
if (wf_smu_drive_fans)
kfree(wf_smu_cpu_fans);
if (wf_smu_cpu_fans)
kfree(wf_smu_cpu_fans);
return 0;
}
static struct platform_driver wf_smu_driver = {
.probe = wf_smu_probe,
.remove = __devexit_p(wf_smu_remove),
.driver = {
.name = "windfarm",
.owner = THIS_MODULE,
},
};
static int __init wf_smu_init(void)
{
int rc = -ENODEV;
if (machine_is_compatible("PowerMac9,1"))
rc = wf_init_pm();
if (rc == 0) {
#ifdef MODULE
request_module("windfarm_smu_controls");
request_module("windfarm_smu_sensors");
request_module("windfarm_lm75_sensor");
request_module("windfarm_cpufreq_clamp");
#endif /* MODULE */
platform_driver_register(&wf_smu_driver);
}
return rc;
}
static void __exit wf_smu_exit(void)
{
platform_driver_unregister(&wf_smu_driver);
}
module_init(wf_smu_init);
module_exit(wf_smu_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:windfarm");

View File

@@ -0,0 +1,329 @@
/*
* Windfarm PowerMac thermal control. SMU based controls
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/completion.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#define VERSION "0.4"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
static int smu_supports_new_fans_ops = 1;
/*
* SMU fans control object
*/
static LIST_HEAD(smu_fans);
struct smu_fan_control {
struct list_head link;
int fan_type; /* 0 = rpm, 1 = pwm */
u32 reg; /* index in SMU */
s32 value; /* current value */
s32 min, max; /* min/max values */
struct wf_control ctrl;
};
#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl)
static int smu_set_fan(int pwm, u8 id, u16 value)
{
struct smu_cmd cmd;
u8 buffer[16];
DECLARE_COMPLETION_ONSTACK(comp);
int rc;
/* Fill SMU command structure */
cmd.cmd = SMU_CMD_FAN_COMMAND;
/* The SMU has an "old" and a "new" way of setting the fan speed
* Unfortunately, I found no reliable way to know which one works
* on a given machine model. After some investigations it appears
* that MacOS X just tries the new one, and if it fails fallbacks
* to the old ones ... Ugh.
*/
retry:
if (smu_supports_new_fans_ops) {
buffer[0] = 0x30;
buffer[1] = id;
*((u16 *)(&buffer[2])) = value;
cmd.data_len = 4;
} else {
if (id > 7)
return -EINVAL;
/* Fill argument buffer */
memset(buffer, 0, 16);
buffer[0] = pwm ? 0x10 : 0x00;
buffer[1] = 0x01 << id;
*((u16 *)&buffer[2 + id * 2]) = value;
cmd.data_len = 14;
}
cmd.reply_len = 16;
cmd.data_buf = cmd.reply_buf = buffer;
cmd.status = 0;
cmd.done = smu_done_complete;
cmd.misc = &comp;
rc = smu_queue_cmd(&cmd);
if (rc)
return rc;
wait_for_completion(&comp);
/* Handle fallback (see coment above) */
if (cmd.status != 0 && smu_supports_new_fans_ops) {
printk(KERN_WARNING "windfarm: SMU failed new fan command "
"falling back to old method\n");
smu_supports_new_fans_ops = 0;
goto retry;
}
return cmd.status;
}
static void smu_fan_release(struct wf_control *ct)
{
struct smu_fan_control *fct = to_smu_fan(ct);
kfree(fct);
}
static int smu_fan_set(struct wf_control *ct, s32 value)
{
struct smu_fan_control *fct = to_smu_fan(ct);
if (value < fct->min)
value = fct->min;
if (value > fct->max)
value = fct->max;
fct->value = value;
return smu_set_fan(fct->fan_type, fct->reg, value);
}
static int smu_fan_get(struct wf_control *ct, s32 *value)
{
struct smu_fan_control *fct = to_smu_fan(ct);
*value = fct->value; /* todo: read from SMU */
return 0;
}
static s32 smu_fan_min(struct wf_control *ct)
{
struct smu_fan_control *fct = to_smu_fan(ct);
return fct->min;
}
static s32 smu_fan_max(struct wf_control *ct)
{
struct smu_fan_control *fct = to_smu_fan(ct);
return fct->max;
}
static struct wf_control_ops smu_fan_ops = {
.set_value = smu_fan_set,
.get_value = smu_fan_get,
.get_min = smu_fan_min,
.get_max = smu_fan_max,
.release = smu_fan_release,
.owner = THIS_MODULE,
};
static struct smu_fan_control *smu_fan_create(struct device_node *node,
int pwm_fan)
{
struct smu_fan_control *fct;
const s32 *v;
const u32 *reg;
const char *l;
fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL);
if (fct == NULL)
return NULL;
fct->ctrl.ops = &smu_fan_ops;
l = of_get_property(node, "location", NULL);
if (l == NULL)
goto fail;
fct->fan_type = pwm_fan;
fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN;
/* We use the name & location here the same way we do for SMU sensors,
* see the comment in windfarm_smu_sensors.c. The locations are a bit
* less consistent here between the iMac and the desktop models, but
* that is good enough for our needs for now at least.
*
* One problem though is that Apple seem to be inconsistent with case
* and the kernel doesn't have strcasecmp =P
*/
fct->ctrl.name = NULL;
/* Names used on desktop models */
if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") ||
!strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan") ||
!strcmp(l, "CPU A EXHAUST"))
fct->ctrl.name = "cpu-rear-fan-0";
else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1") ||
!strcmp(l, "CPU B EXHAUST"))
fct->ctrl.name = "cpu-rear-fan-1";
else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") ||
!strcmp(l, "Front fan 0") || !strcmp(l, "Front fan") ||
!strcmp(l, "CPU A INTAKE"))
fct->ctrl.name = "cpu-front-fan-0";
else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1") ||
!strcmp(l, "CPU B INTAKE"))
fct->ctrl.name = "cpu-front-fan-1";
else if (!strcmp(l, "CPU A PUMP"))
fct->ctrl.name = "cpu-pump-0";
else if (!strcmp(l, "CPU B PUMP"))
fct->ctrl.name = "cpu-pump-1";
else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan") ||
!strcmp(l, "EXPANSION SLOTS INTAKE"))
fct->ctrl.name = "slots-fan";
else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay") ||
!strcmp(l, "DRIVE BAY A INTAKE"))
fct->ctrl.name = "drive-bay-fan";
else if (!strcmp(l, "BACKSIDE"))
fct->ctrl.name = "backside-fan";
/* Names used on iMac models */
if (!strcmp(l, "System Fan") || !strcmp(l, "System fan"))
fct->ctrl.name = "system-fan";
else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan"))
fct->ctrl.name = "cpu-fan";
else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive"))
fct->ctrl.name = "drive-bay-fan";
else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */
fct->ctrl.name = "hard-drive-fan";
else if (!strcmp(l, "ODD Fan")) /* same */
fct->ctrl.name = "optical-drive-fan";
/* Unrecognized fan, bail out */
if (fct->ctrl.name == NULL)
goto fail;
/* Get min & max values*/
v = of_get_property(node, "min-value", NULL);
if (v == NULL)
goto fail;
fct->min = *v;
v = of_get_property(node, "max-value", NULL);
if (v == NULL)
goto fail;
fct->max = *v;
/* Get "reg" value */
reg = of_get_property(node, "reg", NULL);
if (reg == NULL)
goto fail;
fct->reg = *reg;
if (wf_register_control(&fct->ctrl))
goto fail;
return fct;
fail:
kfree(fct);
return NULL;
}
static int __init smu_controls_init(void)
{
struct device_node *smu, *fans, *fan;
if (!smu_present())
return -ENODEV;
smu = of_find_node_by_type(NULL, "smu");
if (smu == NULL)
return -ENODEV;
/* Look for RPM fans */
for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
if (!strcmp(fans->name, "rpm-fans") ||
of_device_is_compatible(fans, "smu-rpm-fans"))
break;
for (fan = NULL;
fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
struct smu_fan_control *fct;
fct = smu_fan_create(fan, 0);
if (fct == NULL) {
printk(KERN_WARNING "windfarm: Failed to create SMU "
"RPM fan %s\n", fan->name);
continue;
}
list_add(&fct->link, &smu_fans);
}
of_node_put(fans);
/* Look for PWM fans */
for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;)
if (!strcmp(fans->name, "pwm-fans"))
break;
for (fan = NULL;
fans && (fan = of_get_next_child(fans, fan)) != NULL;) {
struct smu_fan_control *fct;
fct = smu_fan_create(fan, 1);
if (fct == NULL) {
printk(KERN_WARNING "windfarm: Failed to create SMU "
"PWM fan %s\n", fan->name);
continue;
}
list_add(&fct->link, &smu_fans);
}
of_node_put(fans);
of_node_put(smu);
return 0;
}
static void __exit smu_controls_exit(void)
{
struct smu_fan_control *fct;
while (!list_empty(&smu_fans)) {
fct = list_entry(smu_fans.next, struct smu_fan_control, link);
list_del(&fct->link);
wf_unregister_control(&fct->ctrl);
}
}
module_init(smu_controls_init);
module_exit(smu_controls_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,415 @@
/*
* Windfarm PowerMac thermal control. SMU "satellite" controller sensors.
*
* Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org>
*
* Released under the terms of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <asm/prom.h>
#include <asm/smu.h>
#include <asm/pmac_low_i2c.h>
#include "windfarm.h"
#define VERSION "0.2"
#define DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/* If the cache is older than 800ms we'll refetch it */
#define MAX_AGE msecs_to_jiffies(800)
struct wf_sat {
int nr;
atomic_t refcnt;
struct mutex mutex;
unsigned long last_read; /* jiffies when cache last updated */
u8 cache[16];
struct i2c_client *i2c;
struct device_node *node;
};
static struct wf_sat *sats[2];
struct wf_sat_sensor {
int index;
int index2; /* used for power sensors */
int shift;
struct wf_sat *sat;
struct wf_sensor sens;
};
#define wf_to_sat(c) container_of(c, struct wf_sat_sensor, sens)
struct smu_sdbp_header *smu_sat_get_sdb_partition(unsigned int sat_id, int id,
unsigned int *size)
{
struct wf_sat *sat;
int err;
unsigned int i, len;
u8 *buf;
u8 data[4];
/* TODO: Add the resulting partition to the device-tree */
if (sat_id > 1 || (sat = sats[sat_id]) == NULL)
return NULL;
err = i2c_smbus_write_word_data(sat->i2c, 8, id << 8);
if (err) {
printk(KERN_ERR "smu_sat_get_sdb_part wr error %d\n", err);
return NULL;
}
err = i2c_smbus_read_word_data(sat->i2c, 9);
if (err < 0) {
printk(KERN_ERR "smu_sat_get_sdb_part rd len error\n");
return NULL;
}
len = err;
if (len == 0) {
printk(KERN_ERR "smu_sat_get_sdb_part no partition %x\n", id);
return NULL;
}
len = le16_to_cpu(len);
len = (len + 3) & ~3;
buf = kmalloc(len, GFP_KERNEL);
if (buf == NULL)
return NULL;
for (i = 0; i < len; i += 4) {
err = i2c_smbus_read_i2c_block_data(sat->i2c, 0xa, 4, data);
if (err < 0) {
printk(KERN_ERR "smu_sat_get_sdb_part rd err %d\n",
err);
goto fail;
}
buf[i] = data[1];
buf[i+1] = data[0];
buf[i+2] = data[3];
buf[i+3] = data[2];
}
#ifdef DEBUG
DBG(KERN_DEBUG "sat %d partition %x:", sat_id, id);
for (i = 0; i < len; ++i)
DBG(" %x", buf[i]);
DBG("\n");
#endif
if (size)
*size = len;
return (struct smu_sdbp_header *) buf;
fail:
kfree(buf);
return NULL;
}
EXPORT_SYMBOL_GPL(smu_sat_get_sdb_partition);
/* refresh the cache */
static int wf_sat_read_cache(struct wf_sat *sat)
{
int err;
err = i2c_smbus_read_i2c_block_data(sat->i2c, 0x3f, 16, sat->cache);
if (err < 0)
return err;
sat->last_read = jiffies;
#ifdef LOTSA_DEBUG
{
int i;
DBG(KERN_DEBUG "wf_sat_get: data is");
for (i = 0; i < 16; ++i)
DBG(" %.2x", sat->cache[i]);
DBG("\n");
}
#endif
return 0;
}
static int wf_sat_get(struct wf_sensor *sr, s32 *value)
{
struct wf_sat_sensor *sens = wf_to_sat(sr);
struct wf_sat *sat = sens->sat;
int i, err;
s32 val;
if (sat->i2c == NULL)
return -ENODEV;
mutex_lock(&sat->mutex);
if (time_after(jiffies, (sat->last_read + MAX_AGE))) {
err = wf_sat_read_cache(sat);
if (err)
goto fail;
}
i = sens->index * 2;
val = ((sat->cache[i] << 8) + sat->cache[i+1]) << sens->shift;
if (sens->index2 >= 0) {
i = sens->index2 * 2;
/* 4.12 * 8.8 -> 12.20; shift right 4 to get 16.16 */
val = (val * ((sat->cache[i] << 8) + sat->cache[i+1])) >> 4;
}
*value = val;
err = 0;
fail:
mutex_unlock(&sat->mutex);
return err;
}
static void wf_sat_release(struct wf_sensor *sr)
{
struct wf_sat_sensor *sens = wf_to_sat(sr);
struct wf_sat *sat = sens->sat;
if (atomic_dec_and_test(&sat->refcnt)) {
if (sat->nr >= 0)
sats[sat->nr] = NULL;
kfree(sat);
}
kfree(sens);
}
static struct wf_sensor_ops wf_sat_ops = {
.get_value = wf_sat_get,
.release = wf_sat_release,
.owner = THIS_MODULE,
};
static struct i2c_driver wf_sat_driver;
static void wf_sat_create(struct i2c_adapter *adapter, struct device_node *dev)
{
struct i2c_board_info info;
struct i2c_client *client;
const u32 *reg;
u8 addr;
reg = of_get_property(dev, "reg", NULL);
if (reg == NULL)
return;
addr = *reg;
DBG(KERN_DEBUG "wf_sat: creating sat at address %x\n", addr);
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = (addr >> 1) & 0x7f;
info.platform_data = dev;
strlcpy(info.type, "wf_sat", I2C_NAME_SIZE);
client = i2c_new_device(adapter, &info);
if (client == NULL) {
printk(KERN_ERR "windfarm: failed to attach smu-sat to i2c\n");
return;
}
/*
* Let i2c-core delete that device on driver removal.
* This is safe because i2c-core holds the core_lock mutex for us.
*/
list_add_tail(&client->detected, &wf_sat_driver.clients);
}
static int wf_sat_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device_node *dev = client->dev.platform_data;
struct wf_sat *sat;
struct wf_sat_sensor *sens;
const u32 *reg;
const char *loc, *type;
u8 chip, core;
struct device_node *child;
int shift, cpu, index;
char *name;
int vsens[2], isens[2];
sat = kzalloc(sizeof(struct wf_sat), GFP_KERNEL);
if (sat == NULL)
return -ENOMEM;
sat->nr = -1;
sat->node = of_node_get(dev);
atomic_set(&sat->refcnt, 0);
mutex_init(&sat->mutex);
sat->i2c = client;
i2c_set_clientdata(client, sat);
vsens[0] = vsens[1] = -1;
isens[0] = isens[1] = -1;
child = NULL;
while ((child = of_get_next_child(dev, child)) != NULL) {
reg = of_get_property(child, "reg", NULL);
type = of_get_property(child, "device_type", NULL);
loc = of_get_property(child, "location", NULL);
if (reg == NULL || loc == NULL)
continue;
/* the cooked sensors are between 0x30 and 0x37 */
if (*reg < 0x30 || *reg > 0x37)
continue;
index = *reg - 0x30;
/* expect location to be CPU [AB][01] ... */
if (strncmp(loc, "CPU ", 4) != 0)
continue;
chip = loc[4] - 'A';
core = loc[5] - '0';
if (chip > 1 || core > 1) {
printk(KERN_ERR "wf_sat_create: don't understand "
"location %s for %s\n", loc, child->full_name);
continue;
}
cpu = 2 * chip + core;
if (sat->nr < 0)
sat->nr = chip;
else if (sat->nr != chip) {
printk(KERN_ERR "wf_sat_create: can't cope with "
"multiple CPU chips on one SAT (%s)\n", loc);
continue;
}
if (strcmp(type, "voltage-sensor") == 0) {
name = "cpu-voltage";
shift = 4;
vsens[core] = index;
} else if (strcmp(type, "current-sensor") == 0) {
name = "cpu-current";
shift = 8;
isens[core] = index;
} else if (strcmp(type, "temp-sensor") == 0) {
name = "cpu-temp";
shift = 10;
} else
continue; /* hmmm shouldn't happen */
/* the +16 is enough for "cpu-voltage-n" */
sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);
if (sens == NULL) {
printk(KERN_ERR "wf_sat_create: couldn't create "
"%s sensor %d (no memory)\n", name, cpu);
continue;
}
sens->index = index;
sens->index2 = -1;
sens->shift = shift;
sens->sat = sat;
atomic_inc(&sat->refcnt);
sens->sens.ops = &wf_sat_ops;
sens->sens.name = (char *) (sens + 1);
snprintf(sens->sens.name, 16, "%s-%d", name, cpu);
if (wf_register_sensor(&sens->sens)) {
atomic_dec(&sat->refcnt);
kfree(sens);
}
}
/* make the power sensors */
for (core = 0; core < 2; ++core) {
if (vsens[core] < 0 || isens[core] < 0)
continue;
cpu = 2 * sat->nr + core;
sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL);
if (sens == NULL) {
printk(KERN_ERR "wf_sat_create: couldn't create power "
"sensor %d (no memory)\n", cpu);
continue;
}
sens->index = vsens[core];
sens->index2 = isens[core];
sens->shift = 0;
sens->sat = sat;
atomic_inc(&sat->refcnt);
sens->sens.ops = &wf_sat_ops;
sens->sens.name = (char *) (sens + 1);
snprintf(sens->sens.name, 16, "cpu-power-%d", cpu);
if (wf_register_sensor(&sens->sens)) {
atomic_dec(&sat->refcnt);
kfree(sens);
}
}
if (sat->nr >= 0)
sats[sat->nr] = sat;
return 0;
}
static int wf_sat_attach(struct i2c_adapter *adapter)
{
struct device_node *busnode, *dev = NULL;
struct pmac_i2c_bus *bus;
bus = pmac_i2c_adapter_to_bus(adapter);
if (bus == NULL)
return -ENODEV;
busnode = pmac_i2c_get_bus_node(bus);
while ((dev = of_get_next_child(busnode, dev)) != NULL)
if (of_device_is_compatible(dev, "smu-sat"))
wf_sat_create(adapter, dev);
return 0;
}
static int wf_sat_remove(struct i2c_client *client)
{
struct wf_sat *sat = i2c_get_clientdata(client);
/* XXX TODO */
sat->i2c = NULL;
i2c_set_clientdata(client, NULL);
return 0;
}
static const struct i2c_device_id wf_sat_id[] = {
{ "wf_sat", 0 },
{ }
};
static struct i2c_driver wf_sat_driver = {
.driver = {
.name = "wf_smu_sat",
},
.attach_adapter = wf_sat_attach,
.probe = wf_sat_probe,
.remove = wf_sat_remove,
.id_table = wf_sat_id,
};
static int __init sat_sensors_init(void)
{
return i2c_add_driver(&wf_sat_driver);
}
#if 0 /* uncomment when module_exit() below is uncommented */
static void __exit sat_sensors_exit(void)
{
i2c_del_driver(&wf_sat_driver);
}
#endif
module_init(sat_sensors_init);
/*module_exit(sat_sensors_exit); Uncomment when cleanup is implemented */
MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");
MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,483 @@
/*
* Windfarm PowerMac thermal control. SMU based sensors
*
* (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
* <benh@kernel.crashing.org>
*
* Released under the term of the GNU GPL v2.
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/completion.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/sections.h>
#include <asm/smu.h>
#include "windfarm.h"
#define VERSION "0.2"
#undef DEBUG
#ifdef DEBUG
#define DBG(args...) printk(args)
#else
#define DBG(args...) do { } while(0)
#endif
/*
* Various SMU "partitions" calibration objects for which we
* keep pointers here for use by bits & pieces of the driver
*/
static struct smu_sdbp_cpuvcp *cpuvcp;
static int cpuvcp_version;
static struct smu_sdbp_cpudiode *cpudiode;
static struct smu_sdbp_slotspow *slotspow;
static u8 *debugswitches;
/*
* SMU basic sensors objects
*/
static LIST_HEAD(smu_ads);
struct smu_ad_sensor {
struct list_head link;
u32 reg; /* index in SMU */
struct wf_sensor sens;
};
#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens)
static void smu_ads_release(struct wf_sensor *sr)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
kfree(ads);
}
static int smu_read_adc(u8 id, s32 *value)
{
struct smu_simple_cmd cmd;
DECLARE_COMPLETION_ONSTACK(comp);
int rc;
rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1,
smu_done_complete, &comp, id);
if (rc)
return rc;
wait_for_completion(&comp);
if (cmd.cmd.status != 0)
return cmd.cmd.status;
if (cmd.cmd.reply_len != 2) {
printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n",
id, cmd.cmd.reply_len);
return -EIO;
}
*value = *((u16 *)cmd.buffer);
return 0;
}
static int smu_cputemp_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
int rc;
s32 val;
s64 scaled;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s64)(((u64)val) * (u64)cpudiode->m_value);
scaled >>= 3;
scaled += ((s64)cpudiode->b_value) << 9;
*value = (s32)(scaled << 1);
return 0;
}
static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
s32 val, scaled;
int rc;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read CPU current failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s32)(val * (u32)cpuvcp->curr_scale);
scaled += (s32)cpuvcp->curr_offset;
*value = scaled << 4;
return 0;
}
static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
s32 val, scaled;
int rc;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s32)(val * (u32)cpuvcp->volt_scale);
scaled += (s32)cpuvcp->volt_offset;
*value = scaled << 4;
return 0;
}
static int smu_slotspow_get(struct wf_sensor *sr, s32 *value)
{
struct smu_ad_sensor *ads = to_smu_ads(sr);
s32 val, scaled;
int rc;
rc = smu_read_adc(ads->reg, &val);
if (rc) {
printk(KERN_ERR "windfarm: read slots power failed, err %d\n",
rc);
return rc;
}
/* Ok, we have to scale & adjust, taking units into account */
scaled = (s32)(val * (u32)slotspow->pow_scale);
scaled += (s32)slotspow->pow_offset;
*value = scaled << 4;
return 0;
}
static struct wf_sensor_ops smu_cputemp_ops = {
.get_value = smu_cputemp_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct wf_sensor_ops smu_cpuamp_ops = {
.get_value = smu_cpuamp_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct wf_sensor_ops smu_cpuvolt_ops = {
.get_value = smu_cpuvolt_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct wf_sensor_ops smu_slotspow_ops = {
.get_value = smu_slotspow_get,
.release = smu_ads_release,
.owner = THIS_MODULE,
};
static struct smu_ad_sensor *smu_ads_create(struct device_node *node)
{
struct smu_ad_sensor *ads;
const char *c, *l;
const u32 *v;
ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL);
if (ads == NULL)
return NULL;
c = of_get_property(node, "device_type", NULL);
l = of_get_property(node, "location", NULL);
if (c == NULL || l == NULL)
goto fail;
/* We currently pick the sensors based on the OF name and location
* properties, while Darwin uses the sensor-id's.
* The problem with the IDs is that they are model specific while it
* looks like apple has been doing a reasonably good job at keeping
* the names and locations consistents so I'll stick with the names
* and locations for now.
*/
if (!strcmp(c, "temp-sensor") &&
!strcmp(l, "CPU T-Diode")) {
ads->sens.ops = &smu_cputemp_ops;
ads->sens.name = "cpu-temp";
if (cpudiode == NULL) {
DBG("wf: cpudiode partition (%02x) not found\n",
SMU_SDB_CPUDIODE_ID);
goto fail;
}
} else if (!strcmp(c, "current-sensor") &&
!strcmp(l, "CPU Current")) {
ads->sens.ops = &smu_cpuamp_ops;
ads->sens.name = "cpu-current";
if (cpuvcp == NULL) {
DBG("wf: cpuvcp partition (%02x) not found\n",
SMU_SDB_CPUVCP_ID);
goto fail;
}
} else if (!strcmp(c, "voltage-sensor") &&
!strcmp(l, "CPU Voltage")) {
ads->sens.ops = &smu_cpuvolt_ops;
ads->sens.name = "cpu-voltage";
if (cpuvcp == NULL) {
DBG("wf: cpuvcp partition (%02x) not found\n",
SMU_SDB_CPUVCP_ID);
goto fail;
}
} else if (!strcmp(c, "power-sensor") &&
!strcmp(l, "Slots Power")) {
ads->sens.ops = &smu_slotspow_ops;
ads->sens.name = "slots-power";
if (slotspow == NULL) {
DBG("wf: slotspow partition (%02x) not found\n",
SMU_SDB_SLOTSPOW_ID);
goto fail;
}
} else
goto fail;
v = of_get_property(node, "reg", NULL);
if (v == NULL)
goto fail;
ads->reg = *v;
if (wf_register_sensor(&ads->sens))
goto fail;
return ads;
fail:
kfree(ads);
return NULL;
}
/*
* SMU Power combo sensor object
*/
struct smu_cpu_power_sensor {
struct list_head link;
struct wf_sensor *volts;
struct wf_sensor *amps;
int fake_volts : 1;
int quadratic : 1;
struct wf_sensor sens;
};
#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens)
static struct smu_cpu_power_sensor *smu_cpu_power;
static void smu_cpu_power_release(struct wf_sensor *sr)
{
struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
if (pow->volts)
wf_put_sensor(pow->volts);
if (pow->amps)
wf_put_sensor(pow->amps);
kfree(pow);
}
static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value)
{
struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr);
s32 volts, amps, power;
u64 tmps, tmpa, tmpb;
int rc;
rc = pow->amps->ops->get_value(pow->amps, &amps);
if (rc)
return rc;
if (pow->fake_volts) {
*value = amps * 12 - 0x30000;
return 0;
}
rc = pow->volts->ops->get_value(pow->volts, &volts);
if (rc)
return rc;
power = (s32)((((u64)volts) * ((u64)amps)) >> 16);
if (!pow->quadratic) {
*value = power;
return 0;
}
tmps = (((u64)power) * ((u64)power)) >> 16;
tmpa = ((u64)cpuvcp->power_quads[0]) * tmps;
tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power);
*value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12);
return 0;
}
static struct wf_sensor_ops smu_cpu_power_ops = {
.get_value = smu_cpu_power_get,
.release = smu_cpu_power_release,
.owner = THIS_MODULE,
};
static struct smu_cpu_power_sensor *
smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps)
{
struct smu_cpu_power_sensor *pow;
pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL);
if (pow == NULL)
return NULL;
pow->sens.ops = &smu_cpu_power_ops;
pow->sens.name = "cpu-power";
wf_get_sensor(volts);
pow->volts = volts;
wf_get_sensor(amps);
pow->amps = amps;
/* Some early machines need a faked voltage */
if (debugswitches && ((*debugswitches) & 0x80)) {
printk(KERN_INFO "windfarm: CPU Power sensor using faked"
" voltage !\n");
pow->fake_volts = 1;
} else
pow->fake_volts = 0;
/* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now,
* I yet have to figure out what's up with 8,2 and will have to
* adjust for later, unless we can 100% trust the SDB partition...
*/
if ((machine_is_compatible("PowerMac8,1") ||
machine_is_compatible("PowerMac8,2") ||
machine_is_compatible("PowerMac9,1")) &&
cpuvcp_version >= 2) {
pow->quadratic = 1;
DBG("windfarm: CPU Power using quadratic transform\n");
} else
pow->quadratic = 0;
if (wf_register_sensor(&pow->sens))
goto fail;
return pow;
fail:
kfree(pow);
return NULL;
}
static void smu_fetch_param_partitions(void)
{
const struct smu_sdbp_header *hdr;
/* Get CPU voltage/current/power calibration data */
hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL);
if (hdr != NULL) {
cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1];
/* Keep version around */
cpuvcp_version = hdr->version;
}
/* Get CPU diode calibration data */
hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL);
if (hdr != NULL)
cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1];
/* Get slots power calibration data if any */
hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL);
if (hdr != NULL)
slotspow = (struct smu_sdbp_slotspow *)&hdr[1];
/* Get debug switches if any */
hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL);
if (hdr != NULL)
debugswitches = (u8 *)&hdr[1];
}
static int __init smu_sensors_init(void)
{
struct device_node *smu, *sensors, *s;
struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL;
if (!smu_present())
return -ENODEV;
/* Get parameters partitions */
smu_fetch_param_partitions();
smu = of_find_node_by_type(NULL, "smu");
if (smu == NULL)
return -ENODEV;
/* Look for sensors subdir */
for (sensors = NULL;
(sensors = of_get_next_child(smu, sensors)) != NULL;)
if (!strcmp(sensors->name, "sensors"))
break;
of_node_put(smu);
/* Create basic sensors */
for (s = NULL;
sensors && (s = of_get_next_child(sensors, s)) != NULL;) {
struct smu_ad_sensor *ads;
ads = smu_ads_create(s);
if (ads == NULL)
continue;
list_add(&ads->link, &smu_ads);
/* keep track of cpu voltage & current */
if (!strcmp(ads->sens.name, "cpu-voltage"))
volt_sensor = ads;
else if (!strcmp(ads->sens.name, "cpu-current"))
curr_sensor = ads;
}
of_node_put(sensors);
/* Create CPU power sensor if possible */
if (volt_sensor && curr_sensor)
smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens,
&curr_sensor->sens);
return 0;
}
static void __exit smu_sensors_exit(void)
{
struct smu_ad_sensor *ads;
/* dispose of power sensor */
if (smu_cpu_power)
wf_unregister_sensor(&smu_cpu_power->sens);
/* dispose of basic sensors */
while (!list_empty(&smu_ads)) {
ads = list_entry(smu_ads.next, struct smu_ad_sensor, link);
list_del(&ads->link);
wf_unregister_sensor(&ads->sens);
}
}
module_init(smu_sensors_init);
module_exit(smu_sensors_exit);
MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>");
MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control");
MODULE_LICENSE("GPL");