diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/macintosh | |
download | lwn-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz lwn-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip |
Linux-2.6.12-rc2v2.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!
Diffstat (limited to 'drivers/macintosh')
24 files changed, 18915 insertions, 0 deletions
diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig new file mode 100644 index 000000000000..8a7117a08cf0 --- /dev/null +++ b/drivers/macintosh/Kconfig @@ -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 diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile new file mode 100644 index 000000000000..c3a4705a8295 --- /dev/null +++ b/drivers/macintosh/Makefile @@ -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 diff --git a/drivers/macintosh/adb-iop.c b/drivers/macintosh/adb-iop.c new file mode 100644 index 000000000000..71aeb912ec61 --- /dev/null +++ b/drivers/macintosh/adb-iop.c @@ -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; +} diff --git a/drivers/macintosh/adb.c b/drivers/macintosh/adb.c new file mode 100644 index 000000000000..7297c77f99cf --- /dev/null +++ b/drivers/macintosh/adb.c @@ -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"); +} diff --git a/drivers/macintosh/adbhid.c b/drivers/macintosh/adbhid.c new file mode 100644 index 000000000000..8f93d01d8928 --- /dev/null +++ b/drivers/macintosh/adbhid.c @@ -0,0 +1,1177 @@ +/* + * drivers/input/adbhid.c + * + * ADB HID driver for Power Macintosh computers. + * + * Adapted from drivers/macintosh/mac_keyb.c by Franz Sirl. + * drivers/macintosh/mac_keyb.c was Copyright (C) 1996 Paul Mackerras + * with considerable contributions from Ben Herrenschmidt and others. + * + * Copyright (C) 2000 Franz Sirl. + * + * Adapted to ADB changes and support for more devices by + * Benjamin Herrenschmidt. Adapted from code in MkLinux + * and reworked. + * + * Supported devices: + * + * - Standard 1 button mouse + * - All standard Apple Extended protocol (handler ID 4) + * - mouseman and trackman mice & trackballs + * - PowerBook Trackpad (default setup: enable tapping) + * - MicroSpeed mouse & trackball (needs testing) + * - CH Products Trackball Pro (needs testing) + * - Contour Design (Contour Mouse) + * - Hunter digital (NoHandsMouse) + * - Kensignton TurboMouse 5 (needs testing) + * - Mouse Systems A3 mice and trackballs <aidan@kublai.com> + * - MacAlly 2-buttons mouse (needs testing) <pochini@denise.shiny.it> + * + * To do: + * + * Improve Kensington support. + * Split mouse/kbd + * Move to syfs + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/notifier.h> +#include <linux/input.h> + +#include <linux/adb.h> +#include <linux/cuda.h> +#include <linux/pmu.h> + +#include <asm/machdep.h> +#ifdef CONFIG_PPC_PMAC +#include <asm/pmac_feature.h> +#endif + +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif + +MODULE_AUTHOR("Franz Sirl <Franz.Sirl-kernel@lauterbach.com>"); + +#define KEYB_KEYREG 0 /* register # for key up/down data */ +#define KEYB_LEDREG 2 /* register # for leds on ADB keyboard */ +#define MOUSE_DATAREG 0 /* reg# for movement/button codes from mouse */ + +static int adb_message_handler(struct notifier_block *, unsigned long, void *); +static struct notifier_block adbhid_adb_notifier = { + .notifier_call = adb_message_handler, +}; + +/* Some special keys */ +#define ADB_KEY_DEL 0x33 +#define ADB_KEY_CMD 0x37 +#define ADB_KEY_CAPSLOCK 0x39 +#define ADB_KEY_FN 0x3f +#define ADB_KEY_FWDEL 0x75 +#define ADB_KEY_POWER_OLD 0x7e +#define ADB_KEY_POWER 0x7f + +u8 adb_to_linux_keycodes[128] = { + /* 0x00 */ KEY_A, /* 30 */ + /* 0x01 */ KEY_S, /* 31 */ + /* 0x02 */ KEY_D, /* 32 */ + /* 0x03 */ KEY_F, /* 33 */ + /* 0x04 */ KEY_H, /* 35 */ + /* 0x05 */ KEY_G, /* 34 */ + /* 0x06 */ KEY_Z, /* 44 */ + /* 0x07 */ KEY_X, /* 45 */ + /* 0x08 */ KEY_C, /* 46 */ + /* 0x09 */ KEY_V, /* 47 */ + /* 0x0a */ KEY_102ND, /* 86 */ + /* 0x0b */ KEY_B, /* 48 */ + /* 0x0c */ KEY_Q, /* 16 */ + /* 0x0d */ KEY_W, /* 17 */ + /* 0x0e */ KEY_E, /* 18 */ + /* 0x0f */ KEY_R, /* 19 */ + /* 0x10 */ KEY_Y, /* 21 */ + /* 0x11 */ KEY_T, /* 20 */ + /* 0x12 */ KEY_1, /* 2 */ + /* 0x13 */ KEY_2, /* 3 */ + /* 0x14 */ KEY_3, /* 4 */ + /* 0x15 */ KEY_4, /* 5 */ + /* 0x16 */ KEY_6, /* 7 */ + /* 0x17 */ KEY_5, /* 6 */ + /* 0x18 */ KEY_EQUAL, /* 13 */ + /* 0x19 */ KEY_9, /* 10 */ + /* 0x1a */ KEY_7, /* 8 */ + /* 0x1b */ KEY_MINUS, /* 12 */ + /* 0x1c */ KEY_8, /* 9 */ + /* 0x1d */ KEY_0, /* 11 */ + /* 0x1e */ KEY_RIGHTBRACE, /* 27 */ + /* 0x1f */ KEY_O, /* 24 */ + /* 0x20 */ KEY_U, /* 22 */ + /* 0x21 */ KEY_LEFTBRACE, /* 26 */ + /* 0x22 */ KEY_I, /* 23 */ + /* 0x23 */ KEY_P, /* 25 */ + /* 0x24 */ KEY_ENTER, /* 28 */ + /* 0x25 */ KEY_L, /* 38 */ + /* 0x26 */ KEY_J, /* 36 */ + /* 0x27 */ KEY_APOSTROPHE, /* 40 */ + /* 0x28 */ KEY_K, /* 37 */ + /* 0x29 */ KEY_SEMICOLON, /* 39 */ + /* 0x2a */ KEY_BACKSLASH, /* 43 */ + /* 0x2b */ KEY_COMMA, /* 51 */ + /* 0x2c */ KEY_SLASH, /* 53 */ + /* 0x2d */ KEY_N, /* 49 */ + /* 0x2e */ KEY_M, /* 50 */ + /* 0x2f */ KEY_DOT, /* 52 */ + /* 0x30 */ KEY_TAB, /* 15 */ + /* 0x31 */ KEY_SPACE, /* 57 */ + /* 0x32 */ KEY_GRAVE, /* 41 */ + /* 0x33 */ KEY_BACKSPACE, /* 14 */ + /* 0x34 */ KEY_KPENTER, /* 96 */ + /* 0x35 */ KEY_ESC, /* 1 */ + /* 0x36 */ KEY_LEFTCTRL, /* 29 */ + /* 0x37 */ KEY_LEFTMETA, /* 125 */ + /* 0x38 */ KEY_LEFTSHIFT, /* 42 */ + /* 0x39 */ KEY_CAPSLOCK, /* 58 */ + /* 0x3a */ KEY_LEFTALT, /* 56 */ + /* 0x3b */ KEY_LEFT, /* 105 */ + /* 0x3c */ KEY_RIGHT, /* 106 */ + /* 0x3d */ KEY_DOWN, /* 108 */ + /* 0x3e */ KEY_UP, /* 103 */ + /* 0x3f */ 0, + /* 0x40 */ 0, + /* 0x41 */ KEY_KPDOT, /* 83 */ + /* 0x42 */ 0, + /* 0x43 */ KEY_KPASTERISK, /* 55 */ + /* 0x44 */ 0, + /* 0x45 */ KEY_KPPLUS, /* 78 */ + /* 0x46 */ 0, + /* 0x47 */ KEY_NUMLOCK, /* 69 */ + /* 0x48 */ 0, + /* 0x49 */ 0, + /* 0x4a */ 0, + /* 0x4b */ KEY_KPSLASH, /* 98 */ + /* 0x4c */ KEY_KPENTER, /* 96 */ + /* 0x4d */ 0, + /* 0x4e */ KEY_KPMINUS, /* 74 */ + /* 0x4f */ 0, + /* 0x50 */ 0, + /* 0x51 */ KEY_KPEQUAL, /* 117 */ + /* 0x52 */ KEY_KP0, /* 82 */ + /* 0x53 */ KEY_KP1, /* 79 */ + /* 0x54 */ KEY_KP2, /* 80 */ + /* 0x55 */ KEY_KP3, /* 81 */ + /* 0x56 */ KEY_KP4, /* 75 */ + /* 0x57 */ KEY_KP5, /* 76 */ + /* 0x58 */ KEY_KP6, /* 77 */ + /* 0x59 */ KEY_KP7, /* 71 */ + /* 0x5a */ 0, + /* 0x5b */ KEY_KP8, /* 72 */ + /* 0x5c */ KEY_KP9, /* 73 */ + /* 0x5d */ KEY_YEN, /* 124 */ + /* 0x5e */ KEY_RO, /* 89 */ + /* 0x5f */ KEY_KPCOMMA, /* 121 */ + /* 0x60 */ KEY_F5, /* 63 */ + /* 0x61 */ KEY_F6, /* 64 */ + /* 0x62 */ KEY_F7, /* 65 */ + /* 0x63 */ KEY_F3, /* 61 */ + /* 0x64 */ KEY_F8, /* 66 */ + /* 0x65 */ KEY_F9, /* 67 */ + /* 0x66 */ KEY_HANJA, /* 123 */ + /* 0x67 */ KEY_F11, /* 87 */ + /* 0x68 */ KEY_HANGUEL, /* 122 */ + /* 0x69 */ KEY_SYSRQ, /* 99 */ + /* 0x6a */ 0, + /* 0x6b */ KEY_SCROLLLOCK, /* 70 */ + /* 0x6c */ 0, + /* 0x6d */ KEY_F10, /* 68 */ + /* 0x6e */ KEY_COMPOSE, /* 127 */ + /* 0x6f */ KEY_F12, /* 88 */ + /* 0x70 */ 0, + /* 0x71 */ KEY_PAUSE, /* 119 */ + /* 0x72 */ KEY_INSERT, /* 110 */ + /* 0x73 */ KEY_HOME, /* 102 */ + /* 0x74 */ KEY_PAGEUP, /* 104 */ + /* 0x75 */ KEY_DELETE, /* 111 */ + /* 0x76 */ KEY_F4, /* 62 */ + /* 0x77 */ KEY_END, /* 107 */ + /* 0x78 */ KEY_F2, /* 60 */ + /* 0x79 */ KEY_PAGEDOWN, /* 109 */ + /* 0x7a */ KEY_F1, /* 59 */ + /* 0x7b */ KEY_RIGHTSHIFT, /* 54 */ + /* 0x7c */ KEY_RIGHTALT, /* 100 */ + /* 0x7d */ KEY_RIGHTCTRL, /* 97 */ + /* 0x7e */ KEY_RIGHTMETA, /* 126 */ + /* 0x7f */ KEY_POWER, /* 116 */ +}; + +struct adbhid { + struct input_dev input; + int id; + int default_id; + int original_handler_id; + int current_handler_id; + int mouse_kind; + unsigned char *keycode; + char name[64]; + char phys[32]; + int flags; +}; + +#define FLAG_FN_KEY_PRESSED 0x00000001 +#define FLAG_POWER_FROM_FN 0x00000002 +#define FLAG_EMU_FWDEL_DOWN 0x00000004 + +static struct adbhid *adbhid[16]; + +static void adbhid_probe(void); + +static void adbhid_input_keycode(int, int, int, struct pt_regs *); + +static void init_trackpad(int id); +static void init_trackball(int id); +static void init_turbomouse(int id); +static void init_microspeed(int id); +static void init_ms_a3(int id); + +static struct adb_ids keyboard_ids; +static struct adb_ids mouse_ids; +static struct adb_ids buttons_ids; + +#ifdef CONFIG_PMAC_BACKLIGHT +/* Exported to via-pmu.c */ +int disable_kernel_backlight = 0; +#endif /* CONFIG_PMAC_BACKLIGHT */ + +/* Kind of keyboard, see Apple technote 1152 */ +#define ADB_KEYBOARD_UNKNOWN 0 +#define ADB_KEYBOARD_ANSI 0x0100 +#define ADB_KEYBOARD_ISO 0x0200 +#define ADB_KEYBOARD_JIS 0x0300 + +/* Kind of mouse */ +#define ADBMOUSE_STANDARD_100 0 /* Standard 100cpi mouse (handler 1) */ +#define ADBMOUSE_STANDARD_200 1 /* Standard 200cpi mouse (handler 2) */ +#define ADBMOUSE_EXTENDED 2 /* Apple Extended mouse (handler 4) */ +#define ADBMOUSE_TRACKBALL 3 /* TrackBall (handler 4) */ +#define ADBMOUSE_TRACKPAD 4 /* Apple's PowerBook trackpad (handler 4) */ +#define ADBMOUSE_TURBOMOUSE5 5 /* Turbomouse 5 (previously req. mousehack) */ +#define ADBMOUSE_MICROSPEED 6 /* Microspeed mouse (&trackball ?), MacPoint */ +#define ADBMOUSE_TRACKBALLPRO 7 /* Trackball Pro (special buttons) */ +#define ADBMOUSE_MS_A3 8 /* Mouse systems A3 trackball (handler 3) */ +#define ADBMOUSE_MACALLY2 9 /* MacAlly 2-button mouse */ + +static void +adbhid_keyboard_input(unsigned char *data, int nb, struct pt_regs *regs, int apoll) +{ + int id = (data[0] >> 4) & 0x0f; + + if (!adbhid[id]) { + printk(KERN_ERR "ADB HID on ID %d not yet registered, packet %#02x, %#02x, %#02x, %#02x\n", + id, data[0], data[1], data[2], data[3]); + return; + } + + /* first check this is from register 0 */ + if (nb != 3 || (data[0] & 3) != KEYB_KEYREG) + return; /* ignore it */ + adbhid_input_keycode(id, data[1], 0, regs); + if (!(data[2] == 0xff || (data[2] == 0x7f && data[1] == 0x7f))) + adbhid_input_keycode(id, data[2], 0, regs); +} + +static void +adbhid_input_keycode(int id, int keycode, int repeat, struct pt_regs *regs) +{ + struct adbhid *ahid = adbhid[id]; + int up_flag; + + up_flag = (keycode & 0x80); + keycode &= 0x7f; + + switch (keycode) { + case ADB_KEY_CAPSLOCK: /* Generate down/up events for CapsLock everytime. */ + input_regs(&ahid->input, regs); + input_report_key(&ahid->input, KEY_CAPSLOCK, 1); + input_report_key(&ahid->input, KEY_CAPSLOCK, 0); + input_sync(&ahid->input); + return; +#ifdef CONFIG_PPC_PMAC + case ADB_KEY_POWER_OLD: /* Power key on PBook 3400 needs remapping */ + switch(pmac_call_feature(PMAC_FTR_GET_MB_INFO, + NULL, PMAC_MB_INFO_MODEL, 0)) { + case PMAC_TYPE_COMET: + case PMAC_TYPE_HOOPER: + case PMAC_TYPE_KANGA: + keycode = ADB_KEY_POWER; + } + break; + case ADB_KEY_POWER: + /* Fn + Command will produce a bogus "power" keycode */ + if (ahid->flags & FLAG_FN_KEY_PRESSED) { + keycode = ADB_KEY_CMD; + if (up_flag) + ahid->flags &= ~FLAG_POWER_FROM_FN; + else + ahid->flags |= FLAG_POWER_FROM_FN; + } else if (ahid->flags & FLAG_POWER_FROM_FN) { + keycode = ADB_KEY_CMD; + ahid->flags &= ~FLAG_POWER_FROM_FN; + } + break; + case ADB_KEY_FN: + /* Keep track of the Fn key state */ + if (up_flag) { + ahid->flags &= ~FLAG_FN_KEY_PRESSED; + /* Emulate Fn+delete = forward delete */ + if (ahid->flags & FLAG_EMU_FWDEL_DOWN) { + ahid->flags &= ~FLAG_EMU_FWDEL_DOWN; + keycode = ADB_KEY_FWDEL; + break; + } + } else + ahid->flags |= FLAG_FN_KEY_PRESSED; + /* Swallow the key press */ + return; + case ADB_KEY_DEL: + /* Emulate Fn+delete = forward delete */ + if (ahid->flags & FLAG_FN_KEY_PRESSED) { + keycode = ADB_KEY_FWDEL; + if (up_flag) + ahid->flags &= ~FLAG_EMU_FWDEL_DOWN; + else + ahid->flags |= FLAG_EMU_FWDEL_DOWN; + } + break; +#endif /* CONFIG_PPC_PMAC */ + } + + if (adbhid[id]->keycode[keycode]) { + input_regs(&adbhid[id]->input, regs); + input_report_key(&adbhid[id]->input, + adbhid[id]->keycode[keycode], !up_flag); + input_sync(&adbhid[id]->input); + } else + printk(KERN_INFO "Unhandled ADB key (scancode %#02x) %s.\n", keycode, + up_flag ? "released" : "pressed"); + +} + +static void +adbhid_mouse_input(unsigned char *data, int nb, struct pt_regs *regs, int autopoll) +{ + int id = (data[0] >> 4) & 0x0f; + + if (!adbhid[id]) { + printk(KERN_ERR "ADB HID on ID %d not yet registered\n", id); + return; + } + + /* + Handler 1 -- 100cpi original Apple mouse protocol. + Handler 2 -- 200cpi original Apple mouse protocol. + + For Apple's standard one-button mouse protocol the data array will + contain the following values: + + BITS COMMENTS + data[0] = dddd 1100 ADB command: Talk, register 0, for device dddd. + data[1] = bxxx xxxx First button and x-axis motion. + data[2] = byyy yyyy Second button and y-axis motion. + + Handler 4 -- Apple Extended mouse protocol. + + For Apple's 3-button mouse protocol the data array will contain the + following values: + + BITS COMMENTS + data[0] = dddd 1100 ADB command: Talk, register 0, for device dddd. + data[1] = bxxx xxxx Left button and x-axis motion. + data[2] = byyy yyyy Second button and y-axis motion. + data[3] = byyy bxxx Third button and fourth button. Y is additional + high bits of y-axis motion. XY is additional + high bits of x-axis motion. + + MacAlly 2-button mouse protocol. + + For MacAlly 2-button mouse protocol the data array will contain the + following values: + + BITS COMMENTS + data[0] = dddd 1100 ADB command: Talk, register 0, for device dddd. + data[1] = bxxx xxxx Left button and x-axis motion. + data[2] = byyy yyyy Right button and y-axis motion. + data[3] = ???? ???? unknown + data[4] = ???? ???? unknown + + */ + + /* If it's a trackpad, we alias the second button to the first. + NOTE: Apple sends an ADB flush command to the trackpad when + the first (the real) button is released. We could do + this here using async flush requests. + */ + switch (adbhid[id]->mouse_kind) + { + case ADBMOUSE_TRACKPAD: + data[1] = (data[1] & 0x7f) | ((data[1] & data[2]) & 0x80); + data[2] = data[2] | 0x80; + break; + case ADBMOUSE_MICROSPEED: + data[1] = (data[1] & 0x7f) | ((data[3] & 0x01) << 7); + data[2] = (data[2] & 0x7f) | ((data[3] & 0x02) << 6); + data[3] = (data[3] & 0x77) | ((data[3] & 0x04) << 5) + | (data[3] & 0x08); + break; + case ADBMOUSE_TRACKBALLPRO: + data[1] = (data[1] & 0x7f) | (((data[3] & 0x04) << 5) + & ((data[3] & 0x08) << 4)); + data[2] = (data[2] & 0x7f) | ((data[3] & 0x01) << 7); + data[3] = (data[3] & 0x77) | ((data[3] & 0x02) << 6); + break; + case ADBMOUSE_MS_A3: + data[1] = (data[1] & 0x7f) | ((data[3] & 0x01) << 7); + data[2] = (data[2] & 0x7f) | ((data[3] & 0x02) << 6); + data[3] = ((data[3] & 0x04) << 5); + break; + case ADBMOUSE_MACALLY2: + data[3] = (data[2] & 0x80) ? 0x80 : 0x00; + data[2] |= 0x80; /* Right button is mapped as button 3 */ + nb=4; + break; + } + + input_regs(&adbhid[id]->input, regs); + + input_report_key(&adbhid[id]->input, BTN_LEFT, !((data[1] >> 7) & 1)); + input_report_key(&adbhid[id]->input, BTN_MIDDLE, !((data[2] >> 7) & 1)); + + if (nb >= 4 && adbhid[id]->mouse_kind != ADBMOUSE_TRACKPAD) + input_report_key(&adbhid[id]->input, BTN_RIGHT, !((data[3] >> 7) & 1)); + + input_report_rel(&adbhid[id]->input, REL_X, + ((data[2]&0x7f) < 64 ? (data[2]&0x7f) : (data[2]&0x7f)-128 )); + input_report_rel(&adbhid[id]->input, REL_Y, + ((data[1]&0x7f) < 64 ? (data[1]&0x7f) : (data[1]&0x7f)-128 )); + + input_sync(&adbhid[id]->input); +} + +static void +adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int autopoll) +{ + int id = (data[0] >> 4) & 0x0f; + + if (!adbhid[id]) { + printk(KERN_ERR "ADB HID on ID %d not yet registered\n", id); + return; + } + + input_regs(&adbhid[id]->input, regs); + + switch (adbhid[id]->original_handler_id) { + default: + case 0x02: /* Adjustable keyboard button device */ + { + int down = (data[1] == (data[1] & 0xf)); + + switch (data[1] & 0x0f) { + case 0x0: /* microphone */ + input_report_key(&adbhid[id]->input, KEY_SOUND, down); + break; + + case 0x1: /* mute */ + input_report_key(&adbhid[id]->input, KEY_MUTE, down); + break; + + case 0x2: /* volume decrease */ + input_report_key(&adbhid[id]->input, KEY_VOLUMEDOWN, down); + break; + + case 0x3: /* volume increase */ + input_report_key(&adbhid[id]->input, KEY_VOLUMEUP, down); + break; + + default: + printk(KERN_INFO "Unhandled ADB_MISC event %02x, %02x, %02x, %02x\n", + data[0], data[1], data[2], data[3]); + break; + } + } + break; + + case 0x1f: /* Powerbook button device */ + { + int down = (data[1] == (data[1] & 0xf)); +#ifdef CONFIG_PMAC_BACKLIGHT + int backlight = get_backlight_level(); +#endif + /* + * XXX: Where is the contrast control for the passive? + * -- Cort + */ + + switch (data[1] & 0x0f) { + case 0x8: /* mute */ + input_report_key(&adbhid[id]->input, KEY_MUTE, down); + break; + + case 0x7: /* volume decrease */ + input_report_key(&adbhid[id]->input, KEY_VOLUMEDOWN, down); + break; + + case 0x6: /* volume increase */ + input_report_key(&adbhid[id]->input, KEY_VOLUMEUP, down); + break; + + case 0xb: /* eject */ + input_report_key(&adbhid[id]->input, KEY_EJECTCD, down); + break; + + case 0xa: /* brightness decrease */ +#ifdef CONFIG_PMAC_BACKLIGHT + if (!disable_kernel_backlight) { + if (down && backlight >= 0) { + if (backlight > BACKLIGHT_OFF) + set_backlight_level(backlight-1); + else + set_backlight_level(BACKLIGHT_OFF); + } + } +#endif /* CONFIG_PMAC_BACKLIGHT */ + input_report_key(&adbhid[id]->input, KEY_BRIGHTNESSDOWN, down); + break; + + case 0x9: /* brightness increase */ +#ifdef CONFIG_PMAC_BACKLIGHT + if (!disable_kernel_backlight) { + if (down && backlight >= 0) { + if (backlight < BACKLIGHT_MAX) + set_backlight_level(backlight+1); + else + set_backlight_level(BACKLIGHT_MAX); + } + } +#endif /* CONFIG_PMAC_BACKLIGHT */ + input_report_key(&adbhid[id]->input, KEY_BRIGHTNESSUP, down); + break; + } + } + break; + } + + input_sync(&adbhid[id]->input); +} + +static struct adb_request led_request; +static int leds_pending[16]; +static int leds_req_pending; +static int pending_devs[16]; +static int pending_led_start=0; +static int pending_led_end=0; +static DEFINE_SPINLOCK(leds_lock); + +static void leds_done(struct adb_request *req) +{ + int leds = 0, device = 0, pending = 0; + unsigned long flags; + + spin_lock_irqsave(&leds_lock, flags); + + if (pending_led_start != pending_led_end) { + device = pending_devs[pending_led_start]; + leds = leds_pending[device] & 0xff; + leds_pending[device] = 0; + pending_led_start++; + pending_led_start = (pending_led_start < 16) ? pending_led_start : 0; + pending = leds_req_pending; + } else + leds_req_pending = 0; + spin_unlock_irqrestore(&leds_lock, flags); + if (pending) + adb_request(&led_request, leds_done, 0, 3, + ADB_WRITEREG(device, KEYB_LEDREG), 0xff, ~leds); +} + +static void real_leds(unsigned char leds, int device) +{ + unsigned long flags; + + spin_lock_irqsave(&leds_lock, flags); + if (!leds_req_pending) { + leds_req_pending = 1; + spin_unlock_irqrestore(&leds_lock, flags); + adb_request(&led_request, leds_done, 0, 3, + ADB_WRITEREG(device, KEYB_LEDREG), 0xff, ~leds); + return; + } else { + if (!(leds_pending[device] & 0x100)) { + pending_devs[pending_led_end] = device; + pending_led_end++; + pending_led_end = (pending_led_end < 16) ? pending_led_end : 0; + } + leds_pending[device] = leds | 0x100; + } + spin_unlock_irqrestore(&leds_lock, flags); +} + +/* + * Event callback from the input module. Events that change the state of + * the hardware are processed here. + */ +static int adbhid_kbd_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct adbhid *adbhid = dev->private; + unsigned char leds; + + switch (type) { + case EV_LED: + leds = (test_bit(LED_SCROLLL, dev->led) ? 4 : 0) + | (test_bit(LED_NUML, dev->led) ? 1 : 0) + | (test_bit(LED_CAPSL, dev->led) ? 2 : 0); + real_leds(leds, adbhid->id); + return 0; + } + + return -1; +} + +static int +adb_message_handler(struct notifier_block *this, unsigned long code, void *x) +{ + switch (code) { + case ADB_MSG_PRE_RESET: + case ADB_MSG_POWERDOWN: + /* Stop the repeat timer. Autopoll is already off at this point */ + { + int i; + for (i = 1; i < 16; i++) { + if (adbhid[i]) + del_timer_sync(&adbhid[i]->input.timer); + } + } + + /* Stop pending led requests */ + while(leds_req_pending) + adb_poll(); + break; + + case ADB_MSG_POST_RESET: + adbhid_probe(); + break; + } + return NOTIFY_DONE; +} + +static void +adbhid_input_register(int id, int default_id, int original_handler_id, + int current_handler_id, int mouse_kind) +{ + int i; + + if (adbhid[id]) { + printk(KERN_ERR "Trying to reregister ADB HID on ID %d\n", id); + return; + } + + if (!(adbhid[id] = kmalloc(sizeof(struct adbhid), GFP_KERNEL))) + return; + + memset(adbhid[id], 0, sizeof(struct adbhid)); + sprintf(adbhid[id]->phys, "adb%d:%d.%02x/input", id, default_id, original_handler_id); + + init_input_dev(&adbhid[id]->input); + + adbhid[id]->id = default_id; + adbhid[id]->original_handler_id = original_handler_id; + adbhid[id]->current_handler_id = current_handler_id; + adbhid[id]->mouse_kind = mouse_kind; + adbhid[id]->flags = 0; + adbhid[id]->input.private = adbhid[id]; + adbhid[id]->input.name = adbhid[id]->name; + adbhid[id]->input.phys = adbhid[id]->phys; + adbhid[id]->input.id.bustype = BUS_ADB; + adbhid[id]->input.id.vendor = 0x0001; + adbhid[id]->input.id.product = (id << 12) | (default_id << 8) | original_handler_id; + adbhid[id]->input.id.version = 0x0100; + + switch (default_id) { + case ADB_KEYBOARD: + if (!(adbhid[id]->keycode = kmalloc(sizeof(adb_to_linux_keycodes), GFP_KERNEL))) { + kfree(adbhid[id]); + return; + } + + sprintf(adbhid[id]->name, "ADB keyboard"); + + memcpy(adbhid[id]->keycode, adb_to_linux_keycodes, sizeof(adb_to_linux_keycodes)); + + printk(KERN_INFO "Detected ADB keyboard, type "); + switch (original_handler_id) { + default: + printk("<unknown>.\n"); + adbhid[id]->input.id.version = ADB_KEYBOARD_UNKNOWN; + break; + + case 0x01: case 0x02: case 0x03: case 0x06: case 0x08: + case 0x0C: case 0x10: case 0x18: case 0x1B: case 0x1C: + case 0xC0: case 0xC3: case 0xC6: + printk("ANSI.\n"); + adbhid[id]->input.id.version = ADB_KEYBOARD_ANSI; + break; + + case 0x04: case 0x05: case 0x07: case 0x09: case 0x0D: + case 0x11: case 0x14: case 0x19: case 0x1D: case 0xC1: + case 0xC4: case 0xC7: + printk("ISO, swapping keys.\n"); + adbhid[id]->input.id.version = ADB_KEYBOARD_ISO; + i = adbhid[id]->keycode[10]; + adbhid[id]->keycode[10] = adbhid[id]->keycode[50]; + adbhid[id]->keycode[50] = i; + break; + + case 0x12: case 0x15: case 0x16: case 0x17: case 0x1A: + case 0x1E: case 0xC2: case 0xC5: case 0xC8: case 0xC9: + printk("JIS.\n"); + adbhid[id]->input.id.version = ADB_KEYBOARD_JIS; + break; + } + + for (i = 0; i < 128; i++) + if (adbhid[id]->keycode[i]) + set_bit(adbhid[id]->keycode[i], adbhid[id]->input.keybit); + + adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP); + adbhid[id]->input.ledbit[0] = BIT(LED_SCROLLL) | BIT(LED_CAPSL) | BIT(LED_NUML); + adbhid[id]->input.event = adbhid_kbd_event; + adbhid[id]->input.keycodemax = 127; + adbhid[id]->input.keycodesize = 1; + break; + + case ADB_MOUSE: + sprintf(adbhid[id]->name, "ADB mouse"); + + adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + adbhid[id]->input.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + adbhid[id]->input.relbit[0] = BIT(REL_X) | BIT(REL_Y); + break; + + case ADB_MISC: + switch (original_handler_id) { + case 0x02: /* Adjustable keyboard button device */ + sprintf(adbhid[id]->name, "ADB adjustable keyboard buttons"); + adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + set_bit(KEY_SOUND, adbhid[id]->input.keybit); + set_bit(KEY_MUTE, adbhid[id]->input.keybit); + set_bit(KEY_VOLUMEUP, adbhid[id]->input.keybit); + set_bit(KEY_VOLUMEDOWN, adbhid[id]->input.keybit); + break; + case 0x1f: /* Powerbook button device */ + sprintf(adbhid[id]->name, "ADB Powerbook buttons"); + adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + set_bit(KEY_MUTE, adbhid[id]->input.keybit); + set_bit(KEY_VOLUMEUP, adbhid[id]->input.keybit); + set_bit(KEY_VOLUMEDOWN, adbhid[id]->input.keybit); + set_bit(KEY_BRIGHTNESSUP, adbhid[id]->input.keybit); + set_bit(KEY_BRIGHTNESSDOWN, adbhid[id]->input.keybit); + set_bit(KEY_EJECTCD, adbhid[id]->input.keybit); + break; + } + if (adbhid[id]->name[0]) + break; + /* else fall through */ + + default: + printk(KERN_INFO "Trying to register unknown ADB device to input layer.\n"); + kfree(adbhid[id]); + return; + } + + adbhid[id]->input.keycode = adbhid[id]->keycode; + + input_register_device(&adbhid[id]->input); + + printk(KERN_INFO "input: %s on %s\n", + adbhid[id]->name, adbhid[id]->phys); + + if (default_id == ADB_KEYBOARD) { + /* HACK WARNING!! This should go away as soon there is an utility + * to control that for event devices. + */ + adbhid[id]->input.rep[REP_DELAY] = 500; /* input layer default: 250 */ + adbhid[id]->input.rep[REP_PERIOD] = 66; /* input layer default: 33 */ + } +} + +static void adbhid_input_unregister(int id) +{ + input_unregister_device(&adbhid[id]->input); + if (adbhid[id]->keycode) + kfree(adbhid[id]->keycode); + kfree(adbhid[id]); + adbhid[id] = NULL; +} + + +static u16 +adbhid_input_reregister(int id, int default_id, int org_handler_id, + int cur_handler_id, int mk) +{ + if (adbhid[id]) { + if (adbhid[id]->input.id.product != + ((id << 12)|(default_id << 8)|org_handler_id)) { + adbhid_input_unregister(id); + adbhid_input_register(id, default_id, org_handler_id, + cur_handler_id, mk); + } + } else + adbhid_input_register(id, default_id, org_handler_id, + cur_handler_id, mk); + return 1<<id; +} + +static void +adbhid_input_devcleanup(u16 exist) +{ + int i; + for(i=1; i<16; i++) + if (adbhid[i] && !(exist&(1<<i))) + adbhid_input_unregister(i); +} + +static void +adbhid_probe(void) +{ + struct adb_request req; + int i, default_id, org_handler_id, cur_handler_id; + u16 reg = 0; + + adb_register(ADB_MOUSE, 0, &mouse_ids, adbhid_mouse_input); + adb_register(ADB_KEYBOARD, 0, &keyboard_ids, adbhid_keyboard_input); + adb_register(ADB_MISC, 0, &buttons_ids, adbhid_buttons_input); + + for (i = 0; i < keyboard_ids.nids; i++) { + int id = keyboard_ids.id[i]; + + adb_get_infos(id, &default_id, &org_handler_id); + + /* turn off all leds */ + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id, KEYB_LEDREG), 0xff, 0xff); + + /* Enable full feature set of the keyboard + ->get it to send separate codes for left and right shift, + control, option keys */ +#if 0 /* handler 5 doesn't send separate codes for R modifiers */ + if (adb_try_handler_change(id, 5)) + printk("ADB keyboard at %d, handler set to 5\n", id); + else +#endif + if (adb_try_handler_change(id, 3)) + printk("ADB keyboard at %d, handler set to 3\n", id); + else + printk("ADB keyboard at %d, handler 1\n", id); + + adb_get_infos(id, &default_id, &cur_handler_id); + reg |= adbhid_input_reregister(id, default_id, org_handler_id, + cur_handler_id, 0); + } + + for (i = 0; i < buttons_ids.nids; i++) { + int id = buttons_ids.id[i]; + + adb_get_infos(id, &default_id, &org_handler_id); + reg |= adbhid_input_reregister(id, default_id, org_handler_id, + org_handler_id, 0); + } + + /* Try to switch all mice to handler 4, or 2 for three-button + mode and full resolution. */ + for (i = 0; i < mouse_ids.nids; i++) { + int id = mouse_ids.id[i]; + int mouse_kind; + + adb_get_infos(id, &default_id, &org_handler_id); + + if (adb_try_handler_change(id, 4)) { + printk("ADB mouse at %d, handler set to 4", id); + mouse_kind = ADBMOUSE_EXTENDED; + } + else if (adb_try_handler_change(id, 0x2F)) { + printk("ADB mouse at %d, handler set to 0x2F", id); + mouse_kind = ADBMOUSE_MICROSPEED; + } + else if (adb_try_handler_change(id, 0x42)) { + printk("ADB mouse at %d, handler set to 0x42", id); + mouse_kind = ADBMOUSE_TRACKBALLPRO; + } + else if (adb_try_handler_change(id, 0x66)) { + printk("ADB mouse at %d, handler set to 0x66", id); + mouse_kind = ADBMOUSE_MICROSPEED; + } + else if (adb_try_handler_change(id, 0x5F)) { + printk("ADB mouse at %d, handler set to 0x5F", id); + mouse_kind = ADBMOUSE_MICROSPEED; + } + else if (adb_try_handler_change(id, 3)) { + printk("ADB mouse at %d, handler set to 3", id); + mouse_kind = ADBMOUSE_MS_A3; + } + else if (adb_try_handler_change(id, 2)) { + printk("ADB mouse at %d, handler set to 2", id); + mouse_kind = ADBMOUSE_STANDARD_200; + } + else { + printk("ADB mouse at %d, handler 1", id); + mouse_kind = ADBMOUSE_STANDARD_100; + } + + if ((mouse_kind == ADBMOUSE_TRACKBALLPRO) + || (mouse_kind == ADBMOUSE_MICROSPEED)) { + init_microspeed(id); + } else if (mouse_kind == ADBMOUSE_MS_A3) { + init_ms_a3(id); + } else if (mouse_kind == ADBMOUSE_EXTENDED) { + /* + * Register 1 is usually used for device + * identification. Here, we try to identify + * a known device and call the appropriate + * init function. + */ + adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1, + ADB_READREG(id, 1)); + + if ((req.reply_len) && + (req.reply[1] == 0x9a) && ((req.reply[2] == 0x21) + || (req.reply[2] == 0x20))) { + mouse_kind = ADBMOUSE_TRACKBALL; + init_trackball(id); + } + else if ((req.reply_len >= 4) && + (req.reply[1] == 0x74) && (req.reply[2] == 0x70) && + (req.reply[3] == 0x61) && (req.reply[4] == 0x64)) { + mouse_kind = ADBMOUSE_TRACKPAD; + init_trackpad(id); + } + else if ((req.reply_len >= 4) && + (req.reply[1] == 0x4b) && (req.reply[2] == 0x4d) && + (req.reply[3] == 0x4c) && (req.reply[4] == 0x31)) { + mouse_kind = ADBMOUSE_TURBOMOUSE5; + init_turbomouse(id); + } + else if ((req.reply_len == 9) && + (req.reply[1] == 0x4b) && (req.reply[2] == 0x4f) && + (req.reply[3] == 0x49) && (req.reply[4] == 0x54)) { + if (adb_try_handler_change(id, 0x42)) { + printk("\nADB MacAlly 2-button mouse at %d, handler set to 0x42", id); + mouse_kind = ADBMOUSE_MACALLY2; + } + } + } + printk("\n"); + + adb_get_infos(id, &default_id, &cur_handler_id); + reg |= adbhid_input_reregister(id, default_id, org_handler_id, + cur_handler_id, mouse_kind); + } + adbhid_input_devcleanup(reg); +} + +static void +init_trackpad(int id) +{ + struct adb_request req; + unsigned char r1_buffer[8]; + + printk(" (trackpad)"); + + adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1, + ADB_READREG(id,1)); + if (req.reply_len < 8) + printk("bad length for reg. 1\n"); + else + { + memcpy(r1_buffer, &req.reply[1], 8); + + adb_request(&req, NULL, ADBREQ_SYNC, 9, + ADB_WRITEREG(id,1), + r1_buffer[0], + r1_buffer[1], + r1_buffer[2], + r1_buffer[3], + r1_buffer[4], + r1_buffer[5], + 0x0d, + r1_buffer[7]); + + adb_request(&req, NULL, ADBREQ_SYNC, 9, + ADB_WRITEREG(id,2), + 0x99, + 0x94, + 0x19, + 0xff, + 0xb2, + 0x8a, + 0x1b, + 0x50); + + adb_request(&req, NULL, ADBREQ_SYNC, 9, + ADB_WRITEREG(id,1), + r1_buffer[0], + r1_buffer[1], + r1_buffer[2], + r1_buffer[3], + r1_buffer[4], + r1_buffer[5], + 0x03, /*r1_buffer[6],*/ + r1_buffer[7]); + + /* Without this flush, the trackpad may be locked up */ + adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id)); + } +} + +static void +init_trackball(int id) +{ + struct adb_request req; + + printk(" (trackman/mouseman)"); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 00,0x81); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 01,0x81); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 02,0x81); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 03,0x38); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 00,0x81); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 01,0x81); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 02,0x81); + + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id,1), 03,0x38); +} + +static void +init_turbomouse(int id) +{ + struct adb_request req; + + printk(" (TurboMouse 5)"); + + adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id)); + + adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(3)); + + adb_request(&req, NULL, ADBREQ_SYNC, 9, + ADB_WRITEREG(3,2), + 0xe7, + 0x8c, + 0, + 0, + 0, + 0xff, + 0xff, + 0x94); + + adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(3)); + + adb_request(&req, NULL, ADBREQ_SYNC, 9, + ADB_WRITEREG(3,2), + 0xa5, + 0x14, + 0, + 0, + 0x69, + 0xff, + 0xff, + 0x27); +} + +static void +init_microspeed(int id) +{ + struct adb_request req; + + printk(" (Microspeed/MacPoint or compatible)"); + + adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id)); + + /* This will initialize mice using the Microspeed, MacPoint and + other compatible firmware. Bit 12 enables extended protocol. + + Register 1 Listen (4 Bytes) + 0 - 3 Button is mouse (set also for double clicking!!!) + 4 - 7 Button is locking (affects change speed also) + 8 - 11 Button changes speed + 12 1 = Extended mouse mode, 0 = normal mouse mode + 13 - 15 unused 0 + 16 - 23 normal speed + 24 - 31 changed speed + + Register 1 talk holds version and product identification information. + Register 1 Talk (4 Bytes): + 0 - 7 Product code + 8 - 23 undefined, reserved + 24 - 31 Version number + + Speed 0 is max. 1 to 255 set speed in increments of 1/256 of max. + */ + adb_request(&req, NULL, ADBREQ_SYNC, 5, + ADB_WRITEREG(id,1), + 0x20, /* alt speed = 0x20 (rather slow) */ + 0x00, /* norm speed = 0x00 (fastest) */ + 0x10, /* extended protocol, no speed change */ + 0x07); /* all buttons enabled as mouse buttons, no locking */ + + + adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id)); +} + +static void +init_ms_a3(int id) +{ + struct adb_request req; + + printk(" (Mouse Systems A3 Mouse, or compatible)"); + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(id, 0x2), + 0x00, + 0x07); + + adb_request(&req, NULL, ADBREQ_SYNC, 1, ADB_FLUSH(id)); +} + +static int __init adbhid_init(void) +{ +#ifndef CONFIG_MAC + if ( (_machine != _MACH_chrp) && (_machine != _MACH_Pmac) ) + return 0; +#endif + + led_request.complete = 1; + + adbhid_probe(); + + notifier_chain_register(&adb_client_list, &adbhid_adb_notifier); + + return 0; +} + +static void __exit adbhid_exit(void) +{ +} + +module_init(adbhid_init); +module_exit(adbhid_exit); diff --git a/drivers/macintosh/ans-lcd.c b/drivers/macintosh/ans-lcd.c new file mode 100644 index 000000000000..5e0811dc6536 --- /dev/null +++ b/drivers/macintosh/ans-lcd.c @@ -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); diff --git a/drivers/macintosh/apm_emu.c b/drivers/macintosh/apm_emu.c new file mode 100644 index 000000000000..19d3e05d6825 --- /dev/null +++ b/drivers/macintosh/apm_emu.c @@ -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"); + diff --git a/drivers/macintosh/mac_hid.c b/drivers/macintosh/mac_hid.c new file mode 100644 index 000000000000..5ad3a5a9eb7f --- /dev/null +++ b/drivers/macintosh/mac_hid.c @@ -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); diff --git a/drivers/macintosh/macio-adb.c b/drivers/macintosh/macio-adb.c new file mode 100644 index 000000000000..cf6a6f2248ac --- /dev/null +++ b/drivers/macintosh/macio-adb.c @@ -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); +} diff --git a/drivers/macintosh/macio_asic.c b/drivers/macintosh/macio_asic.c new file mode 100644 index 000000000000..3a609ecd251b --- /dev/null +++ b/drivers/macintosh/macio_asic.c @@ -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); diff --git a/drivers/macintosh/macserial.c b/drivers/macintosh/macserial.c new file mode 100644 index 000000000000..0be3ac6cc169 --- /dev/null +++ b/drivers/macintosh/macserial.c @@ -0,0 +1,3036 @@ +/* + * macserial.c: Serial port driver for Power Macintoshes. + * + * Derived from drivers/sbus/char/sunserial.c by Paul Mackerras. + * + * Copyright (C) 1996 Paul Mackerras (Paul.Mackerras@cs.anu.edu.au) + * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu) + * + * Receive DMA code by Takashi Oe <toe@unlserve.unl.edu>. + * + * $Id: macserial.c,v 1.24.2.4 1999/10/19 04:36:42 paulus Exp $ + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> +#ifdef CONFIG_SERIAL_CONSOLE +#include <linux/console.h> +#endif +#include <linux/slab.h> +#include <linux/bitops.h> + +#include <asm/sections.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/irq.h> +#include <asm/prom.h> +#include <asm/system.h> +#include <asm/segment.h> +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#include <linux/adb.h> +#include <linux/pmu.h> +#ifdef CONFIG_KGDB +#include <asm/kgdb.h> +#endif +#include <asm/dbdma.h> + +#include "macserial.h" + +#ifdef CONFIG_PMAC_PBOOK +static int serial_notify_sleep(struct pmu_sleep_notifier *self, int when); +static struct pmu_sleep_notifier serial_sleep_notifier = { + serial_notify_sleep, + SLEEP_LEVEL_MISC, +}; +#endif + +#define SUPPORT_SERIAL_DMA +#define MACSERIAL_VERSION "2.0" + +/* + * It would be nice to dynamically allocate everything that + * depends on NUM_SERIAL, so we could support any number of + * Z8530s, but for now... + */ +#define NUM_SERIAL 2 /* Max number of ZS chips supported */ +#define NUM_CHANNELS (NUM_SERIAL * 2) /* 2 channels per chip */ + +/* On PowerMacs, the hardware takes care of the SCC recovery time, + but we need the eieio to make sure that the accesses occur + in the order we want. */ +#define RECOVERY_DELAY eieio() + +static struct tty_driver *serial_driver; + +struct mac_zschannel zs_channels[NUM_CHANNELS]; + +struct mac_serial zs_soft[NUM_CHANNELS]; +int zs_channels_found; +struct mac_serial *zs_chain; /* list of all channels */ + +struct tty_struct zs_ttys[NUM_CHANNELS]; + +static int is_powerbook; + +#ifdef CONFIG_SERIAL_CONSOLE +static struct console sercons; +#endif + +#ifdef CONFIG_KGDB +struct mac_zschannel *zs_kgdbchan; +static unsigned char scc_inittab[] = { + 9, 0x80, /* reset A side (CHRA) */ + 13, 0, /* set baud rate divisor */ + 12, 1, + 14, 1, /* baud rate gen enable, src=rtxc (BRENABL) */ + 11, 0x50, /* clocks = br gen (RCBR | TCBR) */ + 5, 0x6a, /* tx 8 bits, assert RTS (Tx8 | TxENAB | RTS) */ + 4, 0x44, /* x16 clock, 1 stop (SB1 | X16CLK)*/ + 3, 0xc1, /* rx enable, 8 bits (RxENABLE | Rx8)*/ +}; +#endif +#define ZS_CLOCK 3686400 /* Z8530 RTxC input clock rate */ + +/* serial subtype definitions */ +#define SERIAL_TYPE_NORMAL 1 + +/* number of characters left in xmit buffer before we ask for more */ +#define WAKEUP_CHARS 256 + +/* + * Debugging. + */ +#undef SERIAL_DEBUG_INTR +#undef SERIAL_DEBUG_OPEN +#undef SERIAL_DEBUG_FLOW +#undef SERIAL_DEBUG_POWER +#undef SERIAL_DEBUG_THROTTLE +#undef SERIAL_DEBUG_STOP +#undef SERIAL_DEBUG_BAUDS + +#define RS_STROBE_TIME 10 +#define RS_ISR_PASS_LIMIT 256 + +#define _INLINE_ inline + +#ifdef SERIAL_DEBUG_OPEN +#define OPNDBG(fmt, arg...) printk(KERN_DEBUG fmt , ## arg) +#else +#define OPNDBG(fmt, arg...) do { } while (0) +#endif +#ifdef SERIAL_DEBUG_POWER +#define PWRDBG(fmt, arg...) printk(KERN_DEBUG fmt , ## arg) +#else +#define PWRDBG(fmt, arg...) do { } while (0) +#endif +#ifdef SERIAL_DEBUG_BAUDS +#define BAUDBG(fmt, arg...) printk(fmt , ## arg) +#else +#define BAUDBG(fmt, arg...) do { } while (0) +#endif + +static void probe_sccs(void); +static void change_speed(struct mac_serial *info, struct termios *old); +static void rs_wait_until_sent(struct tty_struct *tty, int timeout); +static int set_scc_power(struct mac_serial * info, int state); +static int setup_scc(struct mac_serial * info); +static void dbdma_reset(volatile struct dbdma_regs *dma); +static void dbdma_flush(volatile struct dbdma_regs *dma); +static irqreturn_t rs_txdma_irq(int irq, void *dev_id, struct pt_regs *regs); +static irqreturn_t rs_rxdma_irq(int irq, void *dev_id, struct pt_regs *regs); +static void dma_init(struct mac_serial * info); +static void rxdma_start(struct mac_serial * info, int curr); +static void rxdma_to_tty(struct mac_serial * info); + +/* + * tmp_buf is used as a temporary buffer by serial_write. We need to + * lock it in case the copy_from_user blocks while swapping in a page, + * and some other program tries to do a serial write at the same time. + * Since the lock will only come under contention when the system is + * swapping and available memory is low, it makes sense to share one + * buffer across all the serial ports, since it significantly saves + * memory if large numbers of serial ports are open. + */ +static unsigned char *tmp_buf; +static DECLARE_MUTEX(tmp_buf_sem); + + +static inline int __pmac +serial_paranoia_check(struct mac_serial *info, + char *name, const char *routine) +{ +#ifdef SERIAL_PARANOIA_CHECK + static const char badmagic[] = KERN_WARNING + "Warning: bad magic number for serial struct %s in %s\n"; + static const char badinfo[] = KERN_WARNING + "Warning: null mac_serial for %s in %s\n"; + + if (!info) { + printk(badinfo, name, routine); + return 1; + } + if (info->magic != SERIAL_MAGIC) { + printk(badmagic, name, routine); + return 1; + } +#endif + return 0; +} + +/* + * Reading and writing Z8530 registers. + */ +static inline unsigned char __pmac read_zsreg(struct mac_zschannel *channel, + unsigned char reg) +{ + unsigned char retval; + unsigned long flags; + + /* + * We have to make this atomic. + */ + spin_lock_irqsave(&channel->lock, flags); + if (reg != 0) { + *channel->control = reg; + RECOVERY_DELAY; + } + retval = *channel->control; + RECOVERY_DELAY; + spin_unlock_irqrestore(&channel->lock, flags); + return retval; +} + +static inline void __pmac write_zsreg(struct mac_zschannel *channel, + unsigned char reg, unsigned char value) +{ + unsigned long flags; + + spin_lock_irqsave(&channel->lock, flags); + if (reg != 0) { + *channel->control = reg; + RECOVERY_DELAY; + } + *channel->control = value; + RECOVERY_DELAY; + spin_unlock_irqrestore(&channel->lock, flags); + return; +} + +static inline unsigned char __pmac read_zsdata(struct mac_zschannel *channel) +{ + unsigned char retval; + + retval = *channel->data; + RECOVERY_DELAY; + return retval; +} + +static inline void write_zsdata(struct mac_zschannel *channel, + unsigned char value) +{ + *channel->data = value; + RECOVERY_DELAY; + return; +} + +static inline void load_zsregs(struct mac_zschannel *channel, + unsigned char *regs) +{ + ZS_CLEARERR(channel); + ZS_CLEARFIFO(channel); + /* Load 'em up */ + write_zsreg(channel, R4, regs[R4]); + write_zsreg(channel, R10, regs[R10]); + write_zsreg(channel, R3, regs[R3] & ~RxENABLE); + write_zsreg(channel, R5, regs[R5] & ~TxENAB); + write_zsreg(channel, R1, regs[R1]); + write_zsreg(channel, R9, regs[R9]); + write_zsreg(channel, R11, regs[R11]); + write_zsreg(channel, R12, regs[R12]); + write_zsreg(channel, R13, regs[R13]); + write_zsreg(channel, R14, regs[R14]); + write_zsreg(channel, R15, regs[R15]); + write_zsreg(channel, R3, regs[R3]); + write_zsreg(channel, R5, regs[R5]); + return; +} + +/* Sets or clears DTR/RTS on the requested line */ +static inline void zs_rtsdtr(struct mac_serial *ss, int set) +{ + if (set) + ss->curregs[5] |= (RTS | DTR); + else + ss->curregs[5] &= ~(RTS | DTR); + write_zsreg(ss->zs_channel, 5, ss->curregs[5]); + return; +} + +/* Utility routines for the Zilog */ +static inline int get_zsbaud(struct mac_serial *ss) +{ + struct mac_zschannel *channel = ss->zs_channel; + int brg; + + if ((ss->curregs[R11] & TCBR) == 0) { + /* higher rates don't use the baud rate generator */ + return (ss->curregs[R4] & X32CLK)? ZS_CLOCK/32: ZS_CLOCK/16; + } + /* The baud rate is split up between two 8-bit registers in + * what is termed 'BRG time constant' format in my docs for + * the chip, it is a function of the clk rate the chip is + * receiving which happens to be constant. + */ + brg = (read_zsreg(channel, 13) << 8); + brg |= read_zsreg(channel, 12); + return BRG_TO_BPS(brg, (ZS_CLOCK/(ss->clk_divisor))); +} + +/* On receive, this clears errors and the receiver interrupts */ +static inline void rs_recv_clear(struct mac_zschannel *zsc) +{ + write_zsreg(zsc, 0, ERR_RES); + write_zsreg(zsc, 0, RES_H_IUS); /* XXX this is unnecessary */ +} + +/* + * Reset a Descriptor-Based DMA channel. + */ +static void dbdma_reset(volatile struct dbdma_regs *dma) +{ + int i; + + out_le32(&dma->control, (WAKE|FLUSH|PAUSE|RUN) << 16); + + /* + * Yes this looks peculiar, but apparently it needs to be this + * way on some machines. (We need to make sure the DBDMA + * engine has actually got the write above and responded + * to it. - paulus) + */ + for (i = 200; i > 0; --i) + if (ld_le32(&dma->status) & RUN) + udelay(1); +} + +/* + * Tells a DBDMA channel to stop and write any buffered data + * it might have to memory. + */ +static _INLINE_ void dbdma_flush(volatile struct dbdma_regs *dma) +{ + int i = 0; + + out_le32(&dma->control, (FLUSH << 16) | FLUSH); + while (((in_le32(&dma->status) & FLUSH) != 0) && (i++ < 100)) + udelay(1); +} + +/* + * ---------------------------------------------------------------------- + * + * Here starts the interrupt handling routines. All of the following + * subroutines are declared as inline and are folded into + * rs_interrupt(). They were separated out for readability's sake. + * + * - Ted Ts'o (tytso@mit.edu), 7-Mar-93 + * ----------------------------------------------------------------------- + */ + +/* + * This routine is used by the interrupt handler to schedule + * processing in the software interrupt portion of the driver. + */ +static _INLINE_ void rs_sched_event(struct mac_serial *info, + int event) +{ + info->event |= 1 << event; + schedule_work(&info->tqueue); +} + +/* Work out the flag value for a z8530 status value. */ +static _INLINE_ int stat_to_flag(int stat) +{ + int flag; + + if (stat & Rx_OVR) { + flag = TTY_OVERRUN; + } else if (stat & FRM_ERR) { + flag = TTY_FRAME; + } else if (stat & PAR_ERR) { + flag = TTY_PARITY; + } else + flag = 0; + return flag; +} + +static _INLINE_ void receive_chars(struct mac_serial *info, + struct pt_regs *regs) +{ + struct tty_struct *tty = info->tty; + unsigned char ch, stat, flag; + + while ((read_zsreg(info->zs_channel, 0) & Rx_CH_AV) != 0) { + + stat = read_zsreg(info->zs_channel, R1); + ch = read_zsdata(info->zs_channel); + +#ifdef CONFIG_KGDB + if (info->kgdb_channel) { + if (ch == 0x03 || ch == '$') + breakpoint(); + if (stat & (Rx_OVR|FRM_ERR|PAR_ERR)) + write_zsreg(info->zs_channel, 0, ERR_RES); + return; + } +#endif + if (!tty) + continue; + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + tty_flip_buffer_push(tty); + + if (tty->flip.count >= TTY_FLIPBUF_SIZE) { + static int flip_buf_ovf; + if (++flip_buf_ovf <= 1) + printk(KERN_WARNING "FB. overflow: %d\n", + flip_buf_ovf); + break; + } + tty->flip.count++; + { + static int flip_max_cnt; + if (flip_max_cnt < tty->flip.count) + flip_max_cnt = tty->flip.count; + } + flag = stat_to_flag(stat); + if (flag) + /* reset the error indication */ + write_zsreg(info->zs_channel, 0, ERR_RES); + *tty->flip.flag_buf_ptr++ = flag; + *tty->flip.char_buf_ptr++ = ch; + } + if (tty) + tty_flip_buffer_push(tty); +} + +static void transmit_chars(struct mac_serial *info) +{ + if ((read_zsreg(info->zs_channel, 0) & Tx_BUF_EMP) == 0) + return; + info->tx_active = 0; + + if (info->x_char && !info->power_wait) { + /* Send next char */ + write_zsdata(info->zs_channel, info->x_char); + info->x_char = 0; + info->tx_active = 1; + return; + } + + if ((info->xmit_cnt <= 0) || info->tty->stopped || info->tx_stopped + || info->power_wait) { + write_zsreg(info->zs_channel, 0, RES_Tx_P); + return; + } + + /* Send char */ + write_zsdata(info->zs_channel, info->xmit_buf[info->xmit_tail++]); + info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1); + info->xmit_cnt--; + info->tx_active = 1; + + if (info->xmit_cnt < WAKEUP_CHARS) + rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); +} + +static void powerup_done(unsigned long data) +{ + struct mac_serial *info = (struct mac_serial *) data; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + info->power_wait = 0; + transmit_chars(info); + spin_unlock_irqrestore(&info->lock, flags); +} + +static _INLINE_ void status_handle(struct mac_serial *info) +{ + unsigned char status; + + /* Get status from Read Register 0 */ + status = read_zsreg(info->zs_channel, 0); + + /* Check for DCD transitions */ + if (((status ^ info->read_reg_zero) & DCD) != 0 + && info->tty && !C_CLOCAL(info->tty)) { + if (status & DCD) { + wake_up_interruptible(&info->open_wait); + } else { + if (info->tty) + tty_hangup(info->tty); + } + } + + /* Check for CTS transitions */ + if (info->tty && C_CRTSCTS(info->tty)) { + /* + * For some reason, on the Power Macintosh, + * it seems that the CTS bit is 1 when CTS is + * *negated* and 0 when it is asserted. + * The DCD bit doesn't seem to be inverted + * like this. + */ + if ((status & CTS) == 0) { + if (info->tx_stopped) { +#ifdef SERIAL_DEBUG_FLOW + printk(KERN_DEBUG "CTS up\n"); +#endif + info->tx_stopped = 0; + if (!info->tx_active) + transmit_chars(info); + } + } else { +#ifdef SERIAL_DEBUG_FLOW + printk(KERN_DEBUG "CTS down\n"); +#endif + info->tx_stopped = 1; + } + } + + /* Clear status condition... */ + write_zsreg(info->zs_channel, 0, RES_EXT_INT); + info->read_reg_zero = status; +} + +static _INLINE_ void receive_special_dma(struct mac_serial *info) +{ + unsigned char stat, flag; + volatile struct dbdma_regs *rd = &info->rx->dma; + int where = RX_BUF_SIZE; + + spin_lock(&info->rx_dma_lock); + if ((ld_le32(&rd->status) & ACTIVE) != 0) + dbdma_flush(rd); + if (in_le32(&rd->cmdptr) + == virt_to_bus(info->rx_cmds[info->rx_cbuf] + 1)) + where -= in_le16(&info->rx->res_count); + where--; + + stat = read_zsreg(info->zs_channel, R1); + + flag = stat_to_flag(stat); + if (flag) { + info->rx_flag_buf[info->rx_cbuf][where] = flag; + /* reset the error indication */ + write_zsreg(info->zs_channel, 0, ERR_RES); + } + + spin_unlock(&info->rx_dma_lock); +} + +/* + * This is the serial driver's generic interrupt routine + */ +static irqreturn_t rs_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct mac_serial *info = (struct mac_serial *) dev_id; + unsigned char zs_intreg; + int shift; + unsigned long flags; + int handled = 0; + + if (!(info->flags & ZILOG_INITIALIZED)) { + printk(KERN_WARNING "rs_interrupt: irq %d, port not " + "initialized\n", irq); + disable_irq(irq); + return IRQ_NONE; + } + + /* NOTE: The read register 3, which holds the irq status, + * does so for both channels on each chip. Although + * the status value itself must be read from the A + * channel and is only valid when read from channel A. + * Yes... broken hardware... + */ +#define CHAN_IRQMASK (CHBRxIP | CHBTxIP | CHBEXT) + + if (info->zs_chan_a == info->zs_channel) + shift = 3; /* Channel A */ + else + shift = 0; /* Channel B */ + + spin_lock_irqsave(&info->lock, flags); + for (;;) { + zs_intreg = read_zsreg(info->zs_chan_a, 3) >> shift; +#ifdef SERIAL_DEBUG_INTR + printk(KERN_DEBUG "rs_interrupt: irq %d, zs_intreg 0x%x\n", + irq, (int)zs_intreg); +#endif + + if ((zs_intreg & CHAN_IRQMASK) == 0) + break; + handled = 1; + + if (zs_intreg & CHBRxIP) { + /* If we are doing DMA, we only ask for interrupts + on characters with errors or special conditions. */ + if (info->dma_initted) + receive_special_dma(info); + else + receive_chars(info, regs); + } + if (zs_intreg & CHBTxIP) + transmit_chars(info); + if (zs_intreg & CHBEXT) + status_handle(info); + } + spin_unlock_irqrestore(&info->lock, flags); + return IRQ_RETVAL(handled); +} + +/* Transmit DMA interrupt - not used at present */ +static irqreturn_t rs_txdma_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + return IRQ_HANDLED; +} + +/* + * Receive DMA interrupt. + */ +static irqreturn_t rs_rxdma_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct mac_serial *info = (struct mac_serial *) dev_id; + volatile struct dbdma_cmd *cd; + + if (!info->dma_initted) + return IRQ_NONE; + spin_lock(&info->rx_dma_lock); + /* First, confirm that this interrupt is, indeed, coming */ + /* from Rx DMA */ + cd = info->rx_cmds[info->rx_cbuf] + 2; + if ((in_le16(&cd->xfer_status) & (RUN | ACTIVE)) != (RUN | ACTIVE)) { + spin_unlock(&info->rx_dma_lock); + return IRQ_NONE; + } + if (info->rx_fbuf != RX_NO_FBUF) { + info->rx_cbuf = info->rx_fbuf; + if (++info->rx_fbuf == info->rx_nbuf) + info->rx_fbuf = 0; + if (info->rx_fbuf == info->rx_ubuf) + info->rx_fbuf = RX_NO_FBUF; + } + spin_unlock(&info->rx_dma_lock); + return IRQ_HANDLED; +} + +/* + * ------------------------------------------------------------------- + * Here ends the serial interrupt routines. + * ------------------------------------------------------------------- + */ + +/* + * ------------------------------------------------------------ + * rs_stop() and rs_start() + * + * This routines are called before setting or resetting tty->stopped. + * ------------------------------------------------------------ + */ +static void rs_stop(struct tty_struct *tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + +#ifdef SERIAL_DEBUG_STOP + printk(KERN_DEBUG "rs_stop %ld....\n", + tty->ldisc.chars_in_buffer(tty)); +#endif + + if (serial_paranoia_check(info, tty->name, "rs_stop")) + return; + +#if 0 + spin_lock_irqsave(&info->lock, flags); + if (info->curregs[5] & TxENAB) { + info->curregs[5] &= ~TxENAB; + info->pendregs[5] &= ~TxENAB; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + } + spin_unlock_irqrestore(&info->lock, flags); +#endif +} + +static void rs_start(struct tty_struct *tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + unsigned long flags; + +#ifdef SERIAL_DEBUG_STOP + printk(KERN_DEBUG "rs_start %ld....\n", + tty->ldisc.chars_in_buffer(tty)); +#endif + + if (serial_paranoia_check(info, tty->name, "rs_start")) + return; + + spin_lock_irqsave(&info->lock, flags); +#if 0 + if (info->xmit_cnt && info->xmit_buf && !(info->curregs[5] & TxENAB)) { + info->curregs[5] |= TxENAB; + info->pendregs[5] = info->curregs[5]; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + } +#else + if (info->xmit_cnt && info->xmit_buf && !info->tx_active) { + transmit_chars(info); + } +#endif + spin_unlock_irqrestore(&info->lock, flags); +} + +static void do_softint(void *private_) +{ + struct mac_serial *info = (struct mac_serial *) private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) + tty_wakeup(tty); +} + +static int startup(struct mac_serial * info) +{ + int delay; + + OPNDBG("startup() (ttyS%d, irq %d)\n", info->line, info->irq); + + if (info->flags & ZILOG_INITIALIZED) { + OPNDBG(" -> already inited\n"); + return 0; + } + + if (!info->xmit_buf) { + info->xmit_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL); + if (!info->xmit_buf) + return -ENOMEM; + } + + OPNDBG("starting up ttyS%d (irq %d)...\n", info->line, info->irq); + + delay = set_scc_power(info, 1); + + setup_scc(info); + + if (delay) { + unsigned long flags; + + /* delay is in ms */ + spin_lock_irqsave(&info->lock, flags); + info->power_wait = 1; + mod_timer(&info->powerup_timer, + jiffies + (delay * HZ + 999) / 1000); + spin_unlock_irqrestore(&info->lock, flags); + } + + OPNDBG("enabling IRQ on ttyS%d (irq %d)...\n", info->line, info->irq); + + info->flags |= ZILOG_INITIALIZED; + enable_irq(info->irq); + if (info->dma_initted) { + enable_irq(info->rx_dma_irq); + } + + return 0; +} + +static _INLINE_ void rxdma_start(struct mac_serial * info, int curr) +{ + volatile struct dbdma_regs *rd = &info->rx->dma; + volatile struct dbdma_cmd *cd = info->rx_cmds[curr]; + +//printk(KERN_DEBUG "SCC: rxdma_start\n"); + + st_le32(&rd->cmdptr, virt_to_bus(cd)); + out_le32(&rd->control, (RUN << 16) | RUN); +} + +static void rxdma_to_tty(struct mac_serial *info) +{ + struct tty_struct *tty = info->tty; + volatile struct dbdma_regs *rd = &info->rx->dma; + unsigned long flags; + int residue, available, space, do_queue; + + if (!tty) + return; + + do_queue = 0; + spin_lock_irqsave(&info->rx_dma_lock, flags); +more: + space = TTY_FLIPBUF_SIZE - tty->flip.count; + if (!space) { + do_queue++; + goto out; + } + residue = 0; + if (info->rx_ubuf == info->rx_cbuf) { + if ((ld_le32(&rd->status) & ACTIVE) != 0) { + dbdma_flush(rd); + if (in_le32(&rd->cmdptr) + == virt_to_bus(info->rx_cmds[info->rx_cbuf]+1)) + residue = in_le16(&info->rx->res_count); + } + } + available = RX_BUF_SIZE - residue - info->rx_done_bytes; + if (available > space) + available = space; + if (available) { + memcpy(tty->flip.char_buf_ptr, + info->rx_char_buf[info->rx_ubuf] + info->rx_done_bytes, + available); + memcpy(tty->flip.flag_buf_ptr, + info->rx_flag_buf[info->rx_ubuf] + info->rx_done_bytes, + available); + tty->flip.char_buf_ptr += available; + tty->flip.count += available; + tty->flip.flag_buf_ptr += available; + memset(info->rx_flag_buf[info->rx_ubuf] + info->rx_done_bytes, + 0, available); + info->rx_done_bytes += available; + do_queue++; + } + if (info->rx_done_bytes == RX_BUF_SIZE) { + volatile struct dbdma_cmd *cd = info->rx_cmds[info->rx_ubuf]; + + if (info->rx_ubuf == info->rx_cbuf) + goto out; + /* mark rx_char_buf[rx_ubuf] free */ + st_le16(&cd->command, DBDMA_NOP); + cd++; + st_le32(&cd->cmd_dep, 0); + st_le32((unsigned int *)&cd->res_count, 0); + cd++; + st_le16(&cd->xfer_status, 0); + + if (info->rx_fbuf == RX_NO_FBUF) { + info->rx_fbuf = info->rx_ubuf; + if (!(ld_le32(&rd->status) & ACTIVE)) { + dbdma_reset(&info->rx->dma); + rxdma_start(info, info->rx_ubuf); + info->rx_cbuf = info->rx_ubuf; + } + } + info->rx_done_bytes = 0; + if (++info->rx_ubuf == info->rx_nbuf) + info->rx_ubuf = 0; + if (info->rx_fbuf == info->rx_ubuf) + info->rx_fbuf = RX_NO_FBUF; + goto more; + } +out: + spin_unlock_irqrestore(&info->rx_dma_lock, flags); + if (do_queue) + tty_flip_buffer_push(tty); +} + +static void poll_rxdma(unsigned long private_) +{ + struct mac_serial *info = (struct mac_serial *) private_; + unsigned long flags; + + rxdma_to_tty(info); + spin_lock_irqsave(&info->rx_dma_lock, flags); + mod_timer(&info->poll_dma_timer, RX_DMA_TIMER); + spin_unlock_irqrestore(&info->rx_dma_lock, flags); +} + +static void dma_init(struct mac_serial * info) +{ + int i, size; + volatile struct dbdma_cmd *cd; + unsigned char *p; + + info->rx_nbuf = 8; + + /* various mem set up */ + size = sizeof(struct dbdma_cmd) * (3 * info->rx_nbuf + 2) + + (RX_BUF_SIZE * 2 + sizeof(*info->rx_cmds) + + sizeof(*info->rx_char_buf) + sizeof(*info->rx_flag_buf)) + * info->rx_nbuf; + info->dma_priv = kmalloc(size, GFP_KERNEL | GFP_DMA); + if (info->dma_priv == NULL) + return; + memset(info->dma_priv, 0, size); + + info->rx_cmds = (volatile struct dbdma_cmd **)info->dma_priv; + info->rx_char_buf = (unsigned char **) (info->rx_cmds + info->rx_nbuf); + info->rx_flag_buf = info->rx_char_buf + info->rx_nbuf; + p = (unsigned char *) (info->rx_flag_buf + info->rx_nbuf); + for (i = 0; i < info->rx_nbuf; i++, p += RX_BUF_SIZE) + info->rx_char_buf[i] = p; + for (i = 0; i < info->rx_nbuf; i++, p += RX_BUF_SIZE) + info->rx_flag_buf[i] = p; + + /* a bit of DMA programming */ + cd = info->rx_cmds[0] = (volatile struct dbdma_cmd *) DBDMA_ALIGN(p); + st_le16(&cd->command, DBDMA_NOP); + cd++; + st_le16(&cd->req_count, RX_BUF_SIZE); + st_le16(&cd->command, INPUT_MORE); + st_le32(&cd->phy_addr, virt_to_bus(info->rx_char_buf[0])); + cd++; + st_le16(&cd->req_count, 4); + st_le16(&cd->command, STORE_WORD | INTR_ALWAYS); + st_le32(&cd->phy_addr, virt_to_bus(cd-2)); + st_le32(&cd->cmd_dep, DBDMA_STOP); + for (i = 1; i < info->rx_nbuf; i++) { + info->rx_cmds[i] = ++cd; + st_le16(&cd->command, DBDMA_NOP); + cd++; + st_le16(&cd->req_count, RX_BUF_SIZE); + st_le16(&cd->command, INPUT_MORE); + st_le32(&cd->phy_addr, virt_to_bus(info->rx_char_buf[i])); + cd++; + st_le16(&cd->req_count, 4); + st_le16(&cd->command, STORE_WORD | INTR_ALWAYS); + st_le32(&cd->phy_addr, virt_to_bus(cd-2)); + st_le32(&cd->cmd_dep, DBDMA_STOP); + } + cd++; + st_le16(&cd->command, DBDMA_NOP | BR_ALWAYS); + st_le32(&cd->cmd_dep, virt_to_bus(info->rx_cmds[0])); + + /* setup DMA to our liking */ + dbdma_reset(&info->rx->dma); + st_le32(&info->rx->dma.intr_sel, 0x10001); + st_le32(&info->rx->dma.br_sel, 0x10001); + out_le32(&info->rx->dma.wait_sel, 0x10001); + + /* set various flags */ + info->rx_ubuf = 0; + info->rx_cbuf = 0; + info->rx_fbuf = info->rx_ubuf + 1; + if (info->rx_fbuf == info->rx_nbuf) + info->rx_fbuf = RX_NO_FBUF; + info->rx_done_bytes = 0; + + /* setup polling */ + init_timer(&info->poll_dma_timer); + info->poll_dma_timer.function = (void *)&poll_rxdma; + info->poll_dma_timer.data = (unsigned long)info; + + info->dma_initted = 1; +} + +/* + * FixZeroBug....Works around a bug in the SCC receving channel. + * Taken from Darwin code, 15 Sept. 2000 -DanM + * + * The following sequence prevents a problem that is seen with O'Hare ASICs + * (most versions -- also with some Heathrow and Hydra ASICs) where a zero + * at the input to the receiver becomes 'stuck' and locks up the receiver. + * This problem can occur as a result of a zero bit at the receiver input + * coincident with any of the following events: + * + * The SCC is initialized (hardware or software). + * A framing error is detected. + * The clocking option changes from synchronous or X1 asynchronous + * clocking to X16, X32, or X64 asynchronous clocking. + * The decoding mode is changed among NRZ, NRZI, FM0, or FM1. + * + * This workaround attempts to recover from the lockup condition by placing + * the SCC in synchronous loopback mode with a fast clock before programming + * any of the asynchronous modes. + */ +static void fix_zero_bug_scc(struct mac_serial * info) +{ + write_zsreg(info->zs_channel, 9, + (info->zs_channel == info->zs_chan_a? CHRA: CHRB)); + udelay(10); + write_zsreg(info->zs_channel, 9, + ((info->zs_channel == info->zs_chan_a? CHRA: CHRB) | NV)); + + write_zsreg(info->zs_channel, 4, (X1CLK | EXTSYNC)); + + /* I think this is wrong....but, I just copying code.... + */ + write_zsreg(info->zs_channel, 3, (8 & ~RxENABLE)); + + write_zsreg(info->zs_channel, 5, (8 & ~TxENAB)); + write_zsreg(info->zs_channel, 9, NV); /* Didn't we already do this? */ + write_zsreg(info->zs_channel, 11, (RCBR | TCBR)); + write_zsreg(info->zs_channel, 12, 0); + write_zsreg(info->zs_channel, 13, 0); + write_zsreg(info->zs_channel, 14, (LOOPBAK | SSBR)); + write_zsreg(info->zs_channel, 14, (LOOPBAK | SSBR | BRENABL)); + write_zsreg(info->zs_channel, 3, (8 | RxENABLE)); + write_zsreg(info->zs_channel, 0, RES_EXT_INT); + write_zsreg(info->zs_channel, 0, RES_EXT_INT); /* to kill some time */ + + /* The channel should be OK now, but it is probably receiving + * loopback garbage. + * Switch to asynchronous mode, disable the receiver, + * and discard everything in the receive buffer. + */ + write_zsreg(info->zs_channel, 9, NV); + write_zsreg(info->zs_channel, 4, PAR_ENA); + write_zsreg(info->zs_channel, 3, (8 & ~RxENABLE)); + + while (read_zsreg(info->zs_channel, 0) & Rx_CH_AV) { + (void)read_zsreg(info->zs_channel, 8); + write_zsreg(info->zs_channel, 0, RES_EXT_INT); + write_zsreg(info->zs_channel, 0, ERR_RES); + } +} + +static int setup_scc(struct mac_serial * info) +{ + unsigned long flags; + + OPNDBG("setting up ttyS%d SCC...\n", info->line); + + spin_lock_irqsave(&info->lock, flags); + + /* Nice buggy HW ... */ + fix_zero_bug_scc(info); + + /* + * Reset the chip. + */ + write_zsreg(info->zs_channel, 9, + (info->zs_channel == info->zs_chan_a? CHRA: CHRB)); + udelay(10); + write_zsreg(info->zs_channel, 9, 0); + + /* + * Clear the receive FIFO. + */ + ZS_CLEARFIFO(info->zs_channel); + info->xmit_fifo_size = 1; + + /* + * Reset DMAs + */ + if (info->has_dma) + dma_init(info); + + /* + * Clear the interrupt registers. + */ + write_zsreg(info->zs_channel, 0, ERR_RES); + write_zsreg(info->zs_channel, 0, RES_H_IUS); + + /* + * Turn on RTS and DTR. + */ + if (!info->is_irda) + zs_rtsdtr(info, 1); + + /* + * Finally, enable sequencing and interrupts + */ + if (!info->dma_initted) { + /* interrupt on ext/status changes, all received chars, + transmit ready */ + info->curregs[1] = (info->curregs[1] & ~0x18) + | (EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB); + } else { + /* interrupt on ext/status changes, W/Req pin is + receive DMA request */ + info->curregs[1] = (info->curregs[1] & ~(0x18 | TxINT_ENAB)) + | (EXT_INT_ENAB | WT_RDY_RT | WT_FN_RDYFN); + write_zsreg(info->zs_channel, 1, info->curregs[1]); + /* enable W/Req pin */ + info->curregs[1] |= WT_RDY_ENAB; + write_zsreg(info->zs_channel, 1, info->curregs[1]); + /* enable interrupts on transmit ready and receive errors */ + info->curregs[1] |= INT_ERR_Rx | TxINT_ENAB; + } + info->pendregs[1] = info->curregs[1]; + info->curregs[3] |= (RxENABLE | Rx8); + info->pendregs[3] = info->curregs[3]; + info->curregs[5] |= (TxENAB | Tx8); + info->pendregs[5] = info->curregs[5]; + info->curregs[9] |= (NV | MIE); + info->pendregs[9] = info->curregs[9]; + write_zsreg(info->zs_channel, 3, info->curregs[3]); + write_zsreg(info->zs_channel, 5, info->curregs[5]); + write_zsreg(info->zs_channel, 9, info->curregs[9]); + + if (info->tty) + clear_bit(TTY_IO_ERROR, &info->tty->flags); + info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; + + spin_unlock_irqrestore(&info->lock, flags); + + /* + * Set the speed of the serial port + */ + change_speed(info, 0); + + /* Save the current value of RR0 */ + info->read_reg_zero = read_zsreg(info->zs_channel, 0); + + if (info->dma_initted) { + spin_lock_irqsave(&info->rx_dma_lock, flags); + rxdma_start(info, 0); + info->poll_dma_timer.expires = RX_DMA_TIMER; + add_timer(&info->poll_dma_timer); + spin_unlock_irqrestore(&info->rx_dma_lock, flags); + } + + return 0; +} + +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void shutdown(struct mac_serial * info) +{ + OPNDBG("Shutting down serial port %d (irq %d)....\n", info->line, + info->irq); + + if (!(info->flags & ZILOG_INITIALIZED)) { + OPNDBG("(already shutdown)\n"); + return; + } + + if (info->has_dma) { + del_timer(&info->poll_dma_timer); + dbdma_reset(info->tx_dma); + dbdma_reset(&info->rx->dma); + disable_irq(info->tx_dma_irq); + disable_irq(info->rx_dma_irq); + } + disable_irq(info->irq); + + info->pendregs[1] = info->curregs[1] = 0; + write_zsreg(info->zs_channel, 1, 0); /* no interrupts */ + + info->curregs[3] &= ~RxENABLE; + info->pendregs[3] = info->curregs[3]; + write_zsreg(info->zs_channel, 3, info->curregs[3]); + + info->curregs[5] &= ~TxENAB; + if (!info->tty || C_HUPCL(info->tty)) + info->curregs[5] &= ~DTR; + info->pendregs[5] = info->curregs[5]; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + set_scc_power(info, 0); + + if (info->xmit_buf) { + free_page((unsigned long) info->xmit_buf); + info->xmit_buf = 0; + } + + if (info->has_dma && info->dma_priv) { + kfree(info->dma_priv); + info->dma_priv = NULL; + info->dma_initted = 0; + } + + memset(info->curregs, 0, sizeof(info->curregs)); + memset(info->pendregs, 0, sizeof(info->pendregs)); + + info->flags &= ~ZILOG_INITIALIZED; +} + +/* + * Turn power on or off to the SCC and associated stuff + * (port drivers, modem, IR port, etc.) + * Returns the number of milliseconds we should wait before + * trying to use the port. + */ +static int set_scc_power(struct mac_serial * info, int state) +{ + int delay = 0; + + if (state) { + PWRDBG("ttyS%d: powering up hardware\n", info->line); + pmac_call_feature( + PMAC_FTR_SCC_ENABLE, + info->dev_node, info->port_type, 1); + if (info->is_internal_modem) { + pmac_call_feature( + PMAC_FTR_MODEM_ENABLE, + info->dev_node, 0, 1); + delay = 2500; /* wait for 2.5s before using */ + } else if (info->is_irda) + mdelay(50); /* Do better here once the problems + * with blocking have been ironed out + */ + } else { + /* TODO: Make that depend on a timer, don't power down + * immediately + */ + PWRDBG("ttyS%d: shutting down hardware\n", info->line); + if (info->is_internal_modem) { + PWRDBG("ttyS%d: shutting down modem\n", info->line); + pmac_call_feature( + PMAC_FTR_MODEM_ENABLE, + info->dev_node, 0, 0); + } + pmac_call_feature( + PMAC_FTR_SCC_ENABLE, + info->dev_node, info->port_type, 0); + } + return delay; +} + +static void irda_rts_pulses(struct mac_serial *info, int w) +{ + udelay(w); + write_zsreg(info->zs_channel, 5, Tx8 | TxENAB); + udelay(2); + write_zsreg(info->zs_channel, 5, Tx8 | TxENAB | RTS); + udelay(8); + write_zsreg(info->zs_channel, 5, Tx8 | TxENAB); + udelay(4); + write_zsreg(info->zs_channel, 5, Tx8 | TxENAB | RTS); +} + +/* + * Set the irda codec on the imac to the specified baud rate. + */ +static void irda_setup(struct mac_serial *info) +{ + int code, speed, t; + + speed = info->tty->termios->c_cflag & CBAUD; + if (speed < B2400 || speed > B115200) + return; + code = 0x4d + B115200 - speed; + + /* disable serial interrupts and receive DMA */ + write_zsreg(info->zs_channel, 1, info->curregs[1] & ~0x9f); + + /* wait for transmitter to drain */ + t = 10000; + while ((read_zsreg(info->zs_channel, 0) & Tx_BUF_EMP) == 0 + || (read_zsreg(info->zs_channel, 1) & ALL_SNT) == 0) { + if (--t <= 0) { + printk(KERN_ERR "transmitter didn't drain\n"); + return; + } + udelay(10); + } + udelay(100); + + /* set to 8 bits, no parity, 19200 baud, RTS on, DTR off */ + write_zsreg(info->zs_channel, 4, X16CLK | SB1); + write_zsreg(info->zs_channel, 11, TCBR | RCBR); + t = BPS_TO_BRG(19200, ZS_CLOCK/16); + write_zsreg(info->zs_channel, 12, t); + write_zsreg(info->zs_channel, 13, t >> 8); + write_zsreg(info->zs_channel, 14, BRENABL); + write_zsreg(info->zs_channel, 3, Rx8 | RxENABLE); + write_zsreg(info->zs_channel, 5, Tx8 | TxENAB | RTS); + + /* set TxD low for ~104us and pulse RTS */ + udelay(1000); + write_zsdata(info->zs_channel, 0xfe); + irda_rts_pulses(info, 150); + irda_rts_pulses(info, 180); + irda_rts_pulses(info, 50); + udelay(100); + + /* assert DTR, wait 30ms, talk to the chip */ + write_zsreg(info->zs_channel, 5, Tx8 | TxENAB | RTS | DTR); + mdelay(30); + while (read_zsreg(info->zs_channel, 0) & Rx_CH_AV) + read_zsdata(info->zs_channel); + + write_zsdata(info->zs_channel, 1); + t = 1000; + while ((read_zsreg(info->zs_channel, 0) & Rx_CH_AV) == 0) { + if (--t <= 0) { + printk(KERN_ERR "irda_setup timed out on 1st byte\n"); + goto out; + } + udelay(10); + } + t = read_zsdata(info->zs_channel); + if (t != 4) + printk(KERN_ERR "irda_setup 1st byte = %x\n", t); + + write_zsdata(info->zs_channel, code); + t = 1000; + while ((read_zsreg(info->zs_channel, 0) & Rx_CH_AV) == 0) { + if (--t <= 0) { + printk(KERN_ERR "irda_setup timed out on 2nd byte\n"); + goto out; + } + udelay(10); + } + t = read_zsdata(info->zs_channel); + if (t != code) + printk(KERN_ERR "irda_setup 2nd byte = %x (%x)\n", t, code); + + /* Drop DTR again and do some more RTS pulses */ + out: + udelay(100); + write_zsreg(info->zs_channel, 5, Tx8 | TxENAB | RTS); + irda_rts_pulses(info, 80); + + /* We should be right to go now. We assume that load_zsregs + will get called soon to load up the correct baud rate etc. */ + info->curregs[5] = (info->curregs[5] | RTS) & ~DTR; + info->pendregs[5] = info->curregs[5]; +} + +/* + * This routine is called to set the UART divisor registers to match + * the specified baud rate for a serial port. + */ +static void change_speed(struct mac_serial *info, struct termios *old_termios) +{ + unsigned cflag; + int bits; + int brg, baud; + unsigned long flags; + + if (!info->tty || !info->tty->termios) + return; + + cflag = info->tty->termios->c_cflag; + baud = tty_get_baud_rate(info->tty); + if (baud == 0) { + if (old_termios) { + info->tty->termios->c_cflag &= ~CBAUD; + info->tty->termios->c_cflag |= (old_termios->c_cflag & CBAUD); + cflag = info->tty->termios->c_cflag; + baud = tty_get_baud_rate(info->tty); + } + else + baud = info->zs_baud; + } + if (baud > 230400) + baud = 230400; + else if (baud == 0) + baud = 38400; + + spin_lock_irqsave(&info->lock, flags); + info->zs_baud = baud; + info->clk_divisor = 16; + + BAUDBG(KERN_DEBUG "set speed to %d bds, ", baud); + + switch (baud) { + case ZS_CLOCK/16: /* 230400 */ + info->curregs[4] = X16CLK; + info->curregs[11] = 0; + break; + case ZS_CLOCK/32: /* 115200 */ + info->curregs[4] = X32CLK; + info->curregs[11] = 0; + break; + default: + info->curregs[4] = X16CLK; + info->curregs[11] = TCBR | RCBR; + brg = BPS_TO_BRG(baud, ZS_CLOCK/info->clk_divisor); + info->curregs[12] = (brg & 255); + info->curregs[13] = ((brg >> 8) & 255); + info->curregs[14] = BRENABL; + } + + /* byte size and parity */ + info->curregs[3] &= ~RxNBITS_MASK; + info->curregs[5] &= ~TxNBITS_MASK; + switch (cflag & CSIZE) { + case CS5: + info->curregs[3] |= Rx5; + info->curregs[5] |= Tx5; + BAUDBG("5 bits, "); + bits = 7; + break; + case CS6: + info->curregs[3] |= Rx6; + info->curregs[5] |= Tx6; + BAUDBG("6 bits, "); + bits = 8; + break; + case CS7: + info->curregs[3] |= Rx7; + info->curregs[5] |= Tx7; + BAUDBG("7 bits, "); + bits = 9; + break; + case CS8: + default: /* defaults to 8 bits */ + info->curregs[3] |= Rx8; + info->curregs[5] |= Tx8; + BAUDBG("8 bits, "); + bits = 10; + break; + } + info->pendregs[3] = info->curregs[3]; + info->pendregs[5] = info->curregs[5]; + + info->curregs[4] &= ~(SB_MASK | PAR_ENA | PAR_EVEN); + if (cflag & CSTOPB) { + info->curregs[4] |= SB2; + bits++; + BAUDBG("2 stop, "); + } else { + info->curregs[4] |= SB1; + BAUDBG("1 stop, "); + } + if (cflag & PARENB) { + bits++; + info->curregs[4] |= PAR_ENA; + BAUDBG("parity, "); + } + if (!(cflag & PARODD)) { + info->curregs[4] |= PAR_EVEN; + } + info->pendregs[4] = info->curregs[4]; + + if (!(cflag & CLOCAL)) { + if (!(info->curregs[15] & DCDIE)) + info->read_reg_zero = read_zsreg(info->zs_channel, 0); + info->curregs[15] |= DCDIE; + } else + info->curregs[15] &= ~DCDIE; + if (cflag & CRTSCTS) { + info->curregs[15] |= CTSIE; + if ((read_zsreg(info->zs_channel, 0) & CTS) != 0) + info->tx_stopped = 1; + } else { + info->curregs[15] &= ~CTSIE; + info->tx_stopped = 0; + } + info->pendregs[15] = info->curregs[15]; + + /* Calc timeout value. This is pretty broken with high baud rates with HZ=100. + This code would love a larger HZ and a >1 fifo size, but this is not + a priority. The resulting value must be >HZ/2 + */ + info->timeout = ((info->xmit_fifo_size*HZ*bits) / baud); + info->timeout += HZ/50+1; /* Add .02 seconds of slop */ + + BAUDBG("timeout=%d/%ds, base:%d\n", (int)info->timeout, (int)HZ, + (int)info->baud_base); + + /* set the irda codec to the right rate */ + if (info->is_irda) + irda_setup(info); + + /* Load up the new values */ + load_zsregs(info->zs_channel, info->curregs); + + spin_unlock_irqrestore(&info->lock, flags); +} + +static void rs_flush_chars(struct tty_struct *tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->name, "rs_flush_chars")) + return; + + spin_lock_irqsave(&info->lock, flags); + if (!(info->xmit_cnt <= 0 || tty->stopped || info->tx_stopped || + !info->xmit_buf)) + /* Enable transmitter */ + transmit_chars(info); + spin_unlock_irqrestore(&info->lock, flags); +} + +static int rs_write(struct tty_struct * tty, + const unsigned char *buf, int count) +{ + int c, ret = 0; + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->name, "rs_write")) + return 0; + + if (!tty || !info->xmit_buf || !tmp_buf) + return 0; + + while (1) { + spin_lock_irqsave(&info->lock, flags); + c = min_t(int, count, min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, + SERIAL_XMIT_SIZE - info->xmit_head)); + if (c <= 0) { + spin_unlock_irqrestore(&info->lock, flags); + break; + } + memcpy(info->xmit_buf + info->xmit_head, buf, c); + info->xmit_head = ((info->xmit_head + c) & + (SERIAL_XMIT_SIZE-1)); + info->xmit_cnt += c; + spin_unlock_irqrestore(&info->lock, flags); + buf += c; + count -= c; + ret += c; + } + spin_lock_irqsave(&info->lock, flags); + if (info->xmit_cnt && !tty->stopped && !info->tx_stopped + && !info->tx_active) + transmit_chars(info); + spin_unlock_irqrestore(&info->lock, flags); + return ret; +} + +static int rs_write_room(struct tty_struct *tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + int ret; + + if (serial_paranoia_check(info, tty->name, "rs_write_room")) + return 0; + ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1; + if (ret < 0) + ret = 0; + return ret; +} + +static int rs_chars_in_buffer(struct tty_struct *tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + + if (serial_paranoia_check(info, tty->name, "rs_chars_in_buffer")) + return 0; + return info->xmit_cnt; +} + +static void rs_flush_buffer(struct tty_struct *tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->name, "rs_flush_buffer")) + return; + spin_lock_irqsave(&info->lock, flags); + info->xmit_cnt = info->xmit_head = info->xmit_tail = 0; + spin_unlock_irqrestore(&info->lock, flags); + tty_wakeup(tty); +} + +/* + * ------------------------------------------------------------ + * rs_throttle() + * + * This routine is called by the upper-layer tty layer to signal that + * incoming characters should be throttled. + * ------------------------------------------------------------ + */ +static void rs_throttle(struct tty_struct * tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + unsigned long flags; +#ifdef SERIAL_DEBUG_THROTTLE + printk(KERN_DEBUG "throttle %ld....\n",tty->ldisc.chars_in_buffer(tty)); +#endif + + if (serial_paranoia_check(info, tty->name, "rs_throttle")) + return; + + if (I_IXOFF(tty)) { + spin_lock_irqsave(&info->lock, flags); + info->x_char = STOP_CHAR(tty); + if (!info->tx_active) + transmit_chars(info); + spin_unlock_irqrestore(&info->lock, flags); + } + + if (C_CRTSCTS(tty)) { + /* + * Here we want to turn off the RTS line. On Macintoshes, + * the external serial ports using a DIN-8 or DIN-9 + * connector only have the DTR line (which is usually + * wired to both RTS and DTR on an external modem in + * the cable). RTS doesn't go out to the serial port + * socket, it acts as an output enable for the transmit + * data line. So in this case we don't drop RTS. + * + * Macs with internal modems generally do have both RTS + * and DTR wired to the modem, so in that case we do + * drop RTS. + */ + if (info->is_internal_modem) { + spin_lock_irqsave(&info->lock, flags); + info->curregs[5] &= ~RTS; + info->pendregs[5] &= ~RTS; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + spin_unlock_irqrestore(&info->lock, flags); + } + } + +#ifdef CDTRCTS + if (tty->termios->c_cflag & CDTRCTS) { + spin_lock_irqsave(&info->lock, flags); + info->curregs[5] &= ~DTR; + info->pendregs[5] &= ~DTR; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + spin_unlock_irqrestore(&info->lock, flags); + } +#endif /* CDTRCTS */ +} + +static void rs_unthrottle(struct tty_struct * tty) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + unsigned long flags; +#ifdef SERIAL_DEBUG_THROTTLE + printk(KERN_DEBUG "unthrottle %s: %d....\n", + tty->ldisc.chars_in_buffer(tty)); +#endif + + if (serial_paranoia_check(info, tty->name, "rs_unthrottle")) + return; + + if (I_IXOFF(tty)) { + spin_lock_irqsave(&info->lock, flags); + if (info->x_char) + info->x_char = 0; + else { + info->x_char = START_CHAR(tty); + if (!info->tx_active) + transmit_chars(info); + } + spin_unlock_irqrestore(&info->lock, flags); + } + + if (C_CRTSCTS(tty) && info->is_internal_modem) { + /* Assert RTS line */ + spin_lock_irqsave(&info->lock, flags); + info->curregs[5] |= RTS; + info->pendregs[5] |= RTS; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + spin_unlock_irqrestore(&info->lock, flags); + } + +#ifdef CDTRCTS + if (tty->termios->c_cflag & CDTRCTS) { + /* Assert DTR line */ + spin_lock_irqsave(&info->lock, flags); + info->curregs[5] |= DTR; + info->pendregs[5] |= DTR; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + spin_unlock_irqrestore(&info->lock, flags); + } +#endif +} + +/* + * ------------------------------------------------------------ + * rs_ioctl() and friends + * ------------------------------------------------------------ + */ + +static int get_serial_info(struct mac_serial * info, + struct serial_struct __user * retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + memset(&tmp, 0, sizeof(tmp)); + tmp.type = info->type; + tmp.line = info->line; + tmp.port = info->port; + tmp.irq = info->irq; + tmp.flags = info->flags; + tmp.baud_base = info->baud_base; + tmp.close_delay = info->close_delay; + tmp.closing_wait = info->closing_wait; + tmp.custom_divisor = info->custom_divisor; + if (copy_to_user(retinfo,&tmp,sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct mac_serial * info, + struct serial_struct __user * new_info) +{ + struct serial_struct new_serial; + struct mac_serial old_info; + int retval = 0; + + if (copy_from_user(&new_serial,new_info,sizeof(new_serial))) + return -EFAULT; + old_info = *info; + + if (!capable(CAP_SYS_ADMIN)) { + if ((new_serial.baud_base != info->baud_base) || + (new_serial.type != info->type) || + (new_serial.close_delay != info->close_delay) || + ((new_serial.flags & ~ZILOG_USR_MASK) != + (info->flags & ~ZILOG_USR_MASK))) + return -EPERM; + info->flags = ((info->flags & ~ZILOG_USR_MASK) | + (new_serial.flags & ZILOG_USR_MASK)); + info->custom_divisor = new_serial.custom_divisor; + goto check_and_exit; + } + + if (info->count > 1) + return -EBUSY; + + /* + * OK, past this point, all the error checking has been done. + * At this point, we start making changes..... + */ + + info->baud_base = new_serial.baud_base; + info->flags = ((info->flags & ~ZILOG_FLAGS) | + (new_serial.flags & ZILOG_FLAGS)); + info->type = new_serial.type; + info->close_delay = new_serial.close_delay; + info->closing_wait = new_serial.closing_wait; + +check_and_exit: + if (info->flags & ZILOG_INITIALIZED) + retval = setup_scc(info); + return retval; +} + +/* + * get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows an RS485 driver to be written in user space. + */ +static int get_lsr_info(struct mac_serial * info, unsigned int *value) +{ + unsigned char status; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + status = read_zsreg(info->zs_channel, 0); + spin_unlock_irqrestore(&info->lock, flags); + status = (status & Tx_BUF_EMP)? TIOCSER_TEMT: 0; + return put_user(status,value); +} + +static int rs_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct mac_serial * info = (struct mac_serial *)tty->driver_data; + unsigned char control, status; + unsigned long flags; + +#ifdef CONFIG_KGDB + if (info->kgdb_channel) + return -ENODEV; +#endif + if (serial_paranoia_check(info, tty->name, __FUNCTION__)) + return -ENODEV; + + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + + spin_lock_irqsave(&info->lock, flags); + control = info->curregs[5]; + status = read_zsreg(info->zs_channel, 0); + spin_unlock_irqrestore(&info->lock, flags); + return ((control & RTS) ? TIOCM_RTS: 0) + | ((control & DTR) ? TIOCM_DTR: 0) + | ((status & DCD) ? TIOCM_CAR: 0) + | ((status & CTS) ? 0: TIOCM_CTS); +} + +static int rs_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct mac_serial * info = (struct mac_serial *)tty->driver_data; + unsigned int arg, bits; + unsigned long flags; + +#ifdef CONFIG_KGDB + if (info->kgdb_channel) + return -ENODEV; +#endif + if (serial_paranoia_check(info, tty->name, __FUNCTION__)) + return -ENODEV; + + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + + spin_lock_irqsave(&info->lock, flags); + if (set & TIOCM_RTS) + info->curregs[5] |= RTS; + if (set & TIOCM_DTR) + info->curregs[5] |= DTR; + if (clear & TIOCM_RTS) + info->curregs[5] &= ~RTS; + if (clear & TIOCM_DTR) + info->curregs[5] &= ~DTR; + + info->pendregs[5] = info->curregs[5]; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + spin_unlock_irqrestore(&info->lock, flags); + return 0; +} + +/* + * rs_break - turn transmit break condition on/off + */ +static void rs_break(struct tty_struct *tty, int break_state) +{ + struct mac_serial *info = (struct mac_serial *) tty->driver_data; + unsigned long flags; + + if (serial_paranoia_check(info, tty->name, "rs_break")) + return; + + spin_lock_irqsave(&info->lock, flags); + if (break_state == -1) + info->curregs[5] |= SND_BRK; + else + info->curregs[5] &= ~SND_BRK; + write_zsreg(info->zs_channel, 5, info->curregs[5]); + spin_unlock_irqrestore(&info->lock, flags); +} + +static int rs_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct mac_serial * info = (struct mac_serial *)tty->driver_data; + +#ifdef CONFIG_KGDB + if (info->kgdb_channel) + return -ENODEV; +#endif + if (serial_paranoia_check(info, tty->name, "rs_ioctl")) + return -ENODEV; + + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + } + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(info, + (struct serial_struct __user *) arg); + case TIOCSSERIAL: + return set_serial_info(info, + (struct serial_struct __user *) arg); + case TIOCSERGETLSR: /* Get line status register */ + return get_lsr_info(info, (unsigned int *) arg); + + case TIOCSERGSTRUCT: + if (copy_to_user((struct mac_serial __user *) arg, + info, sizeof(struct mac_serial))) + return -EFAULT; + return 0; + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + struct mac_serial *info = (struct mac_serial *)tty->driver_data; + int was_stopped; + + if (tty->termios->c_cflag == old_termios->c_cflag) + return; + was_stopped = info->tx_stopped; + + change_speed(info, old_termios); + + if (was_stopped && !info->tx_stopped) { + tty->hw_stopped = 0; + rs_start(tty); + } +} + +/* + * ------------------------------------------------------------ + * rs_close() + * + * This routine is called when the serial port gets closed. + * Wait for the last remaining data to be sent. + * ------------------------------------------------------------ + */ +static void rs_close(struct tty_struct *tty, struct file * filp) +{ + struct mac_serial * info = (struct mac_serial *)tty->driver_data; + unsigned long flags; + + if (!info || serial_paranoia_check(info, tty->name, "rs_close")) + return; + + spin_lock_irqsave(&info->lock, flags); + + if (tty_hung_up_p(filp)) { + spin_unlock_irqrestore(&info->lock, flags); + return; + } + + OPNDBG("rs_close ttyS%d, count = %d\n", info->line, info->count); + if ((tty->count == 1) && (info->count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. Info->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + printk(KERN_ERR "rs_close: bad serial port count; tty->count " + "is 1, info->count is %d\n", info->count); + info->count = 1; + } + if (--info->count < 0) { + printk(KERN_ERR "rs_close: bad serial port count for " + "ttyS%d: %d\n", info->line, info->count); + info->count = 0; + } + if (info->count) { + spin_unlock_irqrestore(&info->lock, flags); + return; + } + info->flags |= ZILOG_CLOSING; + /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + OPNDBG("waiting end of Tx... (timeout:%d)\n", info->closing_wait); + tty->closing = 1; + if (info->closing_wait != ZILOG_CLOSING_WAIT_NONE) { + spin_unlock_irqrestore(&info->lock, flags); + tty_wait_until_sent(tty, info->closing_wait); + spin_lock_irqsave(&info->lock, flags); + } + + /* + * At this point we stop accepting input. To do this, we + * disable the receiver and receive interrupts. + */ + info->curregs[3] &= ~RxENABLE; + info->pendregs[3] = info->curregs[3]; + write_zsreg(info->zs_channel, 3, info->curregs[3]); + info->curregs[1] &= ~(0x18); /* disable any rx ints */ + info->pendregs[1] = info->curregs[1]; + write_zsreg(info->zs_channel, 1, info->curregs[1]); + ZS_CLEARFIFO(info->zs_channel); + if (info->flags & ZILOG_INITIALIZED) { + /* + * Before we drop DTR, make sure the SCC transmitter + * has completely drained. + */ + OPNDBG("waiting end of Rx...\n"); + spin_unlock_irqrestore(&info->lock, flags); + rs_wait_until_sent(tty, info->timeout); + spin_lock_irqsave(&info->lock, flags); + } + + shutdown(info); + /* restore flags now since shutdown() will have disabled this port's + specific irqs */ + spin_unlock_irqrestore(&info->lock, flags); + + if (tty->driver->flush_buffer) + tty->driver->flush_buffer(tty); + tty_ldisc_flush(tty); + tty->closing = 0; + info->event = 0; + info->tty = 0; + + if (info->blocked_open) { + if (info->close_delay) { + msleep_interruptible(jiffies_to_msecs(info->close_delay)); + } + wake_up_interruptible(&info->open_wait); + } + info->flags &= ~(ZILOG_NORMAL_ACTIVE|ZILOG_CLOSING); + wake_up_interruptible(&info->close_wait); +} + +/* + * rs_wait_until_sent() --- wait until the transmitter is empty + */ +static void rs_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct mac_serial *info = (struct mac_serial *) tty->driver_data; + unsigned long orig_jiffies, char_time; + + if (serial_paranoia_check(info, tty->name, "rs_wait_until_sent")) + return; + +/* printk("rs_wait_until_sent, timeout:%d, tty_stopped:%d, tx_stopped:%d\n", + timeout, tty->stopped, info->tx_stopped); +*/ + orig_jiffies = jiffies; + /* + * Set the check interval to be 1/5 of the estimated time to + * send a single character, and make it at least 1. The check + * interval should also be less than the timeout. + */ + if (info->timeout <= HZ/50) { + printk(KERN_INFO "macserial: invalid info->timeout=%d\n", + info->timeout); + info->timeout = HZ/50+1; + } + + char_time = (info->timeout - HZ/50) / info->xmit_fifo_size; + char_time = char_time / 5; + if (char_time > HZ) { + printk(KERN_WARNING "macserial: char_time %ld >HZ !!!\n", + char_time); + char_time = 1; + } else if (char_time == 0) + char_time = 1; + if (timeout) + char_time = min_t(unsigned long, char_time, timeout); + while ((read_zsreg(info->zs_channel, 1) & ALL_SNT) == 0) { + msleep_interruptible(jiffies_to_msecs(char_time)); + if (signal_pending(current)) + break; + if (timeout && time_after(jiffies, orig_jiffies + timeout)) + break; + } + current->state = TASK_RUNNING; +} + +/* + * rs_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +static void rs_hangup(struct tty_struct *tty) +{ + struct mac_serial * info = (struct mac_serial *)tty->driver_data; + + if (serial_paranoia_check(info, tty->name, "rs_hangup")) + return; + + rs_flush_buffer(tty); + shutdown(info); + info->event = 0; + info->count = 0; + info->flags &= ~ZILOG_NORMAL_ACTIVE; + info->tty = 0; + wake_up_interruptible(&info->open_wait); +} + +/* + * ------------------------------------------------------------ + * rs_open() and friends + * ------------------------------------------------------------ + */ +static int block_til_ready(struct tty_struct *tty, struct file * filp, + struct mac_serial *info) +{ + DECLARE_WAITQUEUE(wait,current); + int retval; + int do_clocal = 0; + + /* + * If the device is in the middle of being closed, then block + * until it's done, and then try again. + */ + if (info->flags & ZILOG_CLOSING) { + interruptible_sleep_on(&info->close_wait); + return -EAGAIN; + } + + /* + * If non-blocking mode is set, or the port is not enabled, + * then make the check up front and then exit. + */ + if ((filp->f_flags & O_NONBLOCK) || + (tty->flags & (1 << TTY_IO_ERROR))) { + info->flags |= ZILOG_NORMAL_ACTIVE; + return 0; + } + + if (tty->termios->c_cflag & CLOCAL) + do_clocal = 1; + + /* + * Block waiting for the carrier detect and the line to become + * free (i.e., not in use by the callout). While we are in + * this loop, info->count is dropped by one, so that + * rs_close() knows when to free things. We restore it upon + * exit, either normal or abnormal. + */ + retval = 0; + add_wait_queue(&info->open_wait, &wait); + OPNDBG("block_til_ready before block: ttyS%d, count = %d\n", + info->line, info->count); + spin_lock_irq(&info->lock); + if (!tty_hung_up_p(filp)) + info->count--; + spin_unlock_irq(&info->lock); + info->blocked_open++; + while (1) { + spin_lock_irq(&info->lock); + if ((tty->termios->c_cflag & CBAUD) && + !info->is_irda) + zs_rtsdtr(info, 1); + spin_unlock_irq(&info->lock); + set_current_state(TASK_INTERRUPTIBLE); + if (tty_hung_up_p(filp) || + !(info->flags & ZILOG_INITIALIZED)) { + retval = -EAGAIN; + break; + } + if (!(info->flags & ZILOG_CLOSING) && + (do_clocal || (read_zsreg(info->zs_channel, 0) & DCD))) + break; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + OPNDBG("block_til_ready blocking: ttyS%d, count = %d\n", + info->line, info->count); + schedule(); + } + current->state = TASK_RUNNING; + remove_wait_queue(&info->open_wait, &wait); + if (!tty_hung_up_p(filp)) + info->count++; + info->blocked_open--; + OPNDBG("block_til_ready after blocking: ttyS%d, count = %d\n", + info->line, info->count); + if (retval) + return retval; + info->flags |= ZILOG_NORMAL_ACTIVE; + return 0; +} + +/* + * This routine is called whenever a serial port is opened. It + * enables interrupts for a serial port, linking in its ZILOG structure into + * the IRQ chain. It also performs the serial-specific + * initialization for the tty structure. + */ +static int rs_open(struct tty_struct *tty, struct file * filp) +{ + struct mac_serial *info; + int retval, line; + unsigned long page; + + line = tty->index; + if ((line < 0) || (line >= zs_channels_found)) { + return -ENODEV; + } + info = zs_soft + line; + +#ifdef CONFIG_KGDB + if (info->kgdb_channel) { + return -ENODEV; + } +#endif + if (serial_paranoia_check(info, tty->name, "rs_open")) + return -ENODEV; + OPNDBG("rs_open %s, count = %d, tty=%p\n", tty->name, + info->count, tty); + + info->count++; + tty->driver_data = info; + info->tty = tty; + + if (!tmp_buf) { + page = get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + if (tmp_buf) + free_page(page); + else + tmp_buf = (unsigned char *) page; + } + + /* + * If the port is the middle of closing, bail out now + */ + if (tty_hung_up_p(filp) || + (info->flags & ZILOG_CLOSING)) { + if (info->flags & ZILOG_CLOSING) + interruptible_sleep_on(&info->close_wait); + return -EAGAIN; + } + + /* + * Start up serial port + */ + + retval = startup(info); + if (retval) + return retval; + + retval = block_til_ready(tty, filp, info); + if (retval) { + OPNDBG("rs_open returning after block_til_ready with %d\n", + retval); + return retval; + } + +#ifdef CONFIG_SERIAL_CONSOLE + if (sercons.cflag && sercons.index == line) { + tty->termios->c_cflag = sercons.cflag; + sercons.cflag = 0; + change_speed(info, 0); + } +#endif + + OPNDBG("rs_open %s successful...\n", tty->name); + return 0; +} + +/* Finally, routines used to initialize the serial driver. */ + +static void show_serial_version(void) +{ + printk(KERN_INFO "PowerMac Z8530 serial driver version " MACSERIAL_VERSION "\n"); +} + +/* + * Initialize one channel, both the mac_serial and mac_zschannel + * structs. We use the dev_node field of the mac_serial struct. + */ +static int +chan_init(struct mac_serial *zss, struct mac_zschannel *zs_chan, + struct mac_zschannel *zs_chan_a) +{ + struct device_node *ch = zss->dev_node; + char *conn; + int len; + struct slot_names_prop { + int count; + char name[1]; + } *slots; + + zss->irq = ch->intrs[0].line; + zss->has_dma = 0; +#if !defined(CONFIG_KGDB) && defined(SUPPORT_SERIAL_DMA) + if (ch->n_addrs >= 3 && ch->n_intrs == 3) + zss->has_dma = 1; +#endif + zss->dma_initted = 0; + + zs_chan->control = (volatile unsigned char *) + ioremap(ch->addrs[0].address, 0x1000); + zs_chan->data = zs_chan->control + 0x10; + spin_lock_init(&zs_chan->lock); + zs_chan->parent = zss; + zss->zs_channel = zs_chan; + zss->zs_chan_a = zs_chan_a; + + /* setup misc varariables */ + zss->kgdb_channel = 0; + + /* For now, we assume you either have a slot-names property + * with "Modem" in it, or your channel is compatible with + * "cobalt". Might need additional fixups + */ + zss->is_internal_modem = device_is_compatible(ch, "cobalt"); + conn = get_property(ch, "AAPL,connector", &len); + zss->is_irda = conn && (strcmp(conn, "infrared") == 0); + zss->port_type = PMAC_SCC_ASYNC; + /* 1999 Powerbook G3 has slot-names property instead */ + slots = (struct slot_names_prop *)get_property(ch, "slot-names", &len); + if (slots && slots->count > 0) { + if (strcmp(slots->name, "IrDA") == 0) + zss->is_irda = 1; + else if (strcmp(slots->name, "Modem") == 0) + zss->is_internal_modem = 1; + } + if (zss->is_irda) + zss->port_type = PMAC_SCC_IRDA; + if (zss->is_internal_modem) { + struct device_node* i2c_modem = find_devices("i2c-modem"); + if (i2c_modem) { + char* mid = get_property(i2c_modem, "modem-id", NULL); + if (mid) switch(*mid) { + case 0x04 : + case 0x05 : + case 0x07 : + case 0x08 : + case 0x0b : + case 0x0c : + zss->port_type = PMAC_SCC_I2S1; + } + printk(KERN_INFO "macserial: i2c-modem detected, id: %d\n", + mid ? (*mid) : 0); + } else { + printk(KERN_INFO "macserial: serial modem detected\n"); + } + } + + while (zss->has_dma) { + zss->dma_priv = NULL; + /* it seems that the last two addresses are the + DMA controllers */ + zss->tx_dma = (volatile struct dbdma_regs *) + ioremap(ch->addrs[ch->n_addrs - 2].address, 0x100); + zss->rx = (volatile struct mac_dma *) + ioremap(ch->addrs[ch->n_addrs - 1].address, 0x100); + zss->tx_dma_irq = ch->intrs[1].line; + zss->rx_dma_irq = ch->intrs[2].line; + spin_lock_init(&zss->rx_dma_lock); + break; + } + + init_timer(&zss->powerup_timer); + zss->powerup_timer.function = powerup_done; + zss->powerup_timer.data = (unsigned long) zss; + return 0; +} + +/* + * /proc fs routines. TODO: Add status lines & error stats + */ +static inline int +line_info(char *buf, struct mac_serial *info) +{ + int ret=0; + unsigned char* connector; + int lenp; + + ret += sprintf(buf, "%d: port:0x%X irq:%d", info->line, info->port, info->irq); + + connector = get_property(info->dev_node, "AAPL,connector", &lenp); + if (connector) + ret+=sprintf(buf+ret," con:%s ", connector); + if (info->is_internal_modem) { + if (!connector) + ret+=sprintf(buf+ret," con:"); + ret+=sprintf(buf+ret,"%s", " (internal modem)"); + } + if (info->is_irda) { + if (!connector) + ret+=sprintf(buf+ret," con:"); + ret+=sprintf(buf+ret,"%s", " (IrDA)"); + } + ret+=sprintf(buf+ret,"\n"); + + return ret; +} + +int macserial_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + int l, len = 0; + off_t begin = 0; + struct mac_serial *info; + + len += sprintf(page, "serinfo:1.0 driver:" MACSERIAL_VERSION "\n"); + for (info = zs_chain; info && len < 4000; info = info->zs_next) { + l = line_info(page + len, info); + len += l; + if (len+begin > off+count) + goto done; + if (len+begin < off) { + begin += len; + len = 0; + } + } + *eof = 1; +done: + if (off >= len+begin) + return 0; + *start = page + (off-begin); + return ((count < begin+len-off) ? count : begin+len-off); +} + +/* Ask the PROM how many Z8530s we have and initialize their zs_channels */ +static void +probe_sccs(void) +{ + struct device_node *dev, *ch; + struct mac_serial **pp; + int n, chip, nchan; + struct mac_zschannel *zs_chan; + int chan_a_index; + + n = 0; + pp = &zs_chain; + zs_chan = zs_channels; + for (dev = find_devices("escc"); dev != 0; dev = dev->next) { + nchan = 0; + chip = n; + if (n >= NUM_CHANNELS) { + printk(KERN_WARNING "Sorry, can't use %s: no more " + "channels\n", dev->full_name); + continue; + } + chan_a_index = 0; + for (ch = dev->child; ch != 0; ch = ch->sibling) { + if (nchan >= 2) { + printk(KERN_WARNING "SCC: Only 2 channels per " + "chip are supported\n"); + break; + } + if (ch->n_addrs < 1 || (ch ->n_intrs < 1)) { + printk("Can't use %s: %d addrs %d intrs\n", + ch->full_name, ch->n_addrs, ch->n_intrs); + continue; + } + + /* The channel with the higher address + will be the A side. */ + if (nchan > 0 && + ch->addrs[0].address + > zs_soft[n-1].dev_node->addrs[0].address) + chan_a_index = 1; + + /* minimal initialization for now */ + zs_soft[n].dev_node = ch; + *pp = &zs_soft[n]; + pp = &zs_soft[n].zs_next; + ++nchan; + ++n; + } + if (nchan == 0) + continue; + + /* set up A side */ + if (chan_init(&zs_soft[chip + chan_a_index], zs_chan, zs_chan)) + continue; + ++zs_chan; + + /* set up B side, if it exists */ + if (nchan > 1) + if (chan_init(&zs_soft[chip + 1 - chan_a_index], + zs_chan, zs_chan - 1)) + continue; + ++zs_chan; + } + *pp = 0; + + zs_channels_found = n; +#ifdef CONFIG_PMAC_PBOOK + if (n) + pmu_register_sleep_notifier(&serial_sleep_notifier); +#endif /* CONFIG_PMAC_PBOOK */ +} + +static struct tty_operations serial_ops = { + .open = rs_open, + .close = rs_close, + .write = rs_write, + .flush_chars = rs_flush_chars, + .write_room = rs_write_room, + .chars_in_buffer = rs_chars_in_buffer, + .flush_buffer = rs_flush_buffer, + .ioctl = rs_ioctl, + .throttle = rs_throttle, + .unthrottle = rs_unthrottle, + .set_termios = rs_set_termios, + .stop = rs_stop, + .start = rs_start, + .hangup = rs_hangup, + .break_ctl = rs_break, + .wait_until_sent = rs_wait_until_sent, + .read_proc = macserial_read_proc, + .tiocmget = rs_tiocmget, + .tiocmset = rs_tiocmset, +}; + +static int macserial_init(void) +{ + int channel, i; + struct mac_serial *info; + + /* Find out how many Z8530 SCCs we have */ + if (zs_chain == 0) + probe_sccs(); + + serial_driver = alloc_tty_driver(zs_channels_found); + if (!serial_driver) + return -ENOMEM; + + /* XXX assume it's a powerbook if we have a via-pmu + * + * This is OK for core99 machines as well. + */ + is_powerbook = find_devices("via-pmu") != 0; + + /* Register the interrupt handler for each one + * We also request the OF resources here as probe_sccs() + * might be called too early for that + */ + for (i = 0; i < zs_channels_found; ++i) { + struct device_node* ch = zs_soft[i].dev_node; + if (!request_OF_resource(ch, 0, NULL)) { + printk(KERN_ERR "macserial: can't request IO resource !\n"); + put_tty_driver(serial_driver); + return -ENODEV; + } + if (zs_soft[i].has_dma) { + if (!request_OF_resource(ch, ch->n_addrs - 2, " (tx dma)")) { + printk(KERN_ERR "macserial: can't request TX DMA resource !\n"); + zs_soft[i].has_dma = 0; + goto no_dma; + } + if (!request_OF_resource(ch, ch->n_addrs - 1, " (rx dma)")) { + release_OF_resource(ch, ch->n_addrs - 2); + printk(KERN_ERR "macserial: can't request RX DMA resource !\n"); + zs_soft[i].has_dma = 0; + goto no_dma; + } + if (request_irq(zs_soft[i].tx_dma_irq, rs_txdma_irq, 0, + "SCC-txdma", &zs_soft[i])) + printk(KERN_ERR "macserial: can't get irq %d\n", + zs_soft[i].tx_dma_irq); + disable_irq(zs_soft[i].tx_dma_irq); + if (request_irq(zs_soft[i].rx_dma_irq, rs_rxdma_irq, 0, + "SCC-rxdma", &zs_soft[i])) + printk(KERN_ERR "macserial: can't get irq %d\n", + zs_soft[i].rx_dma_irq); + disable_irq(zs_soft[i].rx_dma_irq); + } +no_dma: + if (request_irq(zs_soft[i].irq, rs_interrupt, 0, + "SCC", &zs_soft[i])) + printk(KERN_ERR "macserial: can't get irq %d\n", + zs_soft[i].irq); + disable_irq(zs_soft[i].irq); + } + + show_serial_version(); + + /* Initialize the tty_driver structure */ + /* Not all of this is exactly right for us. */ + + serial_driver->owner = THIS_MODULE; + serial_driver->driver_name = "macserial"; + serial_driver->devfs_name = "tts/"; + serial_driver->name = "ttyS"; + serial_driver->major = TTY_MAJOR; + serial_driver->minor_start = 64; + serial_driver->type = TTY_DRIVER_TYPE_SERIAL; + serial_driver->subtype = SERIAL_TYPE_NORMAL; + serial_driver->init_termios = tty_std_termios; + serial_driver->init_termios.c_cflag = + B38400 | CS8 | CREAD | HUPCL | CLOCAL; + serial_driver->flags = TTY_DRIVER_REAL_RAW; + tty_set_operations(serial_driver, &serial_ops); + + if (tty_register_driver(serial_driver)) + printk(KERN_ERR "Error: couldn't register serial driver\n"); + + for (channel = 0; channel < zs_channels_found; ++channel) { +#ifdef CONFIG_KGDB + if (zs_soft[channel].kgdb_channel) { + kgdb_interruptible(1); + continue; + } +#endif + zs_soft[channel].clk_divisor = 16; +/* -- we are not sure the SCC is powered ON at this point + zs_soft[channel].zs_baud = get_zsbaud(&zs_soft[channel]); +*/ + zs_soft[channel].zs_baud = 38400; + + /* If console serial line, then enable interrupts. */ + if (zs_soft[channel].is_cons) { + printk(KERN_INFO "macserial: console line, enabling " + "interrupt %d\n", zs_soft[channel].irq); + panic("macserial: console not supported yet !"); + write_zsreg(zs_soft[channel].zs_channel, R1, + (EXT_INT_ENAB | INT_ALL_Rx | TxINT_ENAB)); + write_zsreg(zs_soft[channel].zs_channel, R9, + (NV | MIE)); + } + } + + for (info = zs_chain, i = 0; info; info = info->zs_next, i++) + { + unsigned char* connector; + int lenp; + +#ifdef CONFIG_KGDB + if (info->kgdb_channel) { + continue; + } +#endif + info->magic = SERIAL_MAGIC; + info->port = (int) info->zs_channel->control; + info->line = i; + info->tty = 0; + info->custom_divisor = 16; + info->timeout = 0; + info->close_delay = 50; + info->closing_wait = 3000; + info->x_char = 0; + info->event = 0; + info->count = 0; + info->blocked_open = 0; + INIT_WORK(&info->tqueue, do_softint, info); + spin_lock_init(&info->lock); + init_waitqueue_head(&info->open_wait); + init_waitqueue_head(&info->close_wait); + info->timeout = HZ; + printk(KERN_INFO "tty%02d at 0x%08x (irq = %d)", info->line, + info->port, info->irq); + printk(" is a Z8530 ESCC"); + connector = get_property(info->dev_node, "AAPL,connector", &lenp); + if (connector) + printk(", port = %s", connector); + if (info->is_internal_modem) + printk(" (internal modem)"); + if (info->is_irda) + printk(" (IrDA)"); + printk("\n"); + } + tmp_buf = 0; + + return 0; +} + +void macserial_cleanup(void) +{ + int i; + unsigned long flags; + struct mac_serial *info; + + for (info = zs_chain, i = 0; info; info = info->zs_next, i++) + set_scc_power(info, 0); + spin_lock_irqsave(&info->lock, flags); + for (i = 0; i < zs_channels_found; ++i) { + free_irq(zs_soft[i].irq, &zs_soft[i]); + if (zs_soft[i].has_dma) { + free_irq(zs_soft[i].tx_dma_irq, &zs_soft[i]); + free_irq(zs_soft[i].rx_dma_irq, &zs_soft[i]); + } + release_OF_resource(zs_soft[i].dev_node, 0); + if (zs_soft[i].has_dma) { + struct device_node* ch = zs_soft[i].dev_node; + release_OF_resource(ch, ch->n_addrs - 2); + release_OF_resource(ch, ch->n_addrs - 1); + } + } + spin_unlock_irqrestore(&info->lock, flags); + tty_unregister_driver(serial_driver); + put_tty_driver(serial_driver); + + if (tmp_buf) { + free_page((unsigned long) tmp_buf); + tmp_buf = 0; + } + +#ifdef CONFIG_PMAC_PBOOK + if (zs_channels_found) + pmu_unregister_sleep_notifier(&serial_sleep_notifier); +#endif /* CONFIG_PMAC_PBOOK */ +} + +module_init(macserial_init); +module_exit(macserial_cleanup); +MODULE_LICENSE("GPL"); + +#if 0 +/* + * register_serial and unregister_serial allows for serial ports to be + * configured at run-time, to support PCMCIA modems. + */ +/* PowerMac: Unused at this time, just here to make things link. */ +int register_serial(struct serial_struct *req) +{ + return -1; +} + +void unregister_serial(int line) +{ + return; +} +#endif + +/* + * ------------------------------------------------------------ + * Serial console driver + * ------------------------------------------------------------ + */ +#ifdef CONFIG_SERIAL_CONSOLE + +/* + * Print a string to the serial port trying not to disturb + * any possible real use of the port... + */ +static void serial_console_write(struct console *co, const char *s, + unsigned count) +{ + struct mac_serial *info = zs_soft + co->index; + int i; + + /* Turn of interrupts and enable the transmitter. */ + write_zsreg(info->zs_channel, R1, info->curregs[1] & ~TxINT_ENAB); + write_zsreg(info->zs_channel, R5, info->curregs[5] | TxENAB | RTS | DTR); + + for (i=0; i<count; i++) { + /* Wait for the transmit buffer to empty. */ + while ((read_zsreg(info->zs_channel, 0) & Tx_BUF_EMP) == 0) { + eieio(); + } + + write_zsdata(info->zs_channel, s[i]); + if (s[i] == 10) { + while ((read_zsreg(info->zs_channel, 0) & Tx_BUF_EMP) + == 0) + eieio(); + + write_zsdata(info->zs_channel, 13); + } + } + + /* Restore the values in the registers. */ + write_zsreg(info->zs_channel, R1, info->curregs[1]); + /* Don't disable the transmitter. */ +} + +static struct tty_driver *serial_driver; + +static struct tty_driver *serial_console_device(struct console *c, int *index) +{ + *index = c->index; + return serial_driver; +} + +/* + * Setup initial baud/bits/parity. We do two things here: + * - construct a cflag setting for the first rs_open() + * - initialize the serial port + * Return non-zero if we didn't find a serial port. + */ +static int __init serial_console_setup(struct console *co, char *options) +{ + struct mac_serial *info; + int baud = 38400; + int bits = 8; + int parity = 'n'; + int cflag = CREAD | HUPCL | CLOCAL; + int brg; + char *s; + long flags; + + /* Find out how many Z8530 SCCs we have */ + if (zs_chain == 0) + probe_sccs(); + + if (zs_chain == 0) + return -1; + + /* Do we have the device asked for? */ + if (co->index >= zs_channels_found) + return -1; + info = zs_soft + co->index; + + set_scc_power(info, 1); + + /* Reset the channel */ + write_zsreg(info->zs_channel, R9, CHRA); + + if (options) { + baud = simple_strtoul(options, NULL, 10); + s = options; + while(*s >= '0' && *s <= '9') + s++; + if (*s) + parity = *s++; + if (*s) + bits = *s - '0'; + } + + /* + * Now construct a cflag setting. + */ + switch(baud) { + case 1200: + cflag |= B1200; + break; + case 2400: + cflag |= B2400; + break; + case 4800: + cflag |= B4800; + break; + case 9600: + cflag |= B9600; + break; + case 19200: + cflag |= B19200; + break; + case 57600: + cflag |= B57600; + break; + case 115200: + cflag |= B115200; + break; + case 38400: + default: + cflag |= B38400; + break; + } + switch(bits) { + case 7: + cflag |= CS7; + break; + default: + case 8: + cflag |= CS8; + break; + } + switch(parity) { + case 'o': case 'O': + cflag |= PARENB | PARODD; + break; + case 'e': case 'E': + cflag |= PARENB; + break; + } + co->cflag = cflag; + + spin_lock_irqsave(&info->lock, flags); + memset(info->curregs, 0, sizeof(info->curregs)); + + info->zs_baud = baud; + info->clk_divisor = 16; + switch (info->zs_baud) { + case ZS_CLOCK/16: /* 230400 */ + info->curregs[4] = X16CLK; + info->curregs[11] = 0; + break; + case ZS_CLOCK/32: /* 115200 */ + info->curregs[4] = X32CLK; + info->curregs[11] = 0; + break; + default: + info->curregs[4] = X16CLK; + info->curregs[11] = TCBR | RCBR; + brg = BPS_TO_BRG(info->zs_baud, ZS_CLOCK/info->clk_divisor); + info->curregs[12] = (brg & 255); + info->curregs[13] = ((brg >> 8) & 255); + info->curregs[14] = BRENABL; + } + + /* byte size and parity */ + info->curregs[3] &= ~RxNBITS_MASK; + info->curregs[5] &= ~TxNBITS_MASK; + switch (cflag & CSIZE) { + case CS5: + info->curregs[3] |= Rx5; + info->curregs[5] |= Tx5; + break; + case CS6: + info->curregs[3] |= Rx6; + info->curregs[5] |= Tx6; + break; + case CS7: + info->curregs[3] |= Rx7; + info->curregs[5] |= Tx7; + break; + case CS8: + default: /* defaults to 8 bits */ + info->curregs[3] |= Rx8; + info->curregs[5] |= Tx8; + break; + } + info->curregs[5] |= TxENAB | RTS | DTR; + info->pendregs[3] = info->curregs[3]; + info->pendregs[5] = info->curregs[5]; + + info->curregs[4] &= ~(SB_MASK | PAR_ENA | PAR_EVEN); + if (cflag & CSTOPB) { + info->curregs[4] |= SB2; + } else { + info->curregs[4] |= SB1; + } + if (cflag & PARENB) { + info->curregs[4] |= PAR_ENA; + if (!(cflag & PARODD)) { + info->curregs[4] |= PAR_EVEN; + } + } + info->pendregs[4] = info->curregs[4]; + + if (!(cflag & CLOCAL)) { + if (!(info->curregs[15] & DCDIE)) + info->read_reg_zero = read_zsreg(info->zs_channel, 0); + info->curregs[15] |= DCDIE; + } else + info->curregs[15] &= ~DCDIE; + if (cflag & CRTSCTS) { + info->curregs[15] |= CTSIE; + if ((read_zsreg(info->zs_channel, 0) & CTS) != 0) + info->tx_stopped = 1; + } else { + info->curregs[15] &= ~CTSIE; + info->tx_stopped = 0; + } + info->pendregs[15] = info->curregs[15]; + + /* Load up the new values */ + load_zsregs(info->zs_channel, info->curregs); + + spin_unlock_irqrestore(&info->lock, flags); + + return 0; +} + +static struct console sercons = { + .name = "ttyS", + .write = serial_console_write, + .device = serial_console_device, + .setup = serial_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +/* + * Register console. + */ +static void __init mac_scc_console_init(void) +{ + register_console(&sercons); +} +console_initcall(mac_scc_console_init); + +#endif /* ifdef CONFIG_SERIAL_CONSOLE */ + +#ifdef CONFIG_KGDB +/* These are for receiving and sending characters under the kgdb + * source level kernel debugger. + */ +void putDebugChar(char kgdb_char) +{ + struct mac_zschannel *chan = zs_kgdbchan; + while ((read_zsreg(chan, 0) & Tx_BUF_EMP) == 0) + udelay(5); + write_zsdata(chan, kgdb_char); +} + +char getDebugChar(void) +{ + struct mac_zschannel *chan = zs_kgdbchan; + while((read_zsreg(chan, 0) & Rx_CH_AV) == 0) + eieio(); /*barrier();*/ + return read_zsdata(chan); +} + +void kgdb_interruptible(int yes) +{ + struct mac_zschannel *chan = zs_kgdbchan; + int one, nine; + nine = read_zsreg(chan, 9); + if (yes == 1) { + one = EXT_INT_ENAB|INT_ALL_Rx; + nine |= MIE; + printk("turning serial ints on\n"); + } else { + one = RxINT_DISAB; + nine &= ~MIE; + printk("turning serial ints off\n"); + } + write_zsreg(chan, 1, one); + write_zsreg(chan, 9, nine); +} + +/* This sets up the serial port we're using, and turns on + * interrupts for that channel, so kgdb is usable once we're done. + */ +static inline void kgdb_chaninit(struct mac_zschannel *ms, int intson, int bps) +{ + int brg; + int i, x; + volatile char *sccc = ms->control; + brg = BPS_TO_BRG(bps, ZS_CLOCK/16); + printk("setting bps on kgdb line to %d [brg=%x]\n", bps, brg); + for (i = 20000; i != 0; --i) { + x = *sccc; eieio(); + } + for (i = 0; i < sizeof(scc_inittab); ++i) { + write_zsreg(ms, scc_inittab[i], scc_inittab[i+1]); + i++; + } +} + +/* This is called at boot time to prime the kgdb serial debugging + * serial line. The 'tty_num' argument is 0 for /dev/ttya and 1 + * for /dev/ttyb which is determined in setup_arch() from the + * boot command line flags. + * XXX at the moment probably only channel A will work + */ +void __init zs_kgdb_hook(int tty_num) +{ + /* Find out how many Z8530 SCCs we have */ + if (zs_chain == 0) + probe_sccs(); + + set_scc_power(&zs_soft[tty_num], 1); + + zs_kgdbchan = zs_soft[tty_num].zs_channel; + zs_soft[tty_num].change_needed = 0; + zs_soft[tty_num].clk_divisor = 16; + zs_soft[tty_num].zs_baud = 38400; + zs_soft[tty_num].kgdb_channel = 1; /* This runs kgdb */ + + /* Turn on transmitter/receiver at 8-bits/char */ + kgdb_chaninit(zs_soft[tty_num].zs_channel, 1, 38400); + printk("KGDB: on channel %d initialized\n", tty_num); + set_debug_traps(); /* init stub */ +} +#endif /* ifdef CONFIG_KGDB */ + +#ifdef CONFIG_PMAC_PBOOK +/* + * notify clients before sleep and reset bus afterwards + */ +int +serial_notify_sleep(struct pmu_sleep_notifier *self, int when) +{ + int i; + + switch (when) { + case PBOOK_SLEEP_REQUEST: + case PBOOK_SLEEP_REJECT: + break; + + case PBOOK_SLEEP_NOW: + for (i=0; i<zs_channels_found; i++) { + struct mac_serial *info = &zs_soft[i]; + if (info->flags & ZILOG_INITIALIZED) { + shutdown(info); + info->flags |= ZILOG_SLEEPING; + } + } + break; + case PBOOK_WAKE: + for (i=0; i<zs_channels_found; i++) { + struct mac_serial *info = &zs_soft[i]; + if (info->flags & ZILOG_SLEEPING) { + info->flags &= ~ZILOG_SLEEPING; + startup(info); + } + } + break; + } + return PBOOK_SLEEP_OK; +} +#endif /* CONFIG_PMAC_PBOOK */ diff --git a/drivers/macintosh/macserial.h b/drivers/macintosh/macserial.h new file mode 100644 index 000000000000..bade11a7a5c3 --- /dev/null +++ b/drivers/macintosh/macserial.h @@ -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) */ diff --git a/drivers/macintosh/mediabay.c b/drivers/macintosh/mediabay.c new file mode 100644 index 000000000000..b8b5a2f1485a --- /dev/null +++ b/drivers/macintosh/mediabay.c @@ -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); diff --git a/drivers/macintosh/nvram.c b/drivers/macintosh/nvram.c new file mode 100644 index 000000000000..30791875fc97 --- /dev/null +++ b/drivers/macintosh/nvram.c @@ -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"); diff --git a/drivers/macintosh/smu.c b/drivers/macintosh/smu.c new file mode 100644 index 000000000000..fb535737d17d --- /dev/null +++ b/drivers/macintosh/smu.c @@ -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; + +} diff --git a/drivers/macintosh/therm_adt746x.c b/drivers/macintosh/therm_adt746x.c new file mode 100644 index 000000000000..e0ac63effa55 --- /dev/null +++ b/drivers/macintosh/therm_adt746x.c @@ -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 <colin@colino.net>"); +MODULE_DESCRIPTION("Driver for ADT746x thermostat in iBook G4 and " + "Powerbook G4 Alu"); +MODULE_LICENSE("GPL"); + +module_param(limit_adjust, int, 0644); +MODULE_PARM_DESC(limit_adjust,"Adjust maximum temperatures (50 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 %dC\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); diff --git a/drivers/macintosh/therm_pm72.c b/drivers/macintosh/therm_pm72.c new file mode 100644 index 000000000000..82336a5a5474 --- /dev/null +++ b/drivers/macintosh/therm_pm72.c @@ -0,0 +1,2080 @@ +/* + * Device driver for the thermostats & fan controller of the + * Apple G5 "PowerMac7,2" desktop machines. + * + * (c) Copyright IBM Corp. 2003-2004 + * + * Maintained by: Benjamin Herrenschmidt + * <benh@kernel.crashing.org> + * + * + * The algorithm used is the PID control algorithm, used the same + * way the published Darwin code does, using the same values that + * are present in the Darwin 7.0 snapshot property lists. + * + * As far as the CPUs control loops are concerned, I use the + * calibration & PID constants provided by the EEPROM, + * I do _not_ embed any value from the property lists, as the ones + * provided by Darwin 7.0 seem to always have an older version that + * what I've seen on the actual computers. + * It would be interesting to verify that though. Darwin has a + * version code of 1.0.0d11 for all control loops it seems, while + * so far, the machines EEPROMs contain a dataset versioned 1.0.0f + * + * Darwin doesn't provide source to all parts, some missing + * bits like the AppleFCU driver or the actual scale of some + * of the values returned by sensors had to be "guessed" some + * way... or based on what Open Firmware does. + * + * I didn't yet figure out how to get the slots power consumption + * out of the FCU, so that part has not been implemented yet and + * the slots fan is set to a fixed 50% PWM, hoping this value is + * safe enough ... + * + * Note: I have observed strange oscillations of the CPU control + * loop on a dual G5 here. When idle, the CPU exhaust fan tend to + * oscillates slowly (over several minutes) between the minimum + * of 300RPMs and approx. 1000 RPMs. I don't know what is causing + * this, it could be some incorrect constant or an error in the + * way I ported the algorithm, or it could be just normal. I + * don't have full understanding on the way Apple tweaked the PID + * algorithm for the CPU control, it is definitely not a standard + * implementation... + * + * TODO: - Check MPU structure version/signature + * - Add things like /sbin/overtemp for non-critical + * overtemp conditions so userland can take some policy + * decisions, like slewing down CPUs + * - Deal with fan and i2c failures in a better way + * - Maybe do a generic PID based on params used for + * U3 and Drives ? Definitely need to factor code a bit + * bettter... also make sensor detection more robust using + * the device-tree to probe for them + * - Figure out how to get the slots consumption and set the + * slots fan accordingly + * + * History: + * + * Nov. 13, 2003 : 0.5 + * - First release + * + * Nov. 14, 2003 : 0.6 + * - Read fan speed from FCU, low level fan routines now deal + * with errors & check fan status, though higher level don't + * do much. + * - Move a bunch of definitions to .h file + * + * Nov. 18, 2003 : 0.7 + * - Fix build on ppc64 kernel + * - Move back statics definitions to .c file + * - Avoid calling schedule_timeout with a negative number + * + * Dec. 18, 2003 : 0.8 + * - Fix typo when reading back fan speed on 2 CPU machines + * + * Mar. 11, 2004 : 0.9 + * - Rework code accessing the ADC chips, make it more robust and + * closer to the chip spec. Also make sure it is configured properly, + * I've seen yet unexplained cases where on startup, I would have stale + * values in the configuration register + * - Switch back to use of target fan speed for PID, thus lowering + * pressure on i2c + * + * Oct. 20, 2004 : 1.1 + * - Add device-tree lookup for fan IDs, should detect liquid cooling + * pumps when present + * - Enable driver for PowerMac7,3 machines + * - Split the U3/Backside cooling on U3 & U3H versions as Darwin does + * - Add new CPU cooling algorithm for machines with liquid cooling + * - Workaround for some PowerMac7,3 with empty "fan" node in the devtree + * - Fix a signed/unsigned compare issue in some PID loops + * + * Mar. 10, 2005 : 1.2 + * - Add basic support for Xserve G5 + * - Retreive pumps min/max from EEPROM image in device-tree (broken) + * - Use min/max macros here or there + * - Latest darwin updated U3H min fan speed to 20% PWM + * + */ + +#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/reboot.h> +#include <linux/kmod.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.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> + +#include "therm_pm72.h" + +#define VERSION "1.2b2" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + + +/* + * Driver statics + */ + +static struct of_device * of_dev; +static struct i2c_adapter * u3_0; +static struct i2c_adapter * u3_1; +static struct i2c_adapter * k2; +static struct i2c_client * fcu; +static struct cpu_pid_state cpu_state[2]; +static struct basckside_pid_params backside_params; +static struct backside_pid_state backside_state; +static struct drives_pid_state drives_state; +static struct dimm_pid_state dimms_state; +static int state; +static int cpu_count; +static int cpu_pid_type; +static pid_t ctrl_task; +static struct completion ctrl_complete; +static int critical_state; +static int rackmac; +static s32 dimm_output_clamp; + +static DECLARE_MUTEX(driver_lock); + +/* + * We have 3 types of CPU PID control. One is "split" old style control + * for intake & exhaust fans, the other is "combined" control for both + * CPUs that also deals with the pumps when present. To be "compatible" + * with OS X at this point, we only use "COMBINED" on the machines that + * are identified as having the pumps (though that identification is at + * least dodgy). Ultimately, we could probably switch completely to this + * algorithm provided we hack it to deal with the UP case + */ +#define CPU_PID_TYPE_SPLIT 0 +#define CPU_PID_TYPE_COMBINED 1 +#define CPU_PID_TYPE_RACKMAC 2 + +/* + * This table describes all fans in the FCU. The "id" and "type" values + * are defaults valid for all earlier machines. Newer machines will + * eventually override the table content based on the device-tree + */ +struct fcu_fan_table +{ + char* loc; /* location code */ + int type; /* 0 = rpm, 1 = pwm, 2 = pump */ + int id; /* id or -1 */ +}; + +#define FCU_FAN_RPM 0 +#define FCU_FAN_PWM 1 + +#define FCU_FAN_ABSENT_ID -1 + +#define FCU_FAN_COUNT ARRAY_SIZE(fcu_fans) + +struct fcu_fan_table fcu_fans[] = { + [BACKSIDE_FAN_PWM_INDEX] = { + .loc = "BACKSIDE,SYS CTRLR FAN", + .type = FCU_FAN_PWM, + .id = BACKSIDE_FAN_PWM_DEFAULT_ID, + }, + [DRIVES_FAN_RPM_INDEX] = { + .loc = "DRIVE BAY", + .type = FCU_FAN_RPM, + .id = DRIVES_FAN_RPM_DEFAULT_ID, + }, + [SLOTS_FAN_PWM_INDEX] = { + .loc = "SLOT,PCI FAN", + .type = FCU_FAN_PWM, + .id = SLOTS_FAN_PWM_DEFAULT_ID, + }, + [CPUA_INTAKE_FAN_RPM_INDEX] = { + .loc = "CPU A INTAKE", + .type = FCU_FAN_RPM, + .id = CPUA_INTAKE_FAN_RPM_DEFAULT_ID, + }, + [CPUA_EXHAUST_FAN_RPM_INDEX] = { + .loc = "CPU A EXHAUST", + .type = FCU_FAN_RPM, + .id = CPUA_EXHAUST_FAN_RPM_DEFAULT_ID, + }, + [CPUB_INTAKE_FAN_RPM_INDEX] = { + .loc = "CPU B INTAKE", + .type = FCU_FAN_RPM, + .id = CPUB_INTAKE_FAN_RPM_DEFAULT_ID, + }, + [CPUB_EXHAUST_FAN_RPM_INDEX] = { + .loc = "CPU B EXHAUST", + .type = FCU_FAN_RPM, + .id = CPUB_EXHAUST_FAN_RPM_DEFAULT_ID, + }, + /* pumps aren't present by default, have to be looked up in the + * device-tree + */ + [CPUA_PUMP_RPM_INDEX] = { + .loc = "CPU A PUMP", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, + [CPUB_PUMP_RPM_INDEX] = { + .loc = "CPU B PUMP", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, + /* Xserve fans */ + [CPU_A1_FAN_RPM_INDEX] = { + .loc = "CPU A 1", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, + [CPU_A2_FAN_RPM_INDEX] = { + .loc = "CPU A 2", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, + [CPU_A3_FAN_RPM_INDEX] = { + .loc = "CPU A 3", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, + [CPU_B1_FAN_RPM_INDEX] = { + .loc = "CPU B 1", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, + [CPU_B2_FAN_RPM_INDEX] = { + .loc = "CPU B 2", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, + [CPU_B3_FAN_RPM_INDEX] = { + .loc = "CPU B 3", + .type = FCU_FAN_RPM, + .id = FCU_FAN_ABSENT_ID, + }, +}; + +/* + * i2c_driver structure to attach to the host i2c controller + */ + +static int therm_pm72_attach(struct i2c_adapter *adapter); +static int therm_pm72_detach(struct i2c_adapter *adapter); + +static struct i2c_driver therm_pm72_driver = +{ + .owner = THIS_MODULE, + .name = "therm_pm72", + .flags = I2C_DF_NOTIFY, + .attach_adapter = therm_pm72_attach, + .detach_adapter = therm_pm72_detach, +}; + +/* + * Utility function to create an i2c_client structure and + * attach it to one of u3 adapters + */ +static struct i2c_client *attach_i2c_chip(int id, const char *name) +{ + struct i2c_client *clt; + struct i2c_adapter *adap; + + if (id & 0x200) + adap = k2; + else if (id & 0x100) + adap = u3_1; + else + adap = u3_0; + if (adap == NULL) + return NULL; + + clt = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (clt == NULL) + return NULL; + memset(clt, 0, sizeof(struct i2c_client)); + + clt->addr = (id >> 1) & 0x7f; + clt->adapter = adap; + clt->driver = &therm_pm72_driver; + strncpy(clt->name, name, I2C_NAME_SIZE-1); + + if (i2c_attach_client(clt)) { + printk(KERN_ERR "therm_pm72: Failed to attach to i2c ID 0x%x\n", id); + kfree(clt); + return NULL; + } + return clt; +} + +/* + * Utility function to get rid of the i2c_client structure + * (will also detach from the adapter hopepfully) + */ +static void detach_i2c_chip(struct i2c_client *clt) +{ + i2c_detach_client(clt); + kfree(clt); +} + +/* + * Here are the i2c chip access wrappers + */ + +static void initialize_adc(struct cpu_pid_state *state) +{ + int rc; + u8 buf[2]; + + /* Read ADC the configuration register and cache it. We + * also make sure Config2 contains proper values, I've seen + * cases where we got stale grabage in there, thus preventing + * proper reading of conv. values + */ + + /* Clear Config2 */ + buf[0] = 5; + buf[1] = 0; + i2c_master_send(state->monitor, buf, 2); + + /* Read & cache Config1 */ + buf[0] = 1; + rc = i2c_master_send(state->monitor, buf, 1); + if (rc > 0) { + rc = i2c_master_recv(state->monitor, buf, 1); + if (rc > 0) { + state->adc_config = buf[0]; + DBG("ADC config reg: %02x\n", state->adc_config); + /* Disable shutdown mode */ + state->adc_config &= 0xfe; + buf[0] = 1; + buf[1] = state->adc_config; + rc = i2c_master_send(state->monitor, buf, 2); + } + } + if (rc <= 0) + printk(KERN_ERR "therm_pm72: Error reading ADC config" + " register !\n"); +} + +static int read_smon_adc(struct cpu_pid_state *state, int chan) +{ + int rc, data, tries = 0; + u8 buf[2]; + + for (;;) { + /* Set channel */ + buf[0] = 1; + buf[1] = (state->adc_config & 0x1f) | (chan << 5); + rc = i2c_master_send(state->monitor, buf, 2); + if (rc <= 0) + goto error; + /* Wait for convertion */ + msleep(1); + /* Switch to data register */ + buf[0] = 4; + rc = i2c_master_send(state->monitor, buf, 1); + if (rc <= 0) + goto error; + /* Read result */ + rc = i2c_master_recv(state->monitor, buf, 2); + if (rc < 0) + goto error; + data = ((u16)buf[0]) << 8 | (u16)buf[1]; + return data >> 6; + error: + DBG("Error reading ADC, retrying...\n"); + if (++tries > 10) { + printk(KERN_ERR "therm_pm72: Error reading ADC !\n"); + return -1; + } + msleep(10); + } +} + +static int read_lm87_reg(struct i2c_client * chip, int reg) +{ + int rc, tries = 0; + u8 buf; + + for (;;) { + /* Set address */ + buf = (u8)reg; + rc = i2c_master_send(chip, &buf, 1); + if (rc <= 0) + goto error; + rc = i2c_master_recv(chip, &buf, 1); + if (rc <= 0) + goto error; + return (int)buf; + error: + DBG("Error reading LM87, retrying...\n"); + if (++tries > 10) { + printk(KERN_ERR "therm_pm72: Error reading LM87 !\n"); + return -1; + } + msleep(10); + } +} + +static int fan_read_reg(int reg, unsigned char *buf, int nb) +{ + int tries, nr, nw; + + buf[0] = reg; + tries = 0; + for (;;) { + nw = i2c_master_send(fcu, buf, 1); + if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nw <= 0) { + printk(KERN_ERR "Failure writing address to FCU: %d", nw); + return -EIO; + } + tries = 0; + for (;;) { + nr = i2c_master_recv(fcu, buf, nb); + if (nr > 0 || (nr < 0 && nr != ENODEV) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nr <= 0) + printk(KERN_ERR "Failure reading data from FCU: %d", nw); + return nr; +} + +static int fan_write_reg(int reg, const unsigned char *ptr, int nb) +{ + int tries, nw; + unsigned char buf[16]; + + buf[0] = reg; + memcpy(buf+1, ptr, nb); + ++nb; + tries = 0; + for (;;) { + nw = i2c_master_send(fcu, buf, nb); + if (nw > 0 || (nw < 0 && nw != EIO) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nw < 0) + printk(KERN_ERR "Failure writing to FCU: %d", nw); + return nw; +} + +static int start_fcu(void) +{ + unsigned char buf = 0xff; + int rc; + + rc = fan_write_reg(0xe, &buf, 1); + if (rc < 0) + return -EIO; + rc = fan_write_reg(0x2e, &buf, 1); + if (rc < 0) + return -EIO; + return 0; +} + +static int set_rpm_fan(int fan_index, int rpm) +{ + unsigned char buf[2]; + int rc, id; + + if (fcu_fans[fan_index].type != FCU_FAN_RPM) + return -EINVAL; + id = fcu_fans[fan_index].id; + if (id == FCU_FAN_ABSENT_ID) + return -EINVAL; + + if (rpm < 300) + rpm = 300; + else if (rpm > 8191) + rpm = 8191; + buf[0] = rpm >> 5; + buf[1] = rpm << 3; + rc = fan_write_reg(0x10 + (id * 2), buf, 2); + if (rc < 0) + return -EIO; + return 0; +} + +static int get_rpm_fan(int fan_index, int programmed) +{ + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + int rc, id, reg_base; + + if (fcu_fans[fan_index].type != FCU_FAN_RPM) + return -EINVAL; + id = fcu_fans[fan_index].id; + if (id == FCU_FAN_ABSENT_ID) + return -EINVAL; + + rc = fan_read_reg(0xb, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << id)) != 0) + return -EFAULT; + rc = fan_read_reg(0xd, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << id)) == 0) + return -ENXIO; + + /* Programmed value or real current speed */ + reg_base = programmed ? 0x10 : 0x11; + rc = fan_read_reg(reg_base + (id * 2), buf, 2); + if (rc != 2) + return -EIO; + + return (buf[0] << 5) | buf[1] >> 3; +} + +static int set_pwm_fan(int fan_index, int pwm) +{ + unsigned char buf[2]; + int rc, id; + + if (fcu_fans[fan_index].type != FCU_FAN_PWM) + return -EINVAL; + id = fcu_fans[fan_index].id; + if (id == FCU_FAN_ABSENT_ID) + return -EINVAL; + + if (pwm < 10) + pwm = 10; + else if (pwm > 100) + pwm = 100; + pwm = (pwm * 2559) / 1000; + buf[0] = pwm; + rc = fan_write_reg(0x30 + (id * 2), buf, 1); + if (rc < 0) + return rc; + return 0; +} + +static int get_pwm_fan(int fan_index) +{ + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + int rc, id; + + if (fcu_fans[fan_index].type != FCU_FAN_PWM) + return -EINVAL; + id = fcu_fans[fan_index].id; + if (id == FCU_FAN_ABSENT_ID) + return -EINVAL; + + rc = fan_read_reg(0x2b, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << id)) != 0) + return -EFAULT; + rc = fan_read_reg(0x2d, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << id)) == 0) + return -ENXIO; + + /* Programmed value or real current speed */ + rc = fan_read_reg(0x30 + (id * 2), buf, 1); + if (rc != 1) + return -EIO; + + return (buf[0] * 1000) / 2559; +} + +/* + * Utility routine to read the CPU calibration EEPROM data + * from the device-tree + */ +static int read_eeprom(int cpu, struct mpu_data *out) +{ + struct device_node *np; + char nodename[64]; + u8 *data; + int len; + + /* prom.c routine for finding a node by path is a bit brain dead + * and requires exact @xxx unit numbers. This is a bit ugly but + * will work for these machines + */ + sprintf(nodename, "/u3@0,f8000000/i2c@f8001000/cpuid@a%d", cpu ? 2 : 0); + np = of_find_node_by_path(nodename); + if (np == NULL) { + printk(KERN_ERR "therm_pm72: Failed to retreive cpuid node from device-tree\n"); + return -ENODEV; + } + data = (u8 *)get_property(np, "cpuid", &len); + if (data == NULL) { + printk(KERN_ERR "therm_pm72: Failed to retreive cpuid property from device-tree\n"); + of_node_put(np); + return -ENODEV; + } + memcpy(out, data, sizeof(struct mpu_data)); + of_node_put(np); + + return 0; +} + +static void fetch_cpu_pumps_minmax(void) +{ + struct cpu_pid_state *state0 = &cpu_state[0]; + struct cpu_pid_state *state1 = &cpu_state[1]; + u16 pump_min = 0, pump_max = 0xffff; + u16 tmp[4]; + + /* Try to fetch pumps min/max infos from eeprom */ + + memcpy(&tmp, &state0->mpu.processor_part_num, 8); + if (tmp[0] != 0xffff && tmp[1] != 0xffff) { + pump_min = max(pump_min, tmp[0]); + pump_max = min(pump_max, tmp[1]); + } + if (tmp[2] != 0xffff && tmp[3] != 0xffff) { + pump_min = max(pump_min, tmp[2]); + pump_max = min(pump_max, tmp[3]); + } + + /* Double check the values, this _IS_ needed as the EEPROM on + * some dual 2.5Ghz G5s seem, at least, to have both min & max + * same to the same value ... (grrrr) + */ + if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) { + pump_min = CPU_PUMP_OUTPUT_MIN; + pump_max = CPU_PUMP_OUTPUT_MAX; + } + + state0->pump_min = state1->pump_min = pump_min; + state0->pump_max = state1->pump_max = pump_max; +} + +/* + * 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... + * + * That sucks a bit, we take the lock because FIX32TOPRINT evaluates + * the input twice... I accept patches :) + */ +#define BUILD_SHOW_FUNC_FIX(name, data) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + ssize_t r; \ + down(&driver_lock); \ + r = sprintf(buf, "%d.%03d", FIX32TOPRINT(data)); \ + up(&driver_lock); \ + return r; \ +} +#define BUILD_SHOW_FUNC_INT(name, data) \ +static ssize_t show_##name(struct device *dev, char *buf) \ +{ \ + return sprintf(buf, "%d", data); \ +} + +BUILD_SHOW_FUNC_FIX(cpu0_temperature, cpu_state[0].last_temp) +BUILD_SHOW_FUNC_FIX(cpu0_voltage, cpu_state[0].voltage) +BUILD_SHOW_FUNC_FIX(cpu0_current, cpu_state[0].current_a) +BUILD_SHOW_FUNC_INT(cpu0_exhaust_fan_rpm, cpu_state[0].rpm) +BUILD_SHOW_FUNC_INT(cpu0_intake_fan_rpm, cpu_state[0].intake_rpm) + +BUILD_SHOW_FUNC_FIX(cpu1_temperature, cpu_state[1].last_temp) +BUILD_SHOW_FUNC_FIX(cpu1_voltage, cpu_state[1].voltage) +BUILD_SHOW_FUNC_FIX(cpu1_current, cpu_state[1].current_a) +BUILD_SHOW_FUNC_INT(cpu1_exhaust_fan_rpm, cpu_state[1].rpm) +BUILD_SHOW_FUNC_INT(cpu1_intake_fan_rpm, cpu_state[1].intake_rpm) + +BUILD_SHOW_FUNC_FIX(backside_temperature, backside_state.last_temp) +BUILD_SHOW_FUNC_INT(backside_fan_pwm, backside_state.pwm) + +BUILD_SHOW_FUNC_FIX(drives_temperature, drives_state.last_temp) +BUILD_SHOW_FUNC_INT(drives_fan_rpm, drives_state.rpm) + +BUILD_SHOW_FUNC_FIX(dimms_temperature, dimms_state.last_temp) + +static DEVICE_ATTR(cpu0_temperature,S_IRUGO,show_cpu0_temperature,NULL); +static DEVICE_ATTR(cpu0_voltage,S_IRUGO,show_cpu0_voltage,NULL); +static DEVICE_ATTR(cpu0_current,S_IRUGO,show_cpu0_current,NULL); +static DEVICE_ATTR(cpu0_exhaust_fan_rpm,S_IRUGO,show_cpu0_exhaust_fan_rpm,NULL); +static DEVICE_ATTR(cpu0_intake_fan_rpm,S_IRUGO,show_cpu0_intake_fan_rpm,NULL); + +static DEVICE_ATTR(cpu1_temperature,S_IRUGO,show_cpu1_temperature,NULL); +static DEVICE_ATTR(cpu1_voltage,S_IRUGO,show_cpu1_voltage,NULL); +static DEVICE_ATTR(cpu1_current,S_IRUGO,show_cpu1_current,NULL); +static DEVICE_ATTR(cpu1_exhaust_fan_rpm,S_IRUGO,show_cpu1_exhaust_fan_rpm,NULL); +static DEVICE_ATTR(cpu1_intake_fan_rpm,S_IRUGO,show_cpu1_intake_fan_rpm,NULL); + +static DEVICE_ATTR(backside_temperature,S_IRUGO,show_backside_temperature,NULL); +static DEVICE_ATTR(backside_fan_pwm,S_IRUGO,show_backside_fan_pwm,NULL); + +static DEVICE_ATTR(drives_temperature,S_IRUGO,show_drives_temperature,NULL); +static DEVICE_ATTR(drives_fan_rpm,S_IRUGO,show_drives_fan_rpm,NULL); + +static DEVICE_ATTR(dimms_temperature,S_IRUGO,show_dimms_temperature,NULL); + +/* + * CPUs fans control loop + */ + +static int do_read_one_cpu_values(struct cpu_pid_state *state, s32 *temp, s32 *power) +{ + s32 ltemp, volts, amps; + int index, rc = 0; + + /* Default (in case of error) */ + *temp = state->cur_temp; + *power = state->cur_power; + + if (cpu_pid_type == CPU_PID_TYPE_RACKMAC) + index = (state->index == 0) ? + CPU_A1_FAN_RPM_INDEX : CPU_B1_FAN_RPM_INDEX; + else + index = (state->index == 0) ? + CPUA_EXHAUST_FAN_RPM_INDEX : CPUB_EXHAUST_FAN_RPM_INDEX; + + /* Read current fan status */ + rc = get_rpm_fan(index, !RPM_PID_USE_ACTUAL_SPEED); + if (rc < 0) { + /* XXX What do we do now ? Nothing for now, keep old value, but + * return error upstream + */ + DBG(" cpu %d, fan reading error !\n", state->index); + } else { + state->rpm = rc; + DBG(" cpu %d, exhaust RPM: %d\n", state->index, state->rpm); + } + + /* Get some sensor readings and scale it */ + ltemp = read_smon_adc(state, 1); + if (ltemp == -1) { + /* XXX What do we do now ? */ + state->overtemp++; + if (rc == 0) + rc = -EIO; + DBG(" cpu %d, temp reading error !\n", state->index); + } else { + /* Fixup temperature according to diode calibration + */ + DBG(" cpu %d, temp raw: %04x, m_diode: %04x, b_diode: %04x\n", + state->index, + ltemp, state->mpu.mdiode, state->mpu.bdiode); + *temp = ((s32)ltemp * (s32)state->mpu.mdiode + ((s32)state->mpu.bdiode << 12)) >> 2; + state->last_temp = *temp; + DBG(" temp: %d.%03d\n", FIX32TOPRINT((*temp))); + } + + /* + * Read voltage & current and calculate power + */ + volts = read_smon_adc(state, 3); + amps = read_smon_adc(state, 4); + + /* Scale voltage and current raw sensor values according to fixed scales + * obtained in Darwin and calculate power from I and V + */ + volts *= ADC_CPU_VOLTAGE_SCALE; + amps *= ADC_CPU_CURRENT_SCALE; + *power = (((u64)volts) * ((u64)amps)) >> 16; + state->voltage = volts; + state->current_a = amps; + state->last_power = *power; + + DBG(" cpu %d, current: %d.%03d, voltage: %d.%03d, power: %d.%03d W\n", + state->index, FIX32TOPRINT(state->current_a), + FIX32TOPRINT(state->voltage), FIX32TOPRINT(*power)); + + return 0; +} + +static void do_cpu_pid(struct cpu_pid_state *state, s32 temp, s32 power) +{ + s32 power_target, integral, derivative, proportional, adj_in_target, sval; + s64 integ_p, deriv_p, prop_p, sum; + int i; + + /* Calculate power target value (could be done once for all) + * and convert to a 16.16 fp number + */ + power_target = ((u32)(state->mpu.pmaxh - state->mpu.padjmax)) << 16; + DBG(" power target: %d.%03d, error: %d.%03d\n", + FIX32TOPRINT(power_target), FIX32TOPRINT(power_target - power)); + + /* Store temperature and power in history array */ + state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE; + state->temp_history[state->cur_temp] = temp; + state->cur_power = (state->cur_power + 1) % state->count_power; + state->power_history[state->cur_power] = power; + state->error_history[state->cur_power] = power_target - power; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (state->count_power - 1); i++) { + state->cur_power = (state->cur_power + 1) % state->count_power; + state->power_history[state->cur_power] = power; + state->error_history[state->cur_power] = power_target - power; + } + for (i = 0; i < (CPU_TEMP_HISTORY_SIZE - 1); i++) { + state->cur_temp = (state->cur_temp + 1) % CPU_TEMP_HISTORY_SIZE; + state->temp_history[state->cur_temp] = temp; + } + state->first = 0; + } + + /* Calculate the integral term normally based on the "power" values */ + sum = 0; + integral = 0; + for (i = 0; i < state->count_power; i++) + integral += state->error_history[i]; + integral *= CPU_PID_INTERVAL; + DBG(" integral: %08x\n", integral); + + /* Calculate the adjusted input (sense value). + * G_r is 12.20 + * integ is 16.16 + * so the result is 28.36 + * + * input target is mpu.ttarget, input max is mpu.tmax + */ + integ_p = ((s64)state->mpu.pid_gr) * (s64)integral; + DBG(" integ_p: %d\n", (int)(integ_p >> 36)); + sval = (state->mpu.tmax << 16) - ((integ_p >> 20) & 0xffffffff); + adj_in_target = (state->mpu.ttarget << 16); + if (adj_in_target > sval) + adj_in_target = sval; + DBG(" adj_in_target: %d.%03d, ttarget: %d\n", FIX32TOPRINT(adj_in_target), + state->mpu.ttarget); + + /* Calculate the derivative term */ + derivative = state->temp_history[state->cur_temp] - + state->temp_history[(state->cur_temp + CPU_TEMP_HISTORY_SIZE - 1) + % CPU_TEMP_HISTORY_SIZE]; + derivative /= CPU_PID_INTERVAL; + deriv_p = ((s64)state->mpu.pid_gd) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + proportional = temp - adj_in_target; + prop_p = ((s64)state->mpu.pid_gp) * (s64)proportional; + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + state->rpm += (s32)sum; +} + +static void do_monitor_cpu_combined(void) +{ + struct cpu_pid_state *state0 = &cpu_state[0]; + struct cpu_pid_state *state1 = &cpu_state[1]; + s32 temp0, power0, temp1, power1; + s32 temp_combi, power_combi; + int rc, intake, pump; + + rc = do_read_one_cpu_values(state0, &temp0, &power0); + if (rc < 0) { + /* XXX What do we do now ? */ + } + state1->overtemp = 0; + rc = do_read_one_cpu_values(state1, &temp1, &power1); + if (rc < 0) { + /* XXX What do we do now ? */ + } + if (state1->overtemp) + state0->overtemp++; + + temp_combi = max(temp0, temp1); + power_combi = max(power0, power1); + + /* Check tmax, increment overtemp if we are there. At tmax+8, we go + * full blown immediately and try to trigger a shutdown + */ + if (temp_combi >= ((state0->mpu.tmax + 8) << 16)) { + printk(KERN_WARNING "Warning ! Temperature way above maximum (%d) !\n", + temp_combi >> 16); + state0->overtemp = CPU_MAX_OVERTEMP; + } else if (temp_combi > (state0->mpu.tmax << 16)) + state0->overtemp++; + else + state0->overtemp = 0; + if (state0->overtemp >= CPU_MAX_OVERTEMP) + critical_state = 1; + if (state0->overtemp > 0) { + state0->rpm = state0->mpu.rmaxn_exhaust_fan; + state0->intake_rpm = intake = state0->mpu.rmaxn_intake_fan; + pump = state0->pump_min; + goto do_set_fans; + } + + /* Do the PID */ + do_cpu_pid(state0, temp_combi, power_combi); + + /* Range check */ + state0->rpm = max(state0->rpm, (int)state0->mpu.rminn_exhaust_fan); + state0->rpm = min(state0->rpm, (int)state0->mpu.rmaxn_exhaust_fan); + + /* Calculate intake fan speed */ + intake = (state0->rpm * CPU_INTAKE_SCALE) >> 16; + intake = max(intake, (int)state0->mpu.rminn_intake_fan); + intake = min(intake, (int)state0->mpu.rmaxn_intake_fan); + state0->intake_rpm = intake; + + /* Calculate pump speed */ + pump = (state0->rpm * state0->pump_max) / + state0->mpu.rmaxn_exhaust_fan; + pump = min(pump, state0->pump_max); + pump = max(pump, state0->pump_min); + + do_set_fans: + /* We copy values from state 0 to state 1 for /sysfs */ + state1->rpm = state0->rpm; + state1->intake_rpm = state0->intake_rpm; + + DBG("** CPU %d RPM: %d Ex, %d, Pump: %d, In, overtemp: %d\n", + state1->index, (int)state1->rpm, intake, pump, state1->overtemp); + + /* We should check for errors, shouldn't we ? But then, what + * do we do once the error occurs ? For FCU notified fan + * failures (-EFAULT) we probably want to notify userland + * some way... + */ + set_rpm_fan(CPUA_INTAKE_FAN_RPM_INDEX, intake); + set_rpm_fan(CPUA_EXHAUST_FAN_RPM_INDEX, state0->rpm); + set_rpm_fan(CPUB_INTAKE_FAN_RPM_INDEX, intake); + set_rpm_fan(CPUB_EXHAUST_FAN_RPM_INDEX, state0->rpm); + + if (fcu_fans[CPUA_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID) + set_rpm_fan(CPUA_PUMP_RPM_INDEX, pump); + if (fcu_fans[CPUB_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID) + set_rpm_fan(CPUB_PUMP_RPM_INDEX, pump); +} + +static void do_monitor_cpu_split(struct cpu_pid_state *state) +{ + s32 temp, power; + int rc, intake; + + /* Read current fan status */ + rc = do_read_one_cpu_values(state, &temp, &power); + if (rc < 0) { + /* XXX What do we do now ? */ + } + + /* Check tmax, increment overtemp if we are there. At tmax+8, we go + * full blown immediately and try to trigger a shutdown + */ + if (temp >= ((state->mpu.tmax + 8) << 16)) { + printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum" + " (%d) !\n", + state->index, temp >> 16); + state->overtemp = CPU_MAX_OVERTEMP; + } else if (temp > (state->mpu.tmax << 16)) + state->overtemp++; + else + state->overtemp = 0; + if (state->overtemp >= CPU_MAX_OVERTEMP) + critical_state = 1; + if (state->overtemp > 0) { + state->rpm = state->mpu.rmaxn_exhaust_fan; + state->intake_rpm = intake = state->mpu.rmaxn_intake_fan; + goto do_set_fans; + } + + /* Do the PID */ + do_cpu_pid(state, temp, power); + + /* Range check */ + state->rpm = max(state->rpm, (int)state->mpu.rminn_exhaust_fan); + state->rpm = min(state->rpm, (int)state->mpu.rmaxn_exhaust_fan); + + /* Calculate intake fan */ + intake = (state->rpm * CPU_INTAKE_SCALE) >> 16; + intake = max(intake, (int)state->mpu.rminn_intake_fan); + intake = min(intake, (int)state->mpu.rmaxn_intake_fan); + state->intake_rpm = intake; + + do_set_fans: + DBG("** CPU %d RPM: %d Ex, %d In, overtemp: %d\n", + state->index, (int)state->rpm, intake, state->overtemp); + + /* We should check for errors, shouldn't we ? But then, what + * do we do once the error occurs ? For FCU notified fan + * failures (-EFAULT) we probably want to notify userland + * some way... + */ + if (state->index == 0) { + set_rpm_fan(CPUA_INTAKE_FAN_RPM_INDEX, intake); + set_rpm_fan(CPUA_EXHAUST_FAN_RPM_INDEX, state->rpm); + } else { + set_rpm_fan(CPUB_INTAKE_FAN_RPM_INDEX, intake); + set_rpm_fan(CPUB_EXHAUST_FAN_RPM_INDEX, state->rpm); + } +} + +static void do_monitor_cpu_rack(struct cpu_pid_state *state) +{ + s32 temp, power, fan_min; + int rc; + + /* Read current fan status */ + rc = do_read_one_cpu_values(state, &temp, &power); + if (rc < 0) { + /* XXX What do we do now ? */ + } + + /* Check tmax, increment overtemp if we are there. At tmax+8, we go + * full blown immediately and try to trigger a shutdown + */ + if (temp >= ((state->mpu.tmax + 8) << 16)) { + printk(KERN_WARNING "Warning ! CPU %d temperature way above maximum" + " (%d) !\n", + state->index, temp >> 16); + state->overtemp = CPU_MAX_OVERTEMP; + } else if (temp > (state->mpu.tmax << 16)) + state->overtemp++; + else + state->overtemp = 0; + if (state->overtemp >= CPU_MAX_OVERTEMP) + critical_state = 1; + if (state->overtemp > 0) { + state->rpm = state->intake_rpm = state->mpu.rmaxn_intake_fan; + goto do_set_fans; + } + + /* Do the PID */ + do_cpu_pid(state, temp, power); + + /* Check clamp from dimms */ + fan_min = dimm_output_clamp; + fan_min = max(fan_min, (int)state->mpu.rminn_intake_fan); + + state->rpm = max(state->rpm, (int)fan_min); + state->rpm = min(state->rpm, (int)state->mpu.rmaxn_intake_fan); + state->intake_rpm = state->rpm; + + do_set_fans: + DBG("** CPU %d RPM: %d overtemp: %d\n", + state->index, (int)state->rpm, state->overtemp); + + /* We should check for errors, shouldn't we ? But then, what + * do we do once the error occurs ? For FCU notified fan + * failures (-EFAULT) we probably want to notify userland + * some way... + */ + if (state->index == 0) { + set_rpm_fan(CPU_A1_FAN_RPM_INDEX, state->rpm); + set_rpm_fan(CPU_A2_FAN_RPM_INDEX, state->rpm); + set_rpm_fan(CPU_A3_FAN_RPM_INDEX, state->rpm); + } else { + set_rpm_fan(CPU_B1_FAN_RPM_INDEX, state->rpm); + set_rpm_fan(CPU_B2_FAN_RPM_INDEX, state->rpm); + set_rpm_fan(CPU_B3_FAN_RPM_INDEX, state->rpm); + } +} + +/* + * Initialize the state structure for one CPU control loop + */ +static int init_cpu_state(struct cpu_pid_state *state, int index) +{ + state->index = index; + state->first = 1; + state->rpm = (cpu_pid_type == CPU_PID_TYPE_RACKMAC) ? 4000 : 1000; + state->overtemp = 0; + state->adc_config = 0x00; + + + if (index == 0) + state->monitor = attach_i2c_chip(SUPPLY_MONITOR_ID, "CPU0_monitor"); + else if (index == 1) + state->monitor = attach_i2c_chip(SUPPLY_MONITORB_ID, "CPU1_monitor"); + if (state->monitor == NULL) + goto fail; + + if (read_eeprom(index, &state->mpu)) + goto fail; + + state->count_power = state->mpu.tguardband; + if (state->count_power > CPU_POWER_HISTORY_SIZE) { + printk(KERN_WARNING "Warning ! too many power history slots\n"); + state->count_power = CPU_POWER_HISTORY_SIZE; + } + DBG("CPU %d Using %d power history entries\n", index, state->count_power); + + if (index == 0) { + device_create_file(&of_dev->dev, &dev_attr_cpu0_temperature); + device_create_file(&of_dev->dev, &dev_attr_cpu0_voltage); + device_create_file(&of_dev->dev, &dev_attr_cpu0_current); + device_create_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm); + device_create_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm); + } else { + device_create_file(&of_dev->dev, &dev_attr_cpu1_temperature); + device_create_file(&of_dev->dev, &dev_attr_cpu1_voltage); + device_create_file(&of_dev->dev, &dev_attr_cpu1_current); + device_create_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm); + device_create_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm); + } + + return 0; + fail: + if (state->monitor) + detach_i2c_chip(state->monitor); + state->monitor = NULL; + + return -ENODEV; +} + +/* + * Dispose of the state data for one CPU control loop + */ +static void dispose_cpu_state(struct cpu_pid_state *state) +{ + if (state->monitor == NULL) + return; + + if (state->index == 0) { + device_remove_file(&of_dev->dev, &dev_attr_cpu0_temperature); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_voltage); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_current); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_exhaust_fan_rpm); + device_remove_file(&of_dev->dev, &dev_attr_cpu0_intake_fan_rpm); + } else { + device_remove_file(&of_dev->dev, &dev_attr_cpu1_temperature); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_voltage); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_current); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_exhaust_fan_rpm); + device_remove_file(&of_dev->dev, &dev_attr_cpu1_intake_fan_rpm); + } + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + +/* + * Motherboard backside & U3 heatsink fan control loop + */ +static void do_monitor_backside(struct backside_pid_state *state) +{ + s32 temp, integral, derivative, fan_min; + s64 integ_p, deriv_p, prop_p, sum; + int i, rc; + + if (--state->ticks != 0) + return; + state->ticks = backside_params.interval; + + DBG("backside:\n"); + + /* Check fan status */ + rc = get_pwm_fan(BACKSIDE_FAN_PWM_INDEX); + if (rc < 0) { + printk(KERN_WARNING "Error %d reading backside fan !\n", rc); + /* XXX What do we do now ? */ + } else + state->pwm = rc; + DBG(" current pwm: %d\n", state->pwm); + + /* Get some sensor readings */ + temp = i2c_smbus_read_byte_data(state->monitor, MAX6690_EXT_TEMP) << 16; + state->last_temp = temp; + DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp), + FIX32TOPRINT(backside_params.input_target)); + + /* Store temperature and error in history array */ + state->cur_sample = (state->cur_sample + 1) % BACKSIDE_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = temp - backside_params.input_target; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (BACKSIDE_PID_HISTORY_SIZE - 1); i++) { + state->cur_sample = (state->cur_sample + 1) % + BACKSIDE_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = + temp - backside_params.input_target; + } + state->first = 0; + } + + /* Calculate the integral term */ + sum = 0; + integral = 0; + for (i = 0; i < BACKSIDE_PID_HISTORY_SIZE; i++) + integral += state->error_history[i]; + integral *= backside_params.interval; + DBG(" integral: %08x\n", integral); + integ_p = ((s64)backside_params.G_r) * (s64)integral; + DBG(" integ_p: %d\n", (int)(integ_p >> 36)); + sum += integ_p; + + /* Calculate the derivative term */ + derivative = state->error_history[state->cur_sample] - + state->error_history[(state->cur_sample + BACKSIDE_PID_HISTORY_SIZE - 1) + % BACKSIDE_PID_HISTORY_SIZE]; + derivative /= backside_params.interval; + deriv_p = ((s64)backside_params.G_d) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + prop_p = ((s64)backside_params.G_p) * (s64)(state->error_history[state->cur_sample]); + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + if (backside_params.additive) + state->pwm += (s32)sum; + else + state->pwm = sum; + + /* Check for clamp */ + fan_min = (dimm_output_clamp * 100) / 14000; + fan_min = max(fan_min, backside_params.output_min); + + state->pwm = max(state->pwm, fan_min); + state->pwm = min(state->pwm, backside_params.output_max); + + DBG("** BACKSIDE PWM: %d\n", (int)state->pwm); + set_pwm_fan(BACKSIDE_FAN_PWM_INDEX, state->pwm); +} + +/* + * Initialize the state structure for the backside fan control loop + */ +static int init_backside_state(struct backside_pid_state *state) +{ + struct device_node *u3; + int u3h = 1; /* conservative by default */ + + /* + * There are different PID params for machines with U3 and machines + * with U3H, pick the right ones now + */ + u3 = of_find_node_by_path("/u3@0,f8000000"); + if (u3 != NULL) { + u32 *vers = (u32 *)get_property(u3, "device-rev", NULL); + if (vers) + if (((*vers) & 0x3f) < 0x34) + u3h = 0; + of_node_put(u3); + } + + if (rackmac) { + backside_params.G_d = BACKSIDE_PID_RACK_G_d; + backside_params.input_target = BACKSIDE_PID_RACK_INPUT_TARGET; + backside_params.output_min = BACKSIDE_PID_U3H_OUTPUT_MIN; + backside_params.interval = BACKSIDE_PID_RACK_INTERVAL; + backside_params.G_p = BACKSIDE_PID_RACK_G_p; + backside_params.G_r = BACKSIDE_PID_G_r; + backside_params.output_max = BACKSIDE_PID_OUTPUT_MAX; + backside_params.additive = 0; + } else if (u3h) { + backside_params.G_d = BACKSIDE_PID_U3H_G_d; + backside_params.input_target = BACKSIDE_PID_U3H_INPUT_TARGET; + backside_params.output_min = BACKSIDE_PID_U3H_OUTPUT_MIN; + backside_params.interval = BACKSIDE_PID_INTERVAL; + backside_params.G_p = BACKSIDE_PID_G_p; + backside_params.G_r = BACKSIDE_PID_G_r; + backside_params.output_max = BACKSIDE_PID_OUTPUT_MAX; + backside_params.additive = 1; + } else { + backside_params.G_d = BACKSIDE_PID_U3_G_d; + backside_params.input_target = BACKSIDE_PID_U3_INPUT_TARGET; + backside_params.output_min = BACKSIDE_PID_U3_OUTPUT_MIN; + backside_params.interval = BACKSIDE_PID_INTERVAL; + backside_params.G_p = BACKSIDE_PID_G_p; + backside_params.G_r = BACKSIDE_PID_G_r; + backside_params.output_max = BACKSIDE_PID_OUTPUT_MAX; + backside_params.additive = 1; + } + + state->ticks = 1; + state->first = 1; + state->pwm = 50; + + state->monitor = attach_i2c_chip(BACKSIDE_MAX_ID, "backside_temp"); + if (state->monitor == NULL) + return -ENODEV; + + device_create_file(&of_dev->dev, &dev_attr_backside_temperature); + device_create_file(&of_dev->dev, &dev_attr_backside_fan_pwm); + + return 0; +} + +/* + * Dispose of the state data for the backside control loop + */ +static void dispose_backside_state(struct backside_pid_state *state) +{ + if (state->monitor == NULL) + return; + + device_remove_file(&of_dev->dev, &dev_attr_backside_temperature); + device_remove_file(&of_dev->dev, &dev_attr_backside_fan_pwm); + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + +/* + * Drives bay fan control loop + */ +static void do_monitor_drives(struct drives_pid_state *state) +{ + s32 temp, integral, derivative; + s64 integ_p, deriv_p, prop_p, sum; + int i, rc; + + if (--state->ticks != 0) + return; + state->ticks = DRIVES_PID_INTERVAL; + + DBG("drives:\n"); + + /* Check fan status */ + rc = get_rpm_fan(DRIVES_FAN_RPM_INDEX, !RPM_PID_USE_ACTUAL_SPEED); + if (rc < 0) { + printk(KERN_WARNING "Error %d reading drives fan !\n", rc); + /* XXX What do we do now ? */ + } else + state->rpm = rc; + DBG(" current rpm: %d\n", state->rpm); + + /* Get some sensor readings */ + temp = le16_to_cpu(i2c_smbus_read_word_data(state->monitor, DS1775_TEMP)) << 8; + state->last_temp = temp; + DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp), + FIX32TOPRINT(DRIVES_PID_INPUT_TARGET)); + + /* Store temperature and error in history array */ + state->cur_sample = (state->cur_sample + 1) % DRIVES_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = temp - DRIVES_PID_INPUT_TARGET; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (DRIVES_PID_HISTORY_SIZE - 1); i++) { + state->cur_sample = (state->cur_sample + 1) % + DRIVES_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = + temp - DRIVES_PID_INPUT_TARGET; + } + state->first = 0; + } + + /* Calculate the integral term */ + sum = 0; + integral = 0; + for (i = 0; i < DRIVES_PID_HISTORY_SIZE; i++) + integral += state->error_history[i]; + integral *= DRIVES_PID_INTERVAL; + DBG(" integral: %08x\n", integral); + integ_p = ((s64)DRIVES_PID_G_r) * (s64)integral; + DBG(" integ_p: %d\n", (int)(integ_p >> 36)); + sum += integ_p; + + /* Calculate the derivative term */ + derivative = state->error_history[state->cur_sample] - + state->error_history[(state->cur_sample + DRIVES_PID_HISTORY_SIZE - 1) + % DRIVES_PID_HISTORY_SIZE]; + derivative /= DRIVES_PID_INTERVAL; + deriv_p = ((s64)DRIVES_PID_G_d) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + prop_p = ((s64)DRIVES_PID_G_p) * (s64)(state->error_history[state->cur_sample]); + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + state->rpm += (s32)sum; + + state->rpm = max(state->rpm, DRIVES_PID_OUTPUT_MIN); + state->rpm = min(state->rpm, DRIVES_PID_OUTPUT_MAX); + + DBG("** DRIVES RPM: %d\n", (int)state->rpm); + set_rpm_fan(DRIVES_FAN_RPM_INDEX, state->rpm); +} + +/* + * Initialize the state structure for the drives bay fan control loop + */ +static int init_drives_state(struct drives_pid_state *state) +{ + state->ticks = 1; + state->first = 1; + state->rpm = 1000; + + state->monitor = attach_i2c_chip(DRIVES_DALLAS_ID, "drives_temp"); + if (state->monitor == NULL) + return -ENODEV; + + device_create_file(&of_dev->dev, &dev_attr_drives_temperature); + device_create_file(&of_dev->dev, &dev_attr_drives_fan_rpm); + + return 0; +} + +/* + * Dispose of the state data for the drives control loop + */ +static void dispose_drives_state(struct drives_pid_state *state) +{ + if (state->monitor == NULL) + return; + + device_remove_file(&of_dev->dev, &dev_attr_drives_temperature); + device_remove_file(&of_dev->dev, &dev_attr_drives_fan_rpm); + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + +/* + * DIMMs temp control loop + */ +static void do_monitor_dimms(struct dimm_pid_state *state) +{ + s32 temp, integral, derivative, fan_min; + s64 integ_p, deriv_p, prop_p, sum; + int i; + + if (--state->ticks != 0) + return; + state->ticks = DIMM_PID_INTERVAL; + + DBG("DIMM:\n"); + + DBG(" current value: %d\n", state->output); + + temp = read_lm87_reg(state->monitor, LM87_INT_TEMP); + if (temp < 0) + return; + temp <<= 16; + state->last_temp = temp; + DBG(" temp: %d.%03d, target: %d.%03d\n", FIX32TOPRINT(temp), + FIX32TOPRINT(DIMM_PID_INPUT_TARGET)); + + /* Store temperature and error in history array */ + state->cur_sample = (state->cur_sample + 1) % DIMM_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = temp - DIMM_PID_INPUT_TARGET; + + /* If first loop, fill the history table */ + if (state->first) { + for (i = 0; i < (DIMM_PID_HISTORY_SIZE - 1); i++) { + state->cur_sample = (state->cur_sample + 1) % + DIMM_PID_HISTORY_SIZE; + state->sample_history[state->cur_sample] = temp; + state->error_history[state->cur_sample] = + temp - DIMM_PID_INPUT_TARGET; + } + state->first = 0; + } + + /* Calculate the integral term */ + sum = 0; + integral = 0; + for (i = 0; i < DIMM_PID_HISTORY_SIZE; i++) + integral += state->error_history[i]; + integral *= DIMM_PID_INTERVAL; + DBG(" integral: %08x\n", integral); + integ_p = ((s64)DIMM_PID_G_r) * (s64)integral; + DBG(" integ_p: %d\n", (int)(integ_p >> 36)); + sum += integ_p; + + /* Calculate the derivative term */ + derivative = state->error_history[state->cur_sample] - + state->error_history[(state->cur_sample + DIMM_PID_HISTORY_SIZE - 1) + % DIMM_PID_HISTORY_SIZE]; + derivative /= DIMM_PID_INTERVAL; + deriv_p = ((s64)DIMM_PID_G_d) * (s64)derivative; + DBG(" deriv_p: %d\n", (int)(deriv_p >> 36)); + sum += deriv_p; + + /* Calculate the proportional term */ + prop_p = ((s64)DIMM_PID_G_p) * (s64)(state->error_history[state->cur_sample]); + DBG(" prop_p: %d\n", (int)(prop_p >> 36)); + sum += prop_p; + + /* Scale sum */ + sum >>= 36; + + DBG(" sum: %d\n", (int)sum); + state->output = (s32)sum; + state->output = max(state->output, DIMM_PID_OUTPUT_MIN); + state->output = min(state->output, DIMM_PID_OUTPUT_MAX); + dimm_output_clamp = state->output; + + DBG("** DIMM clamp value: %d\n", (int)state->output); + + /* Backside PID is only every 5 seconds, force backside fan clamping now */ + fan_min = (dimm_output_clamp * 100) / 14000; + fan_min = max(fan_min, backside_params.output_min); + if (backside_state.pwm < fan_min) { + backside_state.pwm = fan_min; + DBG(" -> applying clamp to backside fan now: %d !\n", fan_min); + set_pwm_fan(BACKSIDE_FAN_PWM_INDEX, fan_min); + } +} + +/* + * Initialize the state structure for the DIMM temp control loop + */ +static int init_dimms_state(struct dimm_pid_state *state) +{ + state->ticks = 1; + state->first = 1; + state->output = 4000; + + state->monitor = attach_i2c_chip(XSERVE_DIMMS_LM87, "dimms_temp"); + if (state->monitor == NULL) + return -ENODEV; + + device_create_file(&of_dev->dev, &dev_attr_dimms_temperature); + + return 0; +} + +/* + * Dispose of the state data for the drives control loop + */ +static void dispose_dimms_state(struct dimm_pid_state *state) +{ + if (state->monitor == NULL) + return; + + device_remove_file(&of_dev->dev, &dev_attr_dimms_temperature); + + detach_i2c_chip(state->monitor); + state->monitor = NULL; +} + +static int call_critical_overtemp(void) +{ + char *argv[] = { critical_overtemp_path, NULL }; + static char *envp[] = { "HOME=/", + "TERM=linux", + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", + NULL }; + + return call_usermodehelper(critical_overtemp_path, argv, envp, 0); +} + + +/* + * Here's the kernel thread that calls the various control loops + */ +static int main_control_loop(void *x) +{ + daemonize("kfand"); + + DBG("main_control_loop started\n"); + + down(&driver_lock); + + if (start_fcu() < 0) { + printk(KERN_ERR "kfand: failed to start FCU\n"); + up(&driver_lock); + goto out; + } + + /* Set the PCI fan once for now */ + set_pwm_fan(SLOTS_FAN_PWM_INDEX, SLOTS_FAN_DEFAULT_PWM); + + /* Initialize ADCs */ + initialize_adc(&cpu_state[0]); + if (cpu_state[1].monitor != NULL) + initialize_adc(&cpu_state[1]); + + up(&driver_lock); + + while (state == state_attached) { + unsigned long elapsed, start; + + start = jiffies; + + down(&driver_lock); + + /* First, we always calculate the new DIMMs state on an Xserve */ + if (rackmac) + do_monitor_dimms(&dimms_state); + + /* Then, the CPUs */ + if (cpu_pid_type == CPU_PID_TYPE_COMBINED) + do_monitor_cpu_combined(); + else if (cpu_pid_type == CPU_PID_TYPE_RACKMAC) { + do_monitor_cpu_rack(&cpu_state[0]); + if (cpu_state[1].monitor != NULL) + do_monitor_cpu_rack(&cpu_state[1]); + // better deal with UP + } else { + do_monitor_cpu_split(&cpu_state[0]); + if (cpu_state[1].monitor != NULL) + do_monitor_cpu_split(&cpu_state[1]); + // better deal with UP + } + /* Then, the rest */ + do_monitor_backside(&backside_state); + if (!rackmac) + do_monitor_drives(&drives_state); + up(&driver_lock); + + if (critical_state == 1) { + printk(KERN_WARNING "Temperature control detected a critical condition\n"); + printk(KERN_WARNING "Attempting to shut down...\n"); + if (call_critical_overtemp()) { + printk(KERN_WARNING "Can't call %s, power off now!\n", + critical_overtemp_path); + machine_power_off(); + } + } + if (critical_state > 0) + critical_state++; + if (critical_state > MAX_CRITICAL_STATE) { + printk(KERN_WARNING "Shutdown timed out, power off now !\n"); + machine_power_off(); + } + + // FIXME: Deal with signals + set_current_state(TASK_INTERRUPTIBLE); + elapsed = jiffies - start; + if (elapsed < HZ) + schedule_timeout(HZ - elapsed); + } + + out: + DBG("main_control_loop ended\n"); + + ctrl_task = 0; + complete_and_exit(&ctrl_complete, 0); +} + +/* + * Dispose the control loops when tearing down + */ +static void dispose_control_loops(void) +{ + dispose_cpu_state(&cpu_state[0]); + dispose_cpu_state(&cpu_state[1]); + dispose_backside_state(&backside_state); + dispose_drives_state(&drives_state); + dispose_dimms_state(&dimms_state); +} + +/* + * Create the control loops. U3-0 i2c bus is up, so we can now + * get to the various sensors + */ +static int create_control_loops(void) +{ + struct device_node *np; + + /* Count CPUs from the device-tree, we don't care how many are + * actually used by Linux + */ + cpu_count = 0; + for (np = NULL; NULL != (np = of_find_node_by_type(np, "cpu"));) + cpu_count++; + + DBG("counted %d CPUs in the device-tree\n", cpu_count); + + /* Decide the type of PID algorithm to use based on the presence of + * the pumps, though that may not be the best way, that is good enough + * for now + */ + if (rackmac) + cpu_pid_type = CPU_PID_TYPE_RACKMAC; + else if (machine_is_compatible("PowerMac7,3") + && (cpu_count > 1) + && fcu_fans[CPUA_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID + && fcu_fans[CPUB_PUMP_RPM_INDEX].id != FCU_FAN_ABSENT_ID) { + printk(KERN_INFO "Liquid cooling pumps detected, using new algorithm !\n"); + cpu_pid_type = CPU_PID_TYPE_COMBINED; + } else + cpu_pid_type = CPU_PID_TYPE_SPLIT; + + /* Create control loops for everything. If any fail, everything + * fails + */ + if (init_cpu_state(&cpu_state[0], 0)) + goto fail; + if (cpu_pid_type == CPU_PID_TYPE_COMBINED) + fetch_cpu_pumps_minmax(); + + if (cpu_count > 1 && init_cpu_state(&cpu_state[1], 1)) + goto fail; + if (init_backside_state(&backside_state)) + goto fail; + if (rackmac && init_dimms_state(&dimms_state)) + goto fail; + if (!rackmac && init_drives_state(&drives_state)) + goto fail; + + DBG("all control loops up !\n"); + + return 0; + + fail: + DBG("failure creating control loops, disposing\n"); + + dispose_control_loops(); + + return -ENODEV; +} + +/* + * Start the control loops after everything is up, that is create + * the thread that will make them run + */ +static void start_control_loops(void) +{ + init_completion(&ctrl_complete); + + ctrl_task = kernel_thread(main_control_loop, NULL, SIGCHLD | CLONE_KERNEL); +} + +/* + * Stop the control loops when tearing down + */ +static void stop_control_loops(void) +{ + if (ctrl_task != 0) + wait_for_completion(&ctrl_complete); +} + +/* + * Attach to the i2c FCU after detecting U3-1 bus + */ +static int attach_fcu(void) +{ + fcu = attach_i2c_chip(FAN_CTRLER_ID, "fcu"); + if (fcu == NULL) + return -ENODEV; + + DBG("FCU attached\n"); + + return 0; +} + +/* + * Detach from the i2c FCU when tearing down + */ +static void detach_fcu(void) +{ + if (fcu) + detach_i2c_chip(fcu); + fcu = NULL; +} + +/* + * Attach to the i2c controller. We probe the various chips based + * on the device-tree nodes and build everything for the driver to + * run, we then kick the driver monitoring thread + */ +static int therm_pm72_attach(struct i2c_adapter *adapter) +{ + down(&driver_lock); + + /* Check state */ + if (state == state_detached) + state = state_attaching; + if (state != state_attaching) { + up(&driver_lock); + return 0; + } + + /* Check if we are looking for one of these */ + if (u3_0 == NULL && !strcmp(adapter->name, "u3 0")) { + u3_0 = adapter; + DBG("found U3-0\n"); + if (k2 || !rackmac) + if (create_control_loops()) + u3_0 = NULL; + } else if (u3_1 == NULL && !strcmp(adapter->name, "u3 1")) { + u3_1 = adapter; + DBG("found U3-1, attaching FCU\n"); + if (attach_fcu()) + u3_1 = NULL; + } else if (k2 == NULL && !strcmp(adapter->name, "mac-io 0")) { + k2 = adapter; + DBG("Found K2\n"); + if (u3_0 && rackmac) + if (create_control_loops()) + k2 = NULL; + } + /* We got all we need, start control loops */ + if (u3_0 != NULL && u3_1 != NULL && (k2 || !rackmac)) { + DBG("everything up, starting control loops\n"); + state = state_attached; + start_control_loops(); + } + up(&driver_lock); + + return 0; +} + +/* + * Called on every adapter when the driver or the i2c controller + * is going away. + */ +static int therm_pm72_detach(struct i2c_adapter *adapter) +{ + down(&driver_lock); + + if (state != state_detached) + state = state_detaching; + + /* Stop control loops if any */ + DBG("stopping control loops\n"); + up(&driver_lock); + stop_control_loops(); + down(&driver_lock); + + if (u3_0 != NULL && !strcmp(adapter->name, "u3 0")) { + DBG("lost U3-0, disposing control loops\n"); + dispose_control_loops(); + u3_0 = NULL; + } + + if (u3_1 != NULL && !strcmp(adapter->name, "u3 1")) { + DBG("lost U3-1, detaching FCU\n"); + detach_fcu(); + u3_1 = NULL; + } + if (u3_0 == NULL && u3_1 == NULL) + state = state_detached; + + up(&driver_lock); + + return 0; +} + +static int fan_check_loc_match(const char *loc, int fan) +{ + char tmp[64]; + char *c, *e; + + strlcpy(tmp, fcu_fans[fan].loc, 64); + + c = tmp; + for (;;) { + e = strchr(c, ','); + if (e) + *e = 0; + if (strcmp(loc, c) == 0) + return 1; + if (e == NULL) + break; + c = e + 1; + } + return 0; +} + +static void fcu_lookup_fans(struct device_node *fcu_node) +{ + struct device_node *np = NULL; + int i; + + /* The table is filled by default with values that are suitable + * for the old machines without device-tree informations. We scan + * the device-tree and override those values with whatever is + * there + */ + + DBG("Looking up FCU controls in device-tree...\n"); + + while ((np = of_get_next_child(fcu_node, np)) != NULL) { + int type = -1; + char *loc; + u32 *reg; + + DBG(" control: %s, type: %s\n", np->name, np->type); + + /* Detect control type */ + if (!strcmp(np->type, "fan-rpm-control") || + !strcmp(np->type, "fan-rpm")) + type = FCU_FAN_RPM; + if (!strcmp(np->type, "fan-pwm-control") || + !strcmp(np->type, "fan-pwm")) + type = FCU_FAN_PWM; + /* Only care about fans for now */ + if (type == -1) + continue; + + /* Lookup for a matching location */ + loc = (char *)get_property(np, "location", NULL); + reg = (u32 *)get_property(np, "reg", NULL); + if (loc == NULL || reg == NULL) + continue; + DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg); + + for (i = 0; i < FCU_FAN_COUNT; i++) { + int fan_id; + + if (!fan_check_loc_match(loc, i)) + continue; + DBG(" location match, index: %d\n", i); + fcu_fans[i].id = FCU_FAN_ABSENT_ID; + if (type != fcu_fans[i].type) { + printk(KERN_WARNING "therm_pm72: Fan type mismatch " + "in device-tree for %s\n", np->full_name); + break; + } + if (type == FCU_FAN_RPM) + fan_id = ((*reg) - 0x10) / 2; + else + fan_id = ((*reg) - 0x30) / 2; + if (fan_id > 7) { + printk(KERN_WARNING "therm_pm72: Can't parse " + "fan ID in device-tree for %s\n", np->full_name); + break; + } + DBG(" fan id -> %d, type -> %d\n", fan_id, type); + fcu_fans[i].id = fan_id; + } + } + + /* Now dump the array */ + printk(KERN_INFO "Detected fan controls:\n"); + for (i = 0; i < FCU_FAN_COUNT; i++) { + if (fcu_fans[i].id == FCU_FAN_ABSENT_ID) + continue; + printk(KERN_INFO " %d: %s fan, id %d, location: %s\n", i, + fcu_fans[i].type == FCU_FAN_RPM ? "RPM" : "PWM", + fcu_fans[i].id, fcu_fans[i].loc); + } +} + +static int fcu_of_probe(struct of_device* dev, const struct of_match *match) +{ + int rc; + + state = state_detached; + + /* Lookup the fans in the device tree */ + fcu_lookup_fans(dev->node); + + /* Add the driver */ + rc = i2c_add_driver(&therm_pm72_driver); + if (rc < 0) + return rc; + return 0; +} + +static int fcu_of_remove(struct of_device* dev) +{ + i2c_del_driver(&therm_pm72_driver); + + return 0; +} + +static struct of_match fcu_of_match[] = +{ + { + .name = OF_ANY_MATCH, + .type = "fcu", + .compatible = OF_ANY_MATCH + }, + {}, +}; + +static struct of_platform_driver fcu_of_platform_driver = +{ + .name = "temperature", + .match_table = fcu_of_match, + .probe = fcu_of_probe, + .remove = fcu_of_remove +}; + +/* + * Check machine type, attach to i2c controller + */ +static int __init therm_pm72_init(void) +{ + struct device_node *np; + + rackmac = machine_is_compatible("RackMac3,1"); + + if (!machine_is_compatible("PowerMac7,2") && + !machine_is_compatible("PowerMac7,3") && + !rackmac) + return -ENODEV; + + printk(KERN_INFO "PowerMac G5 Thermal control driver %s\n", VERSION); + + np = of_find_node_by_type(NULL, "fcu"); + if (np == NULL) { + /* Some machines have strangely broken device-tree */ + np = of_find_node_by_path("/u3@0,f8000000/i2c@f8001000/fan@15e"); + if (np == NULL) { + printk(KERN_ERR "Can't find FCU in device-tree !\n"); + return -ENODEV; + } + } + of_dev = of_platform_device_create(np, "temperature"); + if (of_dev == NULL) { + printk(KERN_ERR "Can't register FCU platform device !\n"); + return -ENODEV; + } + + of_register_driver(&fcu_of_platform_driver); + + return 0; +} + +static void __exit therm_pm72_exit(void) +{ + of_unregister_driver(&fcu_of_platform_driver); + + if (of_dev) + of_device_unregister(of_dev); +} + +module_init(therm_pm72_init); +module_exit(therm_pm72_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Driver for Apple's PowerMac G5 thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/therm_pm72.h b/drivers/macintosh/therm_pm72.h new file mode 100644 index 000000000000..c17e61f9c418 --- /dev/null +++ b/drivers/macintosh/therm_pm72.h @@ -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__ */ diff --git a/drivers/macintosh/therm_windtunnel.c b/drivers/macintosh/therm_windtunnel.c new file mode 100644 index 000000000000..c153699d0f84 --- /dev/null +++ b/drivers/macintosh/therm_windtunnel.c @@ -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 <samuel@ibrium.se>"); +MODULE_DESCRIPTION("Apple G4 (windtunnel) fan controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/macintosh/via-cuda.c b/drivers/macintosh/via-cuda.c new file mode 100644 index 000000000000..417deb5de108 --- /dev/null +++ b/drivers/macintosh/via-cuda.c @@ -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"); + } +} diff --git a/drivers/macintosh/via-macii.c b/drivers/macintosh/via-macii.c new file mode 100644 index 000000000000..e9a159ad3022 --- /dev/null +++ b/drivers/macintosh/via-macii.c @@ -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; +} diff --git a/drivers/macintosh/via-maciisi.c b/drivers/macintosh/via-maciisi.c new file mode 100644 index 000000000000..a1966975d58f --- /dev/null +++ b/drivers/macintosh/via-maciisi.c @@ -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; + } +} diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c new file mode 100644 index 000000000000..cea1e758eb46 --- /dev/null +++ b/drivers/macintosh/via-pmu.c @@ -0,0 +1,3147 @@ +/* + * Device driver for the via-pmu on Apple Powermacs. + * + * The VIA (versatile interface adapter) interfaces to the PMU, + * a 6805 microprocessor core whose primary function is to control + * battery charging and system power on the PowerBook 3400 and 2400. + * The PMU also controls the ADB (Apple Desktop Bus) which connects + * to the keyboard and mouse, as well as the non-volatile RAM + * and the RTC (real time clock) chip. + * + * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi. + * Copyright (C) 2001-2002 Benjamin Herrenschmidt + * + * THIS DRIVER IS BECOMING A TOTAL MESS ! + * - Cleanup atomically disabling reply to PMU events after + * a sleep or a freq. switch + * - Move sleep code out of here to pmac_pm, merge into new + * common PM infrastructure + * - Move backlight code out as well + * - Save/Restore PCI space properly + * + */ +#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/miscdevice.h> +#include <linux/blkdev.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/adb.h> +#include <linux/pmu.h> +#include <linux/cuda.h> +#include <linux/smp_lock.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/pm.h> +#include <linux/proc_fs.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/suspend.h> +#include <linux/syscalls.h> +#include <linux/cpu.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/irq.h> +#include <asm/pmac_feature.h> +#include <asm/uaccess.h> +#include <asm/mmu_context.h> +#include <asm/cputable.h> +#include <asm/time.h> +#ifdef CONFIG_PMAC_BACKLIGHT +#include <asm/backlight.h> +#endif + +/* Some compile options */ +#undef SUSPEND_USES_PMU +#define DEBUG_SLEEP +#undef HACKED_PCI_SAVE + +/* Misc minor number allocated for /dev/pmu */ +#define PMU_MINOR 154 + +/* How many iterations between battery polls */ +#define BATTERY_POLLING_COUNT 2 + +static volatile unsigned char __iomem *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: both active low */ +#define TACK 0x08 /* Transfer acknowledge (input) */ +#define TREQ 0x10 /* Transfer request (output) */ + +/* Bits in ACR */ +#define SR_CTRL 0x1c /* Shift register control bits */ +#define SR_EXT 0x0c /* Shift on external clock */ +#define SR_OUT 0x10 /* Shift out if 1 */ + +/* Bits in IFR and IER */ +#define IER_SET 0x80 /* set bits in IER */ +#define IER_CLR 0 /* clear bits in IER */ +#define SR_INT 0x04 /* Shift register full/empty */ +#define CB2_INT 0x08 +#define CB1_INT 0x10 /* transition on CB1 input */ + +static volatile enum pmu_state { + idle, + sending, + intack, + reading, + reading_intr, + locked, +} pmu_state; + +static volatile enum int_data_state { + int_data_empty, + int_data_fill, + int_data_ready, + int_data_flush +} int_data_state[2] = { int_data_empty, int_data_empty }; + +static struct adb_request *current_req; +static struct adb_request *last_req; +static struct adb_request *req_awaiting_reply; +static unsigned char interrupt_data[2][32]; +static int interrupt_data_len[2]; +static int int_data_last; +static unsigned char *reply_ptr; +static int data_index; +static int data_len; +static volatile int adb_int_pending; +static volatile int disable_poll; +static struct adb_request bright_req_1, bright_req_2; +static struct device_node *vias; +static int pmu_kind = PMU_UNKNOWN; +static int pmu_fully_inited = 0; +static int pmu_has_adb; +static unsigned char __iomem *gpio_reg = NULL; +static int gpio_irq = -1; +static int gpio_irq_enabled = -1; +static volatile int pmu_suspended = 0; +static spinlock_t pmu_lock; +static u8 pmu_intr_mask; +static int pmu_version; +static int drop_interrupts; +#ifdef CONFIG_PMAC_PBOOK +static int option_lid_wakeup = 1; +static int sleep_in_progress; +#endif /* CONFIG_PMAC_PBOOK */ +static unsigned long async_req_locks; +static unsigned int pmu_irq_stats[11]; + +static struct proc_dir_entry *proc_pmu_root; +static struct proc_dir_entry *proc_pmu_info; +static struct proc_dir_entry *proc_pmu_irqstats; +static struct proc_dir_entry *proc_pmu_options; +static int option_server_mode; + +#ifdef CONFIG_PMAC_PBOOK +int pmu_battery_count; +int pmu_cur_battery; +unsigned int pmu_power_flags; +struct pmu_battery_info pmu_batteries[PMU_MAX_BATTERIES]; +static int query_batt_timer = BATTERY_POLLING_COUNT; +static struct adb_request batt_req; +static struct proc_dir_entry *proc_pmu_batt[PMU_MAX_BATTERIES]; +#endif /* CONFIG_PMAC_PBOOK */ + +#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) +extern int disable_kernel_backlight; +#endif /* defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) */ + +int __fake_sleep; +int asleep; +struct notifier_block *sleep_notifier_list; + +#ifdef CONFIG_ADB +static int adb_dev_map = 0; +static int pmu_adb_flags; + +static int pmu_probe(void); +static int pmu_init(void); +static int pmu_send_request(struct adb_request *req, int sync); +static int pmu_adb_autopoll(int devs); +static int pmu_adb_reset_bus(void); +#endif /* CONFIG_ADB */ + +static int init_pmu(void); +static int pmu_queue_request(struct adb_request *req); +static void pmu_start(void); +static irqreturn_t via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs); +static irqreturn_t gpio1_interrupt(int irq, void *arg, struct pt_regs *regs); +static int proc_get_info(char *page, char **start, off_t off, + int count, int *eof, void *data); +static int proc_get_irqstats(char *page, char **start, off_t off, + int count, int *eof, void *data); +#ifdef CONFIG_PMAC_BACKLIGHT +static int pmu_set_backlight_level(int level, void* data); +static int pmu_set_backlight_enable(int on, int level, void* data); +#endif /* CONFIG_PMAC_BACKLIGHT */ +#ifdef CONFIG_PMAC_PBOOK +static void pmu_pass_intr(unsigned char *data, int len); +static int proc_get_batt(char *page, char **start, off_t off, + int count, int *eof, void *data); +#endif /* CONFIG_PMAC_PBOOK */ +static int proc_read_options(char *page, char **start, off_t off, + int count, int *eof, void *data); +static int proc_write_options(struct file *file, const char __user *buffer, + unsigned long count, void *data); + +#ifdef CONFIG_ADB +struct adb_driver via_pmu_driver = { + "PMU", + pmu_probe, + pmu_init, + pmu_send_request, + pmu_adb_autopoll, + pmu_poll_adb, + pmu_adb_reset_bus +}; +#endif /* CONFIG_ADB */ + +extern void low_sleep_handler(void); +extern void enable_kernel_altivec(void); +extern void enable_kernel_fp(void); + +#ifdef DEBUG_SLEEP +int pmu_polled_request(struct adb_request *req); +int pmu_wink(struct adb_request *req); +#endif + +/* + * This table indicates for each PMU opcode: + * - the number of data bytes to be sent with the command, or -1 + * if a length byte should be sent, + * - the number of response bytes which the PMU will return, or + * -1 if it will send a length byte. + */ +static const s8 pmu_data_len[256][2] __openfirmwaredata = { +/* 0 1 2 3 4 5 6 7 */ +/*00*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*08*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*10*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*18*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0, 0}, +/*20*/ {-1, 0},{ 0, 0},{ 2, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*28*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0,-1}, +/*30*/ { 4, 0},{20, 0},{-1, 0},{ 3, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*38*/ { 0, 4},{ 0,20},{ 2,-1},{ 2, 1},{ 3,-1},{-1,-1},{-1,-1},{ 4, 0}, +/*40*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*48*/ { 0, 1},{ 0, 1},{-1,-1},{ 1, 0},{ 1, 0},{-1,-1},{-1,-1},{-1,-1}, +/*50*/ { 1, 0},{ 0, 0},{ 2, 0},{ 2, 0},{-1, 0},{ 1, 0},{ 3, 0},{ 1, 0}, +/*58*/ { 0, 1},{ 1, 0},{ 0, 2},{ 0, 2},{ 0,-1},{-1,-1},{-1,-1},{-1,-1}, +/*60*/ { 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*68*/ { 0, 3},{ 0, 3},{ 0, 2},{ 0, 8},{ 0,-1},{ 0,-1},{-1,-1},{-1,-1}, +/*70*/ { 1, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*78*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{ 5, 1},{ 4, 1},{ 4, 1}, +/*80*/ { 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*88*/ { 0, 5},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*90*/ { 1, 0},{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*98*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*a0*/ { 2, 0},{ 2, 0},{ 2, 0},{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0}, +/*a8*/ { 1, 1},{ 1, 0},{ 3, 0},{ 2, 0},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*b0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*b8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*c0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*c8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*d0*/ { 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*d8*/ { 1, 1},{ 1, 1},{-1,-1},{-1,-1},{ 0, 1},{ 0,-1},{-1,-1},{-1,-1}, +/*e0*/ {-1, 0},{ 4, 0},{ 0, 1},{-1, 0},{-1, 0},{ 4, 0},{-1, 0},{-1, 0}, +/*e8*/ { 3,-1},{-1,-1},{ 0, 1},{-1,-1},{ 0,-1},{-1,-1},{-1,-1},{ 0, 0}, +/*f0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*f8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +}; + +static char *pbook_type[] = { + "Unknown PowerBook", + "PowerBook 2400/3400/3500(G3)", + "PowerBook G3 Series", + "1999 PowerBook G3", + "Core99" +}; + +#ifdef CONFIG_PMAC_BACKLIGHT +static struct backlight_controller pmu_backlight_controller = { + pmu_set_backlight_enable, + pmu_set_backlight_level +}; +#endif /* CONFIG_PMAC_BACKLIGHT */ + +int __openfirmware +find_via_pmu(void) +{ + if (via != 0) + return 1; + vias = find_devices("via-pmu"); + if (vias == 0) + return 0; + if (vias->next != 0) + printk(KERN_WARNING "Warning: only using 1st via-pmu\n"); + + if (vias->n_addrs < 1 || vias->n_intrs < 1) { + printk(KERN_ERR "via-pmu: %d addresses, %d interrupts!\n", + vias->n_addrs, vias->n_intrs); + if (vias->n_addrs < 1 || vias->n_intrs < 1) + return 0; + } + + spin_lock_init(&pmu_lock); + + pmu_has_adb = 1; + + pmu_intr_mask = PMU_INT_PCEJECT | + PMU_INT_SNDBRT | + PMU_INT_ADB | + PMU_INT_TICK; + + if (vias->parent->name && ((strcmp(vias->parent->name, "ohare") == 0) + || device_is_compatible(vias->parent, "ohare"))) + pmu_kind = PMU_OHARE_BASED; + else if (device_is_compatible(vias->parent, "paddington")) + pmu_kind = PMU_PADDINGTON_BASED; + else if (device_is_compatible(vias->parent, "heathrow")) + pmu_kind = PMU_HEATHROW_BASED; + else if (device_is_compatible(vias->parent, "Keylargo") + || device_is_compatible(vias->parent, "K2-Keylargo")) { + struct device_node *gpio, *gpiop; + + pmu_kind = PMU_KEYLARGO_BASED; + pmu_has_adb = (find_type_devices("adb") != NULL); + pmu_intr_mask = PMU_INT_PCEJECT | + PMU_INT_SNDBRT | + PMU_INT_ADB | + PMU_INT_TICK | + PMU_INT_ENVIRONMENT; + + gpiop = find_devices("gpio"); + if (gpiop && gpiop->n_addrs) { + gpio_reg = ioremap(gpiop->addrs->address, 0x10); + gpio = find_devices("extint-gpio1"); + if (gpio == NULL) + gpio = find_devices("pmu-interrupt"); + if (gpio && gpio->parent == gpiop && gpio->n_intrs) + gpio_irq = gpio->intrs[0].line; + } + } else + pmu_kind = PMU_UNKNOWN; + + via = ioremap(vias->addrs->address, 0x2000); + + out_8(&via[IER], IER_CLR | 0x7f); /* disable all intrs */ + out_8(&via[IFR], 0x7f); /* clear IFR */ + + pmu_state = idle; + + if (!init_pmu()) { + via = NULL; + return 0; + } + + printk(KERN_INFO "PMU driver %d initialized for %s, firmware: %02x\n", + PMU_DRIVER_VERSION, pbook_type[pmu_kind], pmu_version); + + sys_ctrler = SYS_CTRLER_PMU; + + return 1; +} + +#ifdef CONFIG_ADB +static int __openfirmware +pmu_probe(void) +{ + return vias == NULL? -ENODEV: 0; +} + +static int __init +pmu_init(void) +{ + if (vias == NULL) + return -ENODEV; + return 0; +} +#endif /* CONFIG_ADB */ + +/* + * We can't wait until pmu_init gets called, that happens too late. + * It happens after IDE and SCSI initialization, which can take a few + * seconds, and by that time the PMU could have given up on us and + * turned us off. + * Thus this is called with arch_initcall rather than device_initcall. + */ +static int __init via_pmu_start(void) +{ + if (vias == NULL) + return -ENODEV; + + bright_req_1.complete = 1; + bright_req_2.complete = 1; +#ifdef CONFIG_PMAC_PBOOK + batt_req.complete = 1; +#endif + + if (request_irq(vias->intrs[0].line, via_pmu_interrupt, 0, "VIA-PMU", + (void *)0)) { + printk(KERN_ERR "VIA-PMU: can't get irq %d\n", + vias->intrs[0].line); + return -EAGAIN; + } + + if (pmu_kind == PMU_KEYLARGO_BASED && gpio_irq != -1) { + if (request_irq(gpio_irq, gpio1_interrupt, 0, "GPIO1 ADB", (void *)0)) + printk(KERN_ERR "pmu: can't get irq %d (GPIO1)\n", gpio_irq); + gpio_irq_enabled = 1; + } + + /* Enable interrupts */ + out_8(&via[IER], IER_SET | SR_INT | CB1_INT); + + pmu_fully_inited = 1; + + /* Make sure PMU settle down before continuing. This is _very_ important + * since the IDE probe may shut interrupts down for quite a bit of time. If + * a PMU communication is pending while this happens, the PMU may timeout + * Not that on Core99 machines, the PMU keeps sending us environement + * messages, we should find a way to either fix IDE or make it call + * pmu_suspend() before masking interrupts. This can also happens while + * scolling with some fbdevs. + */ + do { + pmu_poll(); + } while (pmu_state != idle); + + return 0; +} + +arch_initcall(via_pmu_start); + +/* + * This has to be done after pci_init, which is a subsys_initcall. + */ +static int __init via_pmu_dev_init(void) +{ + if (vias == NULL) + return -ENODEV; + +#ifndef CONFIG_PPC64 + request_OF_resource(vias, 0, NULL); +#endif +#ifdef CONFIG_PMAC_BACKLIGHT + /* Enable backlight */ + register_backlight_controller(&pmu_backlight_controller, NULL, "pmu"); +#endif /* CONFIG_PMAC_BACKLIGHT */ + +#ifdef CONFIG_PMAC_PBOOK + if (machine_is_compatible("AAPL,3400/2400") || + machine_is_compatible("AAPL,3500")) { + int mb = pmac_call_feature(PMAC_FTR_GET_MB_INFO, + NULL, PMAC_MB_INFO_MODEL, 0); + pmu_battery_count = 1; + if (mb == PMAC_TYPE_COMET) + pmu_batteries[0].flags |= PMU_BATT_TYPE_COMET; + else + pmu_batteries[0].flags |= PMU_BATT_TYPE_HOOPER; + } else if (machine_is_compatible("AAPL,PowerBook1998") || + machine_is_compatible("PowerBook1,1")) { + pmu_battery_count = 2; + pmu_batteries[0].flags |= PMU_BATT_TYPE_SMART; + pmu_batteries[1].flags |= PMU_BATT_TYPE_SMART; + } else { + struct device_node* prim = find_devices("power-mgt"); + u32 *prim_info = NULL; + if (prim) + prim_info = (u32 *)get_property(prim, "prim-info", NULL); + if (prim_info) { + /* Other stuffs here yet unknown */ + pmu_battery_count = (prim_info[6] >> 16) & 0xff; + pmu_batteries[0].flags |= PMU_BATT_TYPE_SMART; + if (pmu_battery_count > 1) + pmu_batteries[1].flags |= PMU_BATT_TYPE_SMART; + } + } +#endif /* CONFIG_PMAC_PBOOK */ + /* Create /proc/pmu */ + proc_pmu_root = proc_mkdir("pmu", NULL); + if (proc_pmu_root) { +#ifdef CONFIG_PMAC_PBOOK + int i; + + for (i=0; i<pmu_battery_count; i++) { + char title[16]; + sprintf(title, "battery_%d", i); + proc_pmu_batt[i] = create_proc_read_entry(title, 0, proc_pmu_root, + proc_get_batt, (void *)i); + } +#endif /* CONFIG_PMAC_PBOOK */ + + proc_pmu_info = create_proc_read_entry("info", 0, proc_pmu_root, + proc_get_info, NULL); + proc_pmu_irqstats = create_proc_read_entry("interrupts", 0, proc_pmu_root, + proc_get_irqstats, NULL); + proc_pmu_options = create_proc_entry("options", 0600, proc_pmu_root); + if (proc_pmu_options) { + proc_pmu_options->nlink = 1; + proc_pmu_options->read_proc = proc_read_options; + proc_pmu_options->write_proc = proc_write_options; + } + } + return 0; +} + +device_initcall(via_pmu_dev_init); + +static int __openfirmware +init_pmu(void) +{ + int timeout; + struct adb_request req; + + out_8(&via[B], via[B] | TREQ); /* negate TREQ */ + out_8(&via[DIRB], (via[DIRB] | TREQ) & ~TACK); /* TACK in, TREQ out */ + + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask); + timeout = 100000; + while (!req.complete) { + if (--timeout < 0) { + printk(KERN_ERR "init_pmu: no response from PMU\n"); + return 0; + } + udelay(10); + pmu_poll(); + } + + /* ack all pending interrupts */ + timeout = 100000; + interrupt_data[0][0] = 1; + while (interrupt_data[0][0] || pmu_state != idle) { + if (--timeout < 0) { + printk(KERN_ERR "init_pmu: timed out acking intrs\n"); + return 0; + } + if (pmu_state == idle) + adb_int_pending = 1; + via_pmu_interrupt(0, NULL, NULL); + udelay(10); + } + + /* Tell PMU we are ready. */ + if (pmu_kind == PMU_KEYLARGO_BASED) { + pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2); + while (!req.complete) + pmu_poll(); + } + + /* Read PMU version */ + pmu_request(&req, NULL, 1, PMU_GET_VERSION); + pmu_wait_complete(&req); + if (req.reply_len > 0) + pmu_version = req.reply[0]; + + /* Read server mode setting */ + if (pmu_kind == PMU_KEYLARGO_BASED) { + pmu_request(&req, NULL, 2, PMU_POWER_EVENTS, + PMU_PWR_GET_POWERUP_EVENTS); + pmu_wait_complete(&req); + if (req.reply_len == 2) { + if (req.reply[1] & PMU_PWR_WAKEUP_AC_INSERT) + option_server_mode = 1; + printk(KERN_INFO "via-pmu: Server Mode is %s\n", + option_server_mode ? "enabled" : "disabled"); + } + } + return 1; +} + +int +pmu_get_model(void) +{ + return pmu_kind; +} + +#ifndef CONFIG_PPC64 +static inline void wakeup_decrementer(void) +{ + set_dec(tb_ticks_per_jiffy); + /* No currently-supported powerbook has a 601, + * so use get_tbl, not native + */ + last_jiffy_stamp(0) = tb_last_stamp = get_tbl(); +} +#endif + +static void pmu_set_server_mode(int server_mode) +{ + struct adb_request req; + + if (pmu_kind != PMU_KEYLARGO_BASED) + return; + + option_server_mode = server_mode; + pmu_request(&req, NULL, 2, PMU_POWER_EVENTS, PMU_PWR_GET_POWERUP_EVENTS); + pmu_wait_complete(&req); + if (req.reply_len < 2) + return; + if (server_mode) + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, + PMU_PWR_SET_POWERUP_EVENTS, + req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); + else + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, + PMU_PWR_CLR_POWERUP_EVENTS, + req.reply[0], PMU_PWR_WAKEUP_AC_INSERT); + pmu_wait_complete(&req); +} + +#ifdef CONFIG_PMAC_PBOOK + +/* This new version of the code for 2400/3400/3500 powerbooks + * is inspired from the implementation in gkrellm-pmu + */ +static void __pmac +done_battery_state_ohare(struct adb_request* req) +{ + /* format: + * [0] : flags + * 0x01 : AC indicator + * 0x02 : charging + * 0x04 : battery exist + * 0x08 : + * 0x10 : + * 0x20 : full charged + * 0x40 : pcharge reset + * 0x80 : battery exist + * + * [1][2] : battery voltage + * [3] : CPU temperature + * [4] : battery temperature + * [5] : current + * [6][7] : pcharge + * --tkoba + */ + unsigned int bat_flags = PMU_BATT_TYPE_HOOPER; + long pcharge, charge, vb, vmax, lmax; + long vmax_charging, vmax_charged; + long amperage, voltage, time, max; + int mb = pmac_call_feature(PMAC_FTR_GET_MB_INFO, + NULL, PMAC_MB_INFO_MODEL, 0); + + if (req->reply[0] & 0x01) + pmu_power_flags |= PMU_PWR_AC_PRESENT; + else + pmu_power_flags &= ~PMU_PWR_AC_PRESENT; + + if (mb == PMAC_TYPE_COMET) { + vmax_charged = 189; + vmax_charging = 213; + lmax = 6500; + } else { + vmax_charged = 330; + vmax_charging = 330; + lmax = 6500; + } + vmax = vmax_charged; + + /* If battery installed */ + if (req->reply[0] & 0x04) { + bat_flags |= PMU_BATT_PRESENT; + if (req->reply[0] & 0x02) + bat_flags |= PMU_BATT_CHARGING; + vb = (req->reply[1] << 8) | req->reply[2]; + voltage = (vb * 265 + 72665) / 10; + amperage = req->reply[5]; + if ((req->reply[0] & 0x01) == 0) { + if (amperage > 200) + vb += ((amperage - 200) * 15)/100; + } else if (req->reply[0] & 0x02) { + vb = (vb * 97) / 100; + vmax = vmax_charging; + } + charge = (100 * vb) / vmax; + if (req->reply[0] & 0x40) { + pcharge = (req->reply[6] << 8) + req->reply[7]; + if (pcharge > lmax) + pcharge = lmax; + pcharge *= 100; + pcharge = 100 - pcharge / lmax; + if (pcharge < charge) + charge = pcharge; + } + if (amperage > 0) + time = (charge * 16440) / amperage; + else + time = 0; + max = 100; + amperage = -amperage; + } else + charge = max = amperage = voltage = time = 0; + + pmu_batteries[pmu_cur_battery].flags = bat_flags; + pmu_batteries[pmu_cur_battery].charge = charge; + pmu_batteries[pmu_cur_battery].max_charge = max; + pmu_batteries[pmu_cur_battery].amperage = amperage; + pmu_batteries[pmu_cur_battery].voltage = voltage; + pmu_batteries[pmu_cur_battery].time_remaining = time; + + clear_bit(0, &async_req_locks); +} + +static void __pmac +done_battery_state_smart(struct adb_request* req) +{ + /* format: + * [0] : format of this structure (known: 3,4,5) + * [1] : flags + * + * format 3 & 4: + * + * [2] : charge + * [3] : max charge + * [4] : current + * [5] : voltage + * + * format 5: + * + * [2][3] : charge + * [4][5] : max charge + * [6][7] : current + * [8][9] : voltage + */ + + unsigned int bat_flags = PMU_BATT_TYPE_SMART; + int amperage; + unsigned int capa, max, voltage; + + if (req->reply[1] & 0x01) + pmu_power_flags |= PMU_PWR_AC_PRESENT; + else + pmu_power_flags &= ~PMU_PWR_AC_PRESENT; + + + capa = max = amperage = voltage = 0; + + if (req->reply[1] & 0x04) { + bat_flags |= PMU_BATT_PRESENT; + switch(req->reply[0]) { + case 3: + case 4: capa = req->reply[2]; + max = req->reply[3]; + amperage = *((signed char *)&req->reply[4]); + voltage = req->reply[5]; + break; + case 5: capa = (req->reply[2] << 8) | req->reply[3]; + max = (req->reply[4] << 8) | req->reply[5]; + amperage = *((signed short *)&req->reply[6]); + voltage = (req->reply[8] << 8) | req->reply[9]; + break; + default: + printk(KERN_WARNING "pmu.c : unrecognized battery info, len: %d, %02x %02x %02x %02x\n", + req->reply_len, req->reply[0], req->reply[1], req->reply[2], req->reply[3]); + break; + } + } + + if ((req->reply[1] & 0x01) && (amperage > 0)) + bat_flags |= PMU_BATT_CHARGING; + + pmu_batteries[pmu_cur_battery].flags = bat_flags; + pmu_batteries[pmu_cur_battery].charge = capa; + pmu_batteries[pmu_cur_battery].max_charge = max; + pmu_batteries[pmu_cur_battery].amperage = amperage; + pmu_batteries[pmu_cur_battery].voltage = voltage; + if (amperage) { + if ((req->reply[1] & 0x01) && (amperage > 0)) + pmu_batteries[pmu_cur_battery].time_remaining + = ((max-capa) * 3600) / amperage; + else + pmu_batteries[pmu_cur_battery].time_remaining + = (capa * 3600) / (-amperage); + } else + pmu_batteries[pmu_cur_battery].time_remaining = 0; + + pmu_cur_battery = (pmu_cur_battery + 1) % pmu_battery_count; + + clear_bit(0, &async_req_locks); +} + +static void __pmac +query_battery_state(void) +{ + if (test_and_set_bit(0, &async_req_locks)) + return; + if (pmu_kind == PMU_OHARE_BASED) + pmu_request(&batt_req, done_battery_state_ohare, + 1, PMU_BATTERY_STATE); + else + pmu_request(&batt_req, done_battery_state_smart, + 2, PMU_SMART_BATTERY_STATE, pmu_cur_battery+1); +} + +#endif /* CONFIG_PMAC_PBOOK */ + +static int __pmac +proc_get_info(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char* p = page; + + p += sprintf(p, "PMU driver version : %d\n", PMU_DRIVER_VERSION); + p += sprintf(p, "PMU firmware version : %02x\n", pmu_version); +#ifdef CONFIG_PMAC_PBOOK + p += sprintf(p, "AC Power : %d\n", + ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0)); + p += sprintf(p, "Battery count : %d\n", pmu_battery_count); +#endif /* CONFIG_PMAC_PBOOK */ + + return p - page; +} + +static int __pmac +proc_get_irqstats(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int i; + char* p = page; + static const char *irq_names[] = { + "Total CB1 triggered events", + "Total GPIO1 triggered events", + "PC-Card eject button", + "Sound/Brightness button", + "ADB message", + "Battery state change", + "Environment interrupt", + "Tick timer", + "Ghost interrupt (zero len)", + "Empty interrupt (empty mask)", + "Max irqs in a row" + }; + + for (i=0; i<11; i++) { + p += sprintf(p, " %2u: %10u (%s)\n", + i, pmu_irq_stats[i], irq_names[i]); + } + return p - page; +} + +#ifdef CONFIG_PMAC_PBOOK +static int __pmac +proc_get_batt(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int batnum = (int)data; + char *p = page; + + p += sprintf(p, "\n"); + p += sprintf(p, "flags : %08x\n", + pmu_batteries[batnum].flags); + p += sprintf(p, "charge : %d\n", + pmu_batteries[batnum].charge); + p += sprintf(p, "max_charge : %d\n", + pmu_batteries[batnum].max_charge); + p += sprintf(p, "current : %d\n", + pmu_batteries[batnum].amperage); + p += sprintf(p, "voltage : %d\n", + pmu_batteries[batnum].voltage); + p += sprintf(p, "time rem. : %d\n", + pmu_batteries[batnum].time_remaining); + + return p - page; +} +#endif /* CONFIG_PMAC_PBOOK */ + +static int __pmac +proc_read_options(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *p = page; + +#ifdef CONFIG_PMAC_PBOOK + if (pmu_kind == PMU_KEYLARGO_BASED && + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0) + p += sprintf(p, "lid_wakeup=%d\n", option_lid_wakeup); +#endif /* CONFIG_PMAC_PBOOK */ + if (pmu_kind == PMU_KEYLARGO_BASED) + p += sprintf(p, "server_mode=%d\n", option_server_mode); + + return p - page; +} + +static int __pmac +proc_write_options(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char tmp[33]; + char *label, *val; + unsigned long fcount = count; + + if (!count) + return -EINVAL; + if (count > 32) + count = 32; + if (copy_from_user(tmp, buffer, count)) + return -EFAULT; + tmp[count] = 0; + + label = tmp; + while(*label == ' ') + label++; + val = label; + while(*val && (*val != '=')) { + if (*val == ' ') + *val = 0; + val++; + } + if ((*val) == 0) + return -EINVAL; + *(val++) = 0; + while(*val == ' ') + val++; +#ifdef CONFIG_PMAC_PBOOK + if (pmu_kind == PMU_KEYLARGO_BASED && + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0) + if (!strcmp(label, "lid_wakeup")) + option_lid_wakeup = ((*val) == '1'); +#endif /* CONFIG_PMAC_PBOOK */ + if (pmu_kind == PMU_KEYLARGO_BASED && !strcmp(label, "server_mode")) { + int new_value; + new_value = ((*val) == '1'); + if (new_value != option_server_mode) + pmu_set_server_mode(new_value); + } + return fcount; +} + +#ifdef CONFIG_ADB +/* Send an ADB command */ +static int __pmac +pmu_send_request(struct adb_request *req, int sync) +{ + int i, ret; + + if ((vias == NULL) || (!pmu_fully_inited)) { + req->complete = 1; + return -ENXIO; + } + + ret = -EINVAL; + + switch (req->data[0]) { + case PMU_PACKET: + for (i = 0; i < req->nbytes - 1; ++i) + req->data[i] = req->data[i+1]; + --req->nbytes; + if (pmu_data_len[req->data[0]][1] != 0) { + req->reply[0] = ADB_RET_OK; + req->reply_len = 1; + } else + req->reply_len = 0; + ret = pmu_queue_request(req); + break; + case CUDA_PACKET: + switch (req->data[1]) { + case CUDA_GET_TIME: + if (req->nbytes != 2) + break; + req->data[0] = PMU_READ_RTC; + req->nbytes = 1; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_GET_TIME; + ret = pmu_queue_request(req); + break; + case CUDA_SET_TIME: + if (req->nbytes != 6) + break; + req->data[0] = PMU_SET_RTC; + req->nbytes = 5; + for (i = 1; i <= 4; ++i) + req->data[i] = req->data[i+1]; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_SET_TIME; + ret = pmu_queue_request(req); + break; + } + break; + case ADB_PACKET: + if (!pmu_has_adb) + return -ENXIO; + for (i = req->nbytes - 1; i > 1; --i) + req->data[i+2] = req->data[i]; + req->data[3] = req->nbytes - 2; + req->data[2] = pmu_adb_flags; + /*req->data[1] = req->data[1];*/ + req->data[0] = PMU_ADB_CMD; + req->nbytes += 2; + req->reply_expected = 1; + req->reply_len = 0; + ret = pmu_queue_request(req); + break; + } + if (ret) { + req->complete = 1; + return ret; + } + + if (sync) + while (!req->complete) + pmu_poll(); + + return 0; +} + +/* Enable/disable autopolling */ +static int __pmac +pmu_adb_autopoll(int devs) +{ + struct adb_request req; + + if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb) + return -ENXIO; + + if (devs) { + adb_dev_map = devs; + pmu_request(&req, NULL, 5, PMU_ADB_CMD, 0, 0x86, + adb_dev_map >> 8, adb_dev_map); + pmu_adb_flags = 2; + } else { + pmu_request(&req, NULL, 1, PMU_ADB_POLL_OFF); + pmu_adb_flags = 0; + } + while (!req.complete) + pmu_poll(); + return 0; +} + +/* Reset the ADB bus */ +static int __pmac +pmu_adb_reset_bus(void) +{ + struct adb_request req; + int save_autopoll = adb_dev_map; + + if ((vias == NULL) || (!pmu_fully_inited) || !pmu_has_adb) + return -ENXIO; + + /* anyone got a better idea?? */ + pmu_adb_autopoll(0); + + req.nbytes = 5; + req.done = NULL; + req.data[0] = PMU_ADB_CMD; + req.data[1] = 0; + req.data[2] = ADB_BUSRESET; + req.data[3] = 0; + req.data[4] = 0; + req.reply_len = 0; + req.reply_expected = 1; + if (pmu_queue_request(&req) != 0) { + printk(KERN_ERR "pmu_adb_reset_bus: pmu_queue_request failed\n"); + return -EIO; + } + pmu_wait_complete(&req); + + if (save_autopoll != 0) + pmu_adb_autopoll(save_autopoll); + + return 0; +} +#endif /* CONFIG_ADB */ + +/* Construct and send a pmu request */ +int __openfirmware +pmu_request(struct adb_request *req, void (*done)(struct adb_request *), + int nbytes, ...) +{ + va_list list; + int i; + + if (vias == NULL) + return -ENXIO; + + if (nbytes < 0 || nbytes > 32) { + printk(KERN_ERR "pmu_request: bad nbytes (%d)\n", nbytes); + req->complete = 1; + return -EINVAL; + } + req->nbytes = nbytes; + req->done = done; + va_start(list, nbytes); + for (i = 0; i < nbytes; ++i) + req->data[i] = va_arg(list, int); + va_end(list); + req->reply_len = 0; + req->reply_expected = 0; + return pmu_queue_request(req); +} + +int __pmac +pmu_queue_request(struct adb_request *req) +{ + unsigned long flags; + int nsend; + + if (via == NULL) { + req->complete = 1; + return -ENXIO; + } + if (req->nbytes <= 0) { + req->complete = 1; + return 0; + } + nsend = pmu_data_len[req->data[0]][0]; + if (nsend >= 0 && req->nbytes != nsend + 1) { + req->complete = 1; + return -EINVAL; + } + + req->next = NULL; + req->sent = 0; + req->complete = 0; + + spin_lock_irqsave(&pmu_lock, flags); + if (current_req != 0) { + last_req->next = req; + last_req = req; + } else { + current_req = req; + last_req = req; + if (pmu_state == idle) + pmu_start(); + } + spin_unlock_irqrestore(&pmu_lock, flags); + + return 0; +} + +static inline void +wait_for_ack(void) +{ + /* Sightly increased the delay, I had one occurrence of the message + * reported + */ + int timeout = 4000; + while ((in_8(&via[B]) & TACK) == 0) { + if (--timeout < 0) { + printk(KERN_ERR "PMU not responding (!ack)\n"); + return; + } + udelay(10); + } +} + +/* New PMU seems to be very sensitive to those timings, so we make sure + * PCI is flushed immediately */ +static inline void +send_byte(int x) +{ + volatile unsigned char __iomem *v = via; + + out_8(&v[ACR], in_8(&v[ACR]) | SR_OUT | SR_EXT); + out_8(&v[SR], x); + out_8(&v[B], in_8(&v[B]) & ~TREQ); /* assert TREQ */ + (void)in_8(&v[B]); +} + +static inline void +recv_byte(void) +{ + volatile unsigned char __iomem *v = via; + + out_8(&v[ACR], (in_8(&v[ACR]) & ~SR_OUT) | SR_EXT); + in_8(&v[SR]); /* resets SR */ + out_8(&v[B], in_8(&v[B]) & ~TREQ); + (void)in_8(&v[B]); +} + +static inline void +pmu_done(struct adb_request *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); +} + +static void __pmac +pmu_start(void) +{ + struct adb_request *req; + + /* assert pmu_state == idle */ + /* get the packet to send */ + req = current_req; + if (req == 0 || pmu_state != idle + || (/*req->reply_expected && */req_awaiting_reply)) + return; + + pmu_state = sending; + data_index = 1; + data_len = pmu_data_len[req->data[0]][0]; + + /* Sounds safer to make sure ACK is high before writing. This helped + * kill a problem with ADB and some iBooks + */ + wait_for_ack(); + /* set the shift register to shift out and send a byte */ + send_byte(req->data[0]); +} + +void __openfirmware +pmu_poll(void) +{ + if (!via) + return; + if (disable_poll) + return; + via_pmu_interrupt(0, NULL, NULL); +} + +void __openfirmware +pmu_poll_adb(void) +{ + if (!via) + return; + if (disable_poll) + return; + /* Kicks ADB read when PMU is suspended */ + adb_int_pending = 1; + do { + via_pmu_interrupt(0, NULL, NULL); + } while (pmu_suspended && (adb_int_pending || pmu_state != idle + || req_awaiting_reply)); +} + +void __openfirmware +pmu_wait_complete(struct adb_request *req) +{ + if (!via) + return; + while((pmu_state != idle && pmu_state != locked) || !req->complete) + via_pmu_interrupt(0, NULL, NULL); +} + +/* This function loops until the PMU is idle and prevents it from + * anwsering to ADB interrupts. pmu_request can still be called. + * This is done to avoid spurrious shutdowns when we know we'll have + * interrupts switched off for a long time + */ +void __openfirmware +pmu_suspend(void) +{ + unsigned long flags; +#ifdef SUSPEND_USES_PMU + struct adb_request *req; +#endif + if (!via) + return; + + spin_lock_irqsave(&pmu_lock, flags); + pmu_suspended++; + if (pmu_suspended > 1) { + spin_unlock_irqrestore(&pmu_lock, flags); + return; + } + + do { + spin_unlock_irqrestore(&pmu_lock, flags); + if (req_awaiting_reply) + adb_int_pending = 1; + via_pmu_interrupt(0, NULL, NULL); + spin_lock_irqsave(&pmu_lock, flags); + if (!adb_int_pending && pmu_state == idle && !req_awaiting_reply) { +#ifdef SUSPEND_USES_PMU + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0); + spin_unlock_irqrestore(&pmu_lock, flags); + while(!req.complete) + pmu_poll(); +#else /* SUSPEND_USES_PMU */ + if (gpio_irq >= 0) + disable_irq_nosync(gpio_irq); + out_8(&via[IER], CB1_INT | IER_CLR); + spin_unlock_irqrestore(&pmu_lock, flags); +#endif /* SUSPEND_USES_PMU */ + break; + } + } while (1); +} + +void __openfirmware +pmu_resume(void) +{ + unsigned long flags; + + if (!via || (pmu_suspended < 1)) + return; + + spin_lock_irqsave(&pmu_lock, flags); + pmu_suspended--; + if (pmu_suspended > 0) { + spin_unlock_irqrestore(&pmu_lock, flags); + return; + } + adb_int_pending = 1; +#ifdef SUSPEND_USES_PMU + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask); + spin_unlock_irqrestore(&pmu_lock, flags); + while(!req.complete) + pmu_poll(); +#else /* SUSPEND_USES_PMU */ + if (gpio_irq >= 0) + enable_irq(gpio_irq); + out_8(&via[IER], CB1_INT | IER_SET); + spin_unlock_irqrestore(&pmu_lock, flags); + pmu_poll(); +#endif /* SUSPEND_USES_PMU */ +} + +/* Interrupt data could be the result data from an ADB cmd */ +static void __pmac +pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs) +{ + unsigned char ints, pirq; + int i = 0; + + asleep = 0; + if (drop_interrupts || len < 1) { + adb_int_pending = 0; + pmu_irq_stats[8]++; + return; + } + + /* Get PMU interrupt mask */ + ints = data[0]; + + /* Record zero interrupts for stats */ + if (ints == 0) + pmu_irq_stats[9]++; + + /* Hack to deal with ADB autopoll flag */ + if (ints & PMU_INT_ADB) + ints &= ~(PMU_INT_ADB_AUTO | PMU_INT_AUTO_SRQ_POLL); + +next: + + if (ints == 0) { + if (i > pmu_irq_stats[10]) + pmu_irq_stats[10] = i; + return; + } + + for (pirq = 0; pirq < 8; pirq++) + if (ints & (1 << pirq)) + break; + pmu_irq_stats[pirq]++; + i++; + ints &= ~(1 << pirq); + + /* Note: for some reason, we get an interrupt with len=1, + * data[0]==0 after each normal ADB interrupt, at least + * on the Pismo. Still investigating... --BenH + */ + if ((1 << pirq) & PMU_INT_ADB) { + if ((data[0] & PMU_INT_ADB_AUTO) == 0) { + struct adb_request *req = req_awaiting_reply; + if (req == 0) { + printk(KERN_ERR "PMU: extra ADB reply\n"); + return; + } + req_awaiting_reply = NULL; + if (len <= 2) + req->reply_len = 0; + else { + memcpy(req->reply, data + 1, len - 1); + req->reply_len = len - 1; + } + pmu_done(req); + } else { +#if defined(CONFIG_XMON) && !defined(CONFIG_PPC64) + if (len == 4 && data[1] == 0x2c) { + extern int xmon_wants_key, xmon_adb_keycode; + if (xmon_wants_key) { + xmon_adb_keycode = data[2]; + return; + } + } +#endif /* defined(CONFIG_XMON) && !defined(CONFIG_PPC64) */ +#ifdef CONFIG_ADB + /* + * XXX On the [23]400 the PMU gives us an up + * event for keycodes 0x74 or 0x75 when the PC + * card eject buttons are released, so we + * ignore those events. + */ + if (!(pmu_kind == PMU_OHARE_BASED && len == 4 + && data[1] == 0x2c && data[3] == 0xff + && (data[2] & ~1) == 0xf4)) + adb_input(data+1, len-1, regs, 1); +#endif /* CONFIG_ADB */ + } + } + /* Sound/brightness button pressed */ + else if ((1 << pirq) & PMU_INT_SNDBRT) { +#ifdef CONFIG_PMAC_BACKLIGHT + if (len == 3) +#ifdef CONFIG_INPUT_ADBHID + if (!disable_kernel_backlight) +#endif /* CONFIG_INPUT_ADBHID */ + set_backlight_level(data[1] >> 4); +#endif /* CONFIG_PMAC_BACKLIGHT */ + } + /* Tick interrupt */ + else if ((1 << pirq) & PMU_INT_TICK) { +#ifdef CONFIG_PMAC_PBOOK + /* Environement or tick interrupt, query batteries */ + if (pmu_battery_count) { + if ((--query_batt_timer) == 0) { + query_battery_state(); + query_batt_timer = BATTERY_POLLING_COUNT; + } + } + } + else if ((1 << pirq) & PMU_INT_ENVIRONMENT) { + if (pmu_battery_count) + query_battery_state(); + pmu_pass_intr(data, len); + } else { + pmu_pass_intr(data, len); +#endif /* CONFIG_PMAC_PBOOK */ + } + goto next; +} + +static struct adb_request* __pmac +pmu_sr_intr(struct pt_regs *regs) +{ + struct adb_request *req; + int bite = 0; + + if (via[B] & TREQ) { + printk(KERN_ERR "PMU: spurious SR intr (%x)\n", via[B]); + out_8(&via[IFR], SR_INT); + return NULL; + } + /* The ack may not yet be low when we get the interrupt */ + while ((in_8(&via[B]) & TACK) != 0) + ; + + /* if reading grab the byte, and reset the interrupt */ + if (pmu_state == reading || pmu_state == reading_intr) + bite = in_8(&via[SR]); + + /* reset TREQ and wait for TACK to go high */ + out_8(&via[B], in_8(&via[B]) | TREQ); + wait_for_ack(); + + switch (pmu_state) { + case sending: + req = current_req; + if (data_len < 0) { + data_len = req->nbytes - 1; + send_byte(data_len); + break; + } + if (data_index <= data_len) { + send_byte(req->data[data_index++]); + break; + } + req->sent = 1; + data_len = pmu_data_len[req->data[0]][1]; + if (data_len == 0) { + pmu_state = idle; + current_req = req->next; + if (req->reply_expected) + req_awaiting_reply = req; + else + return req; + } else { + pmu_state = reading; + data_index = 0; + reply_ptr = req->reply + req->reply_len; + recv_byte(); + } + break; + + case intack: + data_index = 0; + data_len = -1; + pmu_state = reading_intr; + reply_ptr = interrupt_data[int_data_last]; + recv_byte(); + if (gpio_irq >= 0 && !gpio_irq_enabled) { + enable_irq(gpio_irq); + gpio_irq_enabled = 1; + } + break; + + case reading: + case reading_intr: + if (data_len == -1) { + data_len = bite; + if (bite > 32) + printk(KERN_ERR "PMU: bad reply len %d\n", bite); + } else if (data_index < 32) { + reply_ptr[data_index++] = bite; + } + if (data_index < data_len) { + recv_byte(); + break; + } + + if (pmu_state == reading_intr) { + pmu_state = idle; + int_data_state[int_data_last] = int_data_ready; + interrupt_data_len[int_data_last] = data_len; + } else { + req = current_req; + /* + * For PMU sleep and freq change requests, we lock the + * PMU until it's explicitely unlocked. This avoids any + * spurrious event polling getting in + */ + current_req = req->next; + req->reply_len += data_index; + if (req->data[0] == PMU_SLEEP || req->data[0] == PMU_CPU_SPEED) + pmu_state = locked; + else + pmu_state = idle; + return req; + } + break; + + default: + printk(KERN_ERR "via_pmu_interrupt: unknown state %d?\n", + pmu_state); + } + return NULL; +} + +static irqreturn_t __pmac +via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs) +{ + unsigned long flags; + int intr; + int nloop = 0; + int int_data = -1; + struct adb_request *req = NULL; + int handled = 0; + + /* This is a bit brutal, we can probably do better */ + spin_lock_irqsave(&pmu_lock, flags); + ++disable_poll; + + for (;;) { + intr = in_8(&via[IFR]) & (SR_INT | CB1_INT); + if (intr == 0) + break; + handled = 1; + if (++nloop > 1000) { + printk(KERN_DEBUG "PMU: stuck in intr loop, " + "intr=%x, ier=%x pmu_state=%d\n", + intr, in_8(&via[IER]), pmu_state); + break; + } + out_8(&via[IFR], intr); + if (intr & CB1_INT) { + adb_int_pending = 1; + pmu_irq_stats[0]++; + } + if (intr & SR_INT) { + req = pmu_sr_intr(regs); + if (req) + break; + } + } + +recheck: + if (pmu_state == idle) { + if (adb_int_pending) { + if (int_data_state[0] == int_data_empty) + int_data_last = 0; + else if (int_data_state[1] == int_data_empty) + int_data_last = 1; + else + goto no_free_slot; + pmu_state = intack; + int_data_state[int_data_last] = int_data_fill; + /* Sounds safer to make sure ACK is high before writing. + * This helped kill a problem with ADB and some iBooks + */ + wait_for_ack(); + send_byte(PMU_INT_ACK); + adb_int_pending = 0; + } else if (current_req) + pmu_start(); + } +no_free_slot: + /* Mark the oldest buffer for flushing */ + if (int_data_state[!int_data_last] == int_data_ready) { + int_data_state[!int_data_last] = int_data_flush; + int_data = !int_data_last; + } else if (int_data_state[int_data_last] == int_data_ready) { + int_data_state[int_data_last] = int_data_flush; + int_data = int_data_last; + } + --disable_poll; + spin_unlock_irqrestore(&pmu_lock, flags); + + /* Deal with completed PMU requests outside of the lock */ + if (req) { + pmu_done(req); + req = NULL; + } + + /* Deal with interrupt datas outside of the lock */ + if (int_data >= 0) { + pmu_handle_data(interrupt_data[int_data], interrupt_data_len[int_data], regs); + spin_lock_irqsave(&pmu_lock, flags); + ++disable_poll; + int_data_state[int_data] = int_data_empty; + int_data = -1; + goto recheck; + } + + return IRQ_RETVAL(handled); +} + +void __pmac +pmu_unlock(void) +{ + unsigned long flags; + + spin_lock_irqsave(&pmu_lock, flags); + if (pmu_state == locked) + pmu_state = idle; + adb_int_pending = 1; + spin_unlock_irqrestore(&pmu_lock, flags); +} + + +static irqreturn_t __pmac +gpio1_interrupt(int irq, void *arg, struct pt_regs *regs) +{ + unsigned long flags; + + if ((in_8(gpio_reg + 0x9) & 0x02) == 0) { + spin_lock_irqsave(&pmu_lock, flags); + if (gpio_irq_enabled > 0) { + disable_irq_nosync(gpio_irq); + gpio_irq_enabled = 0; + } + pmu_irq_stats[1]++; + adb_int_pending = 1; + spin_unlock_irqrestore(&pmu_lock, flags); + via_pmu_interrupt(0, NULL, NULL); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +#ifdef CONFIG_PMAC_BACKLIGHT +static int backlight_to_bright[] __pmacdata = { + 0x7f, 0x46, 0x42, 0x3e, 0x3a, 0x36, 0x32, 0x2e, + 0x2a, 0x26, 0x22, 0x1e, 0x1a, 0x16, 0x12, 0x0e +}; + +static int __openfirmware +pmu_set_backlight_enable(int on, int level, void* data) +{ + struct adb_request req; + + if (vias == NULL) + return -ENODEV; + + if (on) { + pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT, + backlight_to_bright[level]); + pmu_wait_complete(&req); + } + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, + PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF)); + pmu_wait_complete(&req); + + return 0; +} + +static void __openfirmware +pmu_bright_complete(struct adb_request *req) +{ + if (req == &bright_req_1) + clear_bit(1, &async_req_locks); + if (req == &bright_req_2) + clear_bit(2, &async_req_locks); +} + +static int __openfirmware +pmu_set_backlight_level(int level, void* data) +{ + if (vias == NULL) + return -ENODEV; + + if (test_and_set_bit(1, &async_req_locks)) + return -EAGAIN; + pmu_request(&bright_req_1, pmu_bright_complete, 2, PMU_BACKLIGHT_BRIGHT, + backlight_to_bright[level]); + if (test_and_set_bit(2, &async_req_locks)) + return -EAGAIN; + pmu_request(&bright_req_2, pmu_bright_complete, 2, PMU_POWER_CTRL, + PMU_POW_BACKLIGHT | (level > BACKLIGHT_OFF ? + PMU_POW_ON : PMU_POW_OFF)); + + return 0; +} +#endif /* CONFIG_PMAC_BACKLIGHT */ + +void __pmac +pmu_enable_irled(int on) +{ + struct adb_request req; + + if (vias == NULL) + return ; + if (pmu_kind == PMU_KEYLARGO_BASED) + return ; + + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, PMU_POW_IRLED | + (on ? PMU_POW_ON : PMU_POW_OFF)); + pmu_wait_complete(&req); +} + +void __pmac +pmu_restart(void) +{ + struct adb_request req; + + if (via == NULL) + return; + + local_irq_disable(); + + drop_interrupts = 1; + + if (pmu_kind != PMU_KEYLARGO_BASED) { + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB | + PMU_INT_TICK ); + while(!req.complete) + pmu_poll(); + } + + pmu_request(&req, NULL, 1, PMU_RESET); + pmu_wait_complete(&req); + for (;;) + ; +} + +void __pmac +pmu_shutdown(void) +{ + struct adb_request req; + + if (via == NULL) + return; + + local_irq_disable(); + + drop_interrupts = 1; + + if (pmu_kind != PMU_KEYLARGO_BASED) { + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB | + PMU_INT_TICK ); + pmu_wait_complete(&req); + } else { + /* Disable server mode on shutdown or we'll just + * wake up again + */ + pmu_set_server_mode(0); + } + + pmu_request(&req, NULL, 5, PMU_SHUTDOWN, + 'M', 'A', 'T', 'T'); + pmu_wait_complete(&req); + for (;;) + ; +} + +int +pmu_present(void) +{ + return via != 0; +} + +struct pmu_i2c_hdr { + u8 bus; + u8 mode; + u8 bus2; + u8 address; + u8 sub_addr; + u8 comb_addr; + u8 count; +}; + +int +pmu_i2c_combined_read(int bus, int addr, int subaddr, u8* data, int len) +{ + struct adb_request req; + struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; + int retry; + int rc; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + hdr->bus = bus; + hdr->address = addr & 0xfe; + hdr->mode = PMU_I2C_MODE_COMBINED; + hdr->bus2 = 0; + hdr->sub_addr = subaddr; + hdr->comb_addr = addr | 1; + hdr->count = len; + + req.nbytes = sizeof(struct pmu_i2c_hdr) + 1; + req.reply_expected = 0; + req.reply_len = 0; + req.data[0] = PMU_I2C_CMD; + req.reply[0] = 0xff; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_OK) + break; + mdelay(15); + } + if (req.reply[0] != PMU_I2C_STATUS_OK) + return -1; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + mdelay(15); + + hdr->bus = PMU_I2C_BUS_STATUS; + req.reply[0] = 0xff; + + req.nbytes = 2; + req.reply_expected = 0; + req.reply_len = 0; + req.data[0] = PMU_I2C_CMD; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_DATAREAD) { + memcpy(data, &req.reply[1], req.reply_len - 1); + return req.reply_len - 1; + } + } + return -1; +} + +int +pmu_i2c_stdsub_write(int bus, int addr, int subaddr, u8* data, int len) +{ + struct adb_request req; + struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; + int retry; + int rc; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + hdr->bus = bus; + hdr->address = addr & 0xfe; + hdr->mode = PMU_I2C_MODE_STDSUB; + hdr->bus2 = 0; + hdr->sub_addr = subaddr; + hdr->comb_addr = addr & 0xfe; + hdr->count = len; + + req.data[0] = PMU_I2C_CMD; + memcpy(&req.data[sizeof(struct pmu_i2c_hdr) + 1], data, len); + req.nbytes = sizeof(struct pmu_i2c_hdr) + len + 1; + req.reply_expected = 0; + req.reply_len = 0; + req.reply[0] = 0xff; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_OK) + break; + mdelay(15); + } + if (req.reply[0] != PMU_I2C_STATUS_OK) + return -1; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + mdelay(15); + + hdr->bus = PMU_I2C_BUS_STATUS; + req.reply[0] = 0xff; + + req.nbytes = 2; + req.reply_expected = 0; + req.reply_len = 0; + req.data[0] = PMU_I2C_CMD; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_OK) + return len; + } + return -1; +} + +int +pmu_i2c_simple_read(int bus, int addr, u8* data, int len) +{ + struct adb_request req; + struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; + int retry; + int rc; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + hdr->bus = bus; + hdr->address = addr | 1; + hdr->mode = PMU_I2C_MODE_SIMPLE; + hdr->bus2 = 0; + hdr->sub_addr = 0; + hdr->comb_addr = 0; + hdr->count = len; + + req.data[0] = PMU_I2C_CMD; + req.nbytes = sizeof(struct pmu_i2c_hdr) + 1; + req.reply_expected = 0; + req.reply_len = 0; + req.reply[0] = 0xff; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_OK) + break; + mdelay(15); + } + if (req.reply[0] != PMU_I2C_STATUS_OK) + return -1; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + mdelay(15); + + hdr->bus = PMU_I2C_BUS_STATUS; + req.reply[0] = 0xff; + + req.nbytes = 2; + req.reply_expected = 0; + req.reply_len = 0; + req.data[0] = PMU_I2C_CMD; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_DATAREAD) { + memcpy(data, &req.reply[1], req.reply_len - 1); + return req.reply_len - 1; + } + } + return -1; +} + +int +pmu_i2c_simple_write(int bus, int addr, u8* data, int len) +{ + struct adb_request req; + struct pmu_i2c_hdr *hdr = (struct pmu_i2c_hdr *)&req.data[1]; + int retry; + int rc; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + hdr->bus = bus; + hdr->address = addr & 0xfe; + hdr->mode = PMU_I2C_MODE_SIMPLE; + hdr->bus2 = 0; + hdr->sub_addr = 0; + hdr->comb_addr = 0; + hdr->count = len; + + req.data[0] = PMU_I2C_CMD; + memcpy(&req.data[sizeof(struct pmu_i2c_hdr) + 1], data, len); + req.nbytes = sizeof(struct pmu_i2c_hdr) + len + 1; + req.reply_expected = 0; + req.reply_len = 0; + req.reply[0] = 0xff; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_OK) + break; + mdelay(15); + } + if (req.reply[0] != PMU_I2C_STATUS_OK) + return -1; + + for (retry=0; retry<16; retry++) { + memset(&req, 0, sizeof(req)); + + mdelay(15); + + hdr->bus = PMU_I2C_BUS_STATUS; + req.reply[0] = 0xff; + + req.nbytes = 2; + req.reply_expected = 0; + req.reply_len = 0; + req.data[0] = PMU_I2C_CMD; + rc = pmu_queue_request(&req); + if (rc) + return rc; + while(!req.complete) + pmu_poll(); + if (req.reply[0] == PMU_I2C_STATUS_OK) + return len; + } + return -1; +} + +#ifdef CONFIG_PMAC_PBOOK + +static LIST_HEAD(sleep_notifiers); + +int +pmu_register_sleep_notifier(struct pmu_sleep_notifier *n) +{ + struct list_head *list; + struct pmu_sleep_notifier *notifier; + + for (list = sleep_notifiers.next; list != &sleep_notifiers; + list = list->next) { + notifier = list_entry(list, struct pmu_sleep_notifier, list); + if (n->priority > notifier->priority) + break; + } + __list_add(&n->list, list->prev, list); + return 0; +} + +int +pmu_unregister_sleep_notifier(struct pmu_sleep_notifier* n) +{ + if (n->list.next == 0) + return -ENOENT; + list_del(&n->list); + n->list.next = NULL; + return 0; +} + +/* Sleep is broadcast last-to-first */ +static int __pmac +broadcast_sleep(int when, int fallback) +{ + int ret = PBOOK_SLEEP_OK; + struct list_head *list; + struct pmu_sleep_notifier *notifier; + + for (list = sleep_notifiers.prev; list != &sleep_notifiers; + list = list->prev) { + notifier = list_entry(list, struct pmu_sleep_notifier, list); + ret = notifier->notifier_call(notifier, when); + if (ret != PBOOK_SLEEP_OK) { + printk(KERN_DEBUG "sleep %d rejected by %p (%p)\n", + when, notifier, notifier->notifier_call); + for (; list != &sleep_notifiers; list = list->next) { + notifier = list_entry(list, struct pmu_sleep_notifier, list); + notifier->notifier_call(notifier, fallback); + } + return ret; + } + } + return ret; +} + +/* Wake is broadcast first-to-last */ +static int __pmac +broadcast_wake(void) +{ + int ret = PBOOK_SLEEP_OK; + struct list_head *list; + struct pmu_sleep_notifier *notifier; + + for (list = sleep_notifiers.next; list != &sleep_notifiers; + list = list->next) { + notifier = list_entry(list, struct pmu_sleep_notifier, list); + notifier->notifier_call(notifier, PBOOK_WAKE); + } + return ret; +} + +/* + * This struct is used to store config register values for + * PCI devices which may get powered off when we sleep. + */ +static struct pci_save { +#ifndef HACKED_PCI_SAVE + u16 command; + u16 cache_lat; + u16 intr; + u32 rom_address; +#else + u32 config[16]; +#endif +} *pbook_pci_saves; +static int pbook_npci_saves; + +static void __pmac +pbook_alloc_pci_save(void) +{ + int npci; + struct pci_dev *pd = NULL; + + npci = 0; + while ((pd = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pd)) != NULL) { + ++npci; + } + if (npci == 0) + return; + pbook_pci_saves = (struct pci_save *) + kmalloc(npci * sizeof(struct pci_save), GFP_KERNEL); + pbook_npci_saves = npci; +} + +static void __pmac +pbook_free_pci_save(void) +{ + if (pbook_pci_saves == NULL) + return; + kfree(pbook_pci_saves); + pbook_pci_saves = NULL; + pbook_npci_saves = 0; +} + +static void __pmac +pbook_pci_save(void) +{ + struct pci_save *ps = pbook_pci_saves; + struct pci_dev *pd = NULL; + int npci = pbook_npci_saves; + + if (ps == NULL) + return; + + while ((pd = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pd)) != NULL) { + if (npci-- == 0) + return; +#ifndef HACKED_PCI_SAVE + pci_read_config_word(pd, PCI_COMMAND, &ps->command); + pci_read_config_word(pd, PCI_CACHE_LINE_SIZE, &ps->cache_lat); + pci_read_config_word(pd, PCI_INTERRUPT_LINE, &ps->intr); + pci_read_config_dword(pd, PCI_ROM_ADDRESS, &ps->rom_address); +#else + int i; + for (i=1;i<16;i++) + pci_read_config_dword(pd, i<<4, &ps->config[i]); +#endif + ++ps; + } +} + +/* For this to work, we must take care of a few things: If gmac was enabled + * during boot, it will be in the pci dev list. If it's disabled at this point + * (and it will probably be), then you can't access it's config space. + */ +static void __pmac +pbook_pci_restore(void) +{ + u16 cmd; + struct pci_save *ps = pbook_pci_saves - 1; + struct pci_dev *pd = NULL; + int npci = pbook_npci_saves; + int j; + + while ((pd = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pd)) != NULL) { +#ifdef HACKED_PCI_SAVE + int i; + if (npci-- == 0) + return; + ps++; + for (i=2;i<16;i++) + pci_write_config_dword(pd, i<<4, ps->config[i]); + pci_write_config_dword(pd, 4, ps->config[1]); +#else + if (npci-- == 0) + return; + ps++; + if (ps->command == 0) + continue; + pci_read_config_word(pd, PCI_COMMAND, &cmd); + if ((ps->command & ~cmd) == 0) + continue; + switch (pd->hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + for (j = 0; j < 6; ++j) + pci_write_config_dword(pd, + PCI_BASE_ADDRESS_0 + j*4, + pd->resource[j].start); + pci_write_config_dword(pd, PCI_ROM_ADDRESS, + ps->rom_address); + pci_write_config_word(pd, PCI_CACHE_LINE_SIZE, + ps->cache_lat); + pci_write_config_word(pd, PCI_INTERRUPT_LINE, + ps->intr); + pci_write_config_word(pd, PCI_COMMAND, ps->command); + break; + } +#endif + } +} + +#ifdef DEBUG_SLEEP +/* N.B. This doesn't work on the 3400 */ +void __pmac +pmu_blink(int n) +{ + struct adb_request req; + + memset(&req, 0, sizeof(req)); + + for (; n > 0; --n) { + req.nbytes = 4; + req.done = NULL; + req.data[0] = 0xee; + req.data[1] = 4; + req.data[2] = 0; + req.data[3] = 1; + req.reply[0] = ADB_RET_OK; + req.reply_len = 1; + req.reply_expected = 0; + pmu_polled_request(&req); + mdelay(50); + req.nbytes = 4; + req.done = NULL; + req.data[0] = 0xee; + req.data[1] = 4; + req.data[2] = 0; + req.data[3] = 0; + req.reply[0] = ADB_RET_OK; + req.reply_len = 1; + req.reply_expected = 0; + pmu_polled_request(&req); + mdelay(50); + } + mdelay(50); +} +#endif + +/* + * Put the powerbook to sleep. + */ + +static u32 save_via[8] __pmacdata; + +static void __pmac +save_via_state(void) +{ + save_via[0] = in_8(&via[ANH]); + save_via[1] = in_8(&via[DIRA]); + save_via[2] = in_8(&via[B]); + save_via[3] = in_8(&via[DIRB]); + save_via[4] = in_8(&via[PCR]); + save_via[5] = in_8(&via[ACR]); + save_via[6] = in_8(&via[T1CL]); + save_via[7] = in_8(&via[T1CH]); +} +static void __pmac +restore_via_state(void) +{ + out_8(&via[ANH], save_via[0]); + out_8(&via[DIRA], save_via[1]); + out_8(&via[B], save_via[2]); + out_8(&via[DIRB], save_via[3]); + out_8(&via[PCR], save_via[4]); + out_8(&via[ACR], save_via[5]); + out_8(&via[T1CL], save_via[6]); + out_8(&via[T1CH], save_via[7]); + out_8(&via[IER], IER_CLR | 0x7f); /* disable all intrs */ + out_8(&via[IFR], 0x7f); /* clear IFR */ + out_8(&via[IER], IER_SET | SR_INT | CB1_INT); +} + +static int __pmac +pmac_suspend_devices(void) +{ + int ret; + + pm_prepare_console(); + + /* Notify old-style device drivers & userland */ + ret = broadcast_sleep(PBOOK_SLEEP_REQUEST, PBOOK_SLEEP_REJECT); + if (ret != PBOOK_SLEEP_OK) { + printk(KERN_ERR "Sleep rejected by drivers\n"); + return -EBUSY; + } + + /* Sync the disks. */ + /* XXX It would be nice to have some way to ensure that + * nobody is dirtying any new buffers while we wait. That + * could be achieved using the refrigerator for processes + * that swsusp uses + */ + sys_sync(); + + /* Sleep can fail now. May not be very robust but useful for debugging */ + ret = broadcast_sleep(PBOOK_SLEEP_NOW, PBOOK_WAKE); + if (ret != PBOOK_SLEEP_OK) { + printk(KERN_ERR "Driver sleep failed\n"); + return -EBUSY; + } + + /* Send suspend call to devices, hold the device core's dpm_sem */ + ret = device_suspend(PMSG_SUSPEND); + if (ret) { + broadcast_wake(); + printk(KERN_ERR "Driver sleep failed\n"); + return -EBUSY; + } + + preempt_disable(); + + /* Make sure the decrementer won't interrupt us */ + asm volatile("mtdec %0" : : "r" (0x7fffffff)); + /* Make sure any pending DEC interrupt occurring while we did + * the above didn't re-enable the DEC */ + mb(); + asm volatile("mtdec %0" : : "r" (0x7fffffff)); + + /* We can now disable MSR_EE. This code of course works properly only + * on UP machines... For SMP, if we ever implement sleep, we'll have to + * stop the "other" CPUs way before we do all that stuff. + */ + local_irq_disable(); + + /* Broadcast power down irq + * This isn't that useful in most cases (only directly wired devices can + * use this but still... This will take care of sysdev's as well, so + * we exit from here with local irqs disabled and PIC off. + */ + ret = device_power_down(PM_SUSPEND_MEM); + if (ret) { + wakeup_decrementer(); + local_irq_enable(); + preempt_enable(); + device_resume(); + broadcast_wake(); + printk(KERN_ERR "Driver powerdown failed\n"); + return -EBUSY; + } + + /* Wait for completion of async backlight requests */ + while (!bright_req_1.complete || !bright_req_2.complete || + !batt_req.complete) + pmu_poll(); + + /* Giveup the lazy FPU & vec so we don't have to back them + * up from the low level code + */ + enable_kernel_fp(); + +#ifdef CONFIG_ALTIVEC + if (cpu_has_feature(CPU_FTR_ALTIVEC)) + enable_kernel_altivec(); +#endif /* CONFIG_ALTIVEC */ + + return 0; +} + +static int __pmac +pmac_wakeup_devices(void) +{ + mdelay(100); + + /* Power back up system devices (including the PIC) */ + device_power_up(); + + /* Force a poll of ADB interrupts */ + adb_int_pending = 1; + via_pmu_interrupt(0, NULL, NULL); + + /* Restart jiffies & scheduling */ + wakeup_decrementer(); + + /* Re-enable local CPU interrupts */ + local_irq_enable(); + + mdelay(100); + + preempt_enable(); + + /* Resume devices */ + device_resume(); + + /* Notify old style drivers */ + broadcast_wake(); + + pm_restore_console(); + + return 0; +} + +#define GRACKLE_PM (1<<7) +#define GRACKLE_DOZE (1<<5) +#define GRACKLE_NAP (1<<4) +#define GRACKLE_SLEEP (1<<3) + +int __pmac +powerbook_sleep_grackle(void) +{ + unsigned long save_l2cr; + unsigned short pmcr1; + struct adb_request req; + int ret; + struct pci_dev *grackle; + + grackle = pci_find_slot(0, 0); + if (!grackle) + return -ENODEV; + + ret = pmac_suspend_devices(); + if (ret) { + printk(KERN_ERR "Sleep rejected by devices\n"); + return ret; + } + + /* Turn off various things. Darwin does some retry tests here... */ + pmu_request(&req, NULL, 2, PMU_POWER_CTRL0, PMU_POW0_OFF|PMU_POW0_HARD_DRIVE); + pmu_wait_complete(&req); + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, + PMU_POW_OFF|PMU_POW_BACKLIGHT|PMU_POW_IRLED|PMU_POW_MEDIABAY); + pmu_wait_complete(&req); + + /* For 750, save backside cache setting and disable it */ + save_l2cr = _get_L2CR(); /* (returns -1 if not available) */ + + if (!__fake_sleep) { + /* Ask the PMU to put us to sleep */ + pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T'); + pmu_wait_complete(&req); + } + + /* The VIA is supposed not to be restored correctly*/ + save_via_state(); + /* We shut down some HW */ + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1); + + pci_read_config_word(grackle, 0x70, &pmcr1); + /* Apparently, MacOS uses NAP mode for Grackle ??? */ + pmcr1 &= ~(GRACKLE_DOZE|GRACKLE_SLEEP); + pmcr1 |= GRACKLE_PM|GRACKLE_NAP; + pci_write_config_word(grackle, 0x70, pmcr1); + + /* Call low-level ASM sleep handler */ + if (__fake_sleep) + mdelay(5000); + else + low_sleep_handler(); + + /* We're awake again, stop grackle PM */ + pci_read_config_word(grackle, 0x70, &pmcr1); + pmcr1 &= ~(GRACKLE_PM|GRACKLE_DOZE|GRACKLE_SLEEP|GRACKLE_NAP); + pci_write_config_word(grackle, 0x70, pmcr1); + + /* Make sure the PMU is idle */ + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0); + restore_via_state(); + + /* Restore L2 cache */ + if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0) + _set_L2CR(save_l2cr); + + /* Restore userland MMU context */ + set_context(current->active_mm->context, current->active_mm->pgd); + + /* Power things up */ + pmu_unlock(); + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask); + pmu_wait_complete(&req); + pmu_request(&req, NULL, 2, PMU_POWER_CTRL0, + PMU_POW0_ON|PMU_POW0_HARD_DRIVE); + pmu_wait_complete(&req); + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, + PMU_POW_ON|PMU_POW_BACKLIGHT|PMU_POW_CHARGER|PMU_POW_IRLED|PMU_POW_MEDIABAY); + pmu_wait_complete(&req); + + pmac_wakeup_devices(); + + return 0; +} + +static int __pmac +powerbook_sleep_Core99(void) +{ + unsigned long save_l2cr; + unsigned long save_l3cr; + struct adb_request req; + int ret; + + if (pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) < 0) { + printk(KERN_ERR "Sleep mode not supported on this machine\n"); + return -ENOSYS; + } + + if (num_online_cpus() > 1 || cpu_is_offline(0)) + return -EAGAIN; + + ret = pmac_suspend_devices(); + if (ret) { + printk(KERN_ERR "Sleep rejected by devices\n"); + return ret; + } + + printk(KERN_DEBUG "HID1, before: %x\n", mfspr(SPRN_HID1)); + + /* Tell PMU what events will wake us up */ + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, PMU_PWR_CLR_WAKEUP_EVENTS, + 0xff, 0xff); + pmu_wait_complete(&req); + pmu_request(&req, NULL, 4, PMU_POWER_EVENTS, PMU_PWR_SET_WAKEUP_EVENTS, + 0, PMU_PWR_WAKEUP_KEY | + (option_lid_wakeup ? PMU_PWR_WAKEUP_LID_OPEN : 0)); + pmu_wait_complete(&req); + + /* Save the state of the L2 and L3 caches */ + save_l3cr = _get_L3CR(); /* (returns -1 if not available) */ + save_l2cr = _get_L2CR(); /* (returns -1 if not available) */ + + if (!__fake_sleep) { + /* Ask the PMU to put us to sleep */ + pmu_request(&req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T'); + pmu_wait_complete(&req); + } + + /* The VIA is supposed not to be restored correctly*/ + save_via_state(); + + /* Shut down various ASICs. There's a chance that we can no longer + * talk to the PMU after this, so I moved it to _after_ sending the + * sleep command to it. Still need to be checked. + */ + pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 1); + + /* Call low-level ASM sleep handler */ + if (__fake_sleep) + mdelay(5000); + else + low_sleep_handler(); + + /* Restore Apple core ASICs state */ + pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 0); + + /* Restore VIA */ + restore_via_state(); + + /* Restore video */ + pmac_call_early_video_resume(); + + /* Restore L2 cache */ + if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0) + _set_L2CR(save_l2cr); + /* Restore L3 cache */ + if (save_l3cr != 0xffffffff && (save_l3cr & L3CR_L3E) != 0) + _set_L3CR(save_l3cr); + + /* Restore userland MMU context */ + set_context(current->active_mm->context, current->active_mm->pgd); + + /* Tell PMU we are ready */ + pmu_unlock(); + pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2); + pmu_wait_complete(&req); + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, pmu_intr_mask); + pmu_wait_complete(&req); + + printk(KERN_DEBUG "HID1, after: %x\n", mfspr(SPRN_HID1)); + + pmac_wakeup_devices(); + + return 0; +} + +#define PB3400_MEM_CTRL 0xf8000000 +#define PB3400_MEM_CTRL_SLEEP 0x70 + +static int __pmac +powerbook_sleep_3400(void) +{ + int ret, i, x; + unsigned int hid0; + unsigned long p; + struct adb_request sleep_req; + void __iomem *mem_ctrl; + unsigned int __iomem *mem_ctrl_sleep; + + /* first map in the memory controller registers */ + mem_ctrl = ioremap(PB3400_MEM_CTRL, 0x100); + if (mem_ctrl == NULL) { + printk("powerbook_sleep_3400: ioremap failed\n"); + return -ENOMEM; + } + mem_ctrl_sleep = mem_ctrl + PB3400_MEM_CTRL_SLEEP; + + /* Allocate room for PCI save */ + pbook_alloc_pci_save(); + + ret = pmac_suspend_devices(); + if (ret) { + pbook_free_pci_save(); + printk(KERN_ERR "Sleep rejected by devices\n"); + return ret; + } + + /* Save the state of PCI config space for some slots */ + pbook_pci_save(); + + /* Set the memory controller to keep the memory refreshed + while we're asleep */ + for (i = 0x403f; i >= 0x4000; --i) { + out_be32(mem_ctrl_sleep, i); + do { + x = (in_be32(mem_ctrl_sleep) >> 16) & 0x3ff; + } while (x == 0); + if (x >= 0x100) + break; + } + + /* Ask the PMU to put us to sleep */ + pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T'); + while (!sleep_req.complete) + mb(); + + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,1); + + /* displacement-flush the L2 cache - necessary? */ + for (p = KERNELBASE; p < KERNELBASE + 0x100000; p += 0x1000) + i = *(volatile int *)p; + asleep = 1; + + /* Put the CPU into sleep mode */ + asm volatile("mfspr %0,1008" : "=r" (hid0) :); + hid0 = (hid0 & ~(HID0_NAP | HID0_DOZE)) | HID0_SLEEP; + asm volatile("mtspr 1008,%0" : : "r" (hid0)); + _nmask_and_or_msr(0, MSR_POW | MSR_EE); + udelay(10); + + /* OK, we're awake again, start restoring things */ + out_be32(mem_ctrl_sleep, 0x3f); + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,0); + pbook_pci_restore(); + pmu_unlock(); + + /* wait for the PMU interrupt sequence to complete */ + while (asleep) + mb(); + + pmac_wakeup_devices(); + pbook_free_pci_save(); + iounmap(mem_ctrl); + + return 0; +} + +/* + * Support for /dev/pmu device + */ +#define RB_SIZE 0x10 +struct pmu_private { + struct list_head list; + int rb_get; + int rb_put; + struct rb_entry { + unsigned short len; + unsigned char data[16]; + } rb_buf[RB_SIZE]; + wait_queue_head_t wait; + spinlock_t lock; +#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) + int backlight_locker; +#endif /* defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) */ +}; + +static LIST_HEAD(all_pmu_pvt); +static DEFINE_SPINLOCK(all_pvt_lock __pmacdata); + +static void __pmac +pmu_pass_intr(unsigned char *data, int len) +{ + struct pmu_private *pp; + struct list_head *list; + int i; + unsigned long flags; + + if (len > sizeof(pp->rb_buf[0].data)) + len = sizeof(pp->rb_buf[0].data); + spin_lock_irqsave(&all_pvt_lock, flags); + for (list = &all_pmu_pvt; (list = list->next) != &all_pmu_pvt; ) { + pp = list_entry(list, struct pmu_private, list); + spin_lock(&pp->lock); + i = pp->rb_put + 1; + if (i >= RB_SIZE) + i = 0; + if (i != pp->rb_get) { + struct rb_entry *rp = &pp->rb_buf[pp->rb_put]; + rp->len = len; + memcpy(rp->data, data, len); + pp->rb_put = i; + wake_up_interruptible(&pp->wait); + } + spin_unlock(&pp->lock); + } + spin_unlock_irqrestore(&all_pvt_lock, flags); +} + +static int __pmac +pmu_open(struct inode *inode, struct file *file) +{ + struct pmu_private *pp; + unsigned long flags; + + pp = kmalloc(sizeof(struct pmu_private), GFP_KERNEL); + if (pp == 0) + return -ENOMEM; + pp->rb_get = pp->rb_put = 0; + spin_lock_init(&pp->lock); + init_waitqueue_head(&pp->wait); + spin_lock_irqsave(&all_pvt_lock, flags); +#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) + pp->backlight_locker = 0; +#endif /* defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) */ + list_add(&pp->list, &all_pmu_pvt); + spin_unlock_irqrestore(&all_pvt_lock, flags); + file->private_data = pp; + return 0; +} + +static ssize_t __pmac +pmu_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pmu_private *pp = file->private_data; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + int ret = 0; + + if (count < 1 || pp == 0) + return -EINVAL; + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + spin_lock_irqsave(&pp->lock, flags); + add_wait_queue(&pp->wait, &wait); + current->state = TASK_INTERRUPTIBLE; + + for (;;) { + ret = -EAGAIN; + if (pp->rb_get != pp->rb_put) { + int i = pp->rb_get; + struct rb_entry *rp = &pp->rb_buf[i]; + ret = rp->len; + spin_unlock_irqrestore(&pp->lock, flags); + if (ret > count) + ret = count; + if (ret > 0 && copy_to_user(buf, rp->data, ret)) + ret = -EFAULT; + if (++i >= RB_SIZE) + i = 0; + spin_lock_irqsave(&pp->lock, flags); + pp->rb_get = i; + } + if (ret >= 0) + break; + if (file->f_flags & O_NONBLOCK) + break; + ret = -ERESTARTSYS; + if (signal_pending(current)) + break; + spin_unlock_irqrestore(&pp->lock, flags); + schedule(); + spin_lock_irqsave(&pp->lock, flags); + } + current->state = TASK_RUNNING; + remove_wait_queue(&pp->wait, &wait); + spin_unlock_irqrestore(&pp->lock, flags); + + return ret; +} + +static ssize_t __pmac +pmu_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static unsigned int __pmac +pmu_fpoll(struct file *filp, poll_table *wait) +{ + struct pmu_private *pp = filp->private_data; + unsigned int mask = 0; + unsigned long flags; + + if (pp == 0) + return 0; + poll_wait(filp, &pp->wait, wait); + spin_lock_irqsave(&pp->lock, flags); + if (pp->rb_get != pp->rb_put) + mask |= POLLIN; + spin_unlock_irqrestore(&pp->lock, flags); + return mask; +} + +static int __pmac +pmu_release(struct inode *inode, struct file *file) +{ + struct pmu_private *pp = file->private_data; + unsigned long flags; + + lock_kernel(); + if (pp != 0) { + file->private_data = NULL; + spin_lock_irqsave(&all_pvt_lock, flags); + list_del(&pp->list); + spin_unlock_irqrestore(&all_pvt_lock, flags); +#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) + if (pp->backlight_locker) { + spin_lock_irqsave(&pmu_lock, flags); + disable_kernel_backlight--; + spin_unlock_irqrestore(&pmu_lock, flags); + } +#endif /* defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) */ + kfree(pp); + } + unlock_kernel(); + return 0; +} + +/* Note: removed __openfirmware here since it causes link errors */ +static int __pmac +pmu_ioctl(struct inode * inode, struct file *filp, + u_int cmd, u_long arg) +{ + struct pmu_private *pp = filp->private_data; + __u32 __user *argp = (__u32 __user *)arg; + int error; + + switch (cmd) { + case PMU_IOC_SLEEP: + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + if (sleep_in_progress) + return -EBUSY; + sleep_in_progress = 1; + switch (pmu_kind) { + case PMU_OHARE_BASED: + error = powerbook_sleep_3400(); + break; + case PMU_HEATHROW_BASED: + case PMU_PADDINGTON_BASED: + error = powerbook_sleep_grackle(); + break; + case PMU_KEYLARGO_BASED: + error = powerbook_sleep_Core99(); + break; + default: + error = -ENOSYS; + } + sleep_in_progress = 0; + return error; + case PMU_IOC_CAN_SLEEP: + if (pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) < 0) + return put_user(0, argp); + else + return put_user(1, argp); + +#ifdef CONFIG_PMAC_BACKLIGHT + /* Backlight should have its own device or go via + * the fbdev + */ + case PMU_IOC_GET_BACKLIGHT: + if (sleep_in_progress) + return -EBUSY; + error = get_backlight_level(); + if (error < 0) + return error; + return put_user(error, argp); + case PMU_IOC_SET_BACKLIGHT: + { + __u32 value; + if (sleep_in_progress) + return -EBUSY; + error = get_user(value, argp); + if (!error) + error = set_backlight_level(value); + return error; + } +#ifdef CONFIG_INPUT_ADBHID + case PMU_IOC_GRAB_BACKLIGHT: { + unsigned long flags; + if (pp->backlight_locker) + return 0; + pp->backlight_locker = 1; + spin_lock_irqsave(&pmu_lock, flags); + disable_kernel_backlight++; + spin_unlock_irqrestore(&pmu_lock, flags); + return 0; + } +#endif /* CONFIG_INPUT_ADBHID */ +#endif /* CONFIG_PMAC_BACKLIGHT */ + case PMU_IOC_GET_MODEL: + return put_user(pmu_kind, argp); + case PMU_IOC_HAS_ADB: + return put_user(pmu_has_adb, argp); + } + return -EINVAL; +} + +static struct file_operations pmu_device_fops __pmacdata = { + .read = pmu_read, + .write = pmu_write, + .poll = pmu_fpoll, + .ioctl = pmu_ioctl, + .open = pmu_open, + .release = pmu_release, +}; + +static struct miscdevice pmu_device __pmacdata = { + PMU_MINOR, "pmu", &pmu_device_fops +}; + +void pmu_device_init(void) +{ + if (!via) + return; + if (misc_register(&pmu_device) < 0) + printk(KERN_ERR "via-pmu: cannot register misc device.\n"); +} +#endif /* CONFIG_PMAC_PBOOK */ + +#ifdef DEBUG_SLEEP +static inline void __pmac +polled_handshake(volatile unsigned char __iomem *via) +{ + via[B] &= ~TREQ; eieio(); + while ((via[B] & TACK) != 0) + ; + via[B] |= TREQ; eieio(); + while ((via[B] & TACK) == 0) + ; +} + +static inline void __pmac +polled_send_byte(volatile unsigned char __iomem *via, int x) +{ + via[ACR] |= SR_OUT | SR_EXT; eieio(); + via[SR] = x; eieio(); + polled_handshake(via); +} + +static inline int __pmac +polled_recv_byte(volatile unsigned char __iomem *via) +{ + int x; + + via[ACR] = (via[ACR] & ~SR_OUT) | SR_EXT; eieio(); + x = via[SR]; eieio(); + polled_handshake(via); + x = via[SR]; eieio(); + return x; +} + +int __pmac +pmu_polled_request(struct adb_request *req) +{ + unsigned long flags; + int i, l, c; + volatile unsigned char __iomem *v = via; + + req->complete = 1; + c = req->data[0]; + l = pmu_data_len[c][0]; + if (l >= 0 && req->nbytes != l + 1) + return -EINVAL; + + local_irq_save(flags); + while (pmu_state != idle) + pmu_poll(); + + while ((via[B] & TACK) == 0) + ; + polled_send_byte(v, c); + if (l < 0) { + l = req->nbytes - 1; + polled_send_byte(v, l); + } + for (i = 1; i <= l; ++i) + polled_send_byte(v, req->data[i]); + + l = pmu_data_len[c][1]; + if (l < 0) + l = polled_recv_byte(v); + for (i = 0; i < l; ++i) + req->reply[i + req->reply_len] = polled_recv_byte(v); + + if (req->done) + (*req->done)(req); + + local_irq_restore(flags); + return 0; +} +#endif /* DEBUG_SLEEP */ + + +/* FIXME: This is a temporary set of callbacks to enable us + * to do suspend-to-disk. + */ + +#ifdef CONFIG_PM + +static int pmu_sys_suspended = 0; + +static int pmu_sys_suspend(struct sys_device *sysdev, u32 state) +{ + if (state != PM_SUSPEND_DISK || pmu_sys_suspended) + return 0; + + /* Suspend PMU event interrupts */ + pmu_suspend(); + + pmu_sys_suspended = 1; + return 0; +} + +static int pmu_sys_resume(struct sys_device *sysdev) +{ + struct adb_request req; + + if (!pmu_sys_suspended) + return 0; + + /* Tell PMU we are ready */ + pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2); + pmu_wait_complete(&req); + + /* Resume PMU event interrupts */ + pmu_resume(); + + pmu_sys_suspended = 0; + + return 0; +} + +#endif /* CONFIG_PM */ + +static struct sysdev_class pmu_sysclass = { + set_kset_name("pmu"), +}; + +static struct sys_device device_pmu = { + .id = 0, + .cls = &pmu_sysclass, +}; + +static struct sysdev_driver driver_pmu = { +#ifdef CONFIG_PM + .suspend = &pmu_sys_suspend, + .resume = &pmu_sys_resume, +#endif /* CONFIG_PM */ +}; + +static int __init init_pmu_sysfs(void) +{ + int rc; + + rc = sysdev_class_register(&pmu_sysclass); + if (rc) { + printk(KERN_ERR "Failed registering PMU sys class\n"); + return -ENODEV; + } + rc = sysdev_register(&device_pmu); + if (rc) { + printk(KERN_ERR "Failed registering PMU sys device\n"); + return -ENODEV; + } + rc = sysdev_driver_register(&pmu_sysclass, &driver_pmu); + if (rc) { + printk(KERN_ERR "Failed registering PMU sys driver\n"); + return -ENODEV; + } + return 0; +} + +subsys_initcall(init_pmu_sysfs); + +EXPORT_SYMBOL(pmu_request); +EXPORT_SYMBOL(pmu_poll); +EXPORT_SYMBOL(pmu_poll_adb); +EXPORT_SYMBOL(pmu_wait_complete); +EXPORT_SYMBOL(pmu_suspend); +EXPORT_SYMBOL(pmu_resume); +EXPORT_SYMBOL(pmu_unlock); +EXPORT_SYMBOL(pmu_i2c_combined_read); +EXPORT_SYMBOL(pmu_i2c_stdsub_write); +EXPORT_SYMBOL(pmu_i2c_simple_read); +EXPORT_SYMBOL(pmu_i2c_simple_write); +#ifdef CONFIG_PMAC_PBOOK +EXPORT_SYMBOL(pmu_register_sleep_notifier); +EXPORT_SYMBOL(pmu_unregister_sleep_notifier); +EXPORT_SYMBOL(pmu_enable_irled); +EXPORT_SYMBOL(pmu_battery_count); +EXPORT_SYMBOL(pmu_batteries); +EXPORT_SYMBOL(pmu_power_flags); +#endif /* CONFIG_PMAC_PBOOK */ + diff --git a/drivers/macintosh/via-pmu68k.c b/drivers/macintosh/via-pmu68k.c new file mode 100644 index 000000000000..820dc52e30bc --- /dev/null +++ b/drivers/macintosh/via-pmu68k.c @@ -0,0 +1,1063 @@ +/* + * Device driver for the PMU on 68K-based Apple PowerBooks + * + * The VIA (versatile interface adapter) interfaces to the PMU, + * a 6805 microprocessor core whose primary function is to control + * battery charging and system power on the PowerBooks. + * The PMU also controls the ADB (Apple Desktop Bus) which connects + * to the keyboard and mouse, as well as the non-volatile RAM + * and the RTC (real time clock) chip. + * + * Adapted for 68K PMU by Joshua M. Thompson + * + * Based largely on the PowerMac PMU code by Paul Mackerras and + * Fabio Riccardi. + * + * Also based on the PMU driver from MkLinux by Apple Computer, Inc. + * and the Open Software Foundation, Inc. + */ + +#include <stdarg.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/miscdevice.h> +#include <linux/blkdev.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/interrupt.h> + +#include <linux/adb.h> +#include <linux/pmu.h> +#include <linux/cuda.h> + +#include <asm/macintosh.h> +#include <asm/macints.h> +#include <asm/machw.h> +#include <asm/mac_via.h> + +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/uaccess.h> + +/* Misc minor number allocated for /dev/pmu */ +#define PMU_MINOR 154 + +/* VIA registers - spaced 0x200 bytes apart */ +#define RS 0x200 /* skip between registers */ +#define B 0 /* B-side data */ +#define A RS /* A-side data */ +#define DIRB (2*RS) /* B-side direction (1=output) */ +#define DIRA (3*RS) /* A-side direction (1=output) */ +#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */ +#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */ +#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */ +#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */ +#define T2CL (8*RS) /* Timer 2 ctr/latch (low 8 bits) */ +#define T2CH (9*RS) /* Timer 2 counter (high 8 bits) */ +#define SR (10*RS) /* Shift register */ +#define ACR (11*RS) /* Auxiliary control register */ +#define PCR (12*RS) /* Peripheral control register */ +#define IFR (13*RS) /* Interrupt flag register */ +#define IER (14*RS) /* Interrupt enable register */ +#define ANH (15*RS) /* A-side data, no handshake */ + +/* Bits in B data register: both active low */ +#define TACK 0x02 /* Transfer acknowledge (input) */ +#define TREQ 0x04 /* Transfer request (output) */ + +/* Bits in ACR */ +#define SR_CTRL 0x1c /* Shift register control bits */ +#define SR_EXT 0x0c /* Shift on external clock */ +#define SR_OUT 0x10 /* Shift out if 1 */ + +/* Bits in IFR and IER */ +#define SR_INT 0x04 /* Shift register full/empty */ +#define CB1_INT 0x10 /* transition on CB1 input */ + +static enum pmu_state { + idle, + sending, + intack, + reading, + reading_intr, +} pmu_state; + +static struct adb_request *current_req; +static struct adb_request *last_req; +static struct adb_request *req_awaiting_reply; +static unsigned char interrupt_data[32]; +static unsigned char *reply_ptr; +static int data_index; +static int data_len; +static int adb_int_pending; +static int pmu_adb_flags; +static int adb_dev_map = 0; +static struct adb_request bright_req_1, bright_req_2, bright_req_3; +static int pmu_kind = PMU_UNKNOWN; +static int pmu_fully_inited = 0; + +int asleep; +struct notifier_block *sleep_notifier_list; + +static int pmu_probe(void); +static int pmu_init(void); +static void pmu_start(void); +static irqreturn_t pmu_interrupt(int irq, void *arg, struct pt_regs *regs); +static int pmu_send_request(struct adb_request *req, int sync); +static int pmu_autopoll(int devs); +void pmu_poll(void); +static int pmu_reset_bus(void); +static int pmu_queue_request(struct adb_request *req); + +static void pmu_start(void); +static void send_byte(int x); +static void recv_byte(void); +static void pmu_done(struct adb_request *req); +static void pmu_handle_data(unsigned char *data, int len, + struct pt_regs *regs); +static void set_volume(int level); +static void pmu_enable_backlight(int on); +static void pmu_set_brightness(int level); + +struct adb_driver via_pmu_driver = { + "68K PMU", + pmu_probe, + pmu_init, + pmu_send_request, + pmu_autopoll, + pmu_poll, + pmu_reset_bus +}; + +/* + * This table indicates for each PMU opcode: + * - the number of data bytes to be sent with the command, or -1 + * if a length byte should be sent, + * - the number of response bytes which the PMU will return, or + * -1 if it will send a length byte. + */ +static s8 pmu_data_len[256][2] = { +/* 0 1 2 3 4 5 6 7 */ +/*00*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*08*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*10*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*18*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0, 0}, +/*20*/ {-1, 0},{ 0, 0},{ 2, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*28*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{ 0,-1}, +/*30*/ { 4, 0},{20, 0},{-1, 0},{ 3, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*38*/ { 0, 4},{ 0,20},{ 2,-1},{ 2, 1},{ 3,-1},{-1,-1},{-1,-1},{ 4, 0}, +/*40*/ { 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*48*/ { 0, 1},{ 0, 1},{-1,-1},{ 1, 0},{ 1, 0},{-1,-1},{-1,-1},{-1,-1}, +/*50*/ { 1, 0},{ 0, 0},{ 2, 0},{ 2, 0},{-1, 0},{ 1, 0},{ 3, 0},{ 1, 0}, +/*58*/ { 0, 1},{ 1, 0},{ 0, 2},{ 0, 2},{ 0,-1},{-1,-1},{-1,-1},{-1,-1}, +/*60*/ { 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*68*/ { 0, 3},{ 0, 3},{ 0, 2},{ 0, 8},{ 0,-1},{ 0,-1},{-1,-1},{-1,-1}, +/*70*/ { 1, 0},{ 1, 0},{ 1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*78*/ { 0,-1},{ 0,-1},{-1,-1},{-1,-1},{-1,-1},{ 5, 1},{ 4, 1},{ 4, 1}, +/*80*/ { 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*88*/ { 0, 5},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*90*/ { 1, 0},{ 2, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*98*/ { 0, 1},{ 0, 1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*a0*/ { 2, 0},{ 2, 0},{ 2, 0},{ 4, 0},{-1, 0},{ 0, 0},{-1, 0},{-1, 0}, +/*a8*/ { 1, 1},{ 1, 0},{ 3, 0},{ 2, 0},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*b0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*b8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*c0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*c8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +/*d0*/ { 0, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*d8*/ { 1, 1},{ 1, 1},{-1,-1},{-1,-1},{ 0, 1},{ 0,-1},{-1,-1},{-1,-1}, +/*e0*/ {-1, 0},{ 4, 0},{ 0, 1},{-1, 0},{-1, 0},{ 4, 0},{-1, 0},{-1, 0}, +/*e8*/ { 3,-1},{-1,-1},{ 0, 1},{-1,-1},{ 0,-1},{-1,-1},{-1,-1},{ 0, 0}, +/*f0*/ {-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0},{-1, 0}, +/*f8*/ {-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1},{-1,-1}, +}; + +int pmu_probe(void) +{ + if (macintosh_config->adb_type == MAC_ADB_PB1) { + pmu_kind = PMU_68K_V1; + } else if (macintosh_config->adb_type == MAC_ADB_PB2) { + pmu_kind = PMU_68K_V2; + } else { + return -ENODEV; + } + + pmu_state = idle; + + return 0; +} + +static int +pmu_init(void) +{ + int timeout; + volatile struct adb_request req; + + via2[B] |= TREQ; /* negate TREQ */ + via2[DIRB] = (via2[DIRB] | TREQ) & ~TACK; /* TACK in, TREQ out */ + + pmu_request((struct adb_request *) &req, NULL, 2, PMU_SET_INTR_MASK, PMU_INT_ADB); + timeout = 100000; + while (!req.complete) { + if (--timeout < 0) { + printk(KERN_ERR "pmu_init: no response from PMU\n"); + return -EAGAIN; + } + udelay(10); + pmu_poll(); + } + + /* ack all pending interrupts */ + timeout = 100000; + interrupt_data[0] = 1; + while (interrupt_data[0] || pmu_state != idle) { + if (--timeout < 0) { + printk(KERN_ERR "pmu_init: timed out acking intrs\n"); + return -EAGAIN; + } + if (pmu_state == idle) { + adb_int_pending = 1; + pmu_interrupt(0, NULL, NULL); + } + pmu_poll(); + udelay(10); + } + + pmu_request((struct adb_request *) &req, NULL, 2, PMU_SET_INTR_MASK, + PMU_INT_ADB_AUTO|PMU_INT_SNDBRT|PMU_INT_ADB); + timeout = 100000; + while (!req.complete) { + if (--timeout < 0) { + printk(KERN_ERR "pmu_init: no response from PMU\n"); + return -EAGAIN; + } + udelay(10); + pmu_poll(); + } + + bright_req_1.complete = 1; + bright_req_2.complete = 1; + bright_req_3.complete = 1; + + if (request_irq(IRQ_MAC_ADB_SR, pmu_interrupt, 0, "pmu-shift", + pmu_interrupt)) { + printk(KERN_ERR "pmu_init: can't get irq %d\n", + IRQ_MAC_ADB_SR); + return -EAGAIN; + } + if (request_irq(IRQ_MAC_ADB_CL, pmu_interrupt, 0, "pmu-clock", + pmu_interrupt)) { + printk(KERN_ERR "pmu_init: can't get irq %d\n", + IRQ_MAC_ADB_CL); + free_irq(IRQ_MAC_ADB_SR, pmu_interrupt); + return -EAGAIN; + } + + pmu_fully_inited = 1; + + /* Enable backlight */ + pmu_enable_backlight(1); + + printk("adb: PMU 68K driver v0.5 for Unified ADB.\n"); + + return 0; +} + +int +pmu_get_model(void) +{ + return pmu_kind; +} + +/* Send an ADB command */ +static int +pmu_send_request(struct adb_request *req, int sync) +{ + int i, ret; + + if (!pmu_fully_inited) + { + req->complete = 1; + return -ENXIO; + } + + ret = -EINVAL; + + switch (req->data[0]) { + case PMU_PACKET: + for (i = 0; i < req->nbytes - 1; ++i) + req->data[i] = req->data[i+1]; + --req->nbytes; + if (pmu_data_len[req->data[0]][1] != 0) { + req->reply[0] = ADB_RET_OK; + req->reply_len = 1; + } else + req->reply_len = 0; + ret = pmu_queue_request(req); + break; + case CUDA_PACKET: + switch (req->data[1]) { + case CUDA_GET_TIME: + if (req->nbytes != 2) + break; + req->data[0] = PMU_READ_RTC; + req->nbytes = 1; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_GET_TIME; + ret = pmu_queue_request(req); + break; + case CUDA_SET_TIME: + if (req->nbytes != 6) + break; + req->data[0] = PMU_SET_RTC; + req->nbytes = 5; + for (i = 1; i <= 4; ++i) + req->data[i] = req->data[i+1]; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_SET_TIME; + ret = pmu_queue_request(req); + break; + case CUDA_GET_PRAM: + if (req->nbytes != 4) + break; + req->data[0] = PMU_READ_NVRAM; + req->data[1] = req->data[2]; + req->data[2] = req->data[3]; + req->nbytes = 3; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_GET_PRAM; + ret = pmu_queue_request(req); + break; + case CUDA_SET_PRAM: + if (req->nbytes != 5) + break; + req->data[0] = PMU_WRITE_NVRAM; + req->data[1] = req->data[2]; + req->data[2] = req->data[3]; + req->data[3] = req->data[4]; + req->nbytes = 4; + req->reply_len = 3; + req->reply[0] = CUDA_PACKET; + req->reply[1] = 0; + req->reply[2] = CUDA_SET_PRAM; + ret = pmu_queue_request(req); + break; + } + break; + case ADB_PACKET: + for (i = req->nbytes - 1; i > 1; --i) + req->data[i+2] = req->data[i]; + req->data[3] = req->nbytes - 2; + req->data[2] = pmu_adb_flags; + /*req->data[1] = req->data[1];*/ + req->data[0] = PMU_ADB_CMD; + req->nbytes += 2; + req->reply_expected = 1; + req->reply_len = 0; + ret = pmu_queue_request(req); + break; + } + if (ret) + { + req->complete = 1; + return ret; + } + + if (sync) { + while (!req->complete) + pmu_poll(); + } + + return 0; +} + +/* Enable/disable autopolling */ +static int +pmu_autopoll(int devs) +{ + struct adb_request req; + + if (!pmu_fully_inited) return -ENXIO; + + if (devs) { + adb_dev_map = devs; + pmu_request(&req, NULL, 5, PMU_ADB_CMD, 0, 0x86, + adb_dev_map >> 8, adb_dev_map); + pmu_adb_flags = 2; + } else { + pmu_request(&req, NULL, 1, PMU_ADB_POLL_OFF); + pmu_adb_flags = 0; + } + while (!req.complete) + pmu_poll(); + return 0; +} + +/* Reset the ADB bus */ +static int +pmu_reset_bus(void) +{ + struct adb_request req; + long timeout; + int save_autopoll = adb_dev_map; + + if (!pmu_fully_inited) return -ENXIO; + + /* anyone got a better idea?? */ + pmu_autopoll(0); + + req.nbytes = 5; + req.done = NULL; + req.data[0] = PMU_ADB_CMD; + req.data[1] = 0; + req.data[2] = 3; /* ADB_BUSRESET ??? */ + req.data[3] = 0; + req.data[4] = 0; + req.reply_len = 0; + req.reply_expected = 1; + if (pmu_queue_request(&req) != 0) + { + printk(KERN_ERR "pmu_adb_reset_bus: pmu_queue_request failed\n"); + return -EIO; + } + while (!req.complete) + pmu_poll(); + timeout = 100000; + while (!req.complete) { + if (--timeout < 0) { + printk(KERN_ERR "pmu_adb_reset_bus (reset): no response from PMU\n"); + return -EIO; + } + udelay(10); + pmu_poll(); + } + + if (save_autopoll != 0) + pmu_autopoll(save_autopoll); + + return 0; +} + +/* Construct and send a pmu request */ +int +pmu_request(struct adb_request *req, void (*done)(struct adb_request *), + int nbytes, ...) +{ + va_list list; + int i; + + if (nbytes < 0 || nbytes > 32) { + printk(KERN_ERR "pmu_request: bad nbytes (%d)\n", nbytes); + req->complete = 1; + return -EINVAL; + } + req->nbytes = nbytes; + req->done = done; + va_start(list, nbytes); + for (i = 0; i < nbytes; ++i) + req->data[i] = va_arg(list, int); + va_end(list); + if (pmu_data_len[req->data[0]][1] != 0) { + req->reply[0] = ADB_RET_OK; + req->reply_len = 1; + } else + req->reply_len = 0; + req->reply_expected = 0; + return pmu_queue_request(req); +} + +static int +pmu_queue_request(struct adb_request *req) +{ + unsigned long flags; + int nsend; + + if (req->nbytes <= 0) { + req->complete = 1; + return 0; + } + nsend = pmu_data_len[req->data[0]][0]; + if (nsend >= 0 && req->nbytes != nsend + 1) { + req->complete = 1; + return -EINVAL; + } + + req->next = 0; + req->sent = 0; + req->complete = 0; + local_irq_save(flags); + + if (current_req != 0) { + last_req->next = req; + last_req = req; + } else { + current_req = req; + last_req = req; + if (pmu_state == idle) + pmu_start(); + } + + local_irq_restore(flags); + return 0; +} + +static void +send_byte(int x) +{ + via1[ACR] |= SR_CTRL; + via1[SR] = x; + via2[B] &= ~TREQ; /* assert TREQ */ +} + +static void +recv_byte(void) +{ + char c; + + via1[ACR] = (via1[ACR] | SR_EXT) & ~SR_OUT; + c = via1[SR]; /* resets SR */ + via2[B] &= ~TREQ; +} + +static void +pmu_start(void) +{ + unsigned long flags; + struct adb_request *req; + + /* assert pmu_state == idle */ + /* get the packet to send */ + local_irq_save(flags); + req = current_req; + if (req == 0 || pmu_state != idle + || (req->reply_expected && req_awaiting_reply)) + goto out; + + pmu_state = sending; + data_index = 1; + data_len = pmu_data_len[req->data[0]][0]; + + /* set the shift register to shift out and send a byte */ + send_byte(req->data[0]); + +out: + local_irq_restore(flags); +} + +void +pmu_poll(void) +{ + unsigned long flags; + + local_irq_save(flags); + if (via1[IFR] & SR_INT) { + via1[IFR] = SR_INT; + pmu_interrupt(IRQ_MAC_ADB_SR, NULL, NULL); + } + if (via1[IFR] & CB1_INT) { + via1[IFR] = CB1_INT; + pmu_interrupt(IRQ_MAC_ADB_CL, NULL, NULL); + } + local_irq_restore(flags); +} + +static irqreturn_t +pmu_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct adb_request *req; + int timeout, bite = 0; /* to prevent compiler warning */ + +#if 0 + printk("pmu_interrupt: irq %d state %d acr %02X, b %02X data_index %d/%d adb_int_pending %d\n", + irq, pmu_state, (uint) via1[ACR], (uint) via2[B], data_index, data_len, adb_int_pending); +#endif + + if (irq == IRQ_MAC_ADB_CL) { /* CB1 interrupt */ + adb_int_pending = 1; + } else if (irq == IRQ_MAC_ADB_SR) { /* SR interrupt */ + if (via2[B] & TACK) { + printk(KERN_DEBUG "PMU: SR_INT but ack still high! (%x)\n", via2[B]); + } + + /* if reading grab the byte */ + if ((via1[ACR] & SR_OUT) == 0) bite = via1[SR]; + + /* reset TREQ and wait for TACK to go high */ + via2[B] |= TREQ; + timeout = 3200; + while (!(via2[B] & TACK)) { + if (--timeout < 0) { + printk(KERN_ERR "PMU not responding (!ack)\n"); + goto finish; + } + udelay(10); + } + + switch (pmu_state) { + case sending: + req = current_req; + if (data_len < 0) { + data_len = req->nbytes - 1; + send_byte(data_len); + break; + } + if (data_index <= data_len) { + send_byte(req->data[data_index++]); + break; + } + req->sent = 1; + data_len = pmu_data_len[req->data[0]][1]; + if (data_len == 0) { + pmu_state = idle; + current_req = req->next; + if (req->reply_expected) + req_awaiting_reply = req; + else + pmu_done(req); + } else { + pmu_state = reading; + data_index = 0; + reply_ptr = req->reply + req->reply_len; + recv_byte(); + } + break; + + case intack: + data_index = 0; + data_len = -1; + pmu_state = reading_intr; + reply_ptr = interrupt_data; + recv_byte(); + break; + + case reading: + case reading_intr: + if (data_len == -1) { + data_len = bite; + if (bite > 32) + printk(KERN_ERR "PMU: bad reply len %d\n", + bite); + } else { + reply_ptr[data_index++] = bite; + } + if (data_index < data_len) { + recv_byte(); + break; + } + + if (pmu_state == reading_intr) { + pmu_handle_data(interrupt_data, data_index, regs); + } else { + req = current_req; + current_req = req->next; + req->reply_len += data_index; + pmu_done(req); + } + pmu_state = idle; + + break; + + default: + printk(KERN_ERR "pmu_interrupt: unknown state %d?\n", + pmu_state); + } + } +finish: + if (pmu_state == idle) { + if (adb_int_pending) { + pmu_state = intack; + send_byte(PMU_INT_ACK); + adb_int_pending = 0; + } else if (current_req) { + pmu_start(); + } + } + +#if 0 + printk("pmu_interrupt: exit state %d acr %02X, b %02X data_index %d/%d adb_int_pending %d\n", + pmu_state, (uint) via1[ACR], (uint) via2[B], data_index, data_len, adb_int_pending); +#endif + return IRQ_HANDLED; +} + +static void +pmu_done(struct adb_request *req) +{ + req->complete = 1; + if (req->done) + (*req->done)(req); +} + +/* Interrupt data could be the result data from an ADB cmd */ +static void +pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs) +{ + static int show_pmu_ints = 1; + + asleep = 0; + if (len < 1) { + adb_int_pending = 0; + return; + } + if (data[0] & PMU_INT_ADB) { + if ((data[0] & PMU_INT_ADB_AUTO) == 0) { + struct adb_request *req = req_awaiting_reply; + if (req == 0) { + printk(KERN_ERR "PMU: extra ADB reply\n"); + return; + } + req_awaiting_reply = 0; + if (len <= 2) + req->reply_len = 0; + else { + memcpy(req->reply, data + 1, len - 1); + req->reply_len = len - 1; + } + pmu_done(req); + } else { + adb_input(data+1, len-1, regs, 1); + } + } else { + if (data[0] == 0x08 && len == 3) { + /* sound/brightness buttons pressed */ + pmu_set_brightness(data[1] >> 3); + set_volume(data[2]); + } else if (show_pmu_ints + && !(data[0] == PMU_INT_TICK && len == 1)) { + int i; + printk(KERN_DEBUG "pmu intr"); + for (i = 0; i < len; ++i) + printk(" %.2x", data[i]); + printk("\n"); + } + } +} + +int backlight_level = -1; +int backlight_enabled = 0; + +#define LEVEL_TO_BRIGHT(lev) ((lev) < 1? 0x7f: 0x4a - ((lev) << 1)) + +static void +pmu_enable_backlight(int on) +{ + struct adb_request req; + + if (on) { + /* first call: get current backlight value */ + if (backlight_level < 0) { + switch(pmu_kind) { + case PMU_68K_V1: + case PMU_68K_V2: + pmu_request(&req, NULL, 3, PMU_READ_NVRAM, 0x14, 0xe); + while (!req.complete) + pmu_poll(); + printk(KERN_DEBUG "pmu: nvram returned bright: %d\n", (int)req.reply[1]); + backlight_level = req.reply[1]; + break; + default: + backlight_enabled = 0; + return; + } + } + pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT, + LEVEL_TO_BRIGHT(backlight_level)); + while (!req.complete) + pmu_poll(); + } + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, + PMU_POW_BACKLIGHT | (on ? PMU_POW_ON : PMU_POW_OFF)); + while (!req.complete) + pmu_poll(); + backlight_enabled = on; +} + +static void +pmu_set_brightness(int level) +{ + int bright; + + backlight_level = level; + bright = LEVEL_TO_BRIGHT(level); + if (!backlight_enabled) + return; + if (bright_req_1.complete) + pmu_request(&bright_req_1, NULL, 2, PMU_BACKLIGHT_BRIGHT, + bright); + if (bright_req_2.complete) + pmu_request(&bright_req_2, NULL, 2, PMU_POWER_CTRL, + PMU_POW_BACKLIGHT | (bright < 0x7f ? PMU_POW_ON : PMU_POW_OFF)); +} + +void +pmu_enable_irled(int on) +{ + struct adb_request req; + + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, PMU_POW_IRLED | + (on ? PMU_POW_ON : PMU_POW_OFF)); + while (!req.complete) + pmu_poll(); +} + +static void +set_volume(int level) +{ +} + +int +pmu_present(void) +{ + return (pmu_kind != PMU_UNKNOWN); +} + +#if 0 /* needs some work for 68K */ + +/* + * This struct is used to store config register values for + * PCI devices which may get powered off when we sleep. + */ +static struct pci_save { + u16 command; + u16 cache_lat; + u16 intr; +} *pbook_pci_saves; +static int n_pbook_pci_saves; + +static inline void __openfirmware +pbook_pci_save(void) +{ + int npci; + struct pci_dev *pd = NULL; + struct pci_save *ps; + + npci = 0; + while ((pd = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pd)) != NULL) + ++npci; + n_pbook_pci_saves = npci; + if (npci == 0) + return; + ps = (struct pci_save *) kmalloc(npci * sizeof(*ps), GFP_KERNEL); + pbook_pci_saves = ps; + if (ps == NULL) + return; + + pd = NULL; + while ((pd = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pd)) != NULL) { + pci_read_config_word(pd, PCI_COMMAND, &ps->command); + pci_read_config_word(pd, PCI_CACHE_LINE_SIZE, &ps->cache_lat); + pci_read_config_word(pd, PCI_INTERRUPT_LINE, &ps->intr); + ++ps; + --npci; + } +} + +static inline void __openfirmware +pbook_pci_restore(void) +{ + u16 cmd; + struct pci_save *ps = pbook_pci_saves; + struct pci_dev *pd = NULL; + int j; + + while ((pd = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pd)) != NULL) { + if (ps->command == 0) + continue; + pci_read_config_word(pd, PCI_COMMAND, &cmd); + if ((ps->command & ~cmd) == 0) + continue; + switch (pd->hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + for (j = 0; j < 6; ++j) + pci_write_config_dword(pd, + PCI_BASE_ADDRESS_0 + j*4, + pd->resource[j].start); + pci_write_config_dword(pd, PCI_ROM_ADDRESS, + pd->resource[PCI_ROM_RESOURCE].start); + pci_write_config_word(pd, PCI_CACHE_LINE_SIZE, + ps->cache_lat); + pci_write_config_word(pd, PCI_INTERRUPT_LINE, + ps->intr); + pci_write_config_word(pd, PCI_COMMAND, ps->command); + break; + /* other header types not restored at present */ + } + } +} + +/* + * Put the powerbook to sleep. + */ +#define IRQ_ENABLE ((unsigned int *)0xf3000024) +#define MEM_CTRL ((unsigned int *)0xf8000070) + +int __openfirmware powerbook_sleep(void) +{ + int ret, i, x; + static int save_backlight; + static unsigned int save_irqen; + unsigned long msr; + unsigned int hid0; + unsigned long p, wait; + struct adb_request sleep_req; + + /* Notify device drivers */ + ret = notifier_call_chain(&sleep_notifier_list, PBOOK_SLEEP, NULL); + if (ret & NOTIFY_STOP_MASK) + return -EBUSY; + + /* Sync the disks. */ + /* XXX It would be nice to have some way to ensure that + * nobody is dirtying any new buffers while we wait. */ + sys_sync(); + + /* Turn off the display backlight */ + save_backlight = backlight_enabled; + if (save_backlight) + pmu_enable_backlight(0); + + /* Give the disks a little time to actually finish writing */ + for (wait = jiffies + (HZ/4); time_before(jiffies, wait); ) + mb(); + + /* Disable all interrupts except pmu */ + save_irqen = in_le32(IRQ_ENABLE); + for (i = 0; i < 32; ++i) + if (i != vias->intrs[0].line && (save_irqen & (1 << i))) + disable_irq(i); + asm volatile("mtdec %0" : : "r" (0x7fffffff)); + + /* Save the state of PCI config space for some slots */ + pbook_pci_save(); + + /* Set the memory controller to keep the memory refreshed + while we're asleep */ + for (i = 0x403f; i >= 0x4000; --i) { + out_be32(MEM_CTRL, i); + do { + x = (in_be32(MEM_CTRL) >> 16) & 0x3ff; + } while (x == 0); + if (x >= 0x100) + break; + } + + /* Ask the PMU to put us to sleep */ + pmu_request(&sleep_req, NULL, 5, PMU_SLEEP, 'M', 'A', 'T', 'T'); + while (!sleep_req.complete) + mb(); + /* displacement-flush the L2 cache - necessary? */ + for (p = KERNELBASE; p < KERNELBASE + 0x100000; p += 0x1000) + i = *(volatile int *)p; + asleep = 1; + + /* Put the CPU into sleep mode */ + asm volatile("mfspr %0,1008" : "=r" (hid0) :); + hid0 = (hid0 & ~(HID0_NAP | HID0_DOZE)) | HID0_SLEEP; + asm volatile("mtspr 1008,%0" : : "r" (hid0)); + local_save_flags(msr); + msr |= MSR_POW | MSR_EE; + local_irq_restore(msr); + udelay(10); + + /* OK, we're awake again, start restoring things */ + out_be32(MEM_CTRL, 0x3f); + pbook_pci_restore(); + + /* wait for the PMU interrupt sequence to complete */ + while (asleep) + mb(); + + /* reenable interrupts */ + for (i = 0; i < 32; ++i) + if (i != vias->intrs[0].line && (save_irqen & (1 << i))) + enable_irq(i); + + /* Notify drivers */ + notifier_call_chain(&sleep_notifier_list, PBOOK_WAKE, NULL); + + /* reenable ADB autopoll */ + pmu_adb_autopoll(adb_dev_map); + + /* Turn on the screen backlight, if it was on before */ + if (save_backlight) + pmu_enable_backlight(1); + + /* Wait for the hard disk to spin up */ + + return 0; +} + +/* + * Support for /dev/pmu device + */ +static int __openfirmware pmu_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t __openfirmware pmu_read(struct file *file, char *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t __openfirmware pmu_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +/* Note: removed __openfirmware here since it causes link errors */ +static int /*__openfirmware*/ pmu_ioctl(struct inode * inode, struct file *filp, + u_int cmd, u_long arg) +{ + int error; + __u32 value; + + switch (cmd) { + case PMU_IOC_SLEEP: + return -ENOSYS; + case PMU_IOC_GET_BACKLIGHT: + return put_user(backlight_level, (__u32 *)arg); + case PMU_IOC_SET_BACKLIGHT: + error = get_user(value, (__u32 *)arg); + if (!error) + pmu_set_brightness(value); + return error; + case PMU_IOC_GET_MODEL: + return put_user(pmu_kind, (__u32 *)arg); + } + return -EINVAL; +} + +static struct file_operations pmu_device_fops = { + .read = pmu_read, + .write = pmu_write, + .ioctl = pmu_ioctl, + .open = pmu_open, +}; + +static struct miscdevice pmu_device = { + PMU_MINOR, "pmu", &pmu_device_fops +}; + +void pmu_device_init(void) +{ + if (!via) + return; + if (misc_register(&pmu_device) < 0) + printk(KERN_ERR "via-pmu68k: cannot register misc device.\n"); +} +#endif /* CONFIG_PMAC_PBOOK */ + |