mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-29 03:01:39 -04:00
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
This commit is contained in:
commit
1da177e4c3
17291 changed files with 6718755 additions and 0 deletions
198
drivers/macintosh/Kconfig
Normal file
198
drivers/macintosh/Kconfig
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
|
||||
menu "Macintosh device drivers"
|
||||
depends on PPC || MAC
|
||||
|
||||
config ADB
|
||||
bool "Apple Desktop Bus (ADB) support"
|
||||
depends on MAC || PPC_PMAC
|
||||
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 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_PBOOK
|
||||
bool "Power management support for PowerBooks"
|
||||
depends on ADB_PMU
|
||||
---help---
|
||||
This provides support for putting a PowerBook to sleep; it also
|
||||
enables media bay support. Power management works on the
|
||||
PB2400/3400/3500, Wallstreet, Lombard, and Bronze PowerBook G3 and
|
||||
the Titanium Powerbook G4, as well as the iBooks. You should get
|
||||
the power management daemon, pmud, to make it work and you must have
|
||||
the /dev/pmu device (see the pmud README).
|
||||
|
||||
Get pmud from <ftp://ftp.samba.org/pub/ppclinux/pmud/>.
|
||||
|
||||
If you have a PowerBook, you should say Y here.
|
||||
|
||||
You may also want to compile the dma sound driver as a module and
|
||||
have it autoloaded. The act of removing the module shuts down the
|
||||
sound hardware for more power savings.
|
||||
|
||||
config PM
|
||||
bool
|
||||
depends on PPC_PMAC && ADB_PMU && PMAC_PBOOK
|
||||
default y
|
||||
|
||||
config PMAC_APM_EMU
|
||||
tristate "APM emulation"
|
||||
depends on PMAC_PBOOK
|
||||
|
||||
# made a separate option since backlight may end up beeing used
|
||||
# on non-powerbook machines (but only on PMU based ones AFAIK)
|
||||
config PMAC_BACKLIGHT
|
||||
bool "Backlight control for LCD screens"
|
||||
depends on ADB_PMU
|
||||
help
|
||||
Say Y here to build in code to manage the LCD backlight on a
|
||||
Macintosh PowerBook. With this code, the backlight will be turned
|
||||
on and off appropriately on power-management and lid-open/lid-closed
|
||||
events; also, the PowerBook button device will be enabled so you can
|
||||
change the screen brightness.
|
||||
|
||||
config MAC_SERIAL
|
||||
tristate "Support for PowerMac serial ports (OBSOLETE DRIVER)"
|
||||
depends on PPC_PMAC && BROKEN
|
||||
help
|
||||
This driver is obsolete. Use CONFIG_SERIAL_PMACZILOG in
|
||||
"Character devices --> Serial drivers --> PowerMac z85c30" option.
|
||||
|
||||
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"
|
||||
depends on INPUT_ADBHID
|
||||
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_KEYWEST && 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_KEYWEST && PPC_PMAC && !PPC_PMAC64
|
||||
help
|
||||
This driver provides some thermostat and fan control for the
|
||||
iBook G4, and the ATI based aluminium PowerBooks, allowing slighlty
|
||||
better fan behaviour by default, and some manual control.
|
||||
|
||||
config THERM_PM72
|
||||
tristate "Support for thermal management on PowerMac G5"
|
||||
depends on I2C && I2C_KEYWEST && PPC_PMAC64
|
||||
help
|
||||
This driver provides thermostat and fan control for the desktop
|
||||
G5 machines.
|
||||
|
||||
config ANSLCD
|
||||
tristate "Support for ANS LCD display"
|
||||
depends on ADB_CUDA && PPC_PMAC
|
||||
|
||||
endmenu
|
||||
29
drivers/macintosh/Makefile
Normal file
29
drivers/macintosh/Makefile
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# Makefile for the Macintosh-specific device drivers.
|
||||
#
|
||||
|
||||
# Each configuration option enables a list of files.
|
||||
|
||||
obj-$(CONFIG_PPC_PMAC) += macio_asic.o
|
||||
|
||||
obj-$(CONFIG_PMAC_PBOOK) += mediabay.o
|
||||
obj-$(CONFIG_MAC_SERIAL) += macserial.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
|
||||
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
|
||||
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 *, struct pt_regs *);
|
||||
|
||||
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 pt_regs *regs)
|
||||
{
|
||||
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 pt_regs *regs)
|
||||
{
|
||||
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, regs,
|
||||
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 = 0;
|
||||
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, NULL);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
910
drivers/macintosh/adb.c
Normal file
910
drivers/macintosh/adb.c
Normal file
|
|
@ -0,0 +1,910 @@
|
|||
/*
|
||||
* 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/config.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <linux/adb.h>
|
||||
#include <linux/cuda.h>
|
||||
#include <linux/pmu.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/devfs_fs_kernel.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/semaphore.h>
|
||||
#ifdef CONFIG_PPC
|
||||
#include <asm/prom.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_simple *adb_dev_class;
|
||||
|
||||
struct adb_driver *adb_controller;
|
||||
struct notifier_block *adb_client_list = NULL;
|
||||
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_PMAC_PBOOK
|
||||
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, struct pt_regs *, 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(void *data)
|
||||
{
|
||||
adb_probe_task_pid = kernel_thread(adb_probe_task, NULL, SIGCHLD | CLONE_KERNEL);
|
||||
}
|
||||
|
||||
static DECLARE_WORK(adb_reset_work, __adb_probe_task, NULL);
|
||||
|
||||
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 != _MACH_chrp) && (_machine != _MACH_Pmac) )
|
||||
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_PMAC_PBOOK
|
||||
pmu_register_sleep_notifier(&adb_sleep_notifier);
|
||||
#endif /* CONFIG_PMAC_PBOOK */
|
||||
#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_PMAC_PBOOK
|
||||
/*
|
||||
* 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 = 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_PMAC_PBOOK */
|
||||
|
||||
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 = 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 = 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, struct pt_regs *, 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, struct pt_regs *regs, int autopoll)
|
||||
{
|
||||
int i, id;
|
||||
static int dump_adb_input = 0;
|
||||
unsigned long flags;
|
||||
|
||||
void (*handler)(unsigned char *, int, struct pt_regs *, 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, regs, 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 = (struct adb_request *) 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 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;
|
||||
}
|
||||
|
||||
devfs_mk_cdev(MKDEV(ADB_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR, "adb");
|
||||
|
||||
adb_dev_class = class_simple_create(THIS_MODULE, "adb");
|
||||
if (IS_ERR(adb_dev_class)) {
|
||||
return;
|
||||
}
|
||||
class_simple_device_add(adb_dev_class, MKDEV(ADB_MAJOR, 0), NULL, "adb");
|
||||
}
|
||||
1177
drivers/macintosh/adbhid.c
Normal file
1177
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 __pmac
|
||||
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 __pmac
|
||||
anslcd_write_byte_data ( unsigned char c )
|
||||
{
|
||||
out_8(anslcd_ptr + ANSLCD_DATA_IX, c);
|
||||
udelay(anslcd_short_delay);
|
||||
}
|
||||
|
||||
static ssize_t __pmac
|
||||
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 __pmac
|
||||
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 __pmac
|
||||
anslcd_open( struct inode * inode, struct file * file )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
553
drivers/macintosh/apm_emu.c
Normal file
553
drivers/macintosh/apm_emu.c
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
/* 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/config.h>
|
||||
#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 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 = (struct apm_user *)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 = 0xff;
|
||||
unsigned short battery_status = 0xff;
|
||||
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) {
|
||||
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 (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 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;
|
||||
|
||||
misc_register(&apm_device);
|
||||
|
||||
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");
|
||||
|
||||
140
drivers/macintosh/mac_hid.c
Normal file
140
drivers/macintosh/mac_hid.c
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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/config.h>
|
||||
#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 void emumousebtn_input_register(void);
|
||||
static int mouse_emulate_buttons = 0;
|
||||
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 = 0;
|
||||
|
||||
#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 void emumousebtn_input_register(void)
|
||||
{
|
||||
emumousebtn.name = "Macintosh mouse button emulation";
|
||||
|
||||
init_input_dev(&emumousebtn);
|
||||
|
||||
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);
|
||||
|
||||
emumousebtn.id.bustype = BUS_ADB;
|
||||
emumousebtn.id.vendor = 0x0001;
|
||||
emumousebtn.id.product = 0x0001;
|
||||
emumousebtn.id.version = 0x0100;
|
||||
|
||||
input_register_device(&emumousebtn);
|
||||
|
||||
printk(KERN_INFO "input: Macintosh mouse button emulation\n");
|
||||
}
|
||||
|
||||
int __init mac_hid_init(void)
|
||||
{
|
||||
|
||||
emumousebtn_input_register();
|
||||
|
||||
#if defined(CONFIG_SYSCTL)
|
||||
mac_hid_sysctl_header = register_sysctl_table(mac_hid_root_dir, 1);
|
||||
#endif /* CONFIG_SYSCTL */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(mac_hid_init);
|
||||
284
drivers/macintosh/macio-adb.c
Normal file
284
drivers/macintosh/macio-adb.c
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Driver for the ADB controller in the Mac I/O (Hydra) chip.
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/sched.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>
|
||||
|
||||
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, struct pt_regs *regs);
|
||||
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;
|
||||
|
||||
adbs = find_compatible_devices("adb", "chrp,adb0");
|
||||
if (adbs == 0)
|
||||
return -ENXIO;
|
||||
|
||||
#if 0
|
||||
{ int i;
|
||||
|
||||
printk("macio_adb_init: node = %p, addrs =", adbs->node);
|
||||
for (i = 0; i < adbs->n_addrs; ++i)
|
||||
printk(" %x(%x)", adbs->addrs[i].address, adbs->addrs[i].size);
|
||||
printk(", intrs =");
|
||||
for (i = 0; i < adbs->n_intrs; ++i)
|
||||
printk(" %x", adbs->intrs[i].line);
|
||||
printk("\n"); }
|
||||
#endif
|
||||
|
||||
adb = ioremap(adbs->addrs->address, 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);
|
||||
|
||||
if (request_irq(adbs->intrs[0].line, macio_adb_interrupt,
|
||||
0, "ADB", (void *)0)) {
|
||||
printk(KERN_ERR "ADB: can't get irq %d\n",
|
||||
adbs->intrs[0].line);
|
||||
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,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
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, regs, 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, NULL);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
636
drivers/macintosh/macio_asic.c
Normal file
636
drivers/macintosh/macio_asic.c
Normal file
|
|
@ -0,0 +1,636 @@
|
|||
/*
|
||||
* Bus & driver management routines for devices within
|
||||
* a MacIO ASIC. Interface to new driver model mostly
|
||||
* stolen from the PCI 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/config.h>
|
||||
#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 <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_match * 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_match *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, u32 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;
|
||||
}
|
||||
|
||||
struct bus_type macio_bus_type = {
|
||||
.name = "macio",
|
||||
.match = macio_bus_match,
|
||||
.suspend = macio_device_suspend,
|
||||
.resume = macio_device_resume,
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
if (res->flags & IORESOURCE_MEM) {
|
||||
/* Grand Central has too large resource 0 on some machines */
|
||||
if (index == 0 && !strcmp(np->name, "gc")) {
|
||||
np->addrs[0].size = 0x20000;
|
||||
res->end = res->start + 0x1ffff;
|
||||
}
|
||||
/* Airport has bogus resource 2 */
|
||||
if (index >= 2 && !strcmp(np->name, "radio"))
|
||||
return 1;
|
||||
/* DBDMAs may have bogus sizes */
|
||||
if ((res->start & 0x0001f000) == 0x00008000) {
|
||||
np->addrs[index].size = 0x100;
|
||||
res->end = res->start + 0xff;
|
||||
}
|
||||
/* 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 && np->addrs[0].size > 0x1000) {
|
||||
np->addrs[0].size = 0x1000;
|
||||
res->end = res->start + 0xfff;
|
||||
}
|
||||
if (index == 1 && np->addrs[1].size > 0x100) {
|
||||
np->addrs[1].size = 0x100;
|
||||
res->end = res->start + 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
int i, j;
|
||||
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.%08lx:%.*s", chip->lbus.index,
|
||||
#ifdef CONFIG_PCI
|
||||
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 = (u32 *)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);
|
||||
}
|
||||
|
||||
/* For now, we use pre-parsed entries in the device-tree for
|
||||
* interrupt routing and addresses, but we should change that
|
||||
* to dynamically parsed entries and so get rid of most of the
|
||||
* clutter in struct device_node
|
||||
*/
|
||||
for (i = j = 0; i < np->n_intrs; i++) {
|
||||
struct resource *res = &dev->interrupt[j];
|
||||
|
||||
if (j >= MACIO_DEV_COUNT_IRQS)
|
||||
break;
|
||||
res->start = np->intrs[i].line;
|
||||
res->flags = IORESOURCE_IO;
|
||||
if (np->intrs[j].sense)
|
||||
res->flags |= IORESOURCE_IRQ_LOWLEVEL;
|
||||
else
|
||||
res->flags |= IORESOURCE_IRQ_HIGHEDGE;
|
||||
res->name = dev->ofdev.dev.bus_id;
|
||||
if (macio_resource_quirks(np, res, i))
|
||||
memset(res, 0, sizeof(struct resource));
|
||||
else
|
||||
j++;
|
||||
}
|
||||
dev->n_interrupts = j;
|
||||
for (i = j = 0; i < np->n_addrs; i++) {
|
||||
struct resource *res = &dev->resource[j];
|
||||
|
||||
if (j >= MACIO_DEV_COUNT_RESOURCES)
|
||||
break;
|
||||
res->start = np->addrs[i].address;
|
||||
res->end = np->addrs[i].address + np->addrs[i].size - 1;
|
||||
res->flags = IORESOURCE_MEM;
|
||||
res->name = dev->ofdev.dev.bus_id;
|
||||
if (macio_resource_quirks(np, res, i))
|
||||
memset(res, 0, sizeof(struct resource));
|
||||
else {
|
||||
j++;
|
||||
/* 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", i, dev->ofdev.dev.bus_id);
|
||||
}
|
||||
}
|
||||
dev->n_resources = j;
|
||||
|
||||
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)) {
|
||||
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)) {
|
||||
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)) {
|
||||
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)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
/* initialize common driver fields */
|
||||
drv->driver.name = drv->name;
|
||||
drv->driver.bus = &macio_bus_type;
|
||||
drv->driver.probe = macio_device_probe;
|
||||
drv->driver.remove = macio_device_remove;
|
||||
drv->driver.shutdown = macio_device_shutdown;
|
||||
|
||||
/* register with core */
|
||||
count = driver_register(&drv->driver);
|
||||
return count ? count : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. This isn't
|
||||
* the case today, but on the other hand ppc32 doesn't do refcounting. This
|
||||
* will have to be fixed when going to ppc64. --BenH.
|
||||
*/
|
||||
np = pci_device_to_OF_node(pdev);
|
||||
if (np == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
/* This assumption is wrong, fix that here for now until I fix the arch */
|
||||
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);
|
||||
3036
drivers/macintosh/macserial.c
Normal file
3036
drivers/macintosh/macserial.c
Normal file
File diff suppressed because it is too large
Load diff
461
drivers/macintosh/macserial.h
Normal file
461
drivers/macintosh/macserial.h
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
* macserial.h: Definitions for the Macintosh Z8530 serial driver.
|
||||
*
|
||||
* Adapted from drivers/sbus/char/sunserial.h by Paul Mackerras.
|
||||
*
|
||||
* Copyright (C) 1996 Paul Mackerras (Paul.Mackerras@cs.anu.edu.au)
|
||||
* Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
|
||||
*/
|
||||
#ifndef _MACSERIAL_H
|
||||
#define _MACSERIAL_H
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define NUM_ZSREGS 16
|
||||
|
||||
struct serial_struct {
|
||||
int type;
|
||||
int line;
|
||||
int port;
|
||||
int irq;
|
||||
int flags;
|
||||
int xmit_fifo_size;
|
||||
int custom_divisor;
|
||||
int baud_base;
|
||||
unsigned short close_delay;
|
||||
char reserved_char[2];
|
||||
int hub6;
|
||||
unsigned short closing_wait; /* time to wait before closing */
|
||||
unsigned short closing_wait2; /* no longer used... */
|
||||
int reserved[4];
|
||||
};
|
||||
|
||||
/*
|
||||
* For the close wait times, 0 means wait forever for serial port to
|
||||
* flush its output. 65535 means don't wait at all.
|
||||
*/
|
||||
#define ZILOG_CLOSING_WAIT_INF 0
|
||||
#define ZILOG_CLOSING_WAIT_NONE 65535
|
||||
|
||||
/*
|
||||
* Definitions for ZILOG_struct (and serial_struct) flags field
|
||||
*/
|
||||
#define ZILOG_HUP_NOTIFY 0x0001 /* Notify getty on hangups and closes
|
||||
* on the callout port */
|
||||
#define ZILOG_FOURPORT 0x0002 /* Set OU1, OUT2 per AST Fourport settings */
|
||||
#define ZILOG_SAK 0x0004 /* Secure Attention Key (Orange book) */
|
||||
#define ZILOG_SPLIT_TERMIOS 0x0008 /* Separate termios for dialin/callout */
|
||||
|
||||
#define ZILOG_SPD_MASK 0x0030
|
||||
#define ZILOG_SPD_HI 0x0010 /* Use 56000 instead of 38400 bps */
|
||||
|
||||
#define ZILOG_SPD_VHI 0x0020 /* Use 115200 instead of 38400 bps */
|
||||
#define ZILOG_SPD_CUST 0x0030 /* Use user-specified divisor */
|
||||
|
||||
#define ZILOG_SKIP_TEST 0x0040 /* Skip UART test during autoconfiguration */
|
||||
#define ZILOG_AUTO_IRQ 0x0080 /* Do automatic IRQ during autoconfiguration */
|
||||
#define ZILOG_SESSION_LOCKOUT 0x0100 /* Lock out cua opens based on session */
|
||||
#define ZILOG_PGRP_LOCKOUT 0x0200 /* Lock out cua opens based on pgrp */
|
||||
#define ZILOG_CALLOUT_NOHUP 0x0400 /* Don't do hangups for cua device */
|
||||
|
||||
#define ZILOG_FLAGS 0x0FFF /* Possible legal ZILOG flags */
|
||||
#define ZILOG_USR_MASK 0x0430 /* Legal flags that non-privileged
|
||||
* users can set or reset */
|
||||
|
||||
/* Internal flags used only by kernel/chr_drv/serial.c */
|
||||
#define ZILOG_INITIALIZED 0x80000000 /* Serial port was initialized */
|
||||
#define ZILOG_CALLOUT_ACTIVE 0x40000000 /* Call out device is active */
|
||||
#define ZILOG_NORMAL_ACTIVE 0x20000000 /* Normal device is active */
|
||||
#define ZILOG_BOOT_AUTOCONF 0x10000000 /* Autoconfigure port on bootup */
|
||||
#define ZILOG_CLOSING 0x08000000 /* Serial port is closing */
|
||||
#define ZILOG_CTS_FLOW 0x04000000 /* Do CTS flow control */
|
||||
#define ZILOG_CHECK_CD 0x02000000 /* i.e., CLOCAL */
|
||||
#define ZILOG_SLEEPING 0x01000000 /* have shut it down for sleep */
|
||||
|
||||
/* Software state per channel */
|
||||
|
||||
#ifdef __KERNEL__
|
||||
/*
|
||||
* This is our internal structure for each serial port's state.
|
||||
*
|
||||
* Many fields are paralleled by the structure used by the serial_struct
|
||||
* structure.
|
||||
*
|
||||
* For definitions of the flags field, see tty.h
|
||||
*/
|
||||
|
||||
struct mac_serial;
|
||||
|
||||
struct mac_zschannel {
|
||||
volatile unsigned char* control;
|
||||
volatile unsigned char* data;
|
||||
spinlock_t lock;
|
||||
/* Used for debugging */
|
||||
struct mac_serial* parent;
|
||||
};
|
||||
|
||||
struct mac_dma {
|
||||
volatile struct dbdma_regs dma;
|
||||
volatile unsigned short res_count;
|
||||
volatile unsigned short command;
|
||||
volatile unsigned int buf_addr;
|
||||
};
|
||||
|
||||
struct mac_serial {
|
||||
struct mac_serial *zs_next; /* For IRQ servicing chain */
|
||||
struct mac_zschannel *zs_channel; /* Channel registers */
|
||||
struct mac_zschannel *zs_chan_a; /* A side registers */
|
||||
unsigned char read_reg_zero;
|
||||
struct device_node* dev_node;
|
||||
spinlock_t lock;
|
||||
|
||||
char soft_carrier; /* Use soft carrier on this channel */
|
||||
char break_abort; /* Is serial console in, so process brk/abrt */
|
||||
char kgdb_channel; /* Kgdb is running on this channel */
|
||||
char is_cons; /* Is this our console. */
|
||||
char is_internal_modem; /* is connected to an internal modem */
|
||||
char is_irda; /* is connected to an IrDA codec */
|
||||
int port_type; /* Port type for pmac_feature */
|
||||
unsigned char tx_active; /* character is being xmitted */
|
||||
unsigned char tx_stopped; /* output is suspended */
|
||||
unsigned char power_wait; /* waiting for power-up delay to expire */
|
||||
|
||||
/* We need to know the current clock divisor
|
||||
* to read the bps rate the chip has currently
|
||||
* loaded.
|
||||
*/
|
||||
unsigned char clk_divisor; /* May be 1, 16, 32, or 64 */
|
||||
int zs_baud;
|
||||
|
||||
/* Current write register values */
|
||||
unsigned char curregs[NUM_ZSREGS];
|
||||
|
||||
/* Values we need to set next opportunity */
|
||||
unsigned char pendregs[NUM_ZSREGS];
|
||||
|
||||
char change_needed;
|
||||
|
||||
int magic;
|
||||
int baud_base;
|
||||
int port;
|
||||
int irq;
|
||||
int flags; /* defined in tty.h */
|
||||
int type; /* UART type */
|
||||
struct tty_struct *tty;
|
||||
int read_status_mask;
|
||||
int ignore_status_mask;
|
||||
int timeout;
|
||||
int xmit_fifo_size;
|
||||
int custom_divisor;
|
||||
int x_char; /* xon/xoff character */
|
||||
int close_delay;
|
||||
unsigned short closing_wait;
|
||||
unsigned short closing_wait2;
|
||||
unsigned long event;
|
||||
unsigned long last_active;
|
||||
int line;
|
||||
int count; /* # of fd on device */
|
||||
int blocked_open; /* # of blocked opens */
|
||||
unsigned char *xmit_buf;
|
||||
int xmit_head;
|
||||
int xmit_tail;
|
||||
int xmit_cnt;
|
||||
struct work_struct tqueue;
|
||||
wait_queue_head_t open_wait;
|
||||
wait_queue_head_t close_wait;
|
||||
|
||||
volatile struct dbdma_regs *tx_dma;
|
||||
int tx_dma_irq;
|
||||
volatile struct dbdma_cmd *tx_cmds;
|
||||
volatile struct mac_dma *rx;
|
||||
int rx_dma_irq;
|
||||
volatile struct dbdma_cmd **rx_cmds;
|
||||
unsigned char **rx_char_buf;
|
||||
unsigned char **rx_flag_buf;
|
||||
#define RX_BUF_SIZE 256
|
||||
int rx_nbuf;
|
||||
int rx_done_bytes;
|
||||
int rx_ubuf;
|
||||
int rx_fbuf;
|
||||
#define RX_NO_FBUF (-1)
|
||||
int rx_cbuf;
|
||||
spinlock_t rx_dma_lock;
|
||||
int has_dma;
|
||||
int dma_initted;
|
||||
void *dma_priv;
|
||||
struct timer_list poll_dma_timer;
|
||||
#define RX_DMA_TIMER (jiffies + 10*HZ/1000)
|
||||
|
||||
struct timer_list powerup_timer;
|
||||
};
|
||||
|
||||
|
||||
#define SERIAL_MAGIC 0x5301
|
||||
|
||||
/*
|
||||
* The size of the serial xmit buffer is 1 page, or 4096 bytes
|
||||
*/
|
||||
#define SERIAL_XMIT_SIZE 4096
|
||||
|
||||
/*
|
||||
* Events are used to schedule things to happen at timer-interrupt
|
||||
* time, instead of at rs interrupt time.
|
||||
*/
|
||||
#define RS_EVENT_WRITE_WAKEUP 0
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
/* Conversion routines to/from brg time constants from/to bits
|
||||
* per second.
|
||||
*/
|
||||
#define BRG_TO_BPS(brg, freq) ((freq) / 2 / ((brg) + 2))
|
||||
#define BPS_TO_BRG(bps, freq) ((((freq) + (bps)) / (2 * (bps))) - 2)
|
||||
|
||||
/* The Zilog register set */
|
||||
|
||||
#define FLAG 0x7e
|
||||
|
||||
/* Write Register 0 */
|
||||
#define R0 0 /* Register selects */
|
||||
#define R1 1
|
||||
#define R2 2
|
||||
#define R3 3
|
||||
#define R4 4
|
||||
#define R5 5
|
||||
#define R6 6
|
||||
#define R7 7
|
||||
#define R8 8
|
||||
#define R9 9
|
||||
#define R10 10
|
||||
#define R11 11
|
||||
#define R12 12
|
||||
#define R13 13
|
||||
#define R14 14
|
||||
#define R15 15
|
||||
|
||||
#define NULLCODE 0 /* Null Code */
|
||||
#define POINT_HIGH 0x8 /* Select upper half of registers */
|
||||
#define RES_EXT_INT 0x10 /* Reset Ext. Status Interrupts */
|
||||
#define SEND_ABORT 0x18 /* HDLC Abort */
|
||||
#define RES_RxINT_FC 0x20 /* Reset RxINT on First Character */
|
||||
#define RES_Tx_P 0x28 /* Reset TxINT Pending */
|
||||
#define ERR_RES 0x30 /* Error Reset */
|
||||
#define RES_H_IUS 0x38 /* Reset highest IUS */
|
||||
|
||||
#define RES_Rx_CRC 0x40 /* Reset Rx CRC Checker */
|
||||
#define RES_Tx_CRC 0x80 /* Reset Tx CRC Checker */
|
||||
#define RES_EOM_L 0xC0 /* Reset EOM latch */
|
||||
|
||||
/* Write Register 1 */
|
||||
|
||||
#define EXT_INT_ENAB 0x1 /* Ext Int Enable */
|
||||
#define TxINT_ENAB 0x2 /* Tx Int Enable */
|
||||
#define PAR_SPEC 0x4 /* Parity is special condition */
|
||||
|
||||
#define RxINT_DISAB 0 /* Rx Int Disable */
|
||||
#define RxINT_FCERR 0x8 /* Rx Int on First Character Only or Error */
|
||||
#define INT_ALL_Rx 0x10 /* Int on all Rx Characters or error */
|
||||
#define INT_ERR_Rx 0x18 /* Int on error only */
|
||||
|
||||
#define WT_RDY_RT 0x20 /* W/Req reflects recv if 1, xmit if 0 */
|
||||
#define WT_FN_RDYFN 0x40 /* W/Req pin is DMA request if 1, wait if 0 */
|
||||
#define WT_RDY_ENAB 0x80 /* Enable W/Req pin */
|
||||
|
||||
/* Write Register #2 (Interrupt Vector) */
|
||||
|
||||
/* Write Register 3 */
|
||||
|
||||
#define RxENABLE 0x1 /* Rx Enable */
|
||||
#define SYNC_L_INH 0x2 /* Sync Character Load Inhibit */
|
||||
#define ADD_SM 0x4 /* Address Search Mode (SDLC) */
|
||||
#define RxCRC_ENAB 0x8 /* Rx CRC Enable */
|
||||
#define ENT_HM 0x10 /* Enter Hunt Mode */
|
||||
#define AUTO_ENAB 0x20 /* Auto Enables */
|
||||
#define Rx5 0x0 /* Rx 5 Bits/Character */
|
||||
#define Rx7 0x40 /* Rx 7 Bits/Character */
|
||||
#define Rx6 0x80 /* Rx 6 Bits/Character */
|
||||
#define Rx8 0xc0 /* Rx 8 Bits/Character */
|
||||
#define RxNBITS_MASK 0xc0
|
||||
|
||||
/* Write Register 4 */
|
||||
|
||||
#define PAR_ENA 0x1 /* Parity Enable */
|
||||
#define PAR_EVEN 0x2 /* Parity Even/Odd* */
|
||||
|
||||
#define SYNC_ENAB 0 /* Sync Modes Enable */
|
||||
#define SB1 0x4 /* 1 stop bit/char */
|
||||
#define SB15 0x8 /* 1.5 stop bits/char */
|
||||
#define SB2 0xc /* 2 stop bits/char */
|
||||
#define SB_MASK 0xc
|
||||
|
||||
#define MONSYNC 0 /* 8 Bit Sync character */
|
||||
#define BISYNC 0x10 /* 16 bit sync character */
|
||||
#define SDLC 0x20 /* SDLC Mode (01111110 Sync Flag) */
|
||||
#define EXTSYNC 0x30 /* External Sync Mode */
|
||||
|
||||
#define X1CLK 0x0 /* x1 clock mode */
|
||||
#define X16CLK 0x40 /* x16 clock mode */
|
||||
#define X32CLK 0x80 /* x32 clock mode */
|
||||
#define X64CLK 0xC0 /* x64 clock mode */
|
||||
#define XCLK_MASK 0xC0
|
||||
|
||||
/* Write Register 5 */
|
||||
|
||||
#define TxCRC_ENAB 0x1 /* Tx CRC Enable */
|
||||
#define RTS 0x2 /* RTS */
|
||||
#define SDLC_CRC 0x4 /* SDLC/CRC-16 */
|
||||
#define TxENAB 0x8 /* Tx Enable */
|
||||
#define SND_BRK 0x10 /* Send Break */
|
||||
#define Tx5 0x0 /* Tx 5 bits (or less)/character */
|
||||
#define Tx7 0x20 /* Tx 7 bits/character */
|
||||
#define Tx6 0x40 /* Tx 6 bits/character */
|
||||
#define Tx8 0x60 /* Tx 8 bits/character */
|
||||
#define TxNBITS_MASK 0x60
|
||||
#define DTR 0x80 /* DTR */
|
||||
|
||||
/* Write Register 6 (Sync bits 0-7/SDLC Address Field) */
|
||||
|
||||
/* Write Register 7 (Sync bits 8-15/SDLC 01111110) */
|
||||
|
||||
/* Write Register 7' (Some enhanced feature control) */
|
||||
#define ENEXREAD 0x40 /* Enable read of some write registers */
|
||||
|
||||
/* Write Register 8 (transmit buffer) */
|
||||
|
||||
/* Write Register 9 (Master interrupt control) */
|
||||
#define VIS 1 /* Vector Includes Status */
|
||||
#define NV 2 /* No Vector */
|
||||
#define DLC 4 /* Disable Lower Chain */
|
||||
#define MIE 8 /* Master Interrupt Enable */
|
||||
#define STATHI 0x10 /* Status high */
|
||||
#define NORESET 0 /* No reset on write to R9 */
|
||||
#define CHRB 0x40 /* Reset channel B */
|
||||
#define CHRA 0x80 /* Reset channel A */
|
||||
#define FHWRES 0xc0 /* Force hardware reset */
|
||||
|
||||
/* Write Register 10 (misc control bits) */
|
||||
#define BIT6 1 /* 6 bit/8bit sync */
|
||||
#define LOOPMODE 2 /* SDLC Loop mode */
|
||||
#define ABUNDER 4 /* Abort/flag on SDLC xmit underrun */
|
||||
#define MARKIDLE 8 /* Mark/flag on idle */
|
||||
#define GAOP 0x10 /* Go active on poll */
|
||||
#define NRZ 0 /* NRZ mode */
|
||||
#define NRZI 0x20 /* NRZI mode */
|
||||
#define FM1 0x40 /* FM1 (transition = 1) */
|
||||
#define FM0 0x60 /* FM0 (transition = 0) */
|
||||
#define CRCPS 0x80 /* CRC Preset I/O */
|
||||
|
||||
/* Write Register 11 (Clock Mode control) */
|
||||
#define TRxCXT 0 /* TRxC = Xtal output */
|
||||
#define TRxCTC 1 /* TRxC = Transmit clock */
|
||||
#define TRxCBR 2 /* TRxC = BR Generator Output */
|
||||
#define TRxCDP 3 /* TRxC = DPLL output */
|
||||
#define TRxCOI 4 /* TRxC O/I */
|
||||
#define TCRTxCP 0 /* Transmit clock = RTxC pin */
|
||||
#define TCTRxCP 8 /* Transmit clock = TRxC pin */
|
||||
#define TCBR 0x10 /* Transmit clock = BR Generator output */
|
||||
#define TCDPLL 0x18 /* Transmit clock = DPLL output */
|
||||
#define RCRTxCP 0 /* Receive clock = RTxC pin */
|
||||
#define RCTRxCP 0x20 /* Receive clock = TRxC pin */
|
||||
#define RCBR 0x40 /* Receive clock = BR Generator output */
|
||||
#define RCDPLL 0x60 /* Receive clock = DPLL output */
|
||||
#define RTxCX 0x80 /* RTxC Xtal/No Xtal */
|
||||
|
||||
/* Write Register 12 (lower byte of baud rate generator time constant) */
|
||||
|
||||
/* Write Register 13 (upper byte of baud rate generator time constant) */
|
||||
|
||||
/* Write Register 14 (Misc control bits) */
|
||||
#define BRENABL 1 /* Baud rate generator enable */
|
||||
#define BRSRC 2 /* Baud rate generator source */
|
||||
#define DTRREQ 4 /* DTR/Request function */
|
||||
#define AUTOECHO 8 /* Auto Echo */
|
||||
#define LOOPBAK 0x10 /* Local loopback */
|
||||
#define SEARCH 0x20 /* Enter search mode */
|
||||
#define RMC 0x40 /* Reset missing clock */
|
||||
#define DISDPLL 0x60 /* Disable DPLL */
|
||||
#define SSBR 0x80 /* Set DPLL source = BR generator */
|
||||
#define SSRTxC 0xa0 /* Set DPLL source = RTxC */
|
||||
#define SFMM 0xc0 /* Set FM mode */
|
||||
#define SNRZI 0xe0 /* Set NRZI mode */
|
||||
|
||||
/* Write Register 15 (external/status interrupt control) */
|
||||
#define EN85C30 1 /* Enable some 85c30-enhanced registers */
|
||||
#define ZCIE 2 /* Zero count IE */
|
||||
#define ENSTFIFO 4 /* Enable status FIFO (SDLC) */
|
||||
#define DCDIE 8 /* DCD IE */
|
||||
#define SYNCIE 0x10 /* Sync/hunt IE */
|
||||
#define CTSIE 0x20 /* CTS IE */
|
||||
#define TxUIE 0x40 /* Tx Underrun/EOM IE */
|
||||
#define BRKIE 0x80 /* Break/Abort IE */
|
||||
|
||||
|
||||
/* Read Register 0 */
|
||||
#define Rx_CH_AV 0x1 /* Rx Character Available */
|
||||
#define ZCOUNT 0x2 /* Zero count */
|
||||
#define Tx_BUF_EMP 0x4 /* Tx Buffer empty */
|
||||
#define DCD 0x8 /* DCD */
|
||||
#define SYNC_HUNT 0x10 /* Sync/hunt */
|
||||
#define CTS 0x20 /* CTS */
|
||||
#define TxEOM 0x40 /* Tx underrun */
|
||||
#define BRK_ABRT 0x80 /* Break/Abort */
|
||||
|
||||
/* Read Register 1 */
|
||||
#define ALL_SNT 0x1 /* All sent */
|
||||
/* Residue Data for 8 Rx bits/char programmed */
|
||||
#define RES3 0x8 /* 0/3 */
|
||||
#define RES4 0x4 /* 0/4 */
|
||||
#define RES5 0xc /* 0/5 */
|
||||
#define RES6 0x2 /* 0/6 */
|
||||
#define RES7 0xa /* 0/7 */
|
||||
#define RES8 0x6 /* 0/8 */
|
||||
#define RES18 0xe /* 1/8 */
|
||||
#define RES28 0x0 /* 2/8 */
|
||||
/* Special Rx Condition Interrupts */
|
||||
#define PAR_ERR 0x10 /* Parity error */
|
||||
#define Rx_OVR 0x20 /* Rx Overrun Error */
|
||||
#define FRM_ERR 0x40 /* CRC/Framing Error */
|
||||
#define END_FR 0x80 /* End of Frame (SDLC) */
|
||||
|
||||
/* Read Register 2 (channel b only) - Interrupt vector */
|
||||
#define CHB_Tx_EMPTY 0x00
|
||||
#define CHB_EXT_STAT 0x02
|
||||
#define CHB_Rx_AVAIL 0x04
|
||||
#define CHB_SPECIAL 0x06
|
||||
#define CHA_Tx_EMPTY 0x08
|
||||
#define CHA_EXT_STAT 0x0a
|
||||
#define CHA_Rx_AVAIL 0x0c
|
||||
#define CHA_SPECIAL 0x0e
|
||||
#define STATUS_MASK 0x06
|
||||
|
||||
/* Read Register 3 (interrupt pending register) ch a only */
|
||||
#define CHBEXT 0x1 /* Channel B Ext/Stat IP */
|
||||
#define CHBTxIP 0x2 /* Channel B Tx IP */
|
||||
#define CHBRxIP 0x4 /* Channel B Rx IP */
|
||||
#define CHAEXT 0x8 /* Channel A Ext/Stat IP */
|
||||
#define CHATxIP 0x10 /* Channel A Tx IP */
|
||||
#define CHARxIP 0x20 /* Channel A Rx IP */
|
||||
|
||||
/* Read Register 8 (receive data register) */
|
||||
|
||||
/* Read Register 10 (misc status bits) */
|
||||
#define ONLOOP 2 /* On loop */
|
||||
#define LOOPSEND 0x10 /* Loop sending */
|
||||
#define CLK2MIS 0x40 /* Two clocks missing */
|
||||
#define CLK1MIS 0x80 /* One clock missing */
|
||||
|
||||
/* Read Register 12 (lower byte of baud rate generator constant) */
|
||||
|
||||
/* Read Register 13 (upper byte of baud rate generator constant) */
|
||||
|
||||
/* Read Register 15 (value of WR 15) */
|
||||
|
||||
/* Misc macros */
|
||||
#define ZS_CLEARERR(channel) (write_zsreg(channel, 0, ERR_RES))
|
||||
#define ZS_CLEARFIFO(channel) do { volatile unsigned char garbage; \
|
||||
garbage = read_zsdata(channel); \
|
||||
garbage = read_zsdata(channel); \
|
||||
garbage = read_zsdata(channel); \
|
||||
} while(0)
|
||||
|
||||
#endif /* !(_MACSERIAL_H) */
|
||||
851
drivers/macintosh/mediabay.c
Normal file
851
drivers/macintosh/mediabay.c
Normal file
|
|
@ -0,0 +1,851 @@
|
|||
/*
|
||||
* 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/config.h>
|
||||
#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 __pmac
|
||||
ohare_mb_content(struct media_bay_info *bay)
|
||||
{
|
||||
return (MB_IN32(bay, OHARE_MBCR) >> 12) & 7;
|
||||
}
|
||||
|
||||
static u8 __pmac
|
||||
heathrow_mb_content(struct media_bay_info *bay)
|
||||
{
|
||||
return (MB_IN32(bay, HEATHROW_MBCR) >> 12) & 7;
|
||||
}
|
||||
|
||||
static u8 __pmac
|
||||
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 __pmac
|
||||
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 __pmac
|
||||
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 __pmac
|
||||
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 __pmac
|
||||
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 __pmac
|
||||
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 __pmac
|
||||
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 __pmac
|
||||
ohare_mb_un_reset(struct media_bay_info* bay)
|
||||
{
|
||||
MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N);
|
||||
}
|
||||
|
||||
static void __pmac keylargo_mb_init(struct media_bay_info *bay)
|
||||
{
|
||||
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE);
|
||||
}
|
||||
|
||||
static void __pmac heathrow_mb_un_reset(struct media_bay_info* bay)
|
||||
{
|
||||
MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N);
|
||||
}
|
||||
|
||||
static void __pmac keylargo_mb_un_reset(struct media_bay_info* bay)
|
||||
{
|
||||
MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET);
|
||||
}
|
||||
|
||||
static void __pmac ohare_mb_un_reset_ide(struct media_bay_info* bay)
|
||||
{
|
||||
MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N);
|
||||
}
|
||||
|
||||
static void __pmac heathrow_mb_un_reset_ide(struct media_bay_info* bay)
|
||||
{
|
||||
MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N);
|
||||
}
|
||||
|
||||
static void __pmac keylargo_mb_un_reset_ide(struct media_bay_info* bay)
|
||||
{
|
||||
MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N);
|
||||
}
|
||||
|
||||
static inline void __pmac 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 __pmac 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 __pmac 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 __pmac 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 __pmac 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 __pmac 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 __pmac 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_match *match)
|
||||
{
|
||||
struct media_bay_info* bay;
|
||||
u32 __iomem *regbase;
|
||||
struct device_node *ofnode;
|
||||
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, we get the parent address for now (hrm...)
|
||||
*/
|
||||
regbase = (u32 __iomem *)
|
||||
ioremap(ofnode->parent->addrs[0].address, 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 __pmac media_bay_suspend(struct macio_dev *mdev, u32 state)
|
||||
{
|
||||
struct media_bay_info *bay = macio_get_drvdata(mdev);
|
||||
|
||||
if (state != mdev->ofdev.dev.power.power_state && state == PM_SUSPEND_MEM) {
|
||||
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 __pmac media_bay_resume(struct macio_dev *mdev)
|
||||
{
|
||||
struct media_bay_info *bay = macio_get_drvdata(mdev);
|
||||
|
||||
if (mdev->ofdev.dev.power.power_state != 0) {
|
||||
mdev->ofdev.dev.power.power_state = 0;
|
||||
|
||||
/* 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 __pmacdata = {
|
||||
.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 __pmacdata = {
|
||||
.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 __pmacdata = {
|
||||
.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_match media_bay_match[] =
|
||||
{
|
||||
{
|
||||
.name = "media-bay",
|
||||
.type = OF_ANY_MATCH,
|
||||
.compatible = "keylargo-media-bay",
|
||||
.data = &keylargo_mb_ops,
|
||||
},
|
||||
{
|
||||
.name = "media-bay",
|
||||
.type = OF_ANY_MATCH,
|
||||
.compatible = "heathrow-media-bay",
|
||||
.data = &heathrow_mb_ops,
|
||||
},
|
||||
{
|
||||
.name = "media-bay",
|
||||
.type = OF_ANY_MATCH,
|
||||
.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 != _MACH_Pmac)
|
||||
return -ENODEV;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
364
drivers/macintosh/smu.c
Normal file
364
drivers/macintosh/smu.c
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* PowerMac G5 SMU driver
|
||||
*
|
||||
* Copyright 2004 J. Mayer <l_indien@magic.fr>
|
||||
* Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
|
||||
*
|
||||
* Released under the term of the GNU GPL v2.
|
||||
*/
|
||||
|
||||
/*
|
||||
* For now, this driver includes:
|
||||
* - RTC get & set
|
||||
* - reboot & shutdown commands
|
||||
* all synchronous with IRQ disabled (ugh)
|
||||
*
|
||||
* TODO:
|
||||
* rework in a way the PMU driver works, that is asynchronous
|
||||
* with a queue of commands. I'll do that as soon as I have an
|
||||
* SMU based machine at hand. Some more cleanup is needed too,
|
||||
* like maybe fitting it into a platform device, etc...
|
||||
* Also check what's up with cache coherency, and if we really
|
||||
* can't do better than flushing the cache, maybe build a table
|
||||
* of command len/reply len like the PMU driver to only flush
|
||||
* what is actually necessary.
|
||||
* --BenH.
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/machdep.h>
|
||||
#include <asm/pmac_feature.h>
|
||||
#include <asm/smu.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/abs_addr.h>
|
||||
|
||||
#define DEBUG_SMU 1
|
||||
|
||||
#ifdef DEBUG_SMU
|
||||
#define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0)
|
||||
#else
|
||||
#define DPRINTK(fmt, args...) do { } while (0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This is the command buffer passed to the SMU hardware
|
||||
*/
|
||||
struct smu_cmd_buf {
|
||||
u8 cmd;
|
||||
u8 length;
|
||||
u8 data[0x0FFE];
|
||||
};
|
||||
|
||||
struct smu_device {
|
||||
spinlock_t lock;
|
||||
struct device_node *of_node;
|
||||
int db_ack; /* doorbell ack GPIO */
|
||||
int db_req; /* doorbell req GPIO */
|
||||
u32 __iomem *db_buf; /* doorbell buffer */
|
||||
struct smu_cmd_buf *cmd_buf; /* command buffer virtual */
|
||||
u32 cmd_buf_abs; /* command buffer absolute */
|
||||
};
|
||||
|
||||
/*
|
||||
* I don't think there will ever be more than one SMU, so
|
||||
* for now, just hard code that
|
||||
*/
|
||||
static struct smu_device *smu;
|
||||
|
||||
/*
|
||||
* SMU low level communication stuff
|
||||
*/
|
||||
static inline int smu_cmd_stat(struct smu_cmd_buf *cmd_buf, u8 cmd_ack)
|
||||
{
|
||||
rmb();
|
||||
return cmd_buf->cmd == cmd_ack && cmd_buf->length != 0;
|
||||
}
|
||||
|
||||
static inline u8 smu_save_ack_cmd(struct smu_cmd_buf *cmd_buf)
|
||||
{
|
||||
return (~cmd_buf->cmd) & 0xff;
|
||||
}
|
||||
|
||||
static void smu_send_cmd(struct smu_device *dev)
|
||||
{
|
||||
/* SMU command buf is currently cacheable, we need a physical
|
||||
* address. This isn't exactly a DMA mapping here, I suspect
|
||||
* the SMU is actually communicating with us via i2c to the
|
||||
* northbridge or the CPU to access RAM.
|
||||
*/
|
||||
writel(dev->cmd_buf_abs, dev->db_buf);
|
||||
|
||||
/* Ring the SMU doorbell */
|
||||
pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, dev->db_req, 4);
|
||||
pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, dev->db_req, 4);
|
||||
}
|
||||
|
||||
static int smu_cmd_done(struct smu_device *dev)
|
||||
{
|
||||
unsigned long wait = 0;
|
||||
int gpio;
|
||||
|
||||
/* Check the SMU doorbell */
|
||||
do {
|
||||
gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO,
|
||||
NULL, dev->db_ack);
|
||||
if ((gpio & 7) == 7)
|
||||
return 0;
|
||||
udelay(100);
|
||||
} while(++wait < 10000);
|
||||
|
||||
printk(KERN_ERR "SMU timeout !\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int smu_do_cmd(struct smu_device *dev)
|
||||
{
|
||||
int rc;
|
||||
u8 cmd_ack;
|
||||
|
||||
DPRINTK("SMU do_cmd %02x len=%d %02x\n",
|
||||
dev->cmd_buf->cmd, dev->cmd_buf->length,
|
||||
dev->cmd_buf->data[0]);
|
||||
|
||||
cmd_ack = smu_save_ack_cmd(dev->cmd_buf);
|
||||
|
||||
/* Clear cmd_buf cache lines */
|
||||
flush_inval_dcache_range((unsigned long)dev->cmd_buf,
|
||||
((unsigned long)dev->cmd_buf) +
|
||||
sizeof(struct smu_cmd_buf));
|
||||
smu_send_cmd(dev);
|
||||
rc = smu_cmd_done(dev);
|
||||
if (rc == 0)
|
||||
rc = smu_cmd_stat(dev->cmd_buf, cmd_ack) ? 0 : -1;
|
||||
|
||||
DPRINTK("SMU do_cmd %02x len=%d %02x => %d (%02x)\n",
|
||||
dev->cmd_buf->cmd, dev->cmd_buf->length,
|
||||
dev->cmd_buf->data[0], rc, cmd_ack);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* RTC low level commands */
|
||||
static inline int bcd2hex (int n)
|
||||
{
|
||||
return (((n & 0xf0) >> 4) * 10) + (n & 0xf);
|
||||
}
|
||||
|
||||
static inline int hex2bcd (int n)
|
||||
{
|
||||
return ((n / 10) << 4) + (n % 10);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline void smu_fill_set_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
|
||||
{
|
||||
cmd_buf->cmd = 0x8e;
|
||||
cmd_buf->length = 8;
|
||||
cmd_buf->data[0] = 0x00;
|
||||
memset(cmd_buf->data + 1, 0, 7);
|
||||
}
|
||||
|
||||
static inline void smu_fill_get_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
|
||||
{
|
||||
cmd_buf->cmd = 0x8e;
|
||||
cmd_buf->length = 1;
|
||||
cmd_buf->data[0] = 0x01;
|
||||
}
|
||||
|
||||
static inline void smu_fill_dis_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
|
||||
{
|
||||
cmd_buf->cmd = 0x8e;
|
||||
cmd_buf->length = 1;
|
||||
cmd_buf->data[0] = 0x02;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf,
|
||||
struct rtc_time *time)
|
||||
{
|
||||
cmd_buf->cmd = 0x8e;
|
||||
cmd_buf->length = 8;
|
||||
cmd_buf->data[0] = 0x80;
|
||||
cmd_buf->data[1] = hex2bcd(time->tm_sec);
|
||||
cmd_buf->data[2] = hex2bcd(time->tm_min);
|
||||
cmd_buf->data[3] = hex2bcd(time->tm_hour);
|
||||
cmd_buf->data[4] = time->tm_wday;
|
||||
cmd_buf->data[5] = hex2bcd(time->tm_mday);
|
||||
cmd_buf->data[6] = hex2bcd(time->tm_mon) + 1;
|
||||
cmd_buf->data[7] = hex2bcd(time->tm_year - 100);
|
||||
}
|
||||
|
||||
static inline void smu_fill_get_rtc_cmd(struct smu_cmd_buf *cmd_buf)
|
||||
{
|
||||
cmd_buf->cmd = 0x8e;
|
||||
cmd_buf->length = 1;
|
||||
cmd_buf->data[0] = 0x81;
|
||||
}
|
||||
|
||||
static void smu_parse_get_rtc_reply(struct smu_cmd_buf *cmd_buf,
|
||||
struct rtc_time *time)
|
||||
{
|
||||
time->tm_sec = bcd2hex(cmd_buf->data[0]);
|
||||
time->tm_min = bcd2hex(cmd_buf->data[1]);
|
||||
time->tm_hour = bcd2hex(cmd_buf->data[2]);
|
||||
time->tm_wday = bcd2hex(cmd_buf->data[3]);
|
||||
time->tm_mday = bcd2hex(cmd_buf->data[4]);
|
||||
time->tm_mon = bcd2hex(cmd_buf->data[5]) - 1;
|
||||
time->tm_year = bcd2hex(cmd_buf->data[6]) + 100;
|
||||
}
|
||||
|
||||
int smu_get_rtc_time(struct rtc_time *time)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
if (smu == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
memset(time, 0, sizeof(struct rtc_time));
|
||||
spin_lock_irqsave(&smu->lock, flags);
|
||||
smu_fill_get_rtc_cmd(smu->cmd_buf);
|
||||
rc = smu_do_cmd(smu);
|
||||
if (rc == 0)
|
||||
smu_parse_get_rtc_reply(smu->cmd_buf, time);
|
||||
spin_unlock_irqrestore(&smu->lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int smu_set_rtc_time(struct rtc_time *time)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
if (smu == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
spin_lock_irqsave(&smu->lock, flags);
|
||||
smu_fill_set_rtc_cmd(smu->cmd_buf, time);
|
||||
rc = smu_do_cmd(smu);
|
||||
spin_unlock_irqrestore(&smu->lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void smu_shutdown(void)
|
||||
{
|
||||
const unsigned char *command = "SHUTDOWN";
|
||||
unsigned long flags;
|
||||
|
||||
if (smu == NULL)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&smu->lock, flags);
|
||||
smu->cmd_buf->cmd = 0xaa;
|
||||
smu->cmd_buf->length = strlen(command);
|
||||
strcpy(smu->cmd_buf->data, command);
|
||||
smu_do_cmd(smu);
|
||||
for (;;)
|
||||
;
|
||||
spin_unlock_irqrestore(&smu->lock, flags);
|
||||
}
|
||||
|
||||
void smu_restart(void)
|
||||
{
|
||||
const unsigned char *command = "RESTART";
|
||||
unsigned long flags;
|
||||
|
||||
if (smu == NULL)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&smu->lock, flags);
|
||||
smu->cmd_buf->cmd = 0xaa;
|
||||
smu->cmd_buf->length = strlen(command);
|
||||
strcpy(smu->cmd_buf->data, command);
|
||||
smu_do_cmd(smu);
|
||||
for (;;)
|
||||
;
|
||||
spin_unlock_irqrestore(&smu->lock, flags);
|
||||
}
|
||||
|
||||
int smu_present(void)
|
||||
{
|
||||
return smu != NULL;
|
||||
}
|
||||
|
||||
|
||||
int smu_init (void)
|
||||
{
|
||||
struct device_node *np;
|
||||
u32 *data;
|
||||
|
||||
np = of_find_node_by_type(NULL, "smu");
|
||||
if (np == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
if (smu_cmdbuf_abs == 0) {
|
||||
printk(KERN_ERR "SMU: Command buffer not allocated !\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
smu = alloc_bootmem(sizeof(struct smu_device));
|
||||
if (smu == NULL)
|
||||
return -ENOMEM;
|
||||
memset(smu, 0, sizeof(*smu));
|
||||
|
||||
spin_lock_init(&smu->lock);
|
||||
smu->of_node = np;
|
||||
/* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a
|
||||
* 32 bits value safely
|
||||
*/
|
||||
smu->cmd_buf_abs = (u32)smu_cmdbuf_abs;
|
||||
smu->cmd_buf = (struct smu_cmd_buf *)abs_to_virt(smu_cmdbuf_abs);
|
||||
|
||||
np = of_find_node_by_name(NULL, "smu-doorbell");
|
||||
if (np == NULL) {
|
||||
printk(KERN_ERR "SMU: Can't find doorbell GPIO !\n");
|
||||
goto fail;
|
||||
}
|
||||
data = (u32 *)get_property(np, "reg", NULL);
|
||||
of_node_put(np);
|
||||
if (data == NULL) {
|
||||
printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Current setup has one doorbell GPIO that does both doorbell
|
||||
* and ack. GPIOs are at 0x50, best would be to find that out
|
||||
* in the device-tree though.
|
||||
*/
|
||||
smu->db_req = 0x50 + *data;
|
||||
smu->db_ack = 0x50 + *data;
|
||||
|
||||
/* Doorbell buffer is currently hard-coded, I didn't find a proper
|
||||
* device-tree entry giving the address. Best would probably to use
|
||||
* an offset for K2 base though, but let's do it that way for now.
|
||||
*/
|
||||
smu->db_buf = ioremap(0x8000860c, 0x1000);
|
||||
if (smu->db_buf == NULL) {
|
||||
printk(KERN_ERR "SMU: Can't map doorbell buffer pointer !\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
sys_ctrler = SYS_CTRLER_SMU;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
smu = NULL;
|
||||
return -ENXIO;
|
||||
|
||||
}
|
||||
612
drivers/macintosh/therm_adt746x.c
Normal file
612
drivers/macintosh/therm_adt746x.c
Normal file
|
|
@ -0,0 +1,612 @@
|
|||
/*
|
||||
* 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/config.h>
|
||||
#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 <asm/prom.h>
|
||||
#include <asm/machdep.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/of_device.h>
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#define CONFIG_REG 0x40
|
||||
#define MANUAL_MASK 0xe0
|
||||
#define AUTO_MASK 0x20
|
||||
|
||||
static u8 TEMP_REG[3] = {0x26, 0x25, 0x27}; /* local, cpu, gpu */
|
||||
static u8 LIMIT_REG[3] = {0x6b, 0x6a, 0x6c}; /* local, cpu, gpu */
|
||||
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, cpu, gpu */
|
||||
static u8 default_limits_chip[3] = {80, 65, 80}; /* local, cpu, gpu */
|
||||
|
||||
static int limit_adjust = 0;
|
||||
static int fan_speed = -1;
|
||||
|
||||
MODULE_AUTHOR("Colin Leroy <[email protected]>");
|
||||
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 cpu, 70 gpu) "
|
||||
"by N degrees.");
|
||||
|
||||
module_param(fan_speed, int, 0644);
|
||||
MODULE_PARM_DESC(fan_speed,"Specify starting fan speed (0-255) "
|
||||
"(default 64)");
|
||||
|
||||
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 = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "therm_adt746x",
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.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 (speed == -1)
|
||||
printk(KERN_DEBUG "adt746x: Setting speed to automatic "
|
||||
"for %s fan.\n", fan?"GPU":"CPU");
|
||||
else
|
||||
printk(KERN_DEBUG "adt746x: Setting speed to %d "
|
||||
"for %s fan.\n", speed, fan?"GPU":"CPU");
|
||||
} 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;
|
||||
|
||||
printk(KERN_DEBUG "adt746x: setting fans speed to %d "
|
||||
"(limit exceeded by %d on %s) \n",
|
||||
new_speed, var,
|
||||
fan_number?"GPU/pwr":"CPU");
|
||||
write_both_fan_speed(th, new_speed);
|
||||
th->last_var[fan_number] = var;
|
||||
} else if (var < -2) {
|
||||
/* don't stop fan if GPU/power is cold and CPU is not
|
||||
* so cold (lastvar >= -1) */
|
||||
if (i == 2 && lastvar < -1) {
|
||||
if (th->last_speed[fan_number] != 0)
|
||||
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 CPU is heating and GPU/power is not */
|
||||
}
|
||||
}
|
||||
|
||||
static int monitor_task(void *arg)
|
||||
{
|
||||
struct thermostat* th = arg;
|
||||
|
||||
while(!kthread_should_stop()) {
|
||||
if (current->flags & PF_FREEZE)
|
||||
refrigerator(PF_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 CPU 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...
|
||||
*
|
||||
*/
|
||||
#define BUILD_SHOW_FUNC_INT(name, data) \
|
||||
static ssize_t show_##name(struct device *dev, char *buf) \
|
||||
{ \
|
||||
return sprintf(buf, "%d\n", data); \
|
||||
}
|
||||
|
||||
#define BUILD_SHOW_FUNC_FAN(name, data) \
|
||||
static ssize_t show_##name(struct device *dev, 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, const char *buf, size_t n) \
|
||||
{ \
|
||||
int val; \
|
||||
int i; \
|
||||
val = simple_strtol(buf, NULL, 10); \
|
||||
printk(KERN_INFO "Adjusting limits by %d°C\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, 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(cpu_temperature, (read_reg(thermostat, TEMP_REG[1])))
|
||||
BUILD_SHOW_FUNC_INT(gpu_temperature, (read_reg(thermostat, TEMP_REG[2])))
|
||||
BUILD_SHOW_FUNC_INT(cpu_limit, thermostat->limits[1])
|
||||
BUILD_SHOW_FUNC_INT(gpu_limit, thermostat->limits[2])
|
||||
|
||||
BUILD_SHOW_FUNC_INT(specified_fan_speed, fan_speed)
|
||||
BUILD_SHOW_FUNC_FAN(cpu_fan_speed, 0)
|
||||
BUILD_SHOW_FUNC_FAN(gpu_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(cpu_temperature, S_IRUGO,
|
||||
show_cpu_temperature,NULL);
|
||||
static DEVICE_ATTR(gpu_temperature, S_IRUGO,
|
||||
show_gpu_temperature,NULL);
|
||||
static DEVICE_ATTR(cpu_limit, S_IRUGO,
|
||||
show_cpu_limit, NULL);
|
||||
static DEVICE_ATTR(gpu_limit, S_IRUGO,
|
||||
show_gpu_limit, 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(cpu_fan_speed, S_IRUGO,
|
||||
show_cpu_fan_speed, NULL);
|
||||
static DEVICE_ATTR(gpu_fan_speed, S_IRUGO,
|
||||
show_gpu_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;
|
||||
u32 *prop;
|
||||
|
||||
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 = (u32 *)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);
|
||||
|
||||
of_dev = of_platform_device_create(np, "temperatures");
|
||||
|
||||
if (of_dev == NULL) {
|
||||
printk(KERN_ERR "Can't register temperatures device !\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
device_create_file(&of_dev->dev, &dev_attr_cpu_temperature);
|
||||
device_create_file(&of_dev->dev, &dev_attr_gpu_temperature);
|
||||
device_create_file(&of_dev->dev, &dev_attr_cpu_limit);
|
||||
device_create_file(&of_dev->dev, &dev_attr_gpu_limit);
|
||||
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_cpu_fan_speed);
|
||||
if(therm_type == ADT7460)
|
||||
device_create_file(&of_dev->dev, &dev_attr_gpu_fan_speed);
|
||||
|
||||
#ifndef CONFIG_I2C_KEYWEST
|
||||
request_module("i2c-keywest");
|
||||
#endif
|
||||
|
||||
return i2c_add_driver(&thermostat_driver);
|
||||
}
|
||||
|
||||
static void __exit
|
||||
thermostat_exit(void)
|
||||
{
|
||||
if (of_dev) {
|
||||
device_remove_file(&of_dev->dev, &dev_attr_cpu_temperature);
|
||||
device_remove_file(&of_dev->dev, &dev_attr_gpu_temperature);
|
||||
device_remove_file(&of_dev->dev, &dev_attr_cpu_limit);
|
||||
device_remove_file(&of_dev->dev, &dev_attr_gpu_limit);
|
||||
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_cpu_fan_speed);
|
||||
|
||||
if(therm_type == ADT7460)
|
||||
device_remove_file(&of_dev->dev,
|
||||
&dev_attr_gpu_fan_speed);
|
||||
|
||||
of_device_unregister(of_dev);
|
||||
}
|
||||
i2c_del_driver(&thermostat_driver);
|
||||
}
|
||||
|
||||
module_init(thermostat_init);
|
||||
module_exit(thermostat_exit);
|
||||
2080
drivers/macintosh/therm_pm72.c
Normal file
2080
drivers/macintosh/therm_pm72.c
Normal file
File diff suppressed because it is too large
Load diff
297
drivers/macintosh/therm_pm72.h
Normal file
297
drivers/macintosh/therm_pm72.h
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#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 mecanism 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
|
||||
|
||||
/*
|
||||
* 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 50 /* 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 0x6553600
|
||||
#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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* 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;
|
||||
};
|
||||
|
||||
/*
|
||||
* Driver state
|
||||
*/
|
||||
enum {
|
||||
state_detached,
|
||||
state_attaching,
|
||||
state_attached,
|
||||
state_detaching,
|
||||
};
|
||||
|
||||
|
||||
#endif /* __THERM_PMAC_7_2_H__ */
|
||||
531
drivers/macintosh/therm_windtunnel.c
Normal file
531
drivers/macintosh/therm_windtunnel.c
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
/*
|
||||
* 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/config.h>
|
||||
#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_device.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[] = { 0x49, 0x2c, I2C_CLIENT_END };
|
||||
static unsigned short normal_i2c_range[] = { 0x48, 0x4f, 0x2c, 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, 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, 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 = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "therm_windtunnel",
|
||||
.id = I2C_DRIVERID_G4FAN,
|
||||
.flags = I2C_DF_NOTIFY,
|
||||
.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_match *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_match therm_of_match[] = {{
|
||||
.name = "fan",
|
||||
.type = OF_ANY_MATCH,
|
||||
.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 )
|
||||
{
|
||||
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 = (struct apple_thermal_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" );
|
||||
of_node_put( np );
|
||||
|
||||
if( !x.of_dev ) {
|
||||
printk(KERN_ERR "Can't register fan controller!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
of_register_driver( &therm_of_driver );
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit
|
||||
g4fan_exit( void )
|
||||
{
|
||||
of_unregister_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 <[email protected]>");
|
||||
MODULE_DESCRIPTION("Apple G4 (windtunnel) fan controller");
|
||||
MODULE_LICENSE("GPL");
|
||||
628
drivers/macintosh/via-cuda.c
Normal file
628
drivers/macintosh/via-cuda.c
Normal file
|
|
@ -0,0 +1,628 @@
|
|||
/*
|
||||
* 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/config.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/sched.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);
|
||||
|
||||
#ifdef CONFIG_MAC
|
||||
#define CUDA_IRQ IRQ_MAC_ADB
|
||||
#define __openfirmware
|
||||
#define eieio()
|
||||
#else
|
||||
#define CUDA_IRQ vias->intrs[0].line
|
||||
#endif
|
||||
|
||||
/* 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 = 0;
|
||||
|
||||
#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, struct pt_regs *regs);
|
||||
static void cuda_input(unsigned char *buf, int nb, struct pt_regs *regs);
|
||||
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)
|
||||
{
|
||||
int err;
|
||||
struct adb_request req;
|
||||
|
||||
if (vias != 0)
|
||||
return 1;
|
||||
vias = find_devices("via-cuda");
|
||||
if (vias == 0)
|
||||
return 0;
|
||||
if (vias->next != 0)
|
||||
printk(KERN_WARNING "Warning: only using 1st via-cuda\n");
|
||||
|
||||
#if 0
|
||||
{ int i;
|
||||
|
||||
printk("find_via_cuda: node = %p, addrs =", vias->node);
|
||||
for (i = 0; i < vias->n_addrs; ++i)
|
||||
printk(" %x(%x)", vias->addrs[i].address, vias->addrs[i].size);
|
||||
printk(", intrs =");
|
||||
for (i = 0; i < vias->n_intrs; ++i)
|
||||
printk(" %x", vias->intrs[i].line);
|
||||
printk("\n"); }
|
||||
#endif
|
||||
|
||||
if (vias->n_addrs != 1 || vias->n_intrs != 1) {
|
||||
printk(KERN_ERR "via-cuda: expecting 1 address (%d) and 1 interrupt (%d)\n",
|
||||
vias->n_addrs, vias->n_intrs);
|
||||
if (vias->n_addrs < 1 || vias->n_intrs < 1)
|
||||
return 0;
|
||||
}
|
||||
via = ioremap(vias->addrs->address, 0x2000);
|
||||
|
||||
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;
|
||||
}
|
||||
#endif /* CONFIG_PPC */
|
||||
|
||||
static int __init via_cuda_start(void)
|
||||
{
|
||||
if (via == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
#ifdef CONFIG_PPC
|
||||
request_OF_resource(vias, 0, NULL);
|
||||
#endif
|
||||
|
||||
if (request_irq(CUDA_IRQ, cuda_interrupt, 0, "ADB", cuda_interrupt)) {
|
||||
printk(KERN_ERR "cuda_init: can't get irq %d\n", CUDA_IRQ);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
printk("Macintosh CUDA driver v0.5 for Unified ADB.\n");
|
||||
|
||||
cuda_fully_inited = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(via_cuda_start);
|
||||
|
||||
#ifdef CONFIG_ADB
|
||||
static int
|
||||
cuda_probe(void)
|
||||
{
|
||||
#ifdef CONFIG_PPC
|
||||
if (sys_ctrler != SYS_CTRLER_CUDA)
|
||||
return -ENODEV;
|
||||
#else
|
||||
if (macintosh_config->adb_type != MAC_ADB_CUDA)
|
||||
return -ENODEV;
|
||||
via = via1;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init
|
||||
cuda_init(void)
|
||||
{
|
||||
#ifdef CONFIG_PPC
|
||||
if (via == NULL)
|
||||
return -ENODEV;
|
||||
return 0;
|
||||
#else
|
||||
int err = cuda_init_via();
|
||||
if (err) {
|
||||
printk(KERN_ERR "cuda_init_via() failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
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, NULL);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static irqreturn_t
|
||||
cuda_interrupt(int irq, void *arg, struct pt_regs *regs)
|
||||
{
|
||||
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, regs);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void
|
||||
cuda_input(unsigned char *buf, int nb, struct pt_regs *regs)
|
||||
{
|
||||
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, regs, 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");
|
||||
}
|
||||
}
|
||||
653
drivers/macintosh/via-macii.c
Normal file
653
drivers/macintosh/via-macii.c
Normal file
|
|
@ -0,0 +1,653 @@
|
|||
/*
|
||||
* 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/sched.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, struct pt_regs *regs);
|
||||
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 = 0;
|
||||
static int command_byte = 0;
|
||||
static int last_reply = 0;
|
||||
static int last_active = 0;
|
||||
|
||||
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 = 0;
|
||||
|
||||
/* 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 = 0;
|
||||
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, 0, 0);
|
||||
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, struct pt_regs *regs)
|
||||
{
|
||||
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,
|
||||
regs, 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;
|
||||
}
|
||||
661
drivers/macintosh/via-maciisi.c
Normal file
661
drivers/macintosh/via-maciisi.c
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
/*
|
||||
* 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/sched.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 = NULL;
|
||||
static struct adb_request* last_req = NULL;
|
||||
static unsigned char maciisi_rbuf[16];
|
||||
static unsigned char *reply_ptr = NULL;
|
||||
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, struct pt_regs* regs);
|
||||
static void maciisi_input(unsigned char *buf, int nb, struct pt_regs *regs);
|
||||
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");
|
||||
}
|
||||
|
||||
/* 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 = 0;
|
||||
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, 0, 0);
|
||||
}
|
||||
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, struct pt_regs* regs)
|
||||
{
|
||||
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, regs);
|
||||
}
|
||||
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, struct pt_regs *regs)
|
||||
{
|
||||
#ifdef DEBUG_MACIISI_ADB
|
||||
int i;
|
||||
#endif
|
||||
|
||||
switch (buf[0]) {
|
||||
case ADB_PACKET:
|
||||
adb_input(buf+2, nb-2, regs, 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;
|
||||
}
|
||||
}
|
||||
3147
drivers/macintosh/via-pmu.c
Normal file
3147
drivers/macintosh/via-pmu.c
Normal file
File diff suppressed because it is too large
Load diff
1063
drivers/macintosh/via-pmu68k.c
Normal file
1063
drivers/macintosh/via-pmu68k.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue