Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
237
drivers/macintosh/Kconfig
Normal file
237
drivers/macintosh/Kconfig
Normal file
@@ -0,0 +1,237 @@
|
||||
|
||||
menu "Macintosh device drivers"
|
||||
depends on PPC || MAC || X86
|
||||
|
||||
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"
|
||||
depends on PPC_PMAC && PPC32 && PM && ADB_PMU
|
||||
|
||||
config PMAC_MEDIABAY
|
||||
bool "Support PowerBook hotswap media bay"
|
||||
depends on PPC_PMAC && PPC32
|
||||
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 an 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"
|
||||
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 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 procides some support to control the front panel
|
||||
blue LEDs "vu-meter" of the XServer macs.
|
||||
|
||||
endmenu
|
||||
45
drivers/macintosh/Makefile
Normal file
45
drivers/macintosh/Makefile
Normal file
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# 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_PMAC_RACKMETER) += rack-meter.o
|
||||
287
drivers/macintosh/adb-iop.c
Normal file
287
drivers/macintosh/adb-iop.c
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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/bootinfo.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;
|
||||
}
|
||||
909
drivers/macintosh/adb.c
Normal file
909
drivers/macintosh/adb.c
Normal file
@@ -0,0 +1,909 @@
|
||||
/*
|
||||
* 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 <asm/uaccess.h>
|
||||
#include <asm/semaphore.h>
|
||||
#ifdef CONFIG_PPC
|
||||
#include <asm/prom.h>
|
||||
#include <asm/machdep.h>
|
||||
#endif
|
||||
|
||||
|
||||
EXPORT_SYMBOL(adb_controller);
|
||||
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;
|
||||
|
||||
struct adb_driver *adb_controller;
|
||||
BLOCKING_NOTIFIER_HEAD(adb_client_list);
|
||||
static int adb_got_sleep;
|
||||
static int adb_inited;
|
||||
static pid_t adb_probe_task_pid;
|
||||
static DECLARE_MUTEX(adb_probe_mutex);
|
||||
static struct completion adb_probe_task_comp;
|
||||
static int sleepy_trackpad;
|
||||
static int autopoll_devs;
|
||||
int __adb_probe_sync;
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int adb_notify_sleep(struct pmu_sleep_notifier *self, int when);
|
||||
static struct pmu_sleep_notifier adb_sleep_notifier = {
|
||||
adb_notify_sleep,
|
||||
SLEEP_LEVEL_ADB,
|
||||
};
|
||||
#endif
|
||||
|
||||
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_sem 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 DECLARE_MUTEX(adb_handler_sem);
|
||||
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 __inline__ void adb_wait_ms(unsigned int ms)
|
||||
{
|
||||
if (current->pid && adb_probe_task_pid &&
|
||||
adb_probe_task_pid == current->pid)
|
||||
msleep(ms);
|
||||
else
|
||||
mdelay(ms);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
sigset_t blocked;
|
||||
|
||||
strcpy(current->comm, "kadbprobe");
|
||||
|
||||
sigfillset(&blocked);
|
||||
sigprocmask(SIG_BLOCK, &blocked, NULL);
|
||||
flush_signals(current);
|
||||
|
||||
printk(KERN_INFO "adb: starting probe task...\n");
|
||||
do_adb_reset_bus();
|
||||
printk(KERN_INFO "adb: finished probe task...\n");
|
||||
|
||||
adb_probe_task_pid = 0;
|
||||
up(&adb_probe_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
__adb_probe_task(struct work_struct *bullshit)
|
||||
{
|
||||
adb_probe_task_pid = kernel_thread(adb_probe_task, NULL, SIGCHLD | CLONE_KERNEL);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_PM
|
||||
pmu_register_sleep_notifier(&adb_sleep_notifier);
|
||||
#endif /* CONFIG_PM */
|
||||
#ifdef CONFIG_PPC
|
||||
if (machine_is_compatible("AAPL,PowerBook1998") ||
|
||||
machine_is_compatible("PowerBook1,1"))
|
||||
sleepy_trackpad = 1;
|
||||
#endif /* CONFIG_PPC */
|
||||
init_completion(&adb_probe_task_comp);
|
||||
adbdev_init();
|
||||
adb_reset_bus();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
__initcall(adb_init);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
/*
|
||||
* notify clients before sleep and reset bus afterwards
|
||||
*/
|
||||
int
|
||||
adb_notify_sleep(struct pmu_sleep_notifier *self, int when)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (when) {
|
||||
case PBOOK_SLEEP_REQUEST:
|
||||
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);
|
||||
ret = blocking_notifier_call_chain(&adb_client_list,
|
||||
ADB_MSG_POWERDOWN, NULL);
|
||||
if (ret & NOTIFY_STOP_MASK) {
|
||||
up(&adb_probe_mutex);
|
||||
return PBOOK_SLEEP_REFUSE;
|
||||
}
|
||||
break;
|
||||
case PBOOK_SLEEP_REJECT:
|
||||
if (adb_got_sleep) {
|
||||
adb_got_sleep = 0;
|
||||
up(&adb_probe_mutex);
|
||||
adb_reset_bus();
|
||||
}
|
||||
break;
|
||||
|
||||
case PBOOK_SLEEP_NOW:
|
||||
break;
|
||||
case PBOOK_WAKE:
|
||||
adb_got_sleep = 0;
|
||||
up(&adb_probe_mutex);
|
||||
adb_reset_bus();
|
||||
break;
|
||||
}
|
||||
return PBOOK_SLEEP_OK;
|
||||
}
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static int
|
||||
do_adb_reset_bus(void)
|
||||
{
|
||||
int ret, nret;
|
||||
|
||||
if (adb_controller == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
if (adb_controller->autopoll)
|
||||
adb_controller->autopoll(0);
|
||||
|
||||
nret = blocking_notifier_call_chain(&adb_client_list,
|
||||
ADB_MSG_PRE_RESET, NULL);
|
||||
if (nret & NOTIFY_STOP_MASK) {
|
||||
if (adb_controller->autopoll)
|
||||
adb_controller->autopoll(autopoll_devs);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (sleepy_trackpad) {
|
||||
/* Let the trackpad settle down */
|
||||
adb_wait_ms(500);
|
||||
}
|
||||
|
||||
down(&adb_handler_sem);
|
||||
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 */
|
||||
adb_wait_ms(1500);
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
autopoll_devs = adb_scan_bus();
|
||||
if (adb_controller->autopoll)
|
||||
adb_controller->autopoll(autopoll_devs);
|
||||
}
|
||||
up(&adb_handler_sem);
|
||||
|
||||
nret = blocking_notifier_call_chain(&adb_client_list,
|
||||
ADB_MSG_POST_RESET, NULL);
|
||||
if (nret & NOTIFY_STOP_MASK)
|
||||
return -EBUSY;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
adb_poll(void)
|
||||
{
|
||||
if ((adb_controller == NULL)||(adb_controller->poll == NULL))
|
||||
return;
|
||||
adb_controller->poll();
|
||||
}
|
||||
|
||||
static void
|
||||
adb_probe_wakeup(struct adb_request *req)
|
||||
{
|
||||
complete(&adb_probe_task_comp);
|
||||
}
|
||||
|
||||
/* Static request used during probe */
|
||||
static struct adb_request adb_sreq;
|
||||
static unsigned long adb_sreq_lock; // Use semaphore ! */
|
||||
|
||||
int
|
||||
adb_request(struct adb_request *req, void (*done)(struct adb_request *),
|
||||
int flags, int nbytes, ...)
|
||||
{
|
||||
va_list list;
|
||||
int i, use_sreq;
|
||||
int rc;
|
||||
|
||||
if ((adb_controller == NULL) || (adb_controller->send_request == NULL))
|
||||
return -ENXIO;
|
||||
if (nbytes < 1)
|
||||
return -EINVAL;
|
||||
if (req == NULL && (flags & ADBREQ_NOSEND))
|
||||
return -EINVAL;
|
||||
|
||||
if (req == NULL) {
|
||||
if (test_and_set_bit(0,&adb_sreq_lock)) {
|
||||
printk("adb.c: Warning: contention on static request !\n");
|
||||
return -EPERM;
|
||||
}
|
||||
req = &adb_sreq;
|
||||
flags |= ADBREQ_SYNC;
|
||||
use_sreq = 1;
|
||||
} else
|
||||
use_sreq = 0;
|
||||
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 send from the probe thread cause it to
|
||||
* block. Beware that the "done" callback will be overriden !
|
||||
*/
|
||||
if ((flags & ADBREQ_SYNC) &&
|
||||
(current->pid && adb_probe_task_pid &&
|
||||
adb_probe_task_pid == current->pid)) {
|
||||
req->done = adb_probe_wakeup;
|
||||
rc = adb_controller->send_request(req, 0);
|
||||
if (rc || req->complete)
|
||||
goto bail;
|
||||
wait_for_completion(&adb_probe_task_comp);
|
||||
rc = 0;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
rc = adb_controller->send_request(req, flags & ADBREQ_SYNC);
|
||||
bail:
|
||||
if (use_sreq)
|
||||
clear_bit(0, &adb_sreq_lock);
|
||||
|
||||
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;
|
||||
|
||||
down(&adb_handler_sem);
|
||||
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;
|
||||
}
|
||||
}
|
||||
up(&adb_handler_sem);
|
||||
return ids->nids;
|
||||
}
|
||||
|
||||
int
|
||||
adb_unregister(int index)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
|
||||
down(&adb_handler_sem);
|
||||
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);
|
||||
up(&adb_handler_sem);
|
||||
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;
|
||||
|
||||
down(&adb_handler_sem);
|
||||
ret = try_handler_change(address, new_id);
|
||||
up(&adb_handler_sem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
adb_get_infos(int address, int *original_address, int *handler_id)
|
||||
{
|
||||
down(&adb_handler_sem);
|
||||
*original_address = adb_handler[address].original_address;
|
||||
*handler_id = adb_handler[address].handler_id;
|
||||
up(&adb_handler_sem);
|
||||
|
||||
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;
|
||||
down(&adb_handler_sem);
|
||||
req->reply[0] = adb_handler[req->data[2]].original_address;
|
||||
req->reply[1] = adb_handler[req->data[2]].handler_id;
|
||||
up(&adb_handler_sem);
|
||||
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;
|
||||
|
||||
if (iminor(inode) > 0 || adb_controller == NULL)
|
||||
return -ENXIO;
|
||||
state = kmalloc(sizeof(struct adbdev_state), GFP_KERNEL);
|
||||
if (state == 0)
|
||||
return -ENOMEM;
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 void
|
||||
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;
|
||||
class_device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb");
|
||||
}
|
||||
1203
drivers/macintosh/adbhid.c
Normal file
1203
drivers/macintosh/adbhid.c
Normal file
File diff suppressed because it is too large
Load Diff
186
drivers/macintosh/ans-lcd.c
Normal file
186
drivers/macintosh/ans-lcd.c
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* /dev/lcd driver for Apple Network Servers.
|
||||
*/
|
||||
|
||||
#include <linux/types.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/ans-lcd.h>
|
||||
#include <asm/io.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 )
|
||||
{
|
||||
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 = find_devices("lcd");
|
||||
if (!node || !node->parent)
|
||||
return -ENODEV;
|
||||
if (strcmp(node->parent->name, "gc"))
|
||||
return -ENODEV;
|
||||
|
||||
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);
|
||||
557
drivers/macintosh/apm_emu.c
Normal file
557
drivers/macintosh/apm_emu.c
Normal file
@@ -0,0 +1,557 @@
|
||||
/* APM emulation layer for PowerMac
|
||||
*
|
||||
* Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
|
||||
*
|
||||
* Lots of code inherited from apm.c, see appropriate notice in
|
||||
* arch/i386/kernel/apm.c
|
||||
*
|
||||
* 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/module.h>
|
||||
|
||||
#include <linux/poll.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/apm_bios.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/smp_lock.h>
|
||||
|
||||
#include <linux/adb.h>
|
||||
#include <linux/pmu.h>
|
||||
|
||||
#include <asm/system.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/machdep.h>
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DBG(args...) printk(KERN_DEBUG args)
|
||||
//#define DBG(args...) xmon_printf(args)
|
||||
#else
|
||||
#define DBG(args...) do { } while (0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The apm_bios device is one of the misc char devices.
|
||||
* This is its minor number.
|
||||
*/
|
||||
#define APM_MINOR_DEV 134
|
||||
|
||||
/*
|
||||
* Maximum number of events stored
|
||||
*/
|
||||
#define APM_MAX_EVENTS 20
|
||||
|
||||
#define FAKE_APM_BIOS_VERSION 0x0101
|
||||
|
||||
#define APM_USER_NOTIFY_TIMEOUT (5*HZ)
|
||||
|
||||
/*
|
||||
* The per-file APM data
|
||||
*/
|
||||
struct apm_user {
|
||||
int magic;
|
||||
struct apm_user * next;
|
||||
int suser: 1;
|
||||
int suspend_waiting: 1;
|
||||
int suspends_pending;
|
||||
int suspends_read;
|
||||
int event_head;
|
||||
int event_tail;
|
||||
apm_event_t events[APM_MAX_EVENTS];
|
||||
};
|
||||
|
||||
/*
|
||||
* The magic number in apm_user
|
||||
*/
|
||||
#define APM_BIOS_MAGIC 0x4101
|
||||
|
||||
/*
|
||||
* Local variables
|
||||
*/
|
||||
static int suspends_pending;
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
|
||||
static struct apm_user * user_list;
|
||||
|
||||
static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when);
|
||||
static struct pmu_sleep_notifier apm_sleep_notifier = {
|
||||
apm_notify_sleep,
|
||||
SLEEP_LEVEL_USERLAND,
|
||||
};
|
||||
|
||||
static const char driver_version[] = "0.5"; /* no spaces */
|
||||
|
||||
#ifdef DEBUG
|
||||
static char * apm_event_name[] = {
|
||||
"system standby",
|
||||
"system suspend",
|
||||
"normal resume",
|
||||
"critical resume",
|
||||
"low battery",
|
||||
"power status change",
|
||||
"update time",
|
||||
"critical suspend",
|
||||
"user standby",
|
||||
"user suspend",
|
||||
"system standby resume",
|
||||
"capabilities change"
|
||||
};
|
||||
#define NR_APM_EVENT_NAME \
|
||||
(sizeof(apm_event_name) / sizeof(apm_event_name[0]))
|
||||
|
||||
#endif
|
||||
|
||||
static int queue_empty(struct apm_user *as)
|
||||
{
|
||||
return as->event_head == as->event_tail;
|
||||
}
|
||||
|
||||
static apm_event_t get_queued_event(struct apm_user *as)
|
||||
{
|
||||
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
|
||||
return as->events[as->event_tail];
|
||||
}
|
||||
|
||||
static void queue_event(apm_event_t event, struct apm_user *sender)
|
||||
{
|
||||
struct apm_user * as;
|
||||
|
||||
DBG("apm_emu: queue_event(%s)\n", apm_event_name[event-1]);
|
||||
if (user_list == NULL)
|
||||
return;
|
||||
for (as = user_list; as != NULL; as = as->next) {
|
||||
if (as == sender)
|
||||
continue;
|
||||
as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
|
||||
if (as->event_head == as->event_tail) {
|
||||
static int notified;
|
||||
|
||||
if (notified++ == 0)
|
||||
printk(KERN_ERR "apm_emu: an event queue overflowed\n");
|
||||
as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
|
||||
}
|
||||
as->events[as->event_head] = event;
|
||||
if (!as->suser)
|
||||
continue;
|
||||
switch (event) {
|
||||
case APM_SYS_SUSPEND:
|
||||
case APM_USER_SUSPEND:
|
||||
as->suspends_pending++;
|
||||
suspends_pending++;
|
||||
break;
|
||||
case APM_NORMAL_RESUME:
|
||||
as->suspend_waiting = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
wake_up_interruptible(&apm_waitqueue);
|
||||
}
|
||||
|
||||
static int check_apm_user(struct apm_user *as, const char *func)
|
||||
{
|
||||
if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
|
||||
printk(KERN_ERR "apm_emu: %s passed bad filp\n", func);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct apm_user * as;
|
||||
size_t i;
|
||||
apm_event_t event;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
as = fp->private_data;
|
||||
if (check_apm_user(as, "read"))
|
||||
return -EIO;
|
||||
if (count < sizeof(apm_event_t))
|
||||
return -EINVAL;
|
||||
if (queue_empty(as)) {
|
||||
if (fp->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
add_wait_queue(&apm_waitqueue, &wait);
|
||||
repeat:
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
if (queue_empty(as) && !signal_pending(current)) {
|
||||
schedule();
|
||||
goto repeat;
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(&apm_waitqueue, &wait);
|
||||
}
|
||||
i = count;
|
||||
while ((i >= sizeof(event)) && !queue_empty(as)) {
|
||||
event = get_queued_event(as);
|
||||
DBG("apm_emu: do_read, returning: %s\n", apm_event_name[event-1]);
|
||||
if (copy_to_user(buf, &event, sizeof(event))) {
|
||||
if (i < count)
|
||||
break;
|
||||
return -EFAULT;
|
||||
}
|
||||
switch (event) {
|
||||
case APM_SYS_SUSPEND:
|
||||
case APM_USER_SUSPEND:
|
||||
as->suspends_read++;
|
||||
break;
|
||||
}
|
||||
buf += sizeof(event);
|
||||
i -= sizeof(event);
|
||||
}
|
||||
if (i < count)
|
||||
return count - i;
|
||||
if (signal_pending(current))
|
||||
return -ERESTARTSYS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int do_poll(struct file *fp, poll_table * wait)
|
||||
{
|
||||
struct apm_user * as;
|
||||
|
||||
as = fp->private_data;
|
||||
if (check_apm_user(as, "poll"))
|
||||
return 0;
|
||||
poll_wait(fp, &apm_waitqueue, wait);
|
||||
if (!queue_empty(as))
|
||||
return POLLIN | POLLRDNORM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_ioctl(struct inode * inode, struct file *filp,
|
||||
u_int cmd, u_long arg)
|
||||
{
|
||||
struct apm_user * as;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
as = filp->private_data;
|
||||
if (check_apm_user(as, "ioctl"))
|
||||
return -EIO;
|
||||
if (!as->suser)
|
||||
return -EPERM;
|
||||
switch (cmd) {
|
||||
case APM_IOC_SUSPEND:
|
||||
/* If a suspend message was sent to userland, we
|
||||
* consider this as a confirmation message
|
||||
*/
|
||||
if (as->suspends_read > 0) {
|
||||
as->suspends_read--;
|
||||
as->suspends_pending--;
|
||||
suspends_pending--;
|
||||
} else {
|
||||
// Route to PMU suspend ?
|
||||
break;
|
||||
}
|
||||
as->suspend_waiting = 1;
|
||||
add_wait_queue(&apm_waitqueue, &wait);
|
||||
DBG("apm_emu: ioctl waking up sleep waiter !\n");
|
||||
wake_up(&apm_suspend_waitqueue);
|
||||
mb();
|
||||
while(as->suspend_waiting && !signal_pending(current)) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
schedule();
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(&apm_waitqueue, &wait);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_release(struct inode * inode, struct file * filp)
|
||||
{
|
||||
struct apm_user * as;
|
||||
|
||||
as = filp->private_data;
|
||||
if (check_apm_user(as, "release"))
|
||||
return 0;
|
||||
filp->private_data = NULL;
|
||||
lock_kernel();
|
||||
if (as->suspends_pending > 0) {
|
||||
suspends_pending -= as->suspends_pending;
|
||||
if (suspends_pending <= 0)
|
||||
wake_up(&apm_suspend_waitqueue);
|
||||
}
|
||||
if (user_list == as)
|
||||
user_list = as->next;
|
||||
else {
|
||||
struct apm_user * as1;
|
||||
|
||||
for (as1 = user_list;
|
||||
(as1 != NULL) && (as1->next != as);
|
||||
as1 = as1->next)
|
||||
;
|
||||
if (as1 == NULL)
|
||||
printk(KERN_ERR "apm: filp not in user list\n");
|
||||
else
|
||||
as1->next = as->next;
|
||||
}
|
||||
unlock_kernel();
|
||||
kfree(as);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_open(struct inode * inode, struct file * filp)
|
||||
{
|
||||
struct apm_user * as;
|
||||
|
||||
as = kmalloc(sizeof(*as), GFP_KERNEL);
|
||||
if (as == NULL) {
|
||||
printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
|
||||
sizeof(*as));
|
||||
return -ENOMEM;
|
||||
}
|
||||
as->magic = APM_BIOS_MAGIC;
|
||||
as->event_tail = as->event_head = 0;
|
||||
as->suspends_pending = 0;
|
||||
as->suspends_read = 0;
|
||||
/*
|
||||
* XXX - this is a tiny bit broken, when we consider BSD
|
||||
* process accounting. If the device is opened by root, we
|
||||
* instantly flag that we used superuser privs. Who knows,
|
||||
* we might close the device immediately without doing a
|
||||
* privileged operation -- cevans
|
||||
*/
|
||||
as->suser = capable(CAP_SYS_ADMIN);
|
||||
as->next = user_list;
|
||||
user_list = as;
|
||||
filp->private_data = as;
|
||||
|
||||
DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wait for all clients to ack the suspend request. APM API
|
||||
* doesn't provide a way to NAK, but this could be added
|
||||
* here.
|
||||
*/
|
||||
static int wait_all_suspend(void)
|
||||
{
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
add_wait_queue(&apm_suspend_waitqueue, &wait);
|
||||
DBG("apm_emu: wait_all_suspend(), suspends_pending: %d\n", suspends_pending);
|
||||
while(suspends_pending > 0) {
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
schedule();
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(&apm_suspend_waitqueue, &wait);
|
||||
|
||||
DBG("apm_emu: wait_all_suspend() - complete !\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
|
||||
{
|
||||
switch(when) {
|
||||
case PBOOK_SLEEP_REQUEST:
|
||||
queue_event(APM_SYS_SUSPEND, NULL);
|
||||
if (!wait_all_suspend())
|
||||
return PBOOK_SLEEP_REFUSE;
|
||||
break;
|
||||
case PBOOK_SLEEP_REJECT:
|
||||
case PBOOK_WAKE:
|
||||
queue_event(APM_NORMAL_RESUME, NULL);
|
||||
break;
|
||||
}
|
||||
return PBOOK_SLEEP_OK;
|
||||
}
|
||||
|
||||
#define APM_CRITICAL 10
|
||||
#define APM_LOW 30
|
||||
|
||||
static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length)
|
||||
{
|
||||
/* Arguments, with symbols from linux/apm_bios.h. Information is
|
||||
from the Get Power Status (0x0a) call unless otherwise noted.
|
||||
|
||||
0) Linux driver version (this will change if format changes)
|
||||
1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
|
||||
2) APM flags from APM Installation Check (0x00):
|
||||
bit 0: APM_16_BIT_SUPPORT
|
||||
bit 1: APM_32_BIT_SUPPORT
|
||||
bit 2: APM_IDLE_SLOWS_CLOCK
|
||||
bit 3: APM_BIOS_DISABLED
|
||||
bit 4: APM_BIOS_DISENGAGED
|
||||
3) AC line status
|
||||
0x00: Off-line
|
||||
0x01: On-line
|
||||
0x02: On backup power (BIOS >= 1.1 only)
|
||||
0xff: Unknown
|
||||
4) Battery status
|
||||
0x00: High
|
||||
0x01: Low
|
||||
0x02: Critical
|
||||
0x03: Charging
|
||||
0x04: Selected battery not present (BIOS >= 1.2 only)
|
||||
0xff: Unknown
|
||||
5) Battery flag
|
||||
bit 0: High
|
||||
bit 1: Low
|
||||
bit 2: Critical
|
||||
bit 3: Charging
|
||||
bit 7: No system battery
|
||||
0xff: Unknown
|
||||
6) Remaining battery life (percentage of charge):
|
||||
0-100: valid
|
||||
-1: Unknown
|
||||
7) Remaining battery life (time units):
|
||||
Number of remaining minutes or seconds
|
||||
-1: Unknown
|
||||
8) min = minutes; sec = seconds */
|
||||
|
||||
unsigned short ac_line_status;
|
||||
unsigned short battery_status = 0;
|
||||
unsigned short battery_flag = 0xff;
|
||||
int percentage = -1;
|
||||
int time_units = -1;
|
||||
int real_count = 0;
|
||||
int i;
|
||||
char * p = buf;
|
||||
char charging = 0;
|
||||
long charge = -1;
|
||||
long amperage = 0;
|
||||
unsigned long btype = 0;
|
||||
|
||||
ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0);
|
||||
for (i=0; i<pmu_battery_count; i++) {
|
||||
if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
|
||||
battery_status++;
|
||||
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 (0 == battery_status)
|
||||
ac_line_status = 1;
|
||||
battery_status = 0xff;
|
||||
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) {
|
||||
battery_status = 0x03;
|
||||
battery_flag = 0x08;
|
||||
} else if (percentage <= APM_CRITICAL) {
|
||||
battery_status = 0x02;
|
||||
battery_flag = 0x04;
|
||||
} else if (percentage <= APM_LOW) {
|
||||
battery_status = 0x01;
|
||||
battery_flag = 0x02;
|
||||
} else {
|
||||
battery_status = 0x00;
|
||||
battery_flag = 0x01;
|
||||
}
|
||||
}
|
||||
p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
|
||||
driver_version,
|
||||
(FAKE_APM_BIOS_VERSION >> 8) & 0xff,
|
||||
FAKE_APM_BIOS_VERSION & 0xff,
|
||||
0,
|
||||
ac_line_status,
|
||||
battery_status,
|
||||
battery_flag,
|
||||
percentage,
|
||||
time_units,
|
||||
"min");
|
||||
|
||||
return p - buf;
|
||||
}
|
||||
|
||||
static const struct file_operations apm_bios_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.read = do_read,
|
||||
.poll = do_poll,
|
||||
.ioctl = do_ioctl,
|
||||
.open = do_open,
|
||||
.release = do_release,
|
||||
};
|
||||
|
||||
static struct miscdevice apm_device = {
|
||||
APM_MINOR_DEV,
|
||||
"apm_bios",
|
||||
&apm_bios_fops
|
||||
};
|
||||
|
||||
static int __init apm_emu_init(void)
|
||||
{
|
||||
struct proc_dir_entry *apm_proc;
|
||||
|
||||
if (sys_ctrler != SYS_CTRLER_PMU) {
|
||||
printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
|
||||
if (apm_proc)
|
||||
apm_proc->owner = THIS_MODULE;
|
||||
|
||||
if (misc_register(&apm_device) != 0)
|
||||
printk(KERN_INFO "Could not create misc. device for apm\n");
|
||||
|
||||
pmu_register_sleep_notifier(&apm_sleep_notifier);
|
||||
|
||||
printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit apm_emu_exit(void)
|
||||
{
|
||||
pmu_unregister_sleep_notifier(&apm_sleep_notifier);
|
||||
misc_deregister(&apm_device);
|
||||
remove_proc_entry("apm", NULL);
|
||||
|
||||
printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
|
||||
}
|
||||
|
||||
module_init(apm_emu_init);
|
||||
module_exit(apm_emu_exit);
|
||||
|
||||
MODULE_AUTHOR("Benjamin Herrenschmidt");
|
||||
MODULE_DESCRIPTION("APM emulation layer for PowerMac");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
147
drivers/macintosh/mac_hid.c
Normal file
147
drivers/macintosh/mac_hid.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
|
||||
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 */
|
||||
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 */
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(mac_hid_mouse_emulate_buttons);
|
||||
|
||||
static int emumousebtn_input_register(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
emumousebtn = input_allocate_device();
|
||||
if (!emumousebtn)
|
||||
return -ENOMEM;
|
||||
|
||||
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(EV_KEY) | BIT(EV_REL);
|
||||
emumousebtn->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT);
|
||||
emumousebtn->relbit[0] = BIT(REL_X) | BIT(REL_Y);
|
||||
|
||||
ret = input_register_device(emumousebtn);
|
||||
if (ret)
|
||||
input_free_device(emumousebtn);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
274
drivers/macintosh/macio-adb.c
Normal file
274
drivers/macintosh/macio-adb.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
return find_compatible_devices("adb", "chrp,adb0")? 0: -ENODEV;
|
||||
}
|
||||
|
||||
int macio_init(void)
|
||||
{
|
||||
struct device_node *adbs;
|
||||
struct resource r;
|
||||
unsigned int irq;
|
||||
|
||||
adbs = find_compatible_devices("adb", "chrp,adb0");
|
||||
if (adbs == 0)
|
||||
return -ENXIO;
|
||||
|
||||
if (of_address_to_resource(adbs, 0, &r))
|
||||
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);
|
||||
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);
|
||||
}
|
||||
830
drivers/macintosh/macio_asic.c
Normal file
830
drivers/macintosh/macio_asic.c
Normal file
@@ -0,0 +1,830 @@
|
||||
/*
|
||||
* 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 (BUS_ID_SIZE - 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;
|
||||
}
|
||||
|
||||
static int macio_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
struct macio_dev * macio_dev;
|
||||
struct of_device * of;
|
||||
char *scratch;
|
||||
const char *compat, *compat2;
|
||||
|
||||
int i = 0;
|
||||
int length, cplen, cplen2, seen = 0;
|
||||
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
|
||||
macio_dev = to_macio_device(dev);
|
||||
if (!macio_dev)
|
||||
return -ENODEV;
|
||||
|
||||
of = &macio_dev->ofdev;
|
||||
|
||||
/* stuff we want to pass to /sbin/hotplug */
|
||||
envp[i++] = scratch = buffer;
|
||||
length = scnprintf (scratch, buffer_size, "OF_NAME=%s", of->node->name);
|
||||
++length;
|
||||
buffer_size -= length;
|
||||
if ((buffer_size <= 0) || (i >= num_envp))
|
||||
return -ENOMEM;
|
||||
scratch += length;
|
||||
|
||||
envp[i++] = scratch;
|
||||
length = scnprintf (scratch, buffer_size, "OF_TYPE=%s", of->node->type);
|
||||
++length;
|
||||
buffer_size -= length;
|
||||
if ((buffer_size <= 0) || (i >= num_envp))
|
||||
return -ENOMEM;
|
||||
scratch += length;
|
||||
|
||||
/* Since the compatible field can contain pretty much anything
|
||||
* it's not really legal to split it out with commas. We split it
|
||||
* up using a number of environment variables instead. */
|
||||
|
||||
compat = get_property(of->node, "compatible", &cplen);
|
||||
compat2 = compat;
|
||||
cplen2= cplen;
|
||||
while (compat && cplen > 0) {
|
||||
envp[i++] = scratch;
|
||||
length = scnprintf (scratch, buffer_size,
|
||||
"OF_COMPATIBLE_%d=%s", seen, compat);
|
||||
++length;
|
||||
buffer_size -= length;
|
||||
if ((buffer_size <= 0) || (i >= num_envp))
|
||||
return -ENOMEM;
|
||||
scratch += length;
|
||||
length = strlen (compat) + 1;
|
||||
compat += length;
|
||||
cplen -= length;
|
||||
seen++;
|
||||
}
|
||||
|
||||
envp[i++] = scratch;
|
||||
length = scnprintf (scratch, buffer_size, "OF_COMPATIBLE_N=%d", seen);
|
||||
++length;
|
||||
buffer_size -= length;
|
||||
if ((buffer_size <= 0) || (i >= num_envp))
|
||||
return -ENOMEM;
|
||||
scratch += length;
|
||||
|
||||
envp[i++] = scratch;
|
||||
length = scnprintf (scratch, buffer_size, "MODALIAS=of:N%sT%s",
|
||||
of->node->name, of->node->type);
|
||||
/* overwrite '\0' */
|
||||
buffer_size -= length;
|
||||
if ((buffer_size <= 0) || (i >= num_envp))
|
||||
return -ENOMEM;
|
||||
scratch += length;
|
||||
|
||||
if (!compat2) {
|
||||
compat2 = "";
|
||||
cplen2 = 1;
|
||||
}
|
||||
while (cplen2 > 0) {
|
||||
length = snprintf (scratch, buffer_size, "C%s", compat2);
|
||||
buffer_size -= length;
|
||||
if (buffer_size <= 0)
|
||||
return -ENOMEM;
|
||||
scratch += length;
|
||||
length = strlen (compat2) + 1;
|
||||
compat2 += length;
|
||||
cplen2 -= length;
|
||||
}
|
||||
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern struct device_attribute macio_dev_attrs[];
|
||||
|
||||
struct bus_type macio_bus_type = {
|
||||
.name = "macio",
|
||||
.match = macio_bus_match,
|
||||
.uevent = macio_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->ofdev.dev.bus_id;
|
||||
}
|
||||
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 = &dev->interrupt[j];
|
||||
|
||||
if (j >= MACIO_DEV_COUNT_IRQS)
|
||||
break;
|
||||
irq = irq_of_parse_and_map(np, i++);
|
||||
if (irq == NO_IRQ)
|
||||
break;
|
||||
res->start = irq;
|
||||
res->flags = IORESOURCE_IRQ;
|
||||
res->name = dev->ofdev.dev.bus_id;
|
||||
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 = &dev->resource[index];
|
||||
if (index >= MACIO_DEV_COUNT_RESOURCES)
|
||||
break;
|
||||
*res = r;
|
||||
res->name = dev->ofdev.dev.bus_id;
|
||||
|
||||
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->ofdev.dev.bus_id);
|
||||
}
|
||||
}
|
||||
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 = kmalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
memset(dev, 0, sizeof(*dev));
|
||||
|
||||
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 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) {
|
||||
sprintf(dev->ofdev.dev.bus_id, "%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 = get_property(np, "reg", NULL);
|
||||
sprintf(dev->ofdev.dev.bus_id, "%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->ofdev.dev.bus_id);
|
||||
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->ofdev.dev.bus_id);
|
||||
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);
|
||||
75
drivers/macintosh/macio_sysfs.c
Normal file
75
drivers/macintosh/macio_sysfs.c
Normal file
@@ -0,0 +1,75 @@
|
||||
#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 = 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 *of;
|
||||
const char *compat;
|
||||
int cplen;
|
||||
int length;
|
||||
|
||||
of = &to_macio_device (dev)->ofdev;
|
||||
compat = get_property(of->node, "compatible", &cplen);
|
||||
if (!compat) compat = "", cplen = 1;
|
||||
length = sprintf (buf, "of:N%sT%s", of->node->name, of->node->type);
|
||||
buf += length;
|
||||
while (cplen > 0) {
|
||||
int l;
|
||||
length += sprintf (buf, "C%s", compat);
|
||||
buf += length;
|
||||
l = strlen (compat) + 1;
|
||||
compat += l;
|
||||
cplen -= l;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
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_NULL
|
||||
};
|
||||
849
drivers/macintosh/mediabay.c
Normal file
849
drivers/macintosh/mediabay.c
Normal file
@@ -0,0 +1,849 @@
|
||||
/*
|
||||
* 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/hdreg.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ide.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
|
||||
#define MB_IGNORE_SIGNALS
|
||||
|
||||
#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 semaphore lock;
|
||||
#ifdef CONFIG_BLK_DEV_IDE
|
||||
void __iomem *cd_base;
|
||||
int cd_index;
|
||||
int cd_irq;
|
||||
int cd_retry;
|
||||
#endif
|
||||
};
|
||||
|
||||
#define MAX_BAYS 2
|
||||
|
||||
static struct media_bay_info media_bays[MAX_BAYS];
|
||||
int media_bay_count = 0;
|
||||
|
||||
#ifdef CONFIG_BLK_DEV_IDE
|
||||
/* 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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int check_media_bay(struct device_node *which_bay, int what)
|
||||
{
|
||||
#ifdef CONFIG_BLK_DEV_IDE
|
||||
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;
|
||||
}
|
||||
#endif /* CONFIG_BLK_DEV_IDE */
|
||||
return -ENODEV;
|
||||
}
|
||||
EXPORT_SYMBOL(check_media_bay);
|
||||
|
||||
int check_media_bay_by_base(unsigned long base, int what)
|
||||
{
|
||||
#ifdef CONFIG_BLK_DEV_IDE
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int media_bay_set_ide_infos(struct device_node* which_bay, unsigned long base,
|
||||
int irq, int index)
|
||||
{
|
||||
#ifdef CONFIG_BLK_DEV_IDE
|
||||
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;
|
||||
|
||||
down(&bay->lock);
|
||||
|
||||
bay->cd_base = (void __iomem *) base;
|
||||
bay->cd_irq = irq;
|
||||
|
||||
if ((MB_CD != bay->content_id) || bay->state != mb_up) {
|
||||
up(&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;
|
||||
up(&bay->lock);
|
||||
return 0;
|
||||
}
|
||||
mdelay(1);
|
||||
} while(--timeout);
|
||||
printk(KERN_DEBUG "Timeount waiting IDE in bay %d\n", i);
|
||||
up(&bay->lock);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_BLK_DEV_IDE */
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
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
|
||||
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 */
|
||||
break;
|
||||
|
||||
#ifdef CONFIG_BLK_DEV_IDE
|
||||
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) {
|
||||
hw_regs_t hw;
|
||||
|
||||
printk("mediabay %d, registering IDE...\n", i);
|
||||
pmu_suspend();
|
||||
ide_init_hwif_ports(&hw, (unsigned long) bay->cd_base, (unsigned long) 0, NULL);
|
||||
hw.irq = bay->cd_irq;
|
||||
hw.chipset = ide_pmac;
|
||||
bay->cd_index = ide_register_hw(&hw, NULL);
|
||||
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 */
|
||||
|
||||
case mb_powering_down:
|
||||
bay->state = mb_empty;
|
||||
#ifdef CONFIG_BLK_DEV_IDE
|
||||
if (bay->cd_index >= 0) {
|
||||
printk(KERN_DEBUG "Unregistering mb %d ide, index:%d\n", i,
|
||||
bay->cd_index);
|
||||
ide_unregister(bay->cd_index);
|
||||
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 */
|
||||
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;
|
||||
|
||||
strcpy(current->comm, "media-bay");
|
||||
#ifdef MB_IGNORE_SIGNALS
|
||||
sigfillset(¤t->blocked);
|
||||
#endif
|
||||
|
||||
for (;;) {
|
||||
for (i = 0; i < media_bay_count; ++i) {
|
||||
down(&media_bays[i].lock);
|
||||
if (!media_bays[i].sleeping)
|
||||
media_bay_step(i);
|
||||
up(&media_bays[i].lock);
|
||||
}
|
||||
|
||||
msleep_interruptible(MB_POLL_DELAY);
|
||||
if (signal_pending(current))
|
||||
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;
|
||||
init_MUTEX(&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)
|
||||
kernel_thread(media_bay_task, NULL, CLONE_KERNEL);
|
||||
|
||||
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_SUSPEND) {
|
||||
down(&bay->lock);
|
||||
bay->sleeping = 1;
|
||||
set_mb_power(bay, 0);
|
||||
up(&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 */
|
||||
down(&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);
|
||||
up(&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
|
||||
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;
|
||||
up(&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
|
||||
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);
|
||||
131
drivers/macintosh/nvram.c
Normal file
131
drivers/macintosh/nvram.c
Normal 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");
|
||||
616
drivers/macintosh/rack-meter.c
Normal file
616
drivers/macintosh/rack-meter.c
Normal file
@@ -0,0 +1,616 @@
|
||||
/*
|
||||
* 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/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 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) &&
|
||||
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_drv = {
|
||||
.name = "rackmeter",
|
||||
.owner = THIS_MODULE,
|
||||
.match_table = rackmeter_match,
|
||||
.probe = rackmeter_probe,
|
||||
.remove = rackmeter_remove,
|
||||
.shutdown = rackmeter_shutdown,
|
||||
};
|
||||
|
||||
|
||||
static int __init rackmeter_init(void)
|
||||
{
|
||||
pr_debug("rackmeter_init()\n");
|
||||
|
||||
return macio_register_driver(&rackmeter_drv);
|
||||
}
|
||||
|
||||
static void __exit rackmeter_exit(void)
|
||||
{
|
||||
pr_debug("rackmeter_exit()\n");
|
||||
|
||||
macio_unregister_driver(&rackmeter_drv);
|
||||
}
|
||||
|
||||
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");
|
||||
1301
drivers/macintosh/smu.c
Normal file
1301
drivers/macintosh/smu.c
Normal file
File diff suppressed because it is too large
Load Diff
661
drivers/macintosh/therm_adt746x.c
Normal file
661
drivers/macintosh/therm_adt746x.c
Normal file
@@ -0,0 +1,661 @@
|
||||
/*
|
||||
* 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/smp_lock.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/freezer.h>
|
||||
|
||||
#include <asm/prom.h>
|
||||
#include <asm/machdep.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/of_platform.h>
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#define CONFIG_REG 0x40
|
||||
#define MANUAL_MASK 0xe0
|
||||
#define AUTO_MASK 0x20
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
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 int attach_one_thermostat(struct i2c_adapter *adapter, int addr,
|
||||
int busno);
|
||||
|
||||
static void write_both_fan_speed(struct thermostat *th, int speed);
|
||||
static void write_fan_speed(struct thermostat *th, int speed, int fan);
|
||||
|
||||
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, ®_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 int
|
||||
attach_thermostat(struct i2c_adapter *adapter)
|
||||
{
|
||||
unsigned long bus_no;
|
||||
|
||||
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;
|
||||
return attach_one_thermostat(adapter, therm_address, bus_no);
|
||||
}
|
||||
|
||||
static int
|
||||
detach_thermostat(struct i2c_adapter *adapter)
|
||||
{
|
||||
struct thermostat* th;
|
||||
int i;
|
||||
|
||||
if (thermostat == NULL)
|
||||
return 0;
|
||||
|
||||
th = thermostat;
|
||||
|
||||
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);
|
||||
|
||||
i2c_detach_client(&th->clt);
|
||||
|
||||
thermostat = NULL;
|
||||
|
||||
kfree(th);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver thermostat_driver = {
|
||||
.driver = {
|
||||
.name = "therm_adt746x",
|
||||
},
|
||||
.attach_adapter = attach_thermostat,
|
||||
.detach_adapter = detach_thermostat,
|
||||
};
|
||||
|
||||
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]);
|
||||
write_reg(th, MANUAL_MODE[fan], manual|MANUAL_MASK);
|
||||
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);
|
||||
|
||||
write_reg(th,
|
||||
MANUAL_MODE[fan], manual|REM_CONTROL[fan]);
|
||||
} else {
|
||||
manual = read_reg(th, MANUAL_MODE[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;
|
||||
|
||||
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 attach_one_thermostat(struct i2c_adapter *adapter, int addr,
|
||||
int busno)
|
||||
{
|
||||
struct thermostat* th;
|
||||
int rc;
|
||||
int i;
|
||||
|
||||
if (thermostat)
|
||||
return 0;
|
||||
|
||||
th = (struct thermostat *)
|
||||
kmalloc(sizeof(struct thermostat), GFP_KERNEL);
|
||||
|
||||
if (!th)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(th, 0, sizeof(*th));
|
||||
th->clt.addr = addr;
|
||||
th->clt.adapter = adapter;
|
||||
th->clt.driver = &thermostat_driver;
|
||||
strcpy(th->clt.name, "thermostat");
|
||||
|
||||
rc = read_reg(th, 0);
|
||||
if (rc < 0) {
|
||||
printk(KERN_ERR "adt746x: Thermostat failed to read config "
|
||||
"from bus %d !\n",
|
||||
busno);
|
||||
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;
|
||||
|
||||
if (i2c_attach_client(&th->clt)) {
|
||||
printk(KERN_INFO "adt746x: Thermostat failed to attach "
|
||||
"client !\n");
|
||||
thermostat = NULL;
|
||||
kfree(th);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) \
|
||||
{ \
|
||||
u32 val; \
|
||||
val = simple_strtoul(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 (device_is_compatible(np, "adt7460"))
|
||||
therm_type = ADT7460;
|
||||
else if (device_is_compatible(np, "adt7467"))
|
||||
therm_type = ADT7467;
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
prop = get_property(np, "hwsensor-params-version", NULL);
|
||||
printk(KERN_INFO "adt746x: version %d (%ssupported)\n", *prop,
|
||||
(*prop == 1)?"":"un");
|
||||
if (*prop != 1)
|
||||
return -ENODEV;
|
||||
|
||||
prop = get_property(np, "reg", NULL);
|
||||
if (!prop)
|
||||
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 (get_property(np, "hwsensor-location", NULL)) {
|
||||
for (i = 0; i < 3; i++) {
|
||||
sensor_location[i] = 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);
|
||||
|
||||
if (of_dev == NULL) {
|
||||
printk(KERN_ERR "Can't register temperatures device !\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor1_temperature);
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor2_temperature);
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor1_limit);
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor2_limit);
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor1_location);
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor2_location);
|
||||
device_create_file(&of_dev->dev, &dev_attr_limit_adjust);
|
||||
device_create_file(&of_dev->dev, &dev_attr_specified_fan_speed);
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor1_fan_speed);
|
||||
if(therm_type == ADT7460)
|
||||
device_create_file(&of_dev->dev, &dev_attr_sensor2_fan_speed);
|
||||
|
||||
#ifndef CONFIG_I2C_POWERMAC
|
||||
request_module("i2c-powermac");
|
||||
#endif
|
||||
|
||||
return i2c_add_driver(&thermostat_driver);
|
||||
}
|
||||
|
||||
static void __exit
|
||||
thermostat_exit(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);
|
||||
|
||||
of_device_unregister(of_dev);
|
||||
}
|
||||
i2c_del_driver(&thermostat_driver);
|
||||
}
|
||||
|
||||
module_init(thermostat_init);
|
||||
module_exit(thermostat_exit);
|
||||
2259
drivers/macintosh/therm_pm72.c
Normal file
2259
drivers/macintosh/therm_pm72.c
Normal file
File diff suppressed because it is too large
Load Diff
326
drivers/macintosh/therm_pm72.h
Normal file
326
drivers/macintosh/therm_pm72.h
Normal 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__ */
|
||||
533
drivers/macintosh/therm_windtunnel.c
Normal file
533
drivers/macintosh/therm_windtunnel.c
Normal file
@@ -0,0 +1,533 @@
|
||||
/*
|
||||
* 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 <asm/prom.h>
|
||||
#include <asm/machdep.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/of_platform.h>
|
||||
#include <asm/macio.h>
|
||||
|
||||
#define LOG_TEMP 0 /* continously log temperature */
|
||||
|
||||
#define I2C_DRIVERID_G4FAN 0x9001 /* fixme */
|
||||
|
||||
static int do_probe( struct i2c_adapter *adapter, int addr, int kind);
|
||||
|
||||
/* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */
|
||||
static unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b,
|
||||
0x4c, 0x4d, 0x4e, 0x4f,
|
||||
0x2c, 0x2d, 0x2e, 0x2f,
|
||||
I2C_CLIENT_END };
|
||||
|
||||
I2C_CLIENT_INSMOD;
|
||||
|
||||
static struct {
|
||||
volatile int running;
|
||||
struct completion completion;
|
||||
pid_t poll_task;
|
||||
|
||||
struct semaphore 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;
|
||||
|
||||
/* 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 = 65 << 8;
|
||||
x.overheat_hyst = 60 << 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 ); */
|
||||
|
||||
device_create_file( &x.of_dev->dev, &dev_attr_cpu_temperature );
|
||||
device_create_file( &x.of_dev->dev, &dev_attr_case_temperature );
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
daemonize("g4fand");
|
||||
|
||||
down( &x.lock );
|
||||
setup_hardware();
|
||||
|
||||
while( x.running ) {
|
||||
up( &x.lock );
|
||||
|
||||
msleep_interruptible(8000);
|
||||
|
||||
down( &x.lock );
|
||||
poll_temp();
|
||||
}
|
||||
|
||||
restore_regs();
|
||||
up( &x.lock );
|
||||
|
||||
complete_and_exit( &x.completion, 0 );
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* i2c probing and setup */
|
||||
/************************************************************************/
|
||||
|
||||
static int
|
||||
do_attach( struct i2c_adapter *adapter )
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if( strncmp(adapter->name, "uni-n", 5) )
|
||||
return 0;
|
||||
|
||||
if( !x.running ) {
|
||||
ret = i2c_probe( adapter, &addr_data, &do_probe );
|
||||
if( x.thermostat && x.fan ) {
|
||||
x.running = 1;
|
||||
init_completion( &x.completion );
|
||||
x.poll_task = kernel_thread( control_loop, NULL, SIGCHLD | CLONE_KERNEL );
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
do_detach( struct i2c_client *client )
|
||||
{
|
||||
int err;
|
||||
|
||||
if( (err=i2c_detach_client(client)) )
|
||||
printk(KERN_ERR "failed to detach thermostat client\n");
|
||||
else {
|
||||
if( x.running ) {
|
||||
x.running = 0;
|
||||
wait_for_completion( &x.completion );
|
||||
}
|
||||
if( client == x.thermostat )
|
||||
x.thermostat = NULL;
|
||||
else if( client == x.fan )
|
||||
x.fan = NULL;
|
||||
else {
|
||||
printk(KERN_ERR "g4fan: bad client\n");
|
||||
}
|
||||
kfree( client );
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct i2c_driver g4fan_driver = {
|
||||
.driver = {
|
||||
.name = "therm_windtunnel",
|
||||
},
|
||||
.id = I2C_DRIVERID_G4FAN,
|
||||
.attach_adapter = do_attach,
|
||||
.detach_client = do_detach,
|
||||
};
|
||||
|
||||
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 );
|
||||
|
||||
strlcpy( cl->name, "ADM1030 fan controller", sizeof(cl->name) );
|
||||
|
||||
if( !i2c_attach_client(cl) )
|
||||
x.fan = cl;
|
||||
out:
|
||||
if( cl != x.fan )
|
||||
kfree( cl );
|
||||
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;
|
||||
|
||||
strlcpy( cl->name, "DS1775 thermostat", sizeof(cl->name) );
|
||||
|
||||
if( !i2c_attach_client(cl) )
|
||||
x.thermostat = cl;
|
||||
out:
|
||||
if( cl != x.thermostat )
|
||||
kfree( cl );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
do_probe( struct i2c_adapter *adapter, int addr, int kind )
|
||||
{
|
||||
struct i2c_client *cl;
|
||||
|
||||
if( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA
|
||||
| I2C_FUNC_SMBUS_WRITE_BYTE) )
|
||||
return 0;
|
||||
|
||||
if( !(cl=kmalloc(sizeof(*cl), GFP_KERNEL)) )
|
||||
return -ENOMEM;
|
||||
memset( cl, 0, sizeof(struct i2c_client) );
|
||||
|
||||
cl->addr = addr;
|
||||
cl->adapter = adapter;
|
||||
cl->driver = &g4fan_driver;
|
||||
cl->flags = 0;
|
||||
|
||||
if( addr < 0x48 )
|
||||
return attach_fan( cl );
|
||||
return attach_thermostat( cl );
|
||||
}
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* 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 )
|
||||
{
|
||||
return i2c_del_driver( &g4fan_driver );
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
init_MUTEX( &x.lock );
|
||||
|
||||
if( !(np=of_find_node_by_name(NULL, "power-mgt")) )
|
||||
return -ENODEV;
|
||||
info = 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");
|
||||
627
drivers/macintosh/via-cuda.c
Normal file
627
drivers/macintosh/via-cuda.c
Normal file
@@ -0,0 +1,627 @@
|
||||
/*
|
||||
* 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/machw.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;
|
||||
#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 = 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 */
|
||||
|
||||
#ifndef CONFIG_MAC
|
||||
out_8(&via[IFR], 0x7f); /* clear interrupts by writing 1s */
|
||||
out_8(&via[IER], IER_SET|SR_INT); /* enable interrupt from SR */
|
||||
#endif
|
||||
|
||||
/* 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)
|
||||
{
|
||||
unsigned int irq;
|
||||
|
||||
if (via == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
#ifdef CONFIG_MAC
|
||||
irq = IRQ_MAC_ADB;
|
||||
#else /* CONFIG_MAC */
|
||||
irq = irq_of_parse_and_map(vias, 0);
|
||||
if (irq == NO_IRQ) {
|
||||
printk(KERN_ERR "via-cuda: can't map interrupts for %s\n",
|
||||
vias->full_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif /* CONFIG_MAP */
|
||||
|
||||
if (request_irq(irq, cuda_interrupt, 0, "ADB", cuda_interrupt)) {
|
||||
printk(KERN_ERR "via-cuda: can't request irq %d\n", 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;
|
||||
}
|
||||
|
||||
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 */
|
||||
#ifndef CONFIG_MAC
|
||||
out_8(&via[IER], 0x7f); /* disable interrupts from VIA */
|
||||
(void)in_8(&via[IER]);
|
||||
#endif
|
||||
|
||||
/* delay 4ms and then clear any pending interrupt */
|
||||
mdelay(4);
|
||||
(void)in_8(&via[SR]);
|
||||
out_8(&via[IFR], in_8(&via[IFR]) & 0x7f);
|
||||
|
||||
/* 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], in_8(&via[IFR]) & 0x7f);
|
||||
|
||||
/* 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], in_8(&via[IFR]) & 0x7f);
|
||||
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)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/* cuda_interrupt only takes a normal lock, we disable
|
||||
* interrupts here to avoid re-entering and thus deadlocking.
|
||||
* An option would be to disable only the IRQ source with
|
||||
* disable_irq(), would that work on m68k ? --BenH
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
cuda_interrupt(0, NULL);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned char virq;
|
||||
|
||||
spin_lock(&cuda_lock);
|
||||
|
||||
virq = in_8(&via[IFR]) & 0x7f;
|
||||
out_8(&via[IFR], virq);
|
||||
if ((virq & SR_INT) == 0) {
|
||||
spin_unlock(&cuda_lock);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
651
drivers/macintosh/via-macii.c
Normal file
651
drivers/macintosh/via-macii.c
Normal file
@@ -0,0 +1,651 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#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/machw.h>
|
||||
#include <asm/mac_via.h>
|
||||
#include <asm/io.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 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 */
|
||||
|
||||
/* 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_retransmit(int);
|
||||
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,
|
||||
awaiting_reply
|
||||
} macii_state;
|
||||
|
||||
static int need_poll;
|
||||
static int command_byte;
|
||||
static int last_reply;
|
||||
static int last_active;
|
||||
|
||||
static struct adb_request *current_req;
|
||||
static struct adb_request *last_req;
|
||||
static struct adb_request *retry_req;
|
||||
static unsigned char reply_buf[16];
|
||||
static unsigned char *reply_ptr;
|
||||
static int reply_len;
|
||||
static int reading_reply;
|
||||
static int data_index;
|
||||
static int first_byte;
|
||||
static int prefix_len;
|
||||
static int status = ST_IDLE|TREQ;
|
||||
static int last_status;
|
||||
static int driver_running;
|
||||
|
||||
/* debug level 10 required for ADB logging (should be && debug_adb, ideally) */
|
||||
|
||||
/* 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) return err;
|
||||
|
||||
err = request_irq(IRQ_MAC_ADB, macii_interrupt, IRQ_FLG_LOCK, "ADB",
|
||||
macii_interrupt);
|
||||
if (err) return err;
|
||||
|
||||
macii_state = idle;
|
||||
local_irq_restore(flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* initialize the hardware */
|
||||
static int macii_init_via(void)
|
||||
{
|
||||
unsigned char x;
|
||||
|
||||
/* Set the lines up. We want TREQ as input TACK|TIP as output */
|
||||
via[DIRB] = (via[DIRB] | TACK | TIP) & ~TREQ;
|
||||
|
||||
/* Set up state: idle */
|
||||
via[B] |= ST_IDLE;
|
||||
last_status = via[B] & (ST_MASK|TREQ);
|
||||
|
||||
/* 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, tagged on the front of the request queue) */
|
||||
static void macii_queue_poll(void)
|
||||
{
|
||||
static int device = 0;
|
||||
static int in_poll=0;
|
||||
static struct adb_request req;
|
||||
unsigned long flags;
|
||||
|
||||
if (in_poll) printk("macii_queue_poll: double poll!\n");
|
||||
|
||||
in_poll++;
|
||||
if (++device > 15) device = 1;
|
||||
|
||||
adb_request(&req, NULL, ADBREQ_REPLY|ADBREQ_NOSEND, 1,
|
||||
ADB_READREG(device, 0));
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
req.next = current_req;
|
||||
current_req = &req;
|
||||
|
||||
local_irq_restore(flags);
|
||||
macii_start();
|
||||
in_poll--;
|
||||
}
|
||||
|
||||
/* Send an ADB retransmit (Talk, appended to the request queue) */
|
||||
static void macii_retransmit(int device)
|
||||
{
|
||||
static int in_retransmit = 0;
|
||||
static struct adb_request rt;
|
||||
unsigned long flags;
|
||||
|
||||
if (in_retransmit) printk("macii_retransmit: double retransmit!\n");
|
||||
|
||||
in_retransmit++;
|
||||
|
||||
adb_request(&rt, NULL, ADBREQ_REPLY|ADBREQ_NOSEND, 1,
|
||||
ADB_READREG(device, 0));
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
if (current_req != NULL) {
|
||||
last_req->next = &rt;
|
||||
last_req = &rt;
|
||||
} else {
|
||||
current_req = &rt;
|
||||
last_req = &rt;
|
||||
}
|
||||
|
||||
if (macii_state == idle) macii_start();
|
||||
|
||||
local_irq_restore(flags);
|
||||
in_retransmit--;
|
||||
}
|
||||
|
||||
/* 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 i;
|
||||
|
||||
i = macii_write(req);
|
||||
if (i) return i;
|
||||
|
||||
if (sync) {
|
||||
while (!req->complete) macii_poll();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Send an ADB request */
|
||||
static int macii_write(struct adb_request *req)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
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;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
if (current_req != NULL) {
|
||||
last_req->next = req;
|
||||
last_req = req;
|
||||
} else {
|
||||
current_req = req;
|
||||
last_req = req;
|
||||
if (macii_state == idle) macii_start();
|
||||
}
|
||||
|
||||
local_irq_restore(flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Start auto-polling */
|
||||
static int macii_autopoll(int devs)
|
||||
{
|
||||
/* Just ping a random default address */
|
||||
if (!(current_req || retry_req))
|
||||
macii_retransmit( (last_active < 16 && last_active > 0) ? last_active : 3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Prod the chip without interrupts */
|
||||
static void macii_poll(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
local_irq_save(flags);
|
||||
if (via[IFR] & SR_INT) macii_interrupt(0, NULL);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
/* Reset the bus */
|
||||
static int macii_reset_bus(void)
|
||||
{
|
||||
static struct adb_request req;
|
||||
|
||||
/* Command = 0, Address = ignored */
|
||||
adb_request(&req, NULL, 0, 1, ADB_BUSRESET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Start sending ADB packet */
|
||||
static void macii_start(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct adb_request *req;
|
||||
|
||||
req = current_req;
|
||||
if (!req) return;
|
||||
|
||||
/* assert macii_state == idle */
|
||||
if (macii_state != idle) {
|
||||
printk("macii_start: called while driver busy (%p %x %x)!\n",
|
||||
req, macii_state, (uint) via1[B] & (ST_MASK|TREQ));
|
||||
return;
|
||||
}
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
/*
|
||||
* IRQ signaled ?? (means ADB controller wants to send, or might
|
||||
* be end of packet if we were reading)
|
||||
*/
|
||||
#if 0 /* FIXME: This is broke broke broke, for some reason */
|
||||
if ((via[B] & TREQ) == 0) {
|
||||
printk("macii_start: weird poll stuff. huh?\n");
|
||||
/*
|
||||
* FIXME - we need to restart this on a timer
|
||||
* or a collision at boot hangs us.
|
||||
* Never set macii_state to idle here, or macii_start
|
||||
* won't be called again from send_request!
|
||||
* (need to re-check other cases ...)
|
||||
*/
|
||||
/*
|
||||
* if the interrupt handler set the need_poll
|
||||
* flag, it's hopefully a SRQ poll or re-Talk
|
||||
* so we try to send here anyway
|
||||
*/
|
||||
if (!need_poll) {
|
||||
if (console_loglevel == 10)
|
||||
printk("macii_start: device busy - retry %p state %d status %x!\n",
|
||||
req, macii_state,
|
||||
(uint) via[B] & (ST_MASK|TREQ));
|
||||
retry_req = req;
|
||||
/* set ADB status here ? */
|
||||
local_irq_restore(flags);
|
||||
return;
|
||||
} else {
|
||||
need_poll = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* Another retry pending? (sanity check)
|
||||
*/
|
||||
if (retry_req) {
|
||||
retry_req = NULL;
|
||||
}
|
||||
|
||||
/* Now send it. Be careful though, that first byte of the request */
|
||||
/* is actually ADB_PACKET; the real data begins at index 1! */
|
||||
|
||||
/* 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;
|
||||
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* The notorious ADB interrupt handler - does all of the protocol handling,
|
||||
* except for starting new send operations. Relies heavily on the ADB
|
||||
* controller sending and receiving data, thereby generating SR interrupts
|
||||
* for us. This means there has to be always activity on the ADB bus, otherwise
|
||||
* the whole process dies and has to be re-kicked by sending TALK requests ...
|
||||
* CUDA-based Macs seem to solve this with the autopoll option, for MacII-type
|
||||
* ADB the problem isn't solved yet (retransmit of the latest active TALK seems
|
||||
* a good choice; either on timeout or on a timer interrupt).
|
||||
*
|
||||
* 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 seems to be 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 signaled by asserting /IRQ on the interrupt
|
||||
* line. Timeouts are signaled by a sequence of 4 0xFF, with /IRQ asserted on
|
||||
* every other byte. SRQ is probably signaled by 3 or more 0xFF tacked on the
|
||||
* end of a packet. (Thanks to Guido Koerber for eavesdropping on the ADB
|
||||
* protocol with a logic analyzer!!)
|
||||
*
|
||||
* Note: As of 21/10/97, the MacII ADB part works including timeout detection
|
||||
* and retransmit (Talk to the last active device).
|
||||
*/
|
||||
static irqreturn_t macii_interrupt(int irq, void *arg)
|
||||
{
|
||||
int x, adbdir;
|
||||
unsigned long flags;
|
||||
struct adb_request *req;
|
||||
|
||||
last_status = status;
|
||||
|
||||
/* prevent races due to SCSI enabling ints */
|
||||
local_irq_save(flags);
|
||||
|
||||
if (driver_running) {
|
||||
local_irq_restore(flags);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
driver_running = 1;
|
||||
|
||||
status = via[B] & (ST_MASK|TREQ);
|
||||
adbdir = via[ACR] & SR_OUT;
|
||||
|
||||
switch (macii_state) {
|
||||
case idle:
|
||||
x = via[SR];
|
||||
first_byte = x;
|
||||
/* set ADB state = even for first data byte */
|
||||
via[B] = (via[B] & ~ST_MASK) | ST_EVEN;
|
||||
|
||||
reply_buf[0] = first_byte; /* was command_byte?? */
|
||||
reply_ptr = reply_buf + 1;
|
||||
reply_len = 1;
|
||||
prefix_len = 1;
|
||||
reading_reply = 0;
|
||||
|
||||
macii_state = reading;
|
||||
break;
|
||||
|
||||
case awaiting_reply:
|
||||
/* handshake etc. for II ?? */
|
||||
x = via[SR];
|
||||
first_byte = x;
|
||||
/* set ADB state = even for first data byte */
|
||||
via[B] = (via[B] & ~ST_MASK) | ST_EVEN;
|
||||
|
||||
current_req->reply[0] = first_byte;
|
||||
reply_ptr = current_req->reply + 1;
|
||||
reply_len = 1;
|
||||
prefix_len = 1;
|
||||
reading_reply = 1;
|
||||
|
||||
macii_state = reading;
|
||||
break;
|
||||
|
||||
case sending:
|
||||
req = current_req;
|
||||
if (data_index >= req->nbytes) {
|
||||
/* print an error message if a listen command has no data */
|
||||
if (((command_byte & 0x0C) == 0x08)
|
||||
/* && (console_loglevel == 10) */
|
||||
&& (data_index == 2))
|
||||
printk("MacII ADB: listen command with no data: %x!\n",
|
||||
command_byte);
|
||||
/* 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;
|
||||
|
||||
req->sent = 1;
|
||||
|
||||
if (req->reply_expected) {
|
||||
macii_state = awaiting_reply;
|
||||
} else {
|
||||
req->complete = 1;
|
||||
current_req = req->next;
|
||||
if (req->done) (*req->done)(req);
|
||||
macii_state = idle;
|
||||
if (current_req || retry_req)
|
||||
macii_start();
|
||||
else
|
||||
macii_retransmit((command_byte & 0xF0) >> 4);
|
||||
}
|
||||
} 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:
|
||||
|
||||
/* timeout / SRQ handling for II hw */
|
||||
if( (first_byte == 0xFF && (reply_len-prefix_len)==2
|
||||
&& memcmp(reply_ptr-2,"\xFF\xFF",2)==0) ||
|
||||
((reply_len-prefix_len)==3
|
||||
&& memcmp(reply_ptr-3,"\xFF\xFF\xFF",3)==0))
|
||||
{
|
||||
/*
|
||||
* possible timeout (in fact, most probably a
|
||||
* timeout, since SRQ can't be signaled without
|
||||
* transfer on the bus).
|
||||
* The last three bytes seen were FF, together
|
||||
* with the starting byte (in case we started
|
||||
* on 'idle' or 'awaiting_reply') this probably
|
||||
* makes four. So this is mostl likely #5!
|
||||
* The timeout signal is a pattern 1 0 1 0 0..
|
||||
* on /INT, meaning we missed it :-(
|
||||
*/
|
||||
x = via[SR];
|
||||
if (x != 0xFF) printk("MacII ADB: mistaken timeout/SRQ!\n");
|
||||
|
||||
if ((status & TREQ) == (last_status & TREQ)) {
|
||||
/* Not a timeout. Unsolicited SRQ? weird. */
|
||||
/* Terminate the SRQ packet and poll */
|
||||
need_poll = 1;
|
||||
}
|
||||
/* There's no packet to get, so reply is blank */
|
||||
via[B] ^= ST_MASK;
|
||||
reply_ptr -= (reply_len-prefix_len);
|
||||
reply_len = prefix_len;
|
||||
macii_state = read_done;
|
||||
break;
|
||||
} /* end timeout / SRQ handling for II hw. */
|
||||
|
||||
if((reply_len-prefix_len)>3
|
||||
&& memcmp(reply_ptr-3,"\xFF\xFF\xFF",3)==0)
|
||||
{
|
||||
/* SRQ tacked on data packet */
|
||||
/* Terminate the packet (SRQ never ends) */
|
||||
x = via[SR];
|
||||
macii_state = read_done;
|
||||
reply_len -= 3;
|
||||
reply_ptr -= 3;
|
||||
need_poll = 1;
|
||||
/* need to continue; next byte not seen else */
|
||||
} else {
|
||||
/* Sanity check */
|
||||
if (reply_len > 15) reply_len = 0;
|
||||
/* read byte */
|
||||
x = via[SR];
|
||||
*reply_ptr = x;
|
||||
reply_ptr++;
|
||||
reply_len++;
|
||||
}
|
||||
/* The usual handshake ... */
|
||||
|
||||
/*
|
||||
* NetBSD hints that the next to last byte
|
||||
* is sent with IRQ !!
|
||||
* Guido found out it's the last one (0x0),
|
||||
* but IRQ should be asserted already.
|
||||
* Problem with timeout detection: First
|
||||
* transition to /IRQ might be second
|
||||
* byte of timeout packet!
|
||||
* Timeouts are signaled by 4x FF.
|
||||
*/
|
||||
if (((status & TREQ) == 0) && (x == 0x00)) { /* != 0xFF */
|
||||
/* invert state bits, toggle ODD/EVEN */
|
||||
via[B] ^= ST_MASK;
|
||||
|
||||
/* adjust packet length */
|
||||
reply_len--;
|
||||
reply_ptr--;
|
||||
macii_state = read_done;
|
||||
} else {
|
||||
/* not caught: ST_CMD */
|
||||
/* required for re-entry 'reading'! */
|
||||
if ((status & ST_MASK) == ST_IDLE) {
|
||||
/* (in)sanity check - set even */
|
||||
via[B] = (via[B] & ~ST_MASK) | ST_EVEN;
|
||||
} else {
|
||||
/* invert state bits */
|
||||
via[B] ^= ST_MASK;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case read_done:
|
||||
x = via[SR];
|
||||
if (reading_reply) {
|
||||
req = current_req;
|
||||
req->reply_len = reply_ptr - req->reply;
|
||||
req->complete = 1;
|
||||
current_req = req->next;
|
||||
if (req->done) (*req->done)(req);
|
||||
} else {
|
||||
adb_input(reply_buf, reply_ptr - reply_buf, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* remember this device ID; it's the latest we got a
|
||||
* reply from!
|
||||
*/
|
||||
last_reply = command_byte;
|
||||
last_active = (command_byte & 0xF0) >> 4;
|
||||
|
||||
/* SRQ seen before, initiate poll now */
|
||||
if (need_poll) {
|
||||
macii_state = idle;
|
||||
macii_queue_poll();
|
||||
need_poll = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* set ADB state to idle */
|
||||
via[B] = (via[B] & ~ST_MASK) | ST_IDLE;
|
||||
|
||||
/* /IRQ seen, so the ADB controller has data for us */
|
||||
if ((via[B] & TREQ) != 0) {
|
||||
macii_state = reading;
|
||||
|
||||
reply_buf[0] = command_byte;
|
||||
reply_ptr = reply_buf + 1;
|
||||
reply_len = 1;
|
||||
prefix_len = 1;
|
||||
reading_reply = 0;
|
||||
} else {
|
||||
/* no IRQ, send next packet or wait */
|
||||
macii_state = idle;
|
||||
if (current_req)
|
||||
macii_start();
|
||||
else
|
||||
macii_retransmit(last_active);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* reset mutex and interrupts */
|
||||
driver_running = 0;
|
||||
local_irq_restore(flags);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
678
drivers/macintosh/via-maciisi.c
Normal file
678
drivers/macintosh/via-maciisi.c
Normal file
@@ -0,0 +1,678 @@
|
||||
/*
|
||||
* 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/machw.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;
|
||||
}
|
||||
}
|
||||
180
drivers/macintosh/via-pmu-backlight.c
Normal file
180
drivers/macintosh/via-pmu-backlight.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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;
|
||||
static u8 bl_curve[FB_BACKLIGHT_LEVELS];
|
||||
|
||||
static void pmu_backlight_init_curve(u8 off, u8 min, u8 max)
|
||||
{
|
||||
unsigned 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;
|
||||
unsigned long flags;
|
||||
int level = bd->props.brightness;
|
||||
|
||||
spin_lock_irqsave(&pmu_backlight_lock, flags);
|
||||
|
||||
/* Don't update brightness when sleeping */
|
||||
if (sleeping)
|
||||
goto out;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&pmu_backlight_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
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("pmubl: Backlight registration failed\n");
|
||||
goto error;
|
||||
}
|
||||
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("pmubl: Backlight initialized (%s)\n", name);
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
return;
|
||||
}
|
||||
80
drivers/macintosh/via-pmu-event.c
Normal file
80
drivers/macintosh/via-pmu-event.c
Normal 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);
|
||||
8
drivers/macintosh/via-pmu-event.h
Normal file
8
drivers/macintosh/via-pmu-event.h
Normal 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 */
|
||||
144
drivers/macintosh/via-pmu-led.c
Normal file
144
drivers/macintosh/via-pmu-led.c
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 int sleeping;
|
||||
|
||||
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 && !sleeping)
|
||||
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 && !sleeping)
|
||||
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-front-led",
|
||||
#ifdef CONFIG_ADB_PMU_LED_IDE
|
||||
.default_trigger = "ide-disk",
|
||||
#endif
|
||||
.brightness_set = pmu_led_set,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int pmu_led_sleep_call(struct pmu_sleep_notifier *self, int when)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&pmu_blink_lock, flags);
|
||||
|
||||
switch (when) {
|
||||
case PBOOK_SLEEP_REQUEST:
|
||||
sleeping = 1;
|
||||
break;
|
||||
case PBOOK_WAKE:
|
||||
sleeping = 0;
|
||||
break;
|
||||
default:
|
||||
/* do nothing */
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&pmu_blink_lock, flags);
|
||||
|
||||
return PBOOK_SLEEP_OK;
|
||||
}
|
||||
|
||||
static struct pmu_sleep_notifier via_pmu_led_sleep_notif = {
|
||||
.notifier_call = pmu_led_sleep_call,
|
||||
};
|
||||
#endif
|
||||
|
||||
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 = get_property(dt, "model", NULL);
|
||||
if (model == NULL)
|
||||
return -ENODEV;
|
||||
if (strncmp(model, "PowerBook", strlen("PowerBook")) != 0 &&
|
||||
strncmp(model, "iBook", strlen("iBook")) != 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;
|
||||
#ifdef CONFIG_PM
|
||||
pmu_register_sleep_notifier(&via_pmu_led_sleep_notif);
|
||||
#endif
|
||||
return led_classdev_register(NULL, &pmu_led);
|
||||
}
|
||||
|
||||
late_initcall(via_pmu_led_init);
|
||||
2867
drivers/macintosh/via-pmu.c
Normal file
2867
drivers/macintosh/via-pmu.c
Normal file
File diff suppressed because it is too large
Load Diff
1061
drivers/macintosh/via-pmu68k.c
Normal file
1061
drivers/macintosh/via-pmu68k.c
Normal file
File diff suppressed because it is too large
Load Diff
134
drivers/macintosh/windfarm.h
Normal file
134
drivers/macintosh/windfarm.h
Normal 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__ */
|
||||
494
drivers/macintosh/windfarm_core.c
Normal file
494
drivers/macintosh/windfarm_core.c
Normal file
@@ -0,0 +1,494 @@
|
||||
/*
|
||||
* 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/smp_lock.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, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wf_critical_overtemp);
|
||||
|
||||
static int wf_thread_func(void *data)
|
||||
{
|
||||
unsigned long next, delay;
|
||||
|
||||
next = jiffies;
|
||||
|
||||
DBG("wf: thread started\n");
|
||||
|
||||
while(!kthread_should_stop()) {
|
||||
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);
|
||||
|
||||
/* there should be no non-suspend signal, but oh well */
|
||||
if (signal_pending(current) && !try_to_freeze()) {
|
||||
printk(KERN_WARNING "windfarm: thread got sigl !\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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.owner = THIS_MODULE;
|
||||
new_ct->attr.attr.mode = 0644;
|
||||
new_ct->attr.show = wf_show_control;
|
||||
new_ct->attr.store = wf_store_control;
|
||||
device_create_file(&wf_platform_device.dev, &new_ct->attr);
|
||||
|
||||
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.owner = THIS_MODULE;
|
||||
new_sr->attr.attr.mode = 0444;
|
||||
new_sr->attr.show = wf_show_sensor;
|
||||
new_sr->attr.store = NULL;
|
||||
device_create_file(&wf_platform_device.dev, &new_sr->attr);
|
||||
|
||||
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");
|
||||
|
||||
112
drivers/macintosh/windfarm_cpufreq_clamp.c
Normal file
112
drivers/macintosh/windfarm_cpufreq_clamp.c
Normal 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");
|
||||
|
||||
237
drivers/macintosh/windfarm_lm75_sensor.c
Normal file
237
drivers/macintosh/windfarm_lm75_sensor.c
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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)
|
||||
#define i2c_to_lm75(c) container_of(c, struct wf_lm75_sensor, i2c)
|
||||
|
||||
static int wf_lm75_attach(struct i2c_adapter *adapter);
|
||||
static int wf_lm75_detach(struct i2c_client *client);
|
||||
|
||||
static struct i2c_driver wf_lm75_driver = {
|
||||
.driver = {
|
||||
.name = "wf_lm75",
|
||||
},
|
||||
.attach_adapter = wf_lm75_attach,
|
||||
.detach_client = wf_lm75_detach,
|
||||
};
|
||||
|
||||
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.adapter == 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);
|
||||
|
||||
/* check if client is registered and detach from i2c */
|
||||
if (lm->i2c.adapter) {
|
||||
i2c_detach_client(&lm->i2c);
|
||||
lm->i2c.adapter = NULL;
|
||||
}
|
||||
|
||||
kfree(lm);
|
||||
}
|
||||
|
||||
static struct wf_sensor_ops wf_lm75_ops = {
|
||||
.get_value = wf_lm75_get,
|
||||
.release = wf_lm75_release,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter,
|
||||
u8 addr, int ds1775,
|
||||
const char *loc)
|
||||
{
|
||||
struct wf_lm75_sensor *lm;
|
||||
int rc;
|
||||
|
||||
DBG("wf_lm75: creating %s device at address 0x%02x\n",
|
||||
ds1775 ? "ds1775" : "lm75", addr);
|
||||
|
||||
lm = kmalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL);
|
||||
if (lm == NULL)
|
||||
return NULL;
|
||||
memset(lm, 0, sizeof(struct wf_lm75_sensor));
|
||||
|
||||
/* 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"))
|
||||
lm->sens.name = "hd-temp";
|
||||
else
|
||||
goto fail;
|
||||
|
||||
lm->inited = 0;
|
||||
lm->sens.ops = &wf_lm75_ops;
|
||||
lm->ds1775 = ds1775;
|
||||
lm->i2c.addr = (addr >> 1) & 0x7f;
|
||||
lm->i2c.adapter = adapter;
|
||||
lm->i2c.driver = &wf_lm75_driver;
|
||||
strncpy(lm->i2c.name, lm->sens.name, I2C_NAME_SIZE-1);
|
||||
|
||||
rc = i2c_attach_client(&lm->i2c);
|
||||
if (rc) {
|
||||
printk(KERN_ERR "windfarm: failed to attach %s %s to i2c,"
|
||||
" err %d\n", ds1775 ? "ds1775" : "lm75",
|
||||
lm->i2c.name, rc);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (wf_register_sensor(&lm->sens)) {
|
||||
i2c_detach_client(&lm->i2c);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return lm;
|
||||
fail:
|
||||
kfree(lm);
|
||||
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 =
|
||||
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 (device_is_compatible(dev, "lm75"))
|
||||
wf_lm75_create(adapter, addr, 0, loc);
|
||||
/* ds1775 (compatible, better resolution */
|
||||
else if (device_is_compatible(dev, "ds1775"))
|
||||
wf_lm75_create(adapter, addr, 1, loc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wf_lm75_detach(struct i2c_client *client)
|
||||
{
|
||||
struct wf_lm75_sensor *lm = i2c_to_lm75(client);
|
||||
|
||||
DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name);
|
||||
|
||||
/* Mark client detached */
|
||||
lm->i2c.adapter = NULL;
|
||||
|
||||
/* release sensor */
|
||||
wf_unregister_sensor(&lm->sens);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
179
drivers/macintosh/windfarm_max6690_sensor.c
Normal file
179
drivers/macintosh/windfarm_max6690_sensor.c
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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)
|
||||
#define i2c_to_6690(x) container_of((x), struct wf_6690_sensor, i2c)
|
||||
|
||||
static int wf_max6690_attach(struct i2c_adapter *adapter);
|
||||
static int wf_max6690_detach(struct i2c_client *client);
|
||||
|
||||
static struct i2c_driver wf_max6690_driver = {
|
||||
.driver = {
|
||||
.name = "wf_max6690",
|
||||
},
|
||||
.attach_adapter = wf_max6690_attach,
|
||||
.detach_client = wf_max6690_detach,
|
||||
};
|
||||
|
||||
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.adapter == 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);
|
||||
|
||||
if (max->i2c.adapter) {
|
||||
i2c_detach_client(&max->i2c);
|
||||
max->i2c.adapter = NULL;
|
||||
}
|
||||
kfree(max);
|
||||
}
|
||||
|
||||
static struct wf_sensor_ops wf_max6690_ops = {
|
||||
.get_value = wf_max6690_get,
|
||||
.release = wf_max6690_release,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void wf_max6690_create(struct i2c_adapter *adapter, u8 addr)
|
||||
{
|
||||
struct wf_6690_sensor *max;
|
||||
char *name = "backside-temp";
|
||||
|
||||
max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL);
|
||||
if (max == NULL) {
|
||||
printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor %s: "
|
||||
"no memory\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
max->sens.ops = &wf_max6690_ops;
|
||||
max->sens.name = name;
|
||||
max->i2c.addr = addr >> 1;
|
||||
max->i2c.adapter = adapter;
|
||||
max->i2c.driver = &wf_max6690_driver;
|
||||
strncpy(max->i2c.name, name, I2C_NAME_SIZE-1);
|
||||
|
||||
if (i2c_attach_client(&max->i2c)) {
|
||||
printk(KERN_ERR "windfarm: failed to attach MAX6690 sensor\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (wf_register_sensor(&max->sens)) {
|
||||
i2c_detach_client(&max->i2c);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
fail:
|
||||
kfree(max);
|
||||
}
|
||||
|
||||
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 (!device_is_compatible(dev, "max6690"))
|
||||
continue;
|
||||
addr = pmac_i2c_get_dev_addr(dev);
|
||||
loc = get_property(dev, "hwsensor-location", NULL);
|
||||
if (loc == NULL || addr == 0)
|
||||
continue;
|
||||
printk("found max6690, loc=%s addr=0x%02x\n", loc, addr);
|
||||
if (strcmp(loc, "BACKSIDE"))
|
||||
continue;
|
||||
wf_max6690_create(adapter, addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wf_max6690_detach(struct i2c_client *client)
|
||||
{
|
||||
struct wf_6690_sensor *max = i2c_to_6690(client);
|
||||
|
||||
max->i2c.adapter = NULL;
|
||||
wf_unregister_sensor(&max->sens);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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");
|
||||
149
drivers/macintosh/windfarm_pid.c
Normal file
149
drivers/macintosh/windfarm_pid.c
Normal 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");
|
||||
85
drivers/macintosh/windfarm_pid.h
Normal file
85
drivers/macintosh/windfarm_pid.h
Normal 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);
|
||||
713
drivers/macintosh/windfarm_pm112.c
Normal file
713
drivers/macintosh/windfarm_pm112.c
Normal file
@@ -0,0 +1,713 @@
|
||||
/*
|
||||
* 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",
|
||||
.bus = &platform_bus_type,
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
813
drivers/macintosh/windfarm_pm81.c
Normal file
813
drivers/macintosh/windfarm_pm81.c
Normal 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",
|
||||
.bus = &platform_bus_type,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
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");
|
||||
|
||||
744
drivers/macintosh/windfarm_pm91.c
Normal file
744
drivers/macintosh/windfarm_pm91.c
Normal file
@@ -0,0 +1,744 @@
|
||||
/*
|
||||
* 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, ¶m);
|
||||
|
||||
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, ¶m);
|
||||
|
||||
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",
|
||||
.bus = &platform_bus_type,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
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");
|
||||
|
||||
323
drivers/macintosh/windfarm_smu_controls.c
Normal file
323
drivers/macintosh/windfarm_smu_controls.c
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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 = ∁
|
||||
|
||||
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 = 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, "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";
|
||||
|
||||
/* Unrecognized fan, bail out */
|
||||
if (fct->ctrl.name == NULL)
|
||||
goto fail;
|
||||
|
||||
/* Get min & max values*/
|
||||
v = get_property(node, "min-value", NULL);
|
||||
if (v == NULL)
|
||||
goto fail;
|
||||
fct->min = *v;
|
||||
v = get_property(node, "max-value", NULL);
|
||||
if (v == NULL)
|
||||
goto fail;
|
||||
fct->max = *v;
|
||||
|
||||
/* Get "reg" value */
|
||||
reg = 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") ||
|
||||
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");
|
||||
|
||||
413
drivers/macintosh/windfarm_smu_sat.c
Normal file
413
drivers/macintosh/windfarm_smu_sat.c
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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 <asm/semaphore.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 semaphore 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)
|
||||
#define i2c_to_sat(c) container_of(c, struct wf_sat, i2c)
|
||||
|
||||
static int wf_sat_attach(struct i2c_adapter *adapter);
|
||||
static int wf_sat_detach(struct i2c_client *client);
|
||||
|
||||
static struct i2c_driver wf_sat_driver = {
|
||||
.driver = {
|
||||
.name = "wf_smu_sat",
|
||||
},
|
||||
.attach_adapter = wf_sat_attach,
|
||||
.detach_client = wf_sat_detach,
|
||||
};
|
||||
|
||||
/*
|
||||
* XXX i2c_smbus_read_i2c_block_data doesn't pass the requested
|
||||
* length down to the low-level driver, so we use this, which
|
||||
* works well enough with the SMU i2c driver code...
|
||||
*/
|
||||
static int sat_read_block(struct i2c_client *client, u8 command,
|
||||
u8 *values, int len)
|
||||
{
|
||||
union i2c_smbus_data data;
|
||||
int err;
|
||||
|
||||
data.block[0] = len;
|
||||
err = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
|
||||
I2C_SMBUS_READ, command, I2C_SMBUS_I2C_BLOCK_DATA,
|
||||
&data);
|
||||
if (!err)
|
||||
memcpy(values, data.block, len);
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
len = i2c_smbus_read_word_data(&sat->i2c, 9);
|
||||
if (len < 0) {
|
||||
printk(KERN_ERR "smu_sat_get_sdb_part rd len error\n");
|
||||
return NULL;
|
||||
}
|
||||
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 = sat_read_block(&sat->i2c, 0xa, data, 4);
|
||||
if (err) {
|
||||
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 = sat_read_block(&sat->i2c, 0x3f, sat->cache, 16);
|
||||
if (err)
|
||||
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.adapter == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
down(&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:
|
||||
up(&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->i2c.adapter) {
|
||||
i2c_detach_client(&sat->i2c);
|
||||
sat->i2c.adapter = NULL;
|
||||
}
|
||||
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 void wf_sat_create(struct i2c_adapter *adapter, struct device_node *dev)
|
||||
{
|
||||
struct wf_sat *sat;
|
||||
struct wf_sat_sensor *sens;
|
||||
const u32 *reg;
|
||||
const char *loc, *type;
|
||||
u8 addr, chip, core;
|
||||
struct device_node *child;
|
||||
int shift, cpu, index;
|
||||
char *name;
|
||||
int vsens[2], isens[2];
|
||||
|
||||
reg = get_property(dev, "reg", NULL);
|
||||
if (reg == NULL)
|
||||
return;
|
||||
addr = *reg;
|
||||
DBG(KERN_DEBUG "wf_sat: creating sat at address %x\n", addr);
|
||||
|
||||
sat = kzalloc(sizeof(struct wf_sat), GFP_KERNEL);
|
||||
if (sat == NULL)
|
||||
return;
|
||||
sat->nr = -1;
|
||||
sat->node = of_node_get(dev);
|
||||
atomic_set(&sat->refcnt, 0);
|
||||
init_MUTEX(&sat->mutex);
|
||||
sat->i2c.addr = (addr >> 1) & 0x7f;
|
||||
sat->i2c.adapter = adapter;
|
||||
sat->i2c.driver = &wf_sat_driver;
|
||||
strncpy(sat->i2c.name, "smu-sat", I2C_NAME_SIZE-1);
|
||||
|
||||
if (i2c_attach_client(&sat->i2c)) {
|
||||
printk(KERN_ERR "windfarm: failed to attach smu-sat to i2c\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vsens[0] = vsens[1] = -1;
|
||||
isens[0] = isens[1] = -1;
|
||||
child = NULL;
|
||||
while ((child = of_get_next_child(dev, child)) != NULL) {
|
||||
reg = get_property(child, "reg", NULL);
|
||||
type = get_property(child, "device_type", NULL);
|
||||
loc = 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;
|
||||
|
||||
fail:
|
||||
kfree(sat);
|
||||
}
|
||||
|
||||
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 (device_is_compatible(dev, "smu-sat"))
|
||||
wf_sat_create(adapter, dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wf_sat_detach(struct i2c_client *client)
|
||||
{
|
||||
struct wf_sat *sat = i2c_to_sat(client);
|
||||
|
||||
/* XXX TODO */
|
||||
|
||||
sat->i2c.adapter = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init sat_sensors_init(void)
|
||||
{
|
||||
return i2c_add_driver(&wf_sat_driver);
|
||||
}
|
||||
|
||||
static void __exit sat_sensors_exit(void)
|
||||
{
|
||||
i2c_del_driver(&wf_sat_driver);
|
||||
}
|
||||
|
||||
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");
|
||||
483
drivers/macintosh/windfarm_smu_sensors.c
Normal file
483
drivers/macintosh/windfarm_smu_sensors.c
Normal 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 = get_property(node, "device_type", NULL);
|
||||
l = 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 = 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, &s);
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user