diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/macintosh | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
50 files changed, 22467 insertions, 0 deletions
diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig new file mode 100644 index 000000000..a0e717a98 --- /dev/null +++ b/drivers/macintosh/Kconfig @@ -0,0 +1,279 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menuconfig MACINTOSH_DRIVERS + bool "Macintosh device drivers" + depends on PPC || MAC || X86 + default y if (PPC_PMAC || MAC) + help + Say Y here to get to see options for devices used with Macintosh + computers. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if MACINTOSH_DRIVERS + +config ADB + bool "Apple Desktop Bus (ADB) support" + depends on MAC || (PPC_PMAC && PPC32) + help + Apple Desktop Bus (ADB) support is for support of devices which + are connected to an ADB port. ADB devices tend to have 4 pins. + If you have an Apple Macintosh prior to the iMac, an iBook or + PowerBook, or a "Blue and White G3", you probably want to say Y + here. Otherwise say N. + +config ADB_MACII + bool "Include Mac II ADB driver" + depends on ADB && MAC + help + Say Y here if want your kernel to support Macintosh systems that use + the Mac II style ADB. This includes the II, IIx, IIcx, SE/30, IIci, + Quadra 610, Quadra 650, Quadra 700, Quadra 800, Centris 610 and + Centris 650. + +config ADB_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. + +# we want to change this to something like CONFIG_SYSCTRL_CUDA/PMU +config ADB_CUDA + bool "Support for Cuda/Egret based Macs and PowerMacs" + depends on (ADB || PPC_PMAC) && !PPC_PMAC64 + select RTC_LIB + help + This provides support for Cuda/Egret based Macintosh and + Power Macintosh systems. This includes most m68k based Macs, + most Old World 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 or Egret device. + + If unsure say Y. + +config ADB_PMU + bool "Support for PMU based PowerMacs and PowerBooks" + depends on PPC_PMAC || MAC + select RTC_LIB + help + On PowerBooks, iBooks, and recent iMacs and Power Macintoshes, the + PMU is an embedded microprocessor whose primary function is to + control system power, and battery charging on the portable models. + The PMU also controls the ADB (Apple Desktop Bus) which connects to + the keyboard and mouse on some machines, as well as the non-volatile + RAM and the RTC (real time clock) chip. Say Y to enable support for + this device; you should do so if your machine is one of those + mentioned above. + +config ADB_PMU_EVENT + def_bool y + depends on ADB_PMU && INPUT=y + +config ADB_PMU_LED + bool "Support for the Power/iBook front LED" + depends on PPC_PMAC && ADB_PMU + select NEW_LEDS + select LEDS_CLASS + help + Support the front LED on Power/iBooks as a generic LED that can + be triggered by any of the supported triggers. To get the + behaviour of the old CONFIG_BLK_DEV_IDE_PMAC_BLINK, select this + and the disk LED trigger and configure appropriately through sysfs. + +config ADB_PMU_LED_DISK + bool "Use front LED as DISK LED by default" + depends on ATA + depends on ADB_PMU_LED + depends on LEDS_CLASS + select LEDS_TRIGGERS + select LEDS_TRIGGER_DISK + help + This option makes the front LED default to the disk trigger + so that it blinks on disk activity. + +config PMAC_SMU + bool "Support for SMU based PowerMacs" + depends on PPC_PMAC64 + help + This option adds support for the newer G5 iMacs and PowerMacs based + on the "SMU" system control chip which replaces the old PMU. + If you don't know, say Y. + +config PMAC_APM_EMU + tristate "APM emulation" + select APM_EMULATION + depends on ADB_PMU && PM && PPC32 + +config PMAC_MEDIABAY + bool "Support PowerBook hotswap media bay" + depends on PPC_PMAC && PPC32 && BLOCK + help + This option adds support for older PowerBook's hotswap media bay + that can contains batteries, floppy drives, or IDE devices. PCI + devices are not fully supported in the bay as I never had one to + try with + +config PMAC_BACKLIGHT + bool "Backlight control for LCD screens" + depends on PPC_PMAC && ADB_PMU && FB = y && (BROKEN || !PPC64) + select FB_BACKLIGHT + help + Say Y here to enable Macintosh specific extensions of the generic + backlight code. With this enabled, the brightness keys on older + PowerBooks will be enabled so you can change the screen brightness. + Newer models should use a userspace daemon like pbbuttonsd. + +config PMAC_BACKLIGHT_LEGACY + bool "Provide legacy ioctl's on /dev/pmu for the backlight" + depends on PMAC_BACKLIGHT && (BROKEN || !PPC64) + help + Say Y if you want to enable legacy ioctl's on /dev/pmu. This is for + programs which use this old interface. New and updated programs + should use the backlight classes in sysfs. + +config ADB_MACIO + bool "Include MacIO (CHRP) ADB driver" + depends on ADB && PPC_CHRP && !PPC_PMAC64 + help + Say Y here to include direct support for the ADB controller in the + Hydra chip used on PowerPC Macintoshes of the CHRP type. (The Hydra + also includes a MESH II SCSI controller, DBDMA controller, VIA chip, + OpenPIC controller and two RS422/Geoports.) + +config INPUT_ADBHID + bool "Support for ADB input devices (keyboard, mice, ...)" + depends on ADB && INPUT=y + help + Say Y here if you want to have ADB (Apple Desktop Bus) HID devices + such as keyboards, mice, joysticks, trackpads or graphic tablets + handled by the input layer. If you say Y here, make sure to say Y to + the corresponding drivers "Mouse Support" (CONFIG_INPUT_MOUSEDEV) and + "Event interface support" (CONFIG_INPUT_EVDEV) as well. + + If unsure, say Y. + +config MAC_EMUMOUSEBTN + tristate "Support for mouse button 2+3 emulation" + depends on SYSCTL && INPUT + help + This provides generic support for emulating the 2nd and 3rd mouse + button with keypresses. If you say Y here, the emulation is still + disabled by default. The emulation is controlled by these sysctl + entries: + /proc/sys/dev/mac_hid/mouse_button_emulation + /proc/sys/dev/mac_hid/mouse_button2_keycode + /proc/sys/dev/mac_hid/mouse_button3_keycode + + If you have an Apple machine with a 1-button mouse, say Y here. + + To compile this driver as a module, choose M here: the + module will be called mac_hid. + +config THERM_WINDTUNNEL + tristate "Support for thermal management on Windtunnel G4s" + depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64 + help + This driver provides some thermostat and fan control for the desktop + G4 "Windtunnel" + +config THERM_ADT746X + tristate "Support for thermal mgmnt on laptops with ADT 746x chipset" + depends on I2C && I2C_POWERMAC && PPC_PMAC && !PPC_PMAC64 + help + This driver provides some thermostat and fan control for the + iBook G4, and the ATI based aluminium PowerBooks, allowing slightly + better fan behaviour by default, and some manual control. + +config WINDFARM + tristate "New PowerMac thermal control infrastructure" + depends on PPC + +config WINDFARM_PM81 + tristate "Support for thermal management on iMac G5" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU + select I2C_POWERMAC + help + This driver provides thermal control for the iMacG5 + +config WINDFARM_PM72 + tristate "Support for thermal management on PowerMac G5 (AGP)" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU + select I2C_POWERMAC + help + This driver provides thermal control for the PowerMac G5 + "AGP" variants (PowerMac 7,2 and 7,3) + +config WINDFARM_RM31 + tristate "Support for thermal management on Xserve G5" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU + select I2C_POWERMAC + help + This driver provides thermal control for the Xserve G5 + (RackMac3,1) + +config WINDFARM_PM91 + tristate "Support for thermal management on PowerMac9,1" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU + select I2C_POWERMAC + help + This driver provides thermal control for the PowerMac9,1 + which is the recent (SMU based) single CPU desktop G5 + +config WINDFARM_PM112 + tristate "Support for thermal management on PowerMac11,2" + depends on WINDFARM && I2C && PMAC_SMU + select I2C_POWERMAC + help + This driver provides thermal control for the PowerMac11,2 + which are the recent dual and quad G5 machines using the + 970MP dual-core processor. + +config WINDFARM_PM121 + tristate "Support for thermal management on PowerMac12,1" + depends on WINDFARM && I2C && PMAC_SMU + select I2C_POWERMAC + help + This driver provides thermal control for the PowerMac12,1 + which is the iMac G5 (iSight). + +config ANSLCD + tristate "Support for ANS LCD display" + depends on ADB_CUDA && PPC_PMAC + +config PMAC_RACKMETER + tristate "Support for Apple XServe front panel LEDs" + depends on PPC_PMAC + help + This driver provides some support to control the front panel + blue LEDs "vu-meter" of the XServer macs. + +config SENSORS_AMS + tristate "Apple Motion Sensor driver" + depends on PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C) + help + Support for the motion sensor included in PowerBooks. Includes + implementations for PMU and I2C. + + This driver can also be built as a module. If so, the module + will be called ams. + +config SENSORS_AMS_PMU + bool "PMU variant" + depends on SENSORS_AMS && ADB_PMU + default y + help + PMU variant of motion sensor, found in late 2005 PowerBooks. + +config SENSORS_AMS_I2C + bool "I2C variant" + depends on SENSORS_AMS && I2C + default y + help + I2C variant of motion sensor, found in early 2005 PowerBooks and + iBooks. + +endif # MACINTOSH_DRIVERS diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile new file mode 100644 index 000000000..712edcb3e --- /dev/null +++ b/drivers/macintosh/Makefile @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Macintosh-specific device drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_PPC_PMAC) += macio_asic.o macio_sysfs.o + +obj-$(CONFIG_PMAC_MEDIABAY) += mediabay.o +obj-$(CONFIG_MAC_EMUMOUSEBTN) += mac_hid.o +obj-$(CONFIG_INPUT_ADBHID) += adbhid.o +obj-$(CONFIG_ANSLCD) += ans-lcd.o + +obj-$(CONFIG_ADB_PMU) += via-pmu.o +obj-$(CONFIG_ADB_PMU_EVENT) += via-pmu-event.o +obj-$(CONFIG_ADB_PMU_LED) += via-pmu-led.o +obj-$(CONFIG_PMAC_BACKLIGHT) += via-pmu-backlight.o +obj-$(CONFIG_ADB_CUDA) += via-cuda.o +obj-$(CONFIG_PMAC_APM_EMU) += apm_emu.o +obj-$(CONFIG_PMAC_SMU) += smu.o + +obj-$(CONFIG_ADB) += adb.o +obj-$(CONFIG_ADB_MACII) += via-macii.o +obj-$(CONFIG_ADB_IOP) += adb-iop.o +obj-$(CONFIG_ADB_MACIO) += macio-adb.o + +obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o +obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o +obj-$(CONFIG_WINDFARM) += windfarm_core.o +obj-$(CONFIG_WINDFARM_PM72) += windfarm_fcu_controls.o \ + windfarm_ad7417_sensor.o \ + windfarm_lm75_sensor.o \ + windfarm_max6690_sensor.o \ + windfarm_pid.o \ + windfarm_cpufreq_clamp.o \ + windfarm_pm72.o +obj-$(CONFIG_WINDFARM_RM31) += windfarm_fcu_controls.o \ + windfarm_ad7417_sensor.o \ + windfarm_lm75_sensor.o \ + windfarm_lm87_sensor.o \ + windfarm_max6690_sensor.o \ + windfarm_pid.o \ + windfarm_cpufreq_clamp.o \ + windfarm_rm31.o +obj-$(CONFIG_WINDFARM_PM81) += windfarm_smu_controls.o \ + windfarm_smu_sensors.o \ + windfarm_lm75_sensor.o windfarm_pid.o \ + windfarm_cpufreq_clamp.o windfarm_pm81.o +obj-$(CONFIG_WINDFARM_PM91) += windfarm_smu_controls.o \ + windfarm_smu_sensors.o \ + windfarm_lm75_sensor.o windfarm_pid.o \ + windfarm_cpufreq_clamp.o windfarm_pm91.o +obj-$(CONFIG_WINDFARM_PM112) += windfarm_pm112.o windfarm_smu_sat.o \ + windfarm_smu_controls.o \ + windfarm_smu_sensors.o \ + windfarm_max6690_sensor.o \ + windfarm_lm75_sensor.o windfarm_pid.o +obj-$(CONFIG_WINDFARM_PM121) += windfarm_pm121.o windfarm_smu_sat.o \ + windfarm_smu_controls.o \ + windfarm_smu_sensors.o \ + windfarm_max6690_sensor.o \ + windfarm_lm75_sensor.o windfarm_pid.o +obj-$(CONFIG_PMAC_RACKMETER) += rack-meter.o + +obj-$(CONFIG_SENSORS_AMS) += ams/ diff --git a/drivers/macintosh/adb-iop.c b/drivers/macintosh/adb-iop.c new file mode 100644 index 000000000..2633bc254 --- /dev/null +++ b/drivers/macintosh/adb-iop.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 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. + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> + +#include <asm/macintosh.h> +#include <asm/macints.h> +#include <asm/mac_iop.h> +#include <asm/adb_iop.h> +#include <asm/unaligned.h> + +#include <linux/adb.h> + +static struct adb_request *current_req; +static struct adb_request *last_req; +static unsigned int autopoll_devs; +static u8 autopoll_addr; + +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); + +/* ADB command byte structure */ +#define ADDR_MASK 0xF0 +#define OP_MASK 0x0C +#define TALK 0x0C + +struct adb_driver adb_iop_driver = { + .name = "ISM IOP", + .probe = adb_iop_probe, + .init = adb_iop_init, + .send_request = adb_iop_send_request, + .autopoll = adb_iop_autopoll, + .poll = adb_iop_poll, + .reset_bus = adb_iop_reset_bus +}; + +static void adb_iop_done(void) +{ + struct adb_request *req = current_req; + + adb_iop_state = idle; + + req->complete = 1; + current_req = req->next; + if (req->done) + (*req->done)(req); + + if (adb_iop_state == idle) + adb_iop_start(); +} + +/* + * 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) +{ + unsigned long flags; + + local_irq_save(flags); + + adb_iop_state = awaiting_reply; + + local_irq_restore(flags); +} + +/* + * Listen for ADB messages from the IOP. + * + * This will be called when unsolicited IOP messages are received. + * These IOP messages can carry ADB autopoll responses and also occur + * after explicit ADB commands. + */ + +static void adb_iop_listen(struct iop_msg *msg) +{ + struct adb_iopmsg *amsg = (struct adb_iopmsg *)msg->message; + u8 addr = (amsg->cmd & ADDR_MASK) >> 4; + u8 op = amsg->cmd & OP_MASK; + unsigned long flags; + bool req_done = false; + + local_irq_save(flags); + + /* Responses to Talk commands may be unsolicited as they are + * produced when the IOP polls devices. They are mostly timeouts. + */ + if (op == TALK && ((1 << addr) & autopoll_devs)) + autopoll_addr = addr; + + switch (amsg->flags & (ADB_IOP_EXPLICIT | + ADB_IOP_AUTOPOLL | + ADB_IOP_TIMEOUT)) { + case ADB_IOP_EXPLICIT: + case ADB_IOP_EXPLICIT | ADB_IOP_TIMEOUT: + if (adb_iop_state == awaiting_reply) { + struct adb_request *req = current_req; + + if (req->reply_expected) { + req->reply_len = amsg->count + 1; + memcpy(req->reply, &amsg->cmd, req->reply_len); + } + + req_done = true; + } + break; + case ADB_IOP_AUTOPOLL: + if (((1 << addr) & autopoll_devs) && + amsg->cmd == ADB_READREG(addr, 0)) + adb_input(&amsg->cmd, amsg->count + 1, 1); + break; + } + msg->reply[0] = autopoll_addr ? ADB_IOP_AUTOPOLL : 0; + msg->reply[1] = 0; + msg->reply[2] = autopoll_addr ? ADB_READREG(autopoll_addr, 0) : 0; + iop_complete_message(msg); + + if (req_done) + adb_iop_done(); + + 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) +{ + struct adb_request *req; + struct adb_iopmsg amsg; + + /* get the packet to send */ + req = current_req; + if (!req) + return; + + /* 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; + + /* Now send it. The IOP manager will call adb_iop_complete + * when the message has been sent. + */ + iop_send_message(ADB_IOP, ADB_CHAN, req, sizeof(amsg), (__u8 *)&amsg, + adb_iop_complete); +} + +static int adb_iop_probe(void) +{ + if (!iop_ism_present) + return -ENODEV; + return 0; +} + +static int adb_iop_init(void) +{ + pr_info("adb: IOP ISM driver v0.4 for Unified ADB\n"); + iop_listen(ADB_IOP, ADB_CHAN, adb_iop_listen, "ADB"); + return 0; +} + +static 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; + } + + req->next = NULL; + req->sent = 0; + req->complete = 0; + req->reply_len = 0; + + local_irq_save(flags); + + if (current_req) { + last_req->next = req; + last_req = req; + } else { + current_req = req; + last_req = req; + } + + if (adb_iop_state == idle) + adb_iop_start(); + + local_irq_restore(flags); + + return 0; +} + +static void adb_iop_set_ap_complete(struct iop_msg *msg) +{ + struct adb_iopmsg *amsg = (struct adb_iopmsg *)msg->message; + + autopoll_devs = get_unaligned_be16(amsg->data); + if (autopoll_devs & (1 << autopoll_addr)) + return; + autopoll_addr = autopoll_devs ? (ffs(autopoll_devs) - 1) : 0; +} + +static int adb_iop_autopoll(int devs) +{ + struct adb_iopmsg amsg; + unsigned long flags; + unsigned int mask = (unsigned int)devs & 0xFFFE; + + local_irq_save(flags); + + amsg.flags = ADB_IOP_SET_AUTOPOLL | (mask ? ADB_IOP_AUTOPOLL : 0); + amsg.count = 2; + amsg.cmd = 0; + put_unaligned_be16(mask, amsg.data); + + iop_send_message(ADB_IOP, ADB_CHAN, NULL, sizeof(amsg), (__u8 *)&amsg, + adb_iop_set_ap_complete); + + local_irq_restore(flags); + + return 0; +} + +static void adb_iop_poll(void) +{ + iop_ism_irq_poll(ADB_IOP); +} + +static int adb_iop_reset_bus(void) +{ + struct adb_request req; + + /* Command = 0, Address = ignored */ + adb_request(&req, NULL, ADBREQ_NOSEND, 1, ADB_BUSRESET); + adb_iop_send_request(&req, 1); + + /* Don't want any more requests during the Global Reset low time. */ + mdelay(3); + + return 0; +} diff --git a/drivers/macintosh/adb.c b/drivers/macintosh/adb.c new file mode 100644 index 000000000..1bbb9ca08 --- /dev/null +++ b/drivers/macintosh/adb.c @@ -0,0 +1,898 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device driver for the Apple Desktop Bus + * and the /dev/adb device on macintoshes. + * + * Copyright (C) 1996 Paul Mackerras. + * + * Modified to declare controllers as structures, added + * client notification of bus reset and handles PowerBook + * sleep, by Benjamin Herrenschmidt. + * + * To do: + * + * - /sys/bus/adb to list the devices and infos + * - more /dev/adb to allow userland to receive the + * flow of auto-polling datas from a given device. + * - move bus probe to a kernel thread + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/sched/signal.h> +#include <linux/adb.h> +#include <linux/cuda.h> +#include <linux/pmu.h> +#include <linux/notifier.h> +#include <linux/wait.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/kthread.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/of.h> + +#include <linux/uaccess.h> +#ifdef CONFIG_PPC +#include <asm/machdep.h> +#endif + + +EXPORT_SYMBOL(adb_client_list); + +extern struct adb_driver via_macii_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 DEFINE_MUTEX(adb_mutex); +static struct adb_driver *adb_driver_list[] = { +#ifdef CONFIG_ADB_MACII + &via_macii_driver, +#endif +#ifdef CONFIG_ADB_CUDA + &via_cuda_driver, +#endif +#ifdef CONFIG_ADB_IOP + &adb_iop_driver, +#endif +#ifdef CONFIG_ADB_PMU + &via_pmu_driver, +#endif +#ifdef CONFIG_ADB_MACIO + &macio_adb_driver, +#endif + NULL +}; + +static struct class *adb_dev_class; + +static struct adb_driver *adb_controller; +BLOCKING_NOTIFIER_HEAD(adb_client_list); +static int adb_got_sleep; +static int adb_inited; +static DEFINE_SEMAPHORE(adb_probe_mutex); +static int sleepy_trackpad; +static int autopoll_devs; +int __adb_probe_sync; + +static int adb_scan_bus(void); +static int do_adb_reset_bus(void); +static void adbdev_init(void); +static int try_handler_change(int, int); + +static struct adb_handler { + void (*handler)(unsigned char *, int, int); + int original_address; + int handler_id; + int busy; +} adb_handler[16]; + +/* + * The adb_handler_mutex mutex protects all accesses to the original_address + * and handler_id fields of adb_handler[i] for all i, and changes to the + * handler field. + * Accesses to the handler field are protected by the adb_handler_lock + * rwlock. It is held across all calls to any handler, so that by the + * time adb_unregister returns, we know that the old handler isn't being + * called. + */ +static DEFINE_MUTEX(adb_handler_mutex); +static DEFINE_RWLOCK(adb_handler_lock); + +#if 0 +static void printADBreply(struct adb_request *req) +{ + int i; + + printk("adb reply (%d)", req->reply_len); + for(i = 0; i < req->reply_len; i++) + printk(" %x", req->reply[i]); + printk("\n"); + +} +#endif + +static int adb_scan_bus(void) +{ + int i, highFree=0, noMovement; + int devmask = 0; + struct adb_request req; + + /* assumes adb_handler[] is all zeroes at this point */ + for (i = 1; i < 16; i++) { + /* see if there is anything at address i */ + adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1, + (i << 4) | 0xf); + if (req.reply_len > 1) + /* one or more devices at this address */ + adb_handler[i].original_address = i; + else if (i > highFree) + highFree = i; + } + + /* Note we reset noMovement to 0 each time we move a device */ + for (noMovement = 1; noMovement < 2 && highFree > 0; noMovement++) { + for (i = 1; i < 16; i++) { + if (adb_handler[i].original_address == 0) + continue; + /* + * Send a "talk register 3" command to address i + * to provoke a collision if there is more than + * one device at this address. + */ + adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1, + (i << 4) | 0xf); + /* + * Move the device(s) which didn't detect a + * collision to address `highFree'. Hopefully + * this only moves one device. + */ + adb_request(&req, NULL, ADBREQ_SYNC, 3, + (i<< 4) | 0xb, (highFree | 0x60), 0xfe); + /* + * See if anybody actually moved. This is suggested + * by HW TechNote 01: + * + * https://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. */ + 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(KERN_DEBUG "adb device [%d]: %d 0x%X\n", i, + adb_handler[i].original_address, + adb_handler[i].handler_id); + devmask |= 1 << i; + } + return devmask; +} + +/* + * This kernel task handles ADB probing. It dies once probing is + * completed. + */ +static int +adb_probe_task(void *x) +{ + pr_debug("adb: starting probe task...\n"); + do_adb_reset_bus(); + pr_debug("adb: finished probe task...\n"); + + up(&adb_probe_mutex); + + return 0; +} + +static void +__adb_probe_task(struct work_struct *bullshit) +{ + kthread_run(adb_probe_task, NULL, "kadbprobe"); +} + +static DECLARE_WORK(adb_reset_work, __adb_probe_task); + +int +adb_reset_bus(void) +{ + if (__adb_probe_sync) { + do_adb_reset_bus(); + return 0; + } + + down(&adb_probe_mutex); + schedule_work(&adb_reset_work); + return 0; +} + +#ifdef CONFIG_PM +/* + * notify clients before sleep + */ +static int __adb_suspend(struct platform_device *dev, pm_message_t state) +{ + adb_got_sleep = 1; + /* We need to get a lock on the probe thread */ + down(&adb_probe_mutex); + /* Stop autopoll */ + if (adb_controller->autopoll) + adb_controller->autopoll(0); + blocking_notifier_call_chain(&adb_client_list, ADB_MSG_POWERDOWN, NULL); + + return 0; +} + +static int adb_suspend(struct device *dev) +{ + return __adb_suspend(to_platform_device(dev), PMSG_SUSPEND); +} + +static int adb_freeze(struct device *dev) +{ + return __adb_suspend(to_platform_device(dev), PMSG_FREEZE); +} + +static int adb_poweroff(struct device *dev) +{ + return __adb_suspend(to_platform_device(dev), PMSG_HIBERNATE); +} + +/* + * reset bus after sleep + */ +static int __adb_resume(struct platform_device *dev) +{ + adb_got_sleep = 0; + up(&adb_probe_mutex); + adb_reset_bus(); + + return 0; +} + +static int adb_resume(struct device *dev) +{ + return __adb_resume(to_platform_device(dev)); +} +#endif /* CONFIG_PM */ + +static int __init adb_init(void) +{ + struct adb_driver *driver; + int i; + +#ifdef CONFIG_PPC32 + if (!machine_is(chrp) && !machine_is(powermac)) + return 0; +#endif +#ifdef CONFIG_MAC + if (!MACH_IS_MAC) + return 0; +#endif + + /* xmon may do early-init */ + if (adb_inited) + return 0; + adb_inited = 1; + + adb_controller = NULL; + + i = 0; + while ((driver = adb_driver_list[i++]) != NULL) { + if (!driver->probe()) { + adb_controller = driver; + break; + } + } + if (adb_controller != NULL && adb_controller->init && + adb_controller->init()) + adb_controller = NULL; + if (adb_controller == NULL) { + pr_warn("Warning: no ADB interface detected\n"); + } else { +#ifdef CONFIG_PPC + if (of_machine_is_compatible("AAPL,PowerBook1998") || + of_machine_is_compatible("PowerBook1,1")) + sleepy_trackpad = 1; +#endif /* CONFIG_PPC */ + + adbdev_init(); + adb_reset_bus(); + } + return 0; +} + +device_initcall(adb_init); + +static int +do_adb_reset_bus(void) +{ + int ret; + + if (adb_controller == NULL) + return -ENXIO; + + if (adb_controller->autopoll) + adb_controller->autopoll(0); + + blocking_notifier_call_chain(&adb_client_list, + ADB_MSG_PRE_RESET, NULL); + + if (sleepy_trackpad) { + /* Let the trackpad settle down */ + msleep(500); + } + + mutex_lock(&adb_handler_mutex); + write_lock_irq(&adb_handler_lock); + memset(adb_handler, 0, sizeof(adb_handler)); + write_unlock_irq(&adb_handler_lock); + + /* That one is still a bit synchronous, oh well... */ + if (adb_controller->reset_bus) + ret = adb_controller->reset_bus(); + else + ret = 0; + + if (sleepy_trackpad) { + /* Let the trackpad settle down */ + msleep(1500); + } + + if (!ret) { + autopoll_devs = adb_scan_bus(); + if (adb_controller->autopoll) + adb_controller->autopoll(autopoll_devs); + } + mutex_unlock(&adb_handler_mutex); + + blocking_notifier_call_chain(&adb_client_list, + ADB_MSG_POST_RESET, NULL); + + return ret; +} + +void +adb_poll(void) +{ + if ((adb_controller == NULL)||(adb_controller->poll == NULL)) + return; + adb_controller->poll(); +} +EXPORT_SYMBOL(adb_poll); + +static void adb_sync_req_done(struct adb_request *req) +{ + struct completion *comp = req->arg; + + complete(comp); +} + +int +adb_request(struct adb_request *req, void (*done)(struct adb_request *), + int flags, int nbytes, ...) +{ + va_list list; + int i; + int rc; + struct completion comp; + + if ((adb_controller == NULL) || (adb_controller->send_request == NULL)) + return -ENXIO; + if (nbytes < 1) + return -EINVAL; + + req->nbytes = nbytes+1; + req->done = done; + req->reply_expected = flags & ADBREQ_REPLY; + req->data[0] = ADB_PACKET; + va_start(list, nbytes); + for (i = 0; i < nbytes; ++i) + req->data[i+1] = va_arg(list, int); + va_end(list); + + if (flags & ADBREQ_NOSEND) + return 0; + + /* Synchronous requests block using an on-stack completion */ + if (flags & ADBREQ_SYNC) { + WARN_ON(done); + req->done = adb_sync_req_done; + req->arg = ∁ + init_completion(&comp); + } + + rc = adb_controller->send_request(req, 0); + + if ((flags & ADBREQ_SYNC) && !rc && !req->complete) + wait_for_completion(&comp); + + return rc; +} +EXPORT_SYMBOL(adb_request); + + /* Ultimately this should return the number of devices with + the given default id. + And it does it now ! Note: changed behaviour: This function + will now register if default_id _and_ handler_id both match + but handler_id can be left to 0 to match with default_id only. + When handler_id is set, this function will try to adjust + the handler_id id it doesn't match. */ +int +adb_register(int default_id, int handler_id, struct adb_ids *ids, + void (*handler)(unsigned char *, int, int)) +{ + int i; + + mutex_lock(&adb_handler_mutex); + ids->nids = 0; + for (i = 1; i < 16; i++) { + if ((adb_handler[i].original_address == default_id) && + (!handler_id || (handler_id == adb_handler[i].handler_id) || + try_handler_change(i, handler_id))) { + if (adb_handler[i].handler != 0) { + pr_err("Two handlers for ADB device %d\n", + default_id); + continue; + } + write_lock_irq(&adb_handler_lock); + adb_handler[i].handler = handler; + write_unlock_irq(&adb_handler_lock); + ids->id[ids->nids++] = i; + } + } + mutex_unlock(&adb_handler_mutex); + return ids->nids; +} +EXPORT_SYMBOL(adb_register); + +int +adb_unregister(int index) +{ + int ret = -ENODEV; + + mutex_lock(&adb_handler_mutex); + write_lock_irq(&adb_handler_lock); + if (adb_handler[index].handler) { + while(adb_handler[index].busy) { + write_unlock_irq(&adb_handler_lock); + yield(); + write_lock_irq(&adb_handler_lock); + } + ret = 0; + adb_handler[index].handler = NULL; + } + write_unlock_irq(&adb_handler_lock); + mutex_unlock(&adb_handler_mutex); + return ret; +} +EXPORT_SYMBOL(adb_unregister); + +void +adb_input(unsigned char *buf, int nb, int autopoll) +{ + int i, id; + static int dump_adb_input; + unsigned long flags; + + void (*handler)(unsigned char *, int, int); + + /* We skip keystrokes and mouse moves when the sleep process + * has been started. We stop autopoll, but this is another security + */ + if (adb_got_sleep) + return; + + id = buf[0] >> 4; + if (dump_adb_input) { + pr_info("adb packet: "); + for (i = 0; i < nb; ++i) + pr_cont(" %x", buf[i]); + pr_cont(", id = %d\n", id); + } + write_lock_irqsave(&adb_handler_lock, flags); + handler = adb_handler[id].handler; + if (handler != NULL) + adb_handler[id].busy = 1; + write_unlock_irqrestore(&adb_handler_lock, flags); + if (handler != NULL) { + (*handler)(buf, nb, autopoll); + wmb(); + adb_handler[id].busy = 0; + } + +} + +/* Try to change handler to new_id. Will return 1 if successful. */ +static int try_handler_change(int address, int new_id) +{ + struct adb_request req; + + if (adb_handler[address].handler_id == new_id) + return 1; + adb_request(&req, NULL, ADBREQ_SYNC, 3, + ADB_WRITEREG(address, 3), address | 0x20, new_id); + adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1, + ADB_READREG(address, 3)); + if (req.reply_len < 2) + return 0; + if (req.reply[2] != new_id) + return 0; + adb_handler[address].handler_id = req.reply[2]; + + return 1; +} + +int +adb_try_handler_change(int address, int new_id) +{ + int ret; + + mutex_lock(&adb_handler_mutex); + ret = try_handler_change(address, new_id); + mutex_unlock(&adb_handler_mutex); + if (ret) + pr_debug("adb handler change: [%d] 0x%X\n", address, new_id); + return ret; +} +EXPORT_SYMBOL(adb_try_handler_change); + +int +adb_get_infos(int address, int *original_address, int *handler_id) +{ + mutex_lock(&adb_handler_mutex); + *original_address = adb_handler[address].original_address; + *handler_id = adb_handler[address].handler_id; + mutex_unlock(&adb_handler_mutex); + + return (*original_address != 0); +} + + +/* + * /dev/adb device driver. + */ + +#define ADB_MAJOR 56 /* major number for /dev/adb */ + +struct adbdev_state { + spinlock_t lock; + atomic_t n_pending; + struct adb_request *completed; + wait_queue_head_t wait_queue; + int inuse; +}; + +static void adb_write_done(struct adb_request *req) +{ + struct adbdev_state *state = (struct adbdev_state *) req->arg; + unsigned long flags; + + if (!req->complete) { + req->reply_len = 0; + req->complete = 1; + } + spin_lock_irqsave(&state->lock, flags); + atomic_dec(&state->n_pending); + if (!state->inuse) { + kfree(req); + if (atomic_read(&state->n_pending) == 0) { + spin_unlock_irqrestore(&state->lock, flags); + kfree(state); + return; + } + } else { + struct adb_request **ap = &state->completed; + while (*ap != NULL) + ap = &(*ap)->next; + req->next = NULL; + *ap = req; + wake_up_interruptible(&state->wait_queue); + } + spin_unlock_irqrestore(&state->lock, flags); +} + +static int +do_adb_query(struct adb_request *req) +{ + int ret = -EINVAL; + + switch(req->data[1]) { + case ADB_QUERY_GETDEVINFO: + if (req->nbytes < 3 || req->data[2] >= 16) + break; + mutex_lock(&adb_handler_mutex); + req->reply[0] = adb_handler[req->data[2]].original_address; + req->reply[1] = adb_handler[req->data[2]].handler_id; + mutex_unlock(&adb_handler_mutex); + req->complete = 1; + req->reply_len = 2; + adb_write_done(req); + ret = 0; + break; + } + return ret; +} + +static int adb_open(struct inode *inode, struct file *file) +{ + struct adbdev_state *state; + int ret = 0; + + mutex_lock(&adb_mutex); + if (iminor(inode) > 0 || adb_controller == NULL) { + ret = -ENXIO; + goto out; + } + state = kmalloc(sizeof(struct adbdev_state), GFP_KERNEL); + if (state == 0) { + ret = -ENOMEM; + goto out; + } + file->private_data = state; + spin_lock_init(&state->lock); + atomic_set(&state->n_pending, 0); + state->completed = NULL; + init_waitqueue_head(&state->wait_queue); + state->inuse = 1; + +out: + mutex_unlock(&adb_mutex); + return ret; +} + +static int adb_release(struct inode *inode, struct file *file) +{ + struct adbdev_state *state = file->private_data; + unsigned long flags; + + mutex_lock(&adb_mutex); + 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); + } + } + mutex_unlock(&adb_mutex); + 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; + DECLARE_WAITQUEUE(wait, current); + unsigned long flags; + + if (count < 2) + return -EINVAL; + if (count > sizeof(req->reply)) + count = sizeof(req->reply); + + req = NULL; + spin_lock_irqsave(&state->lock, flags); + add_wait_queue(&state->wait_queue, &wait); + set_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); + } + + set_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; + + req = kmalloc(sizeof(struct adb_request), + GFP_KERNEL); + if (req == NULL) + return -ENOMEM; + + req->nbytes = count; + req->done = adb_write_done; + req->arg = (void *) state; + req->complete = 0; + + ret = -EFAULT; + if (copy_from_user(req->data, buf, count)) + goto out; + + atomic_inc(&state->n_pending); + + /* If a probe is in progress or we are sleeping, wait for it to complete */ + down(&adb_probe_mutex); + + /* Queries are special requests sent to the ADB driver itself */ + if (req->data[0] == ADB_QUERY) { + if (count > 1) + ret = do_adb_query(req); + else + ret = -EINVAL; + up(&adb_probe_mutex); + } + /* Special case for ADB_BUSRESET request, all others are sent to + the controller */ + else if ((req->data[0] == ADB_PACKET) && (count > 1) + && (req->data[1] == ADB_BUSRESET)) { + ret = do_adb_reset_bus(); + up(&adb_probe_mutex); + atomic_dec(&state->n_pending); + if (ret == 0) + ret = count; + goto out; + } else { + req->reply_expected = ((req->data[1] & 0xc) == 0xc); + if (adb_controller && adb_controller->send_request) + ret = adb_controller->send_request(req, 0); + else + ret = -ENXIO; + up(&adb_probe_mutex); + } + + if (ret != 0) { + atomic_dec(&state->n_pending); + goto out; + } + return count; + +out: + kfree(req); + return ret; +} + +static const struct file_operations adb_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = adb_read, + .write = adb_write, + .open = adb_open, + .release = adb_release, +}; + +#ifdef CONFIG_PM +static const struct dev_pm_ops adb_dev_pm_ops = { + .suspend = adb_suspend, + .resume = adb_resume, + /* Hibernate hooks */ + .freeze = adb_freeze, + .thaw = adb_resume, + .poweroff = adb_poweroff, + .restore = adb_resume, +}; +#endif + +static struct platform_driver adb_pfdrv = { + .driver = { + .name = "adb", +#ifdef CONFIG_PM + .pm = &adb_dev_pm_ops, +#endif + }, +}; + +static struct platform_device adb_pfdev = { + .name = "adb", +}; + +static int __init +adb_dummy_probe(struct platform_device *dev) +{ + if (dev == &adb_pfdev) + return 0; + return -ENODEV; +} + +static void __init +adbdev_init(void) +{ + if (register_chrdev(ADB_MAJOR, "adb", &adb_fops)) { + pr_err("adb: unable to get major %d\n", ADB_MAJOR); + return; + } + + adb_dev_class = class_create(THIS_MODULE, "adb"); + if (IS_ERR(adb_dev_class)) + return; + device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb"); + + platform_device_register(&adb_pfdev); + platform_driver_probe(&adb_pfdrv, adb_dummy_probe); +} diff --git a/drivers/macintosh/adbhid.c b/drivers/macintosh/adbhid.c new file mode 100644 index 000000000..b2fe7a3dc --- /dev/null +++ b/drivers/macintosh/adbhid.c @@ -0,0 +1,1273 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/macintosh/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/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/backlight.h> +#include <asm/pmac_feature.h> +#endif + +MODULE_AUTHOR("Franz Sirl <Franz.Sirl-kernel@lauterbach.com>"); + +static int restore_capslock_events; +module_param(restore_capslock_events, int, 0644); +MODULE_PARM_DESC(restore_capslock_events, + "Produce keypress events for capslock on both keyup and keydown."); + +#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 + +static const u16 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 */ KEY_FN, /* 0x1d0 */ + /* 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_HANGEUL, /* 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; + u16 *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 +#define FLAG_CAPSLOCK_TRANSLATE 0x00000008 +#define FLAG_CAPSLOCK_DOWN 0x00000010 +#define FLAG_CAPSLOCK_IGNORE_NEXT 0x00000020 +#define FLAG_POWER_KEY_PRESSED 0x00000040 + +static struct adbhid *adbhid[16]; + +static void adbhid_probe(void); + +static void adbhid_input_keycode(int, int, int); + +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; + +/* 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, int apoll) +{ + int id = (data[0] >> 4) & 0x0f; + + if (!adbhid[id]) { + pr_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); + if (!(data[2] == 0xff || (data[2] == 0x7f && data[1] == 0x7f))) + adbhid_input_keycode(id, data[2], 0); +} + +static void +adbhid_input_keycode(int id, int scancode, int repeat) +{ + struct adbhid *ahid = adbhid[id]; + int keycode, up_flag, key; + + keycode = scancode & 0x7f; + up_flag = scancode & 0x80; + + if (restore_capslock_events) { + if (keycode == ADB_KEY_CAPSLOCK && !up_flag) { + /* Key pressed, turning on the CapsLock LED. + * The next 0xff will be interpreted as a release. */ + if (ahid->flags & FLAG_CAPSLOCK_IGNORE_NEXT) { + /* Throw away this key event if it happens + * just after resume. */ + ahid->flags &= ~FLAG_CAPSLOCK_IGNORE_NEXT; + return; + } else { + ahid->flags |= FLAG_CAPSLOCK_TRANSLATE + | FLAG_CAPSLOCK_DOWN; + } + } else if (scancode == 0xff && + !(ahid->flags & FLAG_POWER_KEY_PRESSED)) { + /* Scancode 0xff usually signifies that the capslock + * key was either pressed or released, or that the + * power button was released. */ + if (ahid->flags & FLAG_CAPSLOCK_TRANSLATE) { + keycode = ADB_KEY_CAPSLOCK; + if (ahid->flags & FLAG_CAPSLOCK_DOWN) { + /* Key released */ + up_flag = 1; + ahid->flags &= ~FLAG_CAPSLOCK_DOWN; + } else { + /* Key pressed */ + up_flag = 0; + ahid->flags &= ~FLAG_CAPSLOCK_TRANSLATE; + } + } else { + pr_info("Spurious caps lock event (scancode 0xff).\n"); + } + } + } + + switch (keycode) { + case ADB_KEY_CAPSLOCK: + if (!restore_capslock_events) { + /* Generate down/up events for CapsLock every time. */ + input_report_key(ahid->input, KEY_CAPSLOCK, 1); + input_sync(ahid->input); + input_report_key(ahid->input, KEY_CAPSLOCK, 0); + input_sync(ahid->input); + return; + } + break; +#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: + /* Keep track of the power key state */ + if (up_flag) + ahid->flags &= ~FLAG_POWER_KEY_PRESSED; + else + ahid->flags |= FLAG_POWER_KEY_PRESSED; + + /* 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; + break; + 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 */ + } + + key = adbhid[id]->keycode[keycode]; + if (key) { + input_report_key(adbhid[id]->input, key, !up_flag); + input_sync(adbhid[id]->input); + } else + pr_info("Unhandled ADB key (scancode %#02x) %s.\n", keycode, + up_flag ? "released" : "pressed"); + +} + +static void +adbhid_mouse_input(unsigned char *data, int nb, int autopoll) +{ + int id = (data[0] >> 4) & 0x0f; + + if (!adbhid[id]) { + pr_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_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, int autopoll) +{ + int id = (data[0] >> 4) & 0x0f; + + if (!adbhid[id]) { + pr_err("ADB HID on ID %d not yet registered\n", id); + return; + } + + 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: + pr_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)); + + /* + * 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 (down) + pmac_backlight_key_down(); +#endif + input_report_key(adbhid[id]->input, KEY_BRIGHTNESSDOWN, down); + break; + + case 0x9: /* brightness increase */ +#ifdef CONFIG_PMAC_BACKLIGHT + if (down) + pmac_backlight_key_up(); +#endif + input_report_key(adbhid[id]->input, KEY_BRIGHTNESSUP, down); + break; + + case 0xc: /* videomode switch */ + input_report_key(adbhid[id]->input, KEY_SWITCHVIDEOMODE, down); + break; + + case 0xd: /* keyboard illumination toggle */ + input_report_key(adbhid[id]->input, KEY_KBDILLUMTOGGLE, down); + break; + + case 0xe: /* keyboard illumination decrease */ + input_report_key(adbhid[id]->input, KEY_KBDILLUMDOWN, down); + break; + + case 0xf: + switch (data[1]) { + case 0x8f: + case 0x0f: + /* keyboard illumination increase */ + input_report_key(adbhid[id]->input, KEY_KBDILLUMUP, down); + break; + + case 0x7f: + case 0xff: + /* keypad overlay toogle */ + break; + + default: + pr_info("Unhandled ADB_MISC event %02x, %02x, %02x, %02x\n", + data[0], data[1], data[2], data[3]); + break; + } + break; + default: + pr_info("Unhandled ADB_MISC event %02x, %02x, %02x, %02x\n", + data[0], data[1], data[2], data[3]); + 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; +static int pending_led_end; +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 = input_get_drvdata(dev); + 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 void +adbhid_kbd_capslock_remember(void) +{ + struct adbhid *ahid; + int i; + + for (i = 1; i < 16; i++) { + ahid = adbhid[i]; + + if (ahid && ahid->id == ADB_KEYBOARD) + if (ahid->flags & FLAG_CAPSLOCK_TRANSLATE) + ahid->flags |= FLAG_CAPSLOCK_IGNORE_NEXT; + } +} + +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(); + + /* After resume, and if the capslock LED is on, the PMU will + * send a "capslock down" key event. This confuses the + * restore_capslock_events logic. Remember if the capslock + * LED was on before suspend so the unwanted key event can + * be ignored after resume. */ + if (restore_capslock_events) + adbhid_kbd_capslock_remember(); + + break; + + case ADB_MSG_POST_RESET: + adbhid_probe(); + break; + } + return NOTIFY_DONE; +} + +static int +adbhid_input_register(int id, int default_id, int original_handler_id, + int current_handler_id, int mouse_kind) +{ + struct adbhid *hid; + struct input_dev *input_dev; + int err; + int i; + char *keyboard_type; + + if (adbhid[id]) { + pr_err("Trying to reregister ADB HID on ID %d\n", id); + return -EEXIST; + } + + adbhid[id] = hid = kzalloc(sizeof(struct adbhid), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!hid || !input_dev) { + err = -ENOMEM; + goto fail; + } + + sprintf(hid->phys, "adb%d:%d.%02x/input", id, default_id, original_handler_id); + + hid->input = input_dev; + hid->id = default_id; + hid->original_handler_id = original_handler_id; + hid->current_handler_id = current_handler_id; + hid->mouse_kind = mouse_kind; + hid->flags = 0; + input_set_drvdata(input_dev, hid); + input_dev->name = hid->name; + input_dev->phys = hid->phys; + input_dev->id.bustype = BUS_ADB; + input_dev->id.vendor = 0x0001; + input_dev->id.product = (id << 12) | (default_id << 8) | original_handler_id; + input_dev->id.version = 0x0100; + + switch (default_id) { + case ADB_KEYBOARD: + hid->keycode = kmemdup(adb_to_linux_keycodes, + sizeof(adb_to_linux_keycodes), GFP_KERNEL); + if (!hid->keycode) { + err = -ENOMEM; + goto fail; + } + + sprintf(hid->name, "ADB keyboard"); + + switch (original_handler_id) { + default: + keyboard_type = "<unknown>"; + input_dev->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: + keyboard_type = "ANSI"; + input_dev->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: + keyboard_type = "ISO, swapping keys"; + input_dev->id.version = ADB_KEYBOARD_ISO; + swap(hid->keycode[10], hid->keycode[50]); + break; + + case 0x12: case 0x15: case 0x16: case 0x17: case 0x1A: + case 0x1E: case 0xC2: case 0xC5: case 0xC8: case 0xC9: + keyboard_type = "JIS"; + input_dev->id.version = ADB_KEYBOARD_JIS; + break; + } + pr_info("Detected ADB keyboard, type %s.\n", keyboard_type); + + for (i = 0; i < 128; i++) + if (hid->keycode[i]) + set_bit(hid->keycode[i], input_dev->keybit); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) | + BIT_MASK(EV_REP); + input_dev->ledbit[0] = BIT_MASK(LED_SCROLLL) | + BIT_MASK(LED_CAPSL) | BIT_MASK(LED_NUML); + input_dev->event = adbhid_kbd_event; + input_dev->keycodemax = KEY_FN; + input_dev->keycodesize = sizeof(hid->keycode[0]); + break; + + case ADB_MOUSE: + sprintf(hid->name, "ADB mouse"); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + break; + + case ADB_MISC: + switch (original_handler_id) { + case 0x02: /* Adjustable keyboard button device */ + sprintf(hid->name, "ADB adjustable keyboard buttons"); + input_dev->evbit[0] = BIT_MASK(EV_KEY) | + BIT_MASK(EV_REP); + set_bit(KEY_SOUND, input_dev->keybit); + set_bit(KEY_MUTE, input_dev->keybit); + set_bit(KEY_VOLUMEUP, input_dev->keybit); + set_bit(KEY_VOLUMEDOWN, input_dev->keybit); + break; + case 0x1f: /* Powerbook button device */ + sprintf(hid->name, "ADB Powerbook buttons"); + input_dev->evbit[0] = BIT_MASK(EV_KEY) | + BIT_MASK(EV_REP); + set_bit(KEY_MUTE, input_dev->keybit); + set_bit(KEY_VOLUMEUP, input_dev->keybit); + set_bit(KEY_VOLUMEDOWN, input_dev->keybit); + set_bit(KEY_BRIGHTNESSUP, input_dev->keybit); + set_bit(KEY_BRIGHTNESSDOWN, input_dev->keybit); + set_bit(KEY_EJECTCD, input_dev->keybit); + set_bit(KEY_SWITCHVIDEOMODE, input_dev->keybit); + set_bit(KEY_KBDILLUMTOGGLE, input_dev->keybit); + set_bit(KEY_KBDILLUMDOWN, input_dev->keybit); + set_bit(KEY_KBDILLUMUP, input_dev->keybit); + break; + } + if (hid->name[0]) + break; + fallthrough; + + default: + pr_info("Trying to register unknown ADB device to input layer.\n"); + err = -ENODEV; + goto fail; + } + + input_dev->keycode = hid->keycode; + + err = input_register_device(input_dev); + if (err) + goto fail; + + if (default_id == ADB_KEYBOARD) { + /* HACK WARNING!! This should go away as soon there is an utility + * to control that for event devices. + */ + input_dev->rep[REP_DELAY] = 500; /* input layer default: 250 */ + input_dev->rep[REP_PERIOD] = 66; /* input layer default: 33 */ + } + + return 0; + + fail: input_free_device(input_dev); + if (hid) { + kfree(hid->keycode); + kfree(hid); + } + adbhid[id] = NULL; + return err; +} + +static void adbhid_input_unregister(int id) +{ + input_unregister_device(adbhid[id]->input); + 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)) +#endif + adb_try_handler_change(id, 3); + + adb_get_infos(id, &default_id, &cur_handler_id); + printk(KERN_DEBUG "ADB keyboard at %d has handler 0x%X\n", + 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; + char *desc = "standard"; + + adb_get_infos(id, &default_id, &org_handler_id); + + if (adb_try_handler_change(id, 4)) { + mouse_kind = ADBMOUSE_EXTENDED; + } + else if (adb_try_handler_change(id, 0x2F)) { + mouse_kind = ADBMOUSE_MICROSPEED; + } + else if (adb_try_handler_change(id, 0x42)) { + mouse_kind = ADBMOUSE_TRACKBALLPRO; + } + else if (adb_try_handler_change(id, 0x66)) { + mouse_kind = ADBMOUSE_MICROSPEED; + } + else if (adb_try_handler_change(id, 0x5F)) { + mouse_kind = ADBMOUSE_MICROSPEED; + } + else if (adb_try_handler_change(id, 3)) { + mouse_kind = ADBMOUSE_MS_A3; + } + else if (adb_try_handler_change(id, 2)) { + mouse_kind = ADBMOUSE_STANDARD_200; + } + else { + mouse_kind = ADBMOUSE_STANDARD_100; + } + + if ((mouse_kind == ADBMOUSE_TRACKBALLPRO) + || (mouse_kind == ADBMOUSE_MICROSPEED)) { + desc = "Microspeed/MacPoint or compatible"; + init_microspeed(id); + } else if (mouse_kind == ADBMOUSE_MS_A3) { + desc = "Mouse Systems A3 Mouse or compatible"; + init_ms_a3(id); + } else if (mouse_kind == ADBMOUSE_EXTENDED) { + desc = "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; + desc = "trackman/mouseman"; + 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; + desc = "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; + desc = "TurboMouse 5"; + 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)) { + mouse_kind = ADBMOUSE_MACALLY2; + desc = "MacAlly 2-button"; + } + } + } + + adb_get_infos(id, &default_id, &cur_handler_id); + printk(KERN_DEBUG "ADB mouse (%s) at %d has handler 0x%X\n", + desc, 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]; + + adb_request(&req, NULL, ADBREQ_SYNC | ADBREQ_REPLY, 1, + ADB_READREG(id,1)); + if (req.reply_len < 8) + pr_err("%s: bad length for reg. 1\n", __func__); + 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; + + 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; + + 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; + + 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; + + 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_is(chrp) && !machine_is(powermac)) + return 0; +#endif + + led_request.complete = 1; + + adbhid_probe(); + + blocking_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/ams/Makefile b/drivers/macintosh/ams/Makefile new file mode 100644 index 000000000..3b58d02c9 --- /dev/null +++ b/drivers/macintosh/ams/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for Apple Motion Sensor driver +# + +ams-y := ams-core.o ams-input.o +ams-$(CONFIG_SENSORS_AMS_PMU) += ams-pmu.o +ams-$(CONFIG_SENSORS_AMS_I2C) += ams-i2c.o +obj-$(CONFIG_SENSORS_AMS) += ams.o diff --git a/drivers/macintosh/ams/ams-core.c b/drivers/macintosh/ams/ams-core.c new file mode 100644 index 000000000..877e8cb23 --- /dev/null +++ b/drivers/macintosh/ams/ams-core.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Apple Motion Sensor driver + * + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/of_platform.h> +#include <asm/pmac_pfunc.h> + +#include "ams.h" + +/* There is only one motion sensor per machine */ +struct ams ams_info; + +static bool verbose; +module_param(verbose, bool, 0644); +MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output"); + +/* Call with ams_info.lock held! */ +void ams_sensors(s8 *x, s8 *y, s8 *z) +{ + u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2; + + if (orient & 0x80) + /* X and Y swapped */ + ams_info.get_xyz(y, x, z); + else + ams_info.get_xyz(x, y, z); + + if (orient & 0x04) + *z = ~(*z); + if (orient & 0x02) + *y = ~(*y); + if (orient & 0x01) + *x = ~(*x); +} + +static ssize_t ams_show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + s8 x, y, z; + + mutex_lock(&ams_info.lock); + ams_sensors(&x, &y, &z); + mutex_unlock(&ams_info.lock); + + return sysfs_emit(buf, "%d %d %d\n", x, y, z); +} + +static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL); + +static void ams_handle_irq(void *data) +{ + enum ams_irq irq = *((enum ams_irq *)data); + + spin_lock(&ams_info.irq_lock); + + ams_info.worker_irqs |= irq; + schedule_work(&ams_info.worker); + + spin_unlock(&ams_info.irq_lock); +} + +static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL; +static struct pmf_irq_client ams_freefall_client = { + .owner = THIS_MODULE, + .handler = ams_handle_irq, + .data = &ams_freefall_irq_data, +}; + +static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK; +static struct pmf_irq_client ams_shock_client = { + .owner = THIS_MODULE, + .handler = ams_handle_irq, + .data = &ams_shock_irq_data, +}; + +/* Once hard disk parking is implemented in the kernel, this function can + * trigger it. + */ +static void ams_worker(struct work_struct *work) +{ + unsigned long flags; + u8 irqs_to_clear; + + mutex_lock(&ams_info.lock); + + spin_lock_irqsave(&ams_info.irq_lock, flags); + irqs_to_clear = ams_info.worker_irqs; + + if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) { + if (verbose) + printk(KERN_INFO "ams: freefall detected!\n"); + + ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL; + } + + if (ams_info.worker_irqs & AMS_IRQ_SHOCK) { + if (verbose) + printk(KERN_INFO "ams: shock detected!\n"); + + ams_info.worker_irqs &= ~AMS_IRQ_SHOCK; + } + + spin_unlock_irqrestore(&ams_info.irq_lock, flags); + + ams_info.clear_irq(irqs_to_clear); + + mutex_unlock(&ams_info.lock); +} + +/* Call with ams_info.lock held! */ +int ams_sensor_attach(void) +{ + int result; + const u32 *prop; + + /* Get orientation */ + prop = of_get_property(ams_info.of_node, "orientation", NULL); + if (!prop) + return -ENODEV; + ams_info.orient1 = *prop; + ams_info.orient2 = *(prop + 1); + + /* Register freefall interrupt handler */ + result = pmf_register_irq_client(ams_info.of_node, + "accel-int-1", + &ams_freefall_client); + if (result < 0) + return -ENODEV; + + /* Reset saved irqs */ + ams_info.worker_irqs = 0; + + /* Register shock interrupt handler */ + result = pmf_register_irq_client(ams_info.of_node, + "accel-int-2", + &ams_shock_client); + if (result < 0) + goto release_freefall; + + /* Create device */ + ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL); + if (!ams_info.of_dev) { + result = -ENODEV; + goto release_shock; + } + + /* Create attributes */ + result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current); + if (result) + goto release_of; + + ams_info.vflag = !!(ams_info.get_vendor() & 0x10); + + /* Init input device */ + result = ams_input_init(); + if (result) + goto release_device_file; + + return result; +release_device_file: + device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); +release_of: + of_device_unregister(ams_info.of_dev); +release_shock: + pmf_unregister_irq_client(&ams_shock_client); +release_freefall: + pmf_unregister_irq_client(&ams_freefall_client); + return result; +} + +int __init ams_init(void) +{ + struct device_node *np; + + spin_lock_init(&ams_info.irq_lock); + mutex_init(&ams_info.lock); + INIT_WORK(&ams_info.worker, ams_worker); + +#ifdef CONFIG_SENSORS_AMS_I2C + np = of_find_node_by_name(NULL, "accelerometer"); + if (np && of_device_is_compatible(np, "AAPL,accelerometer_1")) + /* Found I2C motion sensor */ + return ams_i2c_init(np); +#endif + +#ifdef CONFIG_SENSORS_AMS_PMU + np = of_find_node_by_name(NULL, "sms"); + if (np && of_device_is_compatible(np, "sms")) + /* Found PMU motion sensor */ + return ams_pmu_init(np); +#endif + return -ENODEV; +} + +void ams_sensor_detach(void) +{ + /* Remove input device */ + ams_input_exit(); + + /* Remove attributes */ + device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); + + /* Flush interrupt worker + * + * We do this after ams_info.exit(), because an interrupt might + * have arrived before disabling them. + */ + flush_work(&ams_info.worker); + + /* Remove device */ + of_device_unregister(ams_info.of_dev); + + /* Remove handler */ + pmf_unregister_irq_client(&ams_shock_client); + pmf_unregister_irq_client(&ams_freefall_client); +} + +static void __exit ams_exit(void) +{ + /* Shut down implementation */ + ams_info.exit(); +} + +MODULE_AUTHOR("Stelian Pop, Michael Hanselmann"); +MODULE_DESCRIPTION("Apple Motion Sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(ams_init); +module_exit(ams_exit); diff --git a/drivers/macintosh/ams/ams-i2c.c b/drivers/macintosh/ams/ams-i2c.c new file mode 100644 index 000000000..3ded34069 --- /dev/null +++ b/drivers/macintosh/ams/ams-i2c.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Apple Motion Sensor driver (I2C variant) + * + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * Clean room implementation based on the reverse engineered Mac OS X driver by + * Johannes Berg <johannes@sipsolutions.net>, documentation available at + * http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include "ams.h" + +/* AMS registers */ +#define AMS_COMMAND 0x00 /* command register */ +#define AMS_STATUS 0x01 /* status register */ +#define AMS_CTRL1 0x02 /* read control 1 (number of values) */ +#define AMS_CTRL2 0x03 /* read control 2 (offset?) */ +#define AMS_CTRL3 0x04 /* read control 3 (size of each value?) */ +#define AMS_DATA1 0x05 /* read data 1 */ +#define AMS_DATA2 0x06 /* read data 2 */ +#define AMS_DATA3 0x07 /* read data 3 */ +#define AMS_DATA4 0x08 /* read data 4 */ +#define AMS_DATAX 0x20 /* data X */ +#define AMS_DATAY 0x21 /* data Y */ +#define AMS_DATAZ 0x22 /* data Z */ +#define AMS_FREEFALL 0x24 /* freefall int control */ +#define AMS_SHOCK 0x25 /* shock int control */ +#define AMS_SENSLOW 0x26 /* sensitivity low limit */ +#define AMS_SENSHIGH 0x27 /* sensitivity high limit */ +#define AMS_CTRLX 0x28 /* control X */ +#define AMS_CTRLY 0x29 /* control Y */ +#define AMS_CTRLZ 0x2A /* control Z */ +#define AMS_UNKNOWN1 0x2B /* unknown 1 */ +#define AMS_UNKNOWN2 0x2C /* unknown 2 */ +#define AMS_UNKNOWN3 0x2D /* unknown 3 */ +#define AMS_VENDOR 0x2E /* vendor */ + +/* AMS commands - use with the AMS_COMMAND register */ +enum ams_i2c_cmd { + AMS_CMD_NOOP = 0, + AMS_CMD_VERSION, + AMS_CMD_READMEM, + AMS_CMD_WRITEMEM, + AMS_CMD_ERASEMEM, + AMS_CMD_READEE, + AMS_CMD_WRITEEE, + AMS_CMD_RESET, + AMS_CMD_START, +}; + +static int ams_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static void ams_i2c_remove(struct i2c_client *client); + +static const struct i2c_device_id ams_id[] = { + { "MAC,accelerometer_1", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ams_id); + +static struct i2c_driver ams_i2c_driver = { + .driver = { + .name = "ams", + }, + .probe = ams_i2c_probe, + .remove = ams_i2c_remove, + .id_table = ams_id, +}; + +static s32 ams_i2c_read(u8 reg) +{ + return i2c_smbus_read_byte_data(ams_info.i2c_client, reg); +} + +static int ams_i2c_write(u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(ams_info.i2c_client, reg, value); +} + +static int ams_i2c_cmd(enum ams_i2c_cmd cmd) +{ + s32 result; + int count = 3; + + ams_i2c_write(AMS_COMMAND, cmd); + msleep(5); + + while (count--) { + result = ams_i2c_read(AMS_COMMAND); + if (result == 0 || result & 0x80) + return 0; + + schedule_timeout_uninterruptible(HZ / 20); + } + + return -1; +} + +static void ams_i2c_set_irq(enum ams_irq reg, char enable) +{ + if (reg & AMS_IRQ_FREEFALL) { + u8 val = ams_i2c_read(AMS_CTRLX); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_i2c_write(AMS_CTRLX, val); + } + + if (reg & AMS_IRQ_SHOCK) { + u8 val = ams_i2c_read(AMS_CTRLY); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_i2c_write(AMS_CTRLY, val); + } + + if (reg & AMS_IRQ_GLOBAL) { + u8 val = ams_i2c_read(AMS_CTRLZ); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_i2c_write(AMS_CTRLZ, val); + } +} + +static void ams_i2c_clear_irq(enum ams_irq reg) +{ + if (reg & AMS_IRQ_FREEFALL) + ams_i2c_write(AMS_FREEFALL, 0); + + if (reg & AMS_IRQ_SHOCK) + ams_i2c_write(AMS_SHOCK, 0); +} + +static u8 ams_i2c_get_vendor(void) +{ + return ams_i2c_read(AMS_VENDOR); +} + +static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z) +{ + *x = ams_i2c_read(AMS_DATAX); + *y = ams_i2c_read(AMS_DATAY); + *z = ams_i2c_read(AMS_DATAZ); +} + +static int ams_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int vmaj, vmin; + int result; + + /* There can be only one */ + if (unlikely(ams_info.has_device)) + return -ENODEV; + + ams_info.i2c_client = client; + + if (ams_i2c_cmd(AMS_CMD_RESET)) { + printk(KERN_INFO "ams: Failed to reset the device\n"); + return -ENODEV; + } + + if (ams_i2c_cmd(AMS_CMD_START)) { + printk(KERN_INFO "ams: Failed to start the device\n"); + return -ENODEV; + } + + /* get version/vendor information */ + ams_i2c_write(AMS_CTRL1, 0x02); + ams_i2c_write(AMS_CTRL2, 0x85); + ams_i2c_write(AMS_CTRL3, 0x01); + + ams_i2c_cmd(AMS_CMD_READMEM); + + vmaj = ams_i2c_read(AMS_DATA1); + vmin = ams_i2c_read(AMS_DATA2); + if (vmaj != 1 || vmin != 52) { + printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n", + vmaj, vmin); + return -ENODEV; + } + + ams_i2c_cmd(AMS_CMD_VERSION); + + vmaj = ams_i2c_read(AMS_DATA1); + vmin = ams_i2c_read(AMS_DATA2); + if (vmaj != 0 || vmin != 1) { + printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n", + vmaj, vmin); + return -ENODEV; + } + + /* Disable interrupts */ + ams_i2c_set_irq(AMS_IRQ_ALL, 0); + + result = ams_sensor_attach(); + if (result < 0) + return result; + + /* Set default values */ + ams_i2c_write(AMS_SENSLOW, 0x15); + ams_i2c_write(AMS_SENSHIGH, 0x60); + ams_i2c_write(AMS_CTRLX, 0x08); + ams_i2c_write(AMS_CTRLY, 0x0F); + ams_i2c_write(AMS_CTRLZ, 0x4F); + ams_i2c_write(AMS_UNKNOWN1, 0x14); + + /* Clear interrupts */ + ams_i2c_clear_irq(AMS_IRQ_ALL); + + ams_info.has_device = 1; + + /* Enable interrupts */ + ams_i2c_set_irq(AMS_IRQ_ALL, 1); + + printk(KERN_INFO "ams: Found I2C based motion sensor\n"); + + return 0; +} + +static void ams_i2c_remove(struct i2c_client *client) +{ + if (ams_info.has_device) { + ams_sensor_detach(); + + /* Disable interrupts */ + ams_i2c_set_irq(AMS_IRQ_ALL, 0); + + /* Clear interrupts */ + ams_i2c_clear_irq(AMS_IRQ_ALL); + + printk(KERN_INFO "ams: Unloading\n"); + + ams_info.has_device = 0; + } +} + +static void ams_i2c_exit(void) +{ + i2c_del_driver(&ams_i2c_driver); +} + +int __init ams_i2c_init(struct device_node *np) +{ + /* Set implementation stuff */ + ams_info.of_node = np; + ams_info.exit = ams_i2c_exit; + ams_info.get_vendor = ams_i2c_get_vendor; + ams_info.get_xyz = ams_i2c_get_xyz; + ams_info.clear_irq = ams_i2c_clear_irq; + ams_info.bustype = BUS_I2C; + + return i2c_add_driver(&ams_i2c_driver); +} diff --git a/drivers/macintosh/ams/ams-input.c b/drivers/macintosh/ams/ams-input.c new file mode 100644 index 000000000..0da493d44 --- /dev/null +++ b/drivers/macintosh/ams/ams-input.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Apple Motion Sensor driver (joystick emulation) + * + * Copyright (C) 2005 Stelian Pop (stelian@popies.net) + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + */ + +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include "ams.h" + +static bool joystick; +module_param(joystick, bool, S_IRUGO); +MODULE_PARM_DESC(joystick, "Enable the input class device on module load"); + +static bool invert; +module_param(invert, bool, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(invert, "Invert input data on X and Y axis"); + +static DEFINE_MUTEX(ams_input_mutex); + +static void ams_idev_poll(struct input_dev *idev) +{ + s8 x, y, z; + + mutex_lock(&ams_info.lock); + + ams_sensors(&x, &y, &z); + + x -= ams_info.xcalib; + y -= ams_info.ycalib; + z -= ams_info.zcalib; + + input_report_abs(idev, ABS_X, invert ? -x : x); + input_report_abs(idev, ABS_Y, invert ? -y : y); + input_report_abs(idev, ABS_Z, z); + + input_sync(idev); + + mutex_unlock(&ams_info.lock); +} + +/* Call with ams_info.lock held! */ +static int ams_input_enable(void) +{ + struct input_dev *input; + s8 x, y, z; + int error; + + ams_sensors(&x, &y, &z); + ams_info.xcalib = x; + ams_info.ycalib = y; + ams_info.zcalib = z; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + input->name = "Apple Motion Sensor"; + input->id.bustype = ams_info.bustype; + input->id.vendor = 0; + input->dev.parent = &ams_info.of_dev->dev; + + input_set_abs_params(input, ABS_X, -50, 50, 3, 0); + input_set_abs_params(input, ABS_Y, -50, 50, 3, 0); + input_set_abs_params(input, ABS_Z, -50, 50, 3, 0); + input_set_capability(input, EV_KEY, BTN_TOUCH); + + error = input_setup_polling(input, ams_idev_poll); + if (error) + goto err_free_input; + + input_set_poll_interval(input, 25); + + error = input_register_device(input); + if (error) + goto err_free_input; + + ams_info.idev = input; + joystick = true; + + return 0; + +err_free_input: + input_free_device(input); + return error; +} + +static void ams_input_disable(void) +{ + if (ams_info.idev) { + input_unregister_device(ams_info.idev); + ams_info.idev = NULL; + } + + joystick = false; +} + +static ssize_t ams_input_show_joystick(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", joystick); +} + +static ssize_t ams_input_store_joystick(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long enable; + int error = 0; + int ret; + + ret = kstrtoul(buf, 0, &enable); + if (ret) + return ret; + if (enable > 1) + return -EINVAL; + + mutex_lock(&ams_input_mutex); + + if (enable != joystick) { + if (enable) + error = ams_input_enable(); + else + ams_input_disable(); + } + + mutex_unlock(&ams_input_mutex); + + return error ? error : count; +} + +static DEVICE_ATTR(joystick, S_IRUGO | S_IWUSR, + ams_input_show_joystick, ams_input_store_joystick); + +int ams_input_init(void) +{ + if (joystick) + ams_input_enable(); + + return device_create_file(&ams_info.of_dev->dev, &dev_attr_joystick); +} + +void ams_input_exit(void) +{ + device_remove_file(&ams_info.of_dev->dev, &dev_attr_joystick); + + mutex_lock(&ams_input_mutex); + ams_input_disable(); + mutex_unlock(&ams_input_mutex); +} diff --git a/drivers/macintosh/ams/ams-pmu.c b/drivers/macintosh/ams/ams-pmu.c new file mode 100644 index 000000000..1c3ce39e9 --- /dev/null +++ b/drivers/macintosh/ams/ams-pmu.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Apple Motion Sensor driver (PMU variant) + * + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/adb.h> +#include <linux/pmu.h> + +#include "ams.h" + +/* Attitude */ +#define AMS_X 0x00 +#define AMS_Y 0x01 +#define AMS_Z 0x02 + +/* Not exactly known, maybe chip vendor */ +#define AMS_VENDOR 0x03 + +/* Freefall registers */ +#define AMS_FF_CLEAR 0x04 +#define AMS_FF_ENABLE 0x05 +#define AMS_FF_LOW_LIMIT 0x06 +#define AMS_FF_DEBOUNCE 0x07 + +/* Shock registers */ +#define AMS_SHOCK_CLEAR 0x08 +#define AMS_SHOCK_ENABLE 0x09 +#define AMS_SHOCK_HIGH_LIMIT 0x0a +#define AMS_SHOCK_DEBOUNCE 0x0b + +/* Global interrupt and power control register */ +#define AMS_CONTROL 0x0c + +static u8 ams_pmu_cmd; + +static void ams_pmu_req_complete(struct adb_request *req) +{ + complete((struct completion *)req->arg); +} + +/* Only call this function from task context */ +static void ams_pmu_set_register(u8 reg, u8 value) +{ + static struct adb_request req; + DECLARE_COMPLETION(req_complete); + + req.arg = &req_complete; + if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value)) + return; + + wait_for_completion(&req_complete); +} + +/* Only call this function from task context */ +static u8 ams_pmu_get_register(u8 reg) +{ + static struct adb_request req; + DECLARE_COMPLETION(req_complete); + + req.arg = &req_complete; + if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg)) + return 0; + + wait_for_completion(&req_complete); + + if (req.reply_len > 0) + return req.reply[0]; + else + return 0; +} + +/* Enables or disables the specified interrupts */ +static void ams_pmu_set_irq(enum ams_irq reg, char enable) +{ + if (reg & AMS_IRQ_FREEFALL) { + u8 val = ams_pmu_get_register(AMS_FF_ENABLE); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_pmu_set_register(AMS_FF_ENABLE, val); + } + + if (reg & AMS_IRQ_SHOCK) { + u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_pmu_set_register(AMS_SHOCK_ENABLE, val); + } + + if (reg & AMS_IRQ_GLOBAL) { + u8 val = ams_pmu_get_register(AMS_CONTROL); + if (enable) + val |= 0x80; + else + val &= ~0x80; + ams_pmu_set_register(AMS_CONTROL, val); + } +} + +static void ams_pmu_clear_irq(enum ams_irq reg) +{ + if (reg & AMS_IRQ_FREEFALL) + ams_pmu_set_register(AMS_FF_CLEAR, 0x00); + + if (reg & AMS_IRQ_SHOCK) + ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00); +} + +static u8 ams_pmu_get_vendor(void) +{ + return ams_pmu_get_register(AMS_VENDOR); +} + +static void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z) +{ + *x = ams_pmu_get_register(AMS_X); + *y = ams_pmu_get_register(AMS_Y); + *z = ams_pmu_get_register(AMS_Z); +} + +static void ams_pmu_exit(void) +{ + ams_sensor_detach(); + + /* Disable interrupts */ + ams_pmu_set_irq(AMS_IRQ_ALL, 0); + + /* Clear interrupts */ + ams_pmu_clear_irq(AMS_IRQ_ALL); + + ams_info.has_device = 0; + + printk(KERN_INFO "ams: Unloading\n"); +} + +int __init ams_pmu_init(struct device_node *np) +{ + const u32 *prop; + int result; + + /* Set implementation stuff */ + ams_info.of_node = np; + ams_info.exit = ams_pmu_exit; + ams_info.get_vendor = ams_pmu_get_vendor; + ams_info.get_xyz = ams_pmu_get_xyz; + ams_info.clear_irq = ams_pmu_clear_irq; + ams_info.bustype = BUS_HOST; + + /* Get PMU command, should be 0x4e, but we can never know */ + prop = of_get_property(ams_info.of_node, "reg", NULL); + if (!prop) + return -ENODEV; + + ams_pmu_cmd = ((*prop) >> 8) & 0xff; + + /* Disable interrupts */ + ams_pmu_set_irq(AMS_IRQ_ALL, 0); + + /* Clear interrupts */ + ams_pmu_clear_irq(AMS_IRQ_ALL); + + result = ams_sensor_attach(); + if (result < 0) + return result; + + /* Set default values */ + ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15); + ams_pmu_set_register(AMS_FF_ENABLE, 0x08); + ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14); + + ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60); + ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f); + ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14); + + ams_pmu_set_register(AMS_CONTROL, 0x4f); + + /* Clear interrupts */ + ams_pmu_clear_irq(AMS_IRQ_ALL); + + ams_info.has_device = 1; + + /* Enable interrupts */ + ams_pmu_set_irq(AMS_IRQ_ALL, 1); + + printk(KERN_INFO "ams: Found PMU based motion sensor\n"); + + return 0; +} diff --git a/drivers/macintosh/ams/ams.h b/drivers/macintosh/ams/ams.h new file mode 100644 index 000000000..935bdd9cd --- /dev/null +++ b/drivers/macintosh/ams/ams.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/kthread.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/of_device.h> + +enum ams_irq { + AMS_IRQ_FREEFALL = 0x01, + AMS_IRQ_SHOCK = 0x02, + AMS_IRQ_GLOBAL = 0x04, + AMS_IRQ_ALL = + AMS_IRQ_FREEFALL | + AMS_IRQ_SHOCK | + AMS_IRQ_GLOBAL, +}; + +struct ams { + /* Locks */ + spinlock_t irq_lock; + struct mutex lock; + + /* General properties */ + struct device_node *of_node; + struct platform_device *of_dev; + char has_device; + char vflag; + u32 orient1; + u32 orient2; + + /* Interrupt worker */ + struct work_struct worker; + u8 worker_irqs; + + /* Implementation + * + * Only call these functions with the main lock held. + */ + void (*exit)(void); + + void (*get_xyz)(s8 *x, s8 *y, s8 *z); + u8 (*get_vendor)(void); + + void (*clear_irq)(enum ams_irq reg); + +#ifdef CONFIG_SENSORS_AMS_I2C + /* I2C properties */ + struct i2c_client *i2c_client; +#endif + + /* Joystick emulation */ + struct input_dev *idev; + __u16 bustype; + + /* calibrated null values */ + int xcalib, ycalib, zcalib; +}; + +extern struct ams ams_info; + +extern void ams_sensors(s8 *x, s8 *y, s8 *z); +extern int ams_sensor_attach(void); +extern void ams_sensor_detach(void); + +extern int ams_pmu_init(struct device_node *np); +extern int ams_i2c_init(struct device_node *np); + +extern int ams_input_init(void); +extern void ams_input_exit(void); diff --git a/drivers/macintosh/ans-lcd.c b/drivers/macintosh/ans-lcd.c new file mode 100644 index 000000000..fa904b24a --- /dev/null +++ b/drivers/macintosh/ans-lcd.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * /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/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/of.h> + +#include <linux/uaccess.h> +#include <asm/sections.h> +#include <asm/io.h> + +#include "ans-lcd.h" + +#define ANSLCD_ADDR 0xf301c000 +#define ANSLCD_CTRL_IX 0x00 +#define ANSLCD_DATA_IX 0x10 + +static unsigned long anslcd_short_delay = 80; +static unsigned long anslcd_long_delay = 3280; +static volatile unsigned char __iomem *anslcd_ptr; +static DEFINE_MUTEX(anslcd_mutex); + +#undef DEBUG + +static void +anslcd_write_byte_ctrl ( unsigned char c ) +{ +#ifdef DEBUG + printk(KERN_DEBUG "LCD: CTRL byte: %02x\n",c); +#endif + out_8(anslcd_ptr + ANSLCD_CTRL_IX, c); + switch(c) { + case 1: + case 2: + case 3: + udelay(anslcd_long_delay); break; + default: udelay(anslcd_short_delay); + } +} + +static void +anslcd_write_byte_data ( unsigned char c ) +{ + out_8(anslcd_ptr + ANSLCD_DATA_IX, c); + udelay(anslcd_short_delay); +} + +static ssize_t +anslcd_write( struct file * file, const char __user * buf, + size_t count, loff_t *ppos ) +{ + const char __user *p = buf; + int i; + +#ifdef DEBUG + printk(KERN_DEBUG "LCD: write\n"); +#endif + + if (!access_ok(buf, count)) + return -EFAULT; + + mutex_lock(&anslcd_mutex); + for ( i = *ppos; count > 0; ++i, ++p, --count ) + { + char c; + __get_user(c, p); + anslcd_write_byte_data( c ); + } + mutex_unlock(&anslcd_mutex); + *ppos = i; + return p - buf; +} + +static long +anslcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + char ch, __user *temp; + long ret = 0; + +#ifdef DEBUG + printk(KERN_DEBUG "LCD: ioctl(%d,%d)\n",cmd,arg); +#endif + + mutex_lock(&anslcd_mutex); + + 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 ); + break; + 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); + } + break; + case ANSLCD_SETSHORTDELAY: + if (!capable(CAP_SYS_ADMIN)) + ret =-EACCES; + else + anslcd_short_delay=arg; + break; + case ANSLCD_SETLONGDELAY: + if (!capable(CAP_SYS_ADMIN)) + ret = -EACCES; + else + anslcd_long_delay=arg; + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&anslcd_mutex); + return ret; +} + +static int +anslcd_open( struct inode * inode, struct file * file ) +{ + return 0; +} + +const struct file_operations anslcd_fops = { + .write = anslcd_write, + .unlocked_ioctl = anslcd_ioctl, + .open = anslcd_open, + .llseek = default_llseek, +}; + +static struct miscdevice anslcd_dev = { + LCD_MINOR, + "anslcd", + &anslcd_fops +}; + +static const char anslcd_logo[] __initconst = + "********************" /* Line #1 */ + "* LINUX! *" /* Line #3 */ + "* Welcome to *" /* Line #2 */ + "********************"; /* Line #4 */ + +static int __init +anslcd_init(void) +{ + int a; + int retval; + struct device_node* node; + + node = of_find_node_by_name(NULL, "lcd"); + if (!node || !of_node_name_eq(node->parent, "gc")) { + of_node_put(node); + return -ENODEV; + } + of_node_put(node); + + anslcd_ptr = ioremap(ANSLCD_ADDR, 0x20); + + retval = misc_register(&anslcd_dev); + if(retval < 0){ + printk(KERN_INFO "LCD: misc_register failed\n"); + iounmap(anslcd_ptr); + return retval; + } + +#ifdef DEBUG + printk(KERN_DEBUG "LCD: init\n"); +#endif + + mutex_lock(&anslcd_mutex); + 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]); + } + mutex_unlock(&anslcd_mutex); + return 0; +} + +static void __exit +anslcd_exit(void) +{ + misc_deregister(&anslcd_dev); + iounmap(anslcd_ptr); +} + +module_init(anslcd_init); +module_exit(anslcd_exit); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/macintosh/ans-lcd.h b/drivers/macintosh/ans-lcd.h new file mode 100644 index 000000000..bca7d76d4 --- /dev/null +++ b/drivers/macintosh/ans-lcd.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _PPC_ANS_LCD_H +#define _PPC_ANS_LCD_H + +#define ANSLCD_CLEAR 0x01 +#define ANSLCD_SENDCTRL 0x02 +#define ANSLCD_SETSHORTDELAY 0x03 +#define ANSLCD_SETLONGDELAY 0x04 + +#endif diff --git a/drivers/macintosh/apm_emu.c b/drivers/macintosh/apm_emu.c new file mode 100644 index 000000000..5ed0ac65c --- /dev/null +++ b/drivers/macintosh/apm_emu.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * APM emulation for PMU-based machines + * + * Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org) + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/apm-emulation.h> +#include <linux/adb.h> +#include <linux/pmu.h> + +#define APM_CRITICAL 10 +#define APM_LOW 30 + +static void pmu_apm_get_power_status(struct apm_power_info *info) +{ + int percentage = -1; + int batteries = 0; + int time_units = -1; + int real_count = 0; + int i; + char charging = 0; + long charge = -1; + long amperage = 0; + unsigned long btype = 0; + + info->battery_status = APM_BATTERY_STATUS_UNKNOWN; + info->battery_flag = APM_BATTERY_FLAG_UNKNOWN; + info->units = APM_UNITS_MINS; + + if (pmu_power_flags & PMU_PWR_AC_PRESENT) + info->ac_line_status = APM_AC_ONLINE; + else + info->ac_line_status = APM_AC_OFFLINE; + + for (i=0; i<pmu_battery_count; i++) { + if (pmu_batteries[i].flags & PMU_BATT_PRESENT) { + batteries++; + if (percentage < 0) + percentage = 0; + if (charge < 0) + charge = 0; + percentage += (pmu_batteries[i].charge * 100) / + pmu_batteries[i].max_charge; + charge += pmu_batteries[i].charge; + amperage += pmu_batteries[i].amperage; + if (btype == 0) + btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK); + real_count++; + if ((pmu_batteries[i].flags & PMU_BATT_CHARGING)) + charging++; + } + } + if (batteries == 0) + info->ac_line_status = APM_AC_ONLINE; + + if (real_count) { + if (amperage < 0) { + if (btype == PMU_BATT_TYPE_SMART) + time_units = (charge * 59) / (amperage * -1); + else + time_units = (charge * 16440) / (amperage * -60); + } + percentage /= real_count; + if (charging > 0) { + info->battery_status = APM_BATTERY_STATUS_CHARGING; + info->battery_flag = APM_BATTERY_FLAG_CHARGING; + } else if (percentage <= APM_CRITICAL) { + info->battery_status = APM_BATTERY_STATUS_CRITICAL; + info->battery_flag = APM_BATTERY_FLAG_CRITICAL; + } else if (percentage <= APM_LOW) { + info->battery_status = APM_BATTERY_STATUS_LOW; + info->battery_flag = APM_BATTERY_FLAG_LOW; + } else { + info->battery_status = APM_BATTERY_STATUS_HIGH; + info->battery_flag = APM_BATTERY_FLAG_HIGH; + } + } + + info->battery_life = percentage; + info->time = time_units; +} + +static int __init apm_emu_init(void) +{ + apm_get_power_status = pmu_apm_get_power_status; + + printk(KERN_INFO "apm_emu: PMU APM Emulation initialized.\n"); + + return 0; +} + +static void __exit apm_emu_exit(void) +{ + if (apm_get_power_status == pmu_apm_get_power_status) + apm_get_power_status = NULL; + + printk(KERN_INFO "apm_emu: PMU APM Emulation removed.\n"); +} + +module_init(apm_emu_init); +module_exit(apm_emu_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt"); +MODULE_DESCRIPTION("APM emulation for PowerMac"); +MODULE_LICENSE("GPL"); diff --git a/drivers/macintosh/mac_hid.c b/drivers/macintosh/mac_hid.c new file mode 100644 index 000000000..d8c4d5664 --- /dev/null +++ b/drivers/macintosh/mac_hid.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/macintosh/mac_hid.c + * + * HID support stuff for Macintosh computers. + * + * Copyright (C) 2000 Franz Sirl. + * + * This file will soon be removed in favor of an uinput userspace tool. + */ + +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/sysctl.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/slab.h> + +MODULE_LICENSE("GPL"); + +static int mouse_emulate_buttons; +static int mouse_button2_keycode = KEY_RIGHTCTRL; /* right control key */ +static int mouse_button3_keycode = KEY_RIGHTALT; /* right option key */ + +static struct input_dev *mac_hid_emumouse_dev; + +static DEFINE_MUTEX(mac_hid_emumouse_mutex); + +static int mac_hid_create_emumouse(void) +{ + static struct lock_class_key mac_hid_emumouse_dev_event_class; + static struct lock_class_key mac_hid_emumouse_dev_mutex_class; + int err; + + mac_hid_emumouse_dev = input_allocate_device(); + if (!mac_hid_emumouse_dev) + return -ENOMEM; + + lockdep_set_class(&mac_hid_emumouse_dev->event_lock, + &mac_hid_emumouse_dev_event_class); + lockdep_set_class(&mac_hid_emumouse_dev->mutex, + &mac_hid_emumouse_dev_mutex_class); + + mac_hid_emumouse_dev->name = "Macintosh mouse button emulation"; + mac_hid_emumouse_dev->id.bustype = BUS_ADB; + mac_hid_emumouse_dev->id.vendor = 0x0001; + mac_hid_emumouse_dev->id.product = 0x0001; + mac_hid_emumouse_dev->id.version = 0x0100; + + mac_hid_emumouse_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL); + mac_hid_emumouse_dev->keybit[BIT_WORD(BTN_MOUSE)] = + BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE) | BIT_MASK(BTN_RIGHT); + mac_hid_emumouse_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + err = input_register_device(mac_hid_emumouse_dev); + if (err) { + input_free_device(mac_hid_emumouse_dev); + mac_hid_emumouse_dev = NULL; + return err; + } + + return 0; +} + +static void mac_hid_destroy_emumouse(void) +{ + input_unregister_device(mac_hid_emumouse_dev); + mac_hid_emumouse_dev = NULL; +} + +static bool mac_hid_emumouse_filter(struct input_handle *handle, + unsigned int type, unsigned int code, + int value) +{ + unsigned int btn; + + if (type != EV_KEY) + return false; + + if (code == mouse_button2_keycode) + btn = BTN_MIDDLE; + else if (code == mouse_button3_keycode) + btn = BTN_RIGHT; + else + return false; + + input_report_key(mac_hid_emumouse_dev, btn, value); + input_sync(mac_hid_emumouse_dev); + + return true; +} + +static int mac_hid_emumouse_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + /* Don't bind to ourselves */ + if (dev == mac_hid_emumouse_dev) + return -ENODEV; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "mac-button-emul"; + + error = input_register_handle(handle); + if (error) { + printk(KERN_ERR + "mac_hid: Failed to register button emulation handle, " + "error %d\n", error); + goto err_free; + } + + error = input_open_device(handle); + if (error) { + printk(KERN_ERR + "mac_hid: Failed to open input device, error %d\n", + error); + goto err_unregister; + } + + return 0; + + err_unregister: + input_unregister_handle(handle); + err_free: + kfree(handle); + return error; +} + +static void mac_hid_emumouse_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id mac_hid_emumouse_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + { }, +}; + +MODULE_DEVICE_TABLE(input, mac_hid_emumouse_ids); + +static struct input_handler mac_hid_emumouse_handler = { + .filter = mac_hid_emumouse_filter, + .connect = mac_hid_emumouse_connect, + .disconnect = mac_hid_emumouse_disconnect, + .name = "mac-button-emul", + .id_table = mac_hid_emumouse_ids, +}; + +static int mac_hid_start_emulation(void) +{ + int err; + + err = mac_hid_create_emumouse(); + if (err) + return err; + + err = input_register_handler(&mac_hid_emumouse_handler); + if (err) { + mac_hid_destroy_emumouse(); + return err; + } + + return 0; +} + +static void mac_hid_stop_emulation(void) +{ + input_unregister_handler(&mac_hid_emumouse_handler); + mac_hid_destroy_emumouse(); +} + +static int mac_hid_toggle_emumouse(struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + int *valp = table->data; + int old_val = *valp; + int rc; + + rc = mutex_lock_killable(&mac_hid_emumouse_mutex); + if (rc) + return rc; + + rc = proc_dointvec(table, write, buffer, lenp, ppos); + + if (rc == 0 && write && *valp != old_val) { + if (*valp == 1) + rc = mac_hid_start_emulation(); + else if (*valp == 0) + mac_hid_stop_emulation(); + else + rc = -EINVAL; + } + + /* Restore the old value in case of error */ + if (rc) + *valp = old_val; + + mutex_unlock(&mac_hid_emumouse_mutex); + + return rc; +} + +/* file(s) in /proc/sys/dev/mac_hid */ +static struct ctl_table mac_hid_files[] = { + { + .procname = "mouse_button_emulation", + .data = &mouse_emulate_buttons, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = mac_hid_toggle_emumouse, + }, + { + .procname = "mouse_button2_keycode", + .data = &mouse_button2_keycode, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "mouse_button3_keycode", + .data = &mouse_button3_keycode, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { } +}; + +static struct ctl_table_header *mac_hid_sysctl_header; + +static int __init mac_hid_init(void) +{ + mac_hid_sysctl_header = register_sysctl("dev/mac_hid", mac_hid_files); + if (!mac_hid_sysctl_header) + return -ENOMEM; + + return 0; +} +module_init(mac_hid_init); + +static void __exit mac_hid_exit(void) +{ + unregister_sysctl_table(mac_hid_sysctl_header); + + if (mouse_emulate_buttons) + mac_hid_stop_emulation(); +} +module_exit(mac_hid_exit); diff --git a/drivers/macintosh/macio-adb.c b/drivers/macintosh/macio-adb.c new file mode 100644 index 000000000..cd4e34d15 --- /dev/null +++ b/drivers/macintosh/macio-adb.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the ADB controller in the Mac I/O (Hydra) chip. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/pgtable.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/adb.h> + +#include <asm/io.h> +#include <asm/hydra.h> +#include <asm/irq.h> +#include <linux/init.h> +#include <linux/ioport.h> + +struct preg { + unsigned char r; + char pad[15]; +}; + +struct adb_regs { + struct preg intr; + struct preg data[9]; + struct preg intr_enb; + struct preg dcount; + struct preg error; + struct preg ctrl; + struct preg autopoll; + struct preg active_hi; + struct preg active_lo; + struct preg test; +}; + +/* Bits in intr and intr_enb registers */ +#define DFB 1 /* data from bus */ +#define TAG 2 /* transfer access grant */ + +/* Bits in dcount register */ +#define HMB 0x0f /* how many bytes */ +#define APD 0x10 /* auto-poll data */ + +/* Bits in error register */ +#define NRE 1 /* no response error */ +#define DLE 2 /* data lost error */ + +/* Bits in ctrl register */ +#define TAR 1 /* transfer access request */ +#define DTB 2 /* data to bus */ +#define CRE 4 /* command response expected */ +#define ADB_RST 8 /* ADB reset */ + +/* Bits in autopoll register */ +#define APE 1 /* autopoll enable */ + +static volatile struct adb_regs __iomem *adb; +static struct adb_request *current_req, *last_req; +static DEFINE_SPINLOCK(macio_lock); + +static int macio_probe(void); +static int macio_init(void); +static irqreturn_t macio_adb_interrupt(int irq, void *arg); +static int macio_send_request(struct adb_request *req, int sync); +static int macio_adb_autopoll(int devs); +static void macio_adb_poll(void); +static int macio_adb_reset_bus(void); + +struct adb_driver macio_adb_driver = { + .name = "MACIO", + .probe = macio_probe, + .init = macio_init, + .send_request = macio_send_request, + .autopoll = macio_adb_autopoll, + .poll = macio_adb_poll, + .reset_bus = macio_adb_reset_bus, +}; + +int macio_probe(void) +{ + struct device_node *np; + + np = of_find_compatible_node(NULL, "adb", "chrp,adb0"); + if (np) { + of_node_put(np); + return 0; + } + return -ENODEV; +} + +int macio_init(void) +{ + struct device_node *adbs; + struct resource r; + unsigned int irq; + + adbs = of_find_compatible_node(NULL, "adb", "chrp,adb0"); + if (adbs == 0) + return -ENXIO; + + if (of_address_to_resource(adbs, 0, &r)) { + of_node_put(adbs); + return -ENXIO; + } + adb = ioremap(r.start, sizeof(struct adb_regs)); + if (!adb) { + of_node_put(adbs); + return -ENOMEM; + } + + out_8(&adb->ctrl.r, 0); + out_8(&adb->intr.r, 0); + out_8(&adb->error.r, 0); + out_8(&adb->active_hi.r, 0xff); /* for now, set all devices active */ + out_8(&adb->active_lo.r, 0xff); + out_8(&adb->autopoll.r, APE); + + irq = irq_of_parse_and_map(adbs, 0); + of_node_put(adbs); + if (request_irq(irq, macio_adb_interrupt, 0, "ADB", (void *)0)) { + printk(KERN_ERR "ADB: can't get irq %d\n", irq); + return -EAGAIN; + } + out_8(&adb->intr_enb.r, DFB | TAG); + + printk("adb: mac-io driver 1.0 for unified ADB\n"); + + return 0; +} + +static int macio_adb_autopoll(int devs) +{ + unsigned long flags; + + spin_lock_irqsave(&macio_lock, flags); + out_8(&adb->active_hi.r, devs >> 8); + out_8(&adb->active_lo.r, devs); + out_8(&adb->autopoll.r, devs? APE: 0); + spin_unlock_irqrestore(&macio_lock, flags); + return 0; +} + +static int macio_adb_reset_bus(void) +{ + unsigned long flags; + int timeout = 1000000; + + /* Hrm... we may want to not lock interrupts for so + * long ... oh well, who uses that chip anyway ? :) + * That function will be seldom 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); + spin_unlock_irqrestore(&macio_lock, flags); + return -1; + } + } + spin_unlock_irqrestore(&macio_lock, flags); + return 0; +} + +/* Send an ADB command */ +static int macio_send_request(struct adb_request *req, int sync) +{ + unsigned long flags; + int i; + + if (req->data[0] != ADB_PACKET) + return -EINVAL; + + for (i = 0; i < req->nbytes - 1; ++i) + req->data[i] = req->data[i+1]; + --req->nbytes; + + req->next = NULL; + req->sent = 0; + req->complete = 0; + req->reply_len = 0; + + spin_lock_irqsave(&macio_lock, flags); + if (current_req != 0) { + last_req->next = req; + last_req = req; + } else { + current_req = last_req = req; + out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR); + } + spin_unlock_irqrestore(&macio_lock, flags); + + if (sync) { + while (!req->complete) + macio_adb_poll(); + } + + return 0; +} + +static irqreturn_t macio_adb_interrupt(int irq, void *arg) +{ + int i, n, err; + struct adb_request *req = NULL; + unsigned char ibuf[16]; + int ibuf_len = 0; + int complete = 0; + int autopoll = 0; + int handled = 0; + + spin_lock(&macio_lock); + if (in_8(&adb->intr.r) & TAG) { + handled = 1; + if ((req = current_req) != 0) { + /* put the current request in */ + for (i = 0; i < req->nbytes; ++i) + out_8(&adb->data[i].r, req->data[i]); + out_8(&adb->dcount.r, req->nbytes & HMB); + req->sent = 1; + if (req->reply_expected) { + out_8(&adb->ctrl.r, DTB + CRE); + } else { + out_8(&adb->ctrl.r, DTB); + current_req = req->next; + complete = 1; + if (current_req) + out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR); + } + } + out_8(&adb->intr.r, 0); + } + + if (in_8(&adb->intr.r) & DFB) { + handled = 1; + err = in_8(&adb->error.r); + if (current_req && current_req->sent) { + /* this is the response to a command */ + req = current_req; + if (err == 0) { + req->reply_len = in_8(&adb->dcount.r) & HMB; + for (i = 0; i < req->reply_len; ++i) + req->reply[i] = in_8(&adb->data[i].r); + } + current_req = req->next; + complete = 1; + if (current_req) + out_8(&adb->ctrl.r, in_8(&adb->ctrl.r) | TAR); + } else if (err == 0) { + /* autopoll data */ + n = in_8(&adb->dcount.r) & HMB; + for (i = 0; i < n; ++i) + ibuf[i] = in_8(&adb->data[i].r); + ibuf_len = n; + autopoll = (in_8(&adb->dcount.r) & APD) != 0; + } + out_8(&adb->error.r, 0); + out_8(&adb->intr.r, 0); + } + spin_unlock(&macio_lock); + if (complete && req) { + void (*done)(struct adb_request *) = req->done; + mb(); + req->complete = 1; + /* Here, we assume that if the request has a done member, the + * struct request will survive to setting req->complete to 1 + */ + if (done) + (*done)(req); + } + if (ibuf_len) + adb_input(ibuf, ibuf_len, autopoll); + + return IRQ_RETVAL(handled); +} + +static void macio_adb_poll(void) +{ + unsigned long flags; + + local_irq_save(flags); + if (in_8(&adb->intr.r) != 0) + macio_adb_interrupt(0, NULL); + local_irq_restore(flags); +} diff --git a/drivers/macintosh/macio_asic.c b/drivers/macintosh/macio_asic.c new file mode 100644 index 000000000..3bc1f374e --- /dev/null +++ b/drivers/macintosh/macio_asic.c @@ -0,0 +1,792 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Bus & driver management routines for devices within + * a MacIO ASIC. Interface to new driver model mostly + * stolen from the PCI version. + * + * Copyright (C) 2005 Ben. Herrenschmidt (benh@kernel.crashing.org) + * + * TODO: + * + * - Don't probe below media bay by default, but instead provide + * some hooks for media bay to dynamically add/remove it's own + * sub-devices. + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> + +#include <asm/machdep.h> +#include <asm/macio.h> +#include <asm/pmac_feature.h> + +#undef DEBUG + +#define MAX_NODE_NAME_SIZE (20 - 12) + +static struct macio_chip *macio_on_hold; + +static int macio_bus_match(struct device *dev, struct device_driver *drv) +{ + const struct of_device_id * matches = drv->of_match_table; + + if (!matches) + return 0; + + return of_match_device(matches, dev) != NULL; +} + +struct macio_dev *macio_dev_get(struct macio_dev *dev) +{ + struct device *tmp; + + if (!dev) + return NULL; + tmp = get_device(&dev->ofdev.dev); + if (tmp) + return to_macio_device(tmp); + else + return NULL; +} + +void macio_dev_put(struct macio_dev *dev) +{ + if (dev) + put_device(&dev->ofdev.dev); +} + + +static int macio_device_probe(struct device *dev) +{ + int error = -ENODEV; + struct macio_driver *drv; + struct macio_dev *macio_dev; + const struct of_device_id *match; + + drv = to_macio_driver(dev->driver); + macio_dev = to_macio_device(dev); + + if (!drv->probe) + return error; + + macio_dev_get(macio_dev); + + match = of_match_device(drv->driver.of_match_table, dev); + if (match) + error = drv->probe(macio_dev, match); + if (error) + macio_dev_put(macio_dev); + + return error; +} + +static void 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); +} + +static void macio_device_shutdown(struct device *dev) +{ + struct macio_dev * macio_dev = to_macio_device(dev); + struct macio_driver * drv = to_macio_driver(dev->driver); + + if (dev->driver && drv->shutdown) + drv->shutdown(macio_dev); +} + +static int macio_device_suspend(struct device *dev, pm_message_t state) +{ + struct macio_dev * macio_dev = to_macio_device(dev); + struct macio_driver * drv = to_macio_driver(dev->driver); + + if (dev->driver && drv->suspend) + return drv->suspend(macio_dev, state); + return 0; +} + +static int macio_device_resume(struct device * dev) +{ + struct macio_dev * macio_dev = to_macio_device(dev); + struct macio_driver * drv = to_macio_driver(dev->driver); + + if (dev->driver && drv->resume) + return drv->resume(macio_dev); + return 0; +} + +extern const struct attribute_group *macio_dev_groups[]; + +struct bus_type macio_bus_type = { + .name = "macio", + .match = macio_bus_match, + .uevent = of_device_uevent_modalias, + .probe = macio_device_probe, + .remove = macio_device_remove, + .shutdown = macio_device_shutdown, + .suspend = macio_device_suspend, + .resume = macio_device_resume, + .dev_groups = macio_dev_groups, +}; + +static int __init macio_bus_driver_init(void) +{ + return bus_register(&macio_bus_type); +} + +postcore_initcall(macio_bus_driver_init); + + +/** + * macio_release_dev - free a macio device structure when all users of it are + * finished. + * @dev: device that's been disconnected + * + * Will be called only by the device core when all users of this macio device + * are done. This currently means never as we don't hot remove any macio + * device yet, though that will happen with mediabay based devices in a later + * implementation. + */ +static void macio_release_dev(struct device *dev) +{ + struct macio_dev *mdev; + + mdev = to_macio_device(dev); + kfree(mdev); +} + +/** + * macio_resource_quirks - tweak or skip some resources for a device + * @np: pointer to the device node + * @res: resulting resource + * @index: index of resource in node + * + * If this routine returns non-null, then the resource is completely + * skipped. + */ +static int macio_resource_quirks(struct device_node *np, struct resource *res, + int index) +{ + /* Only quirks for memory resources for now */ + if ((res->flags & IORESOURCE_MEM) == 0) + return 0; + + /* Grand Central has too large resource 0 on some machines */ + if (index == 0 && of_node_name_eq(np, "gc")) + res->end = res->start + 0x1ffff; + + /* Airport has bogus resource 2 */ + if (index >= 2 && of_node_name_eq(np, "radio")) + return 1; + +#ifndef CONFIG_PPC64 + /* DBDMAs may have bogus sizes */ + if ((res->start & 0x0001f000) == 0x00008000) + res->end = res->start + 0xff; +#endif /* CONFIG_PPC64 */ + + /* ESCC parent eats child resources. We could have added a + * level of hierarchy, but I don't really feel the need + * for it + */ + if (of_node_name_eq(np, "escc")) + return 1; + + /* ESCC has bogus resources >= 3 */ + if (index >= 3 && (of_node_name_eq(np, "ch-a") || + of_node_name_eq(np, "ch-b"))) + return 1; + + /* Media bay has too many resources, keep only first one */ + if (index > 0 && of_node_name_eq(np, "media-bay")) + return 1; + + /* Some older IDE resources have bogus sizes */ + if (of_node_name_eq(np, "IDE") || of_node_name_eq(np, "ATA") || + of_node_is_type(np, "ide") || of_node_is_type(np, "ata")) { + if (index == 0 && (res->end - res->start) > 0xfff) + res->end = res->start + 0xfff; + if (index == 1 && (res->end - res->start) > 0xff) + res->end = res->start + 0xff; + } + return 0; +} + +static void macio_create_fixup_irq(struct macio_dev *dev, int index, + unsigned int line) +{ + unsigned int irq; + + irq = irq_create_mapping(NULL, line); + if (!irq) { + dev->interrupt[index].start = irq; + dev->interrupt[index].flags = IORESOURCE_IRQ; + dev->interrupt[index].name = dev_name(&dev->ofdev.dev); + } + if (dev->n_interrupts <= index) + dev->n_interrupts = index + 1; +} + +static void macio_add_missing_resources(struct macio_dev *dev) +{ + struct device_node *np = dev->ofdev.dev.of_node; + unsigned int irq_base; + + /* Gatwick has some missing interrupts on child nodes */ + if (dev->bus->chip->type != macio_gatwick) + return; + + /* irq_base is always 64 on gatwick. I have no cleaner way to get + * that value from here at this point + */ + irq_base = 64; + + /* Fix SCC */ + if (of_node_name_eq(np, "ch-a")) { + macio_create_fixup_irq(dev, 0, 15 + irq_base); + macio_create_fixup_irq(dev, 1, 4 + irq_base); + macio_create_fixup_irq(dev, 2, 5 + irq_base); + printk(KERN_INFO "macio: fixed SCC irqs on gatwick\n"); + } + + /* Fix media-bay */ + if (of_node_name_eq(np, "media-bay")) { + macio_create_fixup_irq(dev, 0, 29 + irq_base); + printk(KERN_INFO "macio: fixed media-bay irq on gatwick\n"); + } + + /* Fix left media bay childs */ + if (dev->media_bay != NULL && of_node_name_eq(np, "floppy")) { + macio_create_fixup_irq(dev, 0, 19 + irq_base); + macio_create_fixup_irq(dev, 1, 1 + irq_base); + printk(KERN_INFO "macio: fixed left floppy irqs\n"); + } + if (dev->media_bay != NULL && of_node_name_eq(np, "ata4")) { + macio_create_fixup_irq(dev, 0, 14 + irq_base); + macio_create_fixup_irq(dev, 0, 3 + irq_base); + printk(KERN_INFO "macio: fixed left ide irqs\n"); + } +} + +static void macio_setup_interrupts(struct macio_dev *dev) +{ + struct device_node *np = dev->ofdev.dev.of_node; + unsigned int irq; + int i = 0, j = 0; + + for (;;) { + struct resource *res; + + if (j >= MACIO_DEV_COUNT_IRQS) + break; + res = &dev->interrupt[j]; + irq = irq_of_parse_and_map(np, i++); + if (!irq) + break; + res->start = irq; + res->flags = IORESOURCE_IRQ; + res->name = dev_name(&dev->ofdev.dev); + if (macio_resource_quirks(np, res, i - 1)) { + memset(res, 0, sizeof(struct resource)); + continue; + } else + j++; + } + dev->n_interrupts = j; +} + +static void macio_setup_resources(struct macio_dev *dev, + struct resource *parent_res) +{ + struct device_node *np = dev->ofdev.dev.of_node; + struct resource r; + int index; + + for (index = 0; of_address_to_resource(np, index, &r) == 0; index++) { + struct resource *res; + if (index >= MACIO_DEV_COUNT_RESOURCES) + break; + res = &dev->resource[index]; + *res = r; + res->name = dev_name(&dev->ofdev.dev); + + if (macio_resource_quirks(np, res, index)) { + memset(res, 0, sizeof(struct resource)); + continue; + } + /* Currently, we consider failure as harmless, this may + * change in the future, once I've found all the device + * tree bugs in older machines & worked around them + */ + if (insert_resource(parent_res, res)) { + printk(KERN_WARNING "Can't request resource " + "%d for MacIO device %s\n", + index, dev_name(&dev->ofdev.dev)); + } + } + dev->n_resources = index; +} + +/** + * macio_add_one_device - Add one device from OF node to the device tree + * @chip: pointer to the macio_chip holding the device + * @np: pointer to the device node in the OF tree + * @in_bay: set to 1 if device is part of a media-bay + * + * When media-bay is changed to hotswap drivers, this function will + * be exposed to the bay driver some way... + */ +static struct macio_dev * macio_add_one_device(struct macio_chip *chip, + struct device *parent, + struct device_node *np, + struct macio_dev *in_bay, + struct resource *parent_res) +{ + char name[MAX_NODE_NAME_SIZE + 1]; + struct macio_dev *dev; + const u32 *reg; + + if (np == NULL) + return NULL; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->bus = &chip->lbus; + dev->media_bay = in_bay; + dev->ofdev.dev.of_node = np; + dev->ofdev.archdata.dma_mask = 0xffffffffUL; + dev->ofdev.dev.dma_mask = &dev->ofdev.archdata.dma_mask; + dev->ofdev.dev.coherent_dma_mask = dev->ofdev.archdata.dma_mask; + dev->ofdev.dev.parent = parent; + dev->ofdev.dev.bus = &macio_bus_type; + dev->ofdev.dev.release = macio_release_dev; + dev->ofdev.dev.dma_parms = &dev->dma_parms; + + /* Standard DMA paremeters */ + dma_set_max_seg_size(&dev->ofdev.dev, 65536); + dma_set_seg_boundary(&dev->ofdev.dev, 0xffffffff); + +#if defined(CONFIG_PCI) && defined(CONFIG_DMA_OPS) + /* Set the DMA ops to the ones from the PCI device, this could be + * fishy if we didn't know that on PowerMac it's always direct ops + * or iommu ops that will work fine + * + * To get all the fields, copy all archdata + */ + dev->ofdev.dev.archdata = chip->lbus.pdev->dev.archdata; + dev->ofdev.dev.dma_ops = chip->lbus.pdev->dev.dma_ops; +#endif /* CONFIG_PCI && CONFIG_DMA_OPS */ + +#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 */ + snprintf(name, sizeof(name), "%pOFn", np); + if (np == chip->of_node) { + dev_set_name(&dev->ofdev.dev, "%1d.%08x:%.*s", + chip->lbus.index, +#ifdef CONFIG_PCI + (unsigned int)pci_resource_start(chip->lbus.pdev, 0), +#else + 0, /* NuBus may want to do something better here */ +#endif + MAX_NODE_NAME_SIZE, name); + } else { + reg = of_get_property(np, "reg", NULL); + dev_set_name(&dev->ofdev.dev, "%1d.%08x:%.*s", + chip->lbus.index, + reg ? *reg : 0, MAX_NODE_NAME_SIZE, name); + } + + /* Setup interrupts & resources */ + macio_setup_interrupts(dev); + macio_setup_resources(dev, parent_res); + macio_add_missing_resources(dev); + + /* Register with core */ + if (of_device_register(&dev->ofdev) != 0) { + printk(KERN_DEBUG"macio: device registration error for %s!\n", + dev_name(&dev->ofdev.dev)); + put_device(&dev->ofdev.dev); + return NULL; + } + + return dev; +} + +static int macio_skip_device(struct device_node *np) +{ + return of_node_name_prefix(np, "battery") || + of_node_name_prefix(np, "escc-legacy"); +} + +/** + * 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_each_child_of_node(pnode, np) { + if (macio_skip_device(np)) + continue; + of_node_get(np); + mdev = macio_add_one_device(chip, &rdev->ofdev.dev, np, NULL, + root_res); + if (mdev == NULL) + of_node_put(np); + else if (of_node_name_prefix(np, "media-bay")) + mbdev = mdev; + else if (of_node_name_prefix(np, "escc")) + sdev = mdev; + } + + /* Add media bay devices if any */ + if (mbdev) { + pnode = mbdev->ofdev.dev.of_node; + for_each_child_of_node(pnode, np) { + if (macio_skip_device(np)) + continue; + of_node_get(np); + if (macio_add_one_device(chip, &mbdev->ofdev.dev, np, + mbdev, root_res) == NULL) + of_node_put(np); + } + } + + /* Add serial ports if any */ + if (sdev) { + pnode = sdev->ofdev.dev.of_node; + for_each_child_of_node(pnode, np) { + if (macio_skip_device(np)) + continue; + of_node_get(np); + if (macio_add_one_device(chip, &sdev->ofdev.dev, np, + NULL, root_res) == NULL) + of_node_put(np); + } + } +} + + +/** + * macio_register_driver - Registers a new MacIO device driver + * @drv: pointer to the driver definition structure + */ +int macio_register_driver(struct macio_driver *drv) +{ + /* initialize common driver fields */ + drv->driver.bus = &macio_bus_type; + + /* register with core */ + return driver_register(&drv->driver); +} + +/** + * macio_unregister_driver - Unregisters a new MacIO device driver + * @drv: pointer to the driver definition structure + */ +void macio_unregister_driver(struct macio_driver *drv) +{ + driver_unregister(&drv->driver); +} + +/* Managed MacIO resources */ +struct macio_devres { + u32 res_mask; +}; + +static void maciom_release(struct device *gendev, void *res) +{ + struct macio_dev *dev = to_macio_device(gendev); + struct macio_devres *dr = res; + int i, max; + + max = min(dev->n_resources, 32); + for (i = 0; i < max; i++) { + if (dr->res_mask & (1 << i)) + macio_release_resource(dev, i); + } +} + +int macio_enable_devres(struct macio_dev *dev) +{ + struct macio_devres *dr; + + dr = devres_find(&dev->ofdev.dev, maciom_release, NULL, NULL); + if (!dr) { + dr = devres_alloc(maciom_release, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + } + return devres_get(&dev->ofdev.dev, dr, NULL, NULL) != NULL; +} + +static struct macio_devres * find_macio_dr(struct macio_dev *dev) +{ + return devres_find(&dev->ofdev.dev, maciom_release, NULL, NULL); +} + +/** + * 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) +{ + struct macio_devres *dr = find_macio_dr(dev); + + 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; + + if (dr && resource_no < 32) + dr->res_mask |= 1 << resource_no; + + return 0; + +err_out: + printk (KERN_WARNING "MacIO: Unable to reserve resource #%d:%lx@%lx" + " for device %s\n", + resource_no, + macio_resource_len(dev, resource_no), + macio_resource_start(dev, resource_no), + dev_name(&dev->ofdev.dev)); + return -EBUSY; +} + +/** + * macio_release_resource - Release an MMIO resource + * @dev: pointer to the device holding the resource + * @resource_no: resource number to release + */ +void macio_release_resource(struct macio_dev *dev, int resource_no) +{ + struct macio_devres *dr = find_macio_dr(dev); + + if (macio_resource_len(dev, resource_no) == 0) + return; + release_mem_region(macio_resource_start(dev, resource_no), + macio_resource_len(dev, resource_no)); + if (dr && resource_no < 32) + dr->res_mask &= ~(1 << 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 macio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct device_node* np; + struct macio_chip* chip; + + if (ent->vendor != PCI_VENDOR_ID_APPLE) + return -ENODEV; + + /* Note regarding refcounting: We assume pci_device_to_OF_node() is + * ported to new OF APIs and returns a node with refcount incremented. + */ + np = pci_device_to_OF_node(pdev); + if (np == NULL) + return -ENODEV; + + /* The above assumption is wrong !!! + * fix that here for now until I fix the arch code + */ + of_node_get(np); + + /* We also assume that pmac_feature will have done a get() on nodes + * stored in the macio chips array + */ + chip = macio_find(np, macio_unknown); + of_node_put(np); + if (chip == NULL) + return -ENODEV; + + /* XXX Need locking ??? */ + if (chip->lbus.pdev == NULL) { + chip->lbus.pdev = pdev; + chip->lbus.chip = chip; + pci_set_drvdata(pdev, &chip->lbus); + pci_set_master(pdev); + } + + printk(KERN_INFO "MacIO PCI driver attached to %s chipset\n", + chip->name); + + /* + * HACK ALERT: The WallStreet PowerBook and some OHare based machines + * have 2 macio ASICs. I must probe the "main" one first or IDE + * ordering will be incorrect. So I put on "hold" the second one since + * it seem to appear first on PCI + */ + if (chip->type == macio_gatwick || chip->type == macio_ohareII) + if (macio_chips[0].lbus.pdev == NULL) { + macio_on_hold = chip; + return 0; + } + + macio_pci_add_devices(chip); + if (macio_on_hold && macio_chips[0].lbus.pdev != NULL) { + macio_pci_add_devices(macio_on_hold); + macio_on_hold = NULL; + } + + return 0; +} + +static void 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 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 = "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); +EXPORT_SYMBOL(macio_enable_devres); + diff --git a/drivers/macintosh/macio_sysfs.c b/drivers/macintosh/macio_sysfs.c new file mode 100644 index 000000000..2bbe359b2 --- /dev/null +++ b/drivers/macintosh/macio_sysfs.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/stat.h> +#include <asm/macio.h> + +static ssize_t +compatible_show (struct device *dev, struct device_attribute *attr, char *buf) +{ + struct platform_device *of; + const char *compat; + int cplen; + int length = 0; + + of = &to_macio_device (dev)->ofdev; + compat = of_get_property(of->dev.of_node, "compatible", &cplen); + if (!compat) { + *buf = '\0'; + return 0; + } + while (cplen > 0) { + int l; + length += sprintf (buf, "%s\n", compat); + buf += length; + l = strlen (compat) + 1; + compat += l; + cplen -= l; + } + + return length; +} +static DEVICE_ATTR_RO(compatible); + +static ssize_t modalias_show (struct device *dev, struct device_attribute *attr, + char *buf) +{ + return of_device_modalias(dev, buf, PAGE_SIZE); +} + +static ssize_t devspec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *ofdev; + + ofdev = to_platform_device(dev); + return sprintf(buf, "%pOF\n", ofdev->dev.of_node); +} +static DEVICE_ATTR_RO(modalias); +static DEVICE_ATTR_RO(devspec); + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%pOFn\n", dev->of_node); +} +static DEVICE_ATTR_RO(name); + +static ssize_t type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", of_node_get_device_type(dev->of_node)); +} +static DEVICE_ATTR_RO(type); + +static struct attribute *macio_dev_attrs[] = { + &dev_attr_name.attr, + &dev_attr_type.attr, + &dev_attr_compatible.attr, + &dev_attr_modalias.attr, + &dev_attr_devspec.attr, + NULL, +}; + +static const struct attribute_group macio_dev_group = { + .attrs = macio_dev_attrs, +}; + +const struct attribute_group *macio_dev_groups[] = { + &macio_dev_group, + NULL, +}; diff --git a/drivers/macintosh/mediabay.c b/drivers/macintosh/mediabay.c new file mode 100644 index 000000000..36070c658 --- /dev/null +++ b/drivers/macintosh/mediabay.c @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the media bay on the PowerBook 3400 and 2400. + * + * Copyright (C) 1998 Paul Mackerras. + * + * Various evolutions by Benjamin Herrenschmidt & Henry Worth + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/stddef.h> +#include <linux/init.h> +#include <linux/kthread.h> +#include <linux/mutex.h> +#include <linux/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_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; + const struct mb_ops* ops; + int index; + int cached_gpio; + int sleeping; + int user_lock; + struct mutex lock; +}; + +#define MAX_BAYS 2 + +static struct media_bay_info media_bays[MAX_BAYS]; +static int media_bay_count = 0; + +/* + * 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 calling into the driver + */ +#define MB_IDE_WAIT 1000 + +/* + * 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_up, /* Media bay full */ + mb_powering_down /* Powering down (avoid too fast down/up) */ +}; + +#define MB_POWER_SOUND 0x08 +#define MB_POWER_FLOPPY 0x04 +#define MB_POWER_ATA 0x02 +#define MB_POWER_PCI 0x01 +#define MB_POWER_OFF 0x00 + +/* + * Functions for polling content of media bay + */ + +static u8 +ohare_mb_content(struct media_bay_info *bay) +{ + return (MB_IN32(bay, OHARE_MBCR) >> 12) & 7; +} + +static u8 +heathrow_mb_content(struct media_bay_info *bay) +{ + return (MB_IN32(bay, HEATHROW_MBCR) >> 12) & 7; +} + +static u8 +keylargo_mb_content(struct media_bay_info *bay) +{ + int new_gpio; + + new_gpio = MB_IN8(bay, KL_GPIO_MEDIABAY_IRQ) & KEYLARGO_GPIO_INPUT_DATA; + if (new_gpio) { + bay->cached_gpio = new_gpio; + return MB_NO; + } else if (bay->cached_gpio != new_gpio) { + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE); + (void)MB_IN32(bay, KEYLARGO_MBCR); + udelay(5); + MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F); + (void)MB_IN32(bay, KEYLARGO_MBCR); + udelay(5); + bay->cached_gpio = new_gpio; + } + return (MB_IN32(bay, KEYLARGO_MBCR) >> 4) & 7; +} + +/* + * Functions for powering up/down the bay, puts the bay device + * into reset state as well + */ + +static void +ohare_mb_power(struct media_bay_info* bay, int on_off) +{ + if (on_off) { + /* Power up device, assert it's reset line */ + MB_BIC(bay, OHARE_FCR, OH_BAY_RESET_N); + MB_BIC(bay, OHARE_FCR, OH_BAY_POWER_N); + } else { + /* Disable all devices */ + MB_BIC(bay, OHARE_FCR, OH_BAY_DEV_MASK); + MB_BIC(bay, OHARE_FCR, OH_FLOPPY_ENABLE); + /* Cut power from bay, release reset line */ + MB_BIS(bay, OHARE_FCR, OH_BAY_POWER_N); + MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N); + MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N); + } + MB_BIC(bay, OHARE_MBCR, 0x00000F00); +} + +static void +heathrow_mb_power(struct media_bay_info* bay, int on_off) +{ + if (on_off) { + /* Power up device, assert it's reset line */ + MB_BIC(bay, HEATHROW_FCR, HRW_BAY_RESET_N); + MB_BIC(bay, HEATHROW_FCR, HRW_BAY_POWER_N); + } else { + /* Disable all devices */ + MB_BIC(bay, HEATHROW_FCR, HRW_BAY_DEV_MASK); + MB_BIC(bay, HEATHROW_FCR, HRW_SWIM_ENABLE); + /* Cut power from bay, release reset line */ + MB_BIS(bay, HEATHROW_FCR, HRW_BAY_POWER_N); + MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N); + MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N); + } + MB_BIC(bay, HEATHROW_MBCR, 0x00000F00); +} + +static void +keylargo_mb_power(struct media_bay_info* bay, int on_off) +{ + if (on_off) { + /* Power up device, assert it's reset line */ + MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET); + MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER); + } else { + /* Disable all devices */ + MB_BIC(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_MASK); + MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE); + /* Cut power from bay, release reset line */ + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_POWER); + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET); + MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N); + } + MB_BIC(bay, KEYLARGO_MBCR, 0x0000000F); +} + +/* + * Functions for configuring the media bay for a given type of device, + * enable the related busses + */ + +static int +ohare_mb_setup_bus(struct media_bay_info* bay, u8 device_id) +{ + switch(device_id) { + case MB_FD: + case MB_FD1: + MB_BIS(bay, OHARE_FCR, OH_BAY_FLOPPY_ENABLE); + MB_BIS(bay, OHARE_FCR, OH_FLOPPY_ENABLE); + return 0; + case MB_CD: + MB_BIC(bay, OHARE_FCR, OH_IDE1_RESET_N); + MB_BIS(bay, OHARE_FCR, OH_BAY_IDE_ENABLE); + return 0; + case MB_PCI: + MB_BIS(bay, OHARE_FCR, OH_BAY_PCI_ENABLE); + return 0; + } + return -ENODEV; +} + +static int +heathrow_mb_setup_bus(struct media_bay_info* bay, u8 device_id) +{ + switch(device_id) { + case MB_FD: + case MB_FD1: + MB_BIS(bay, HEATHROW_FCR, HRW_BAY_FLOPPY_ENABLE); + MB_BIS(bay, HEATHROW_FCR, HRW_SWIM_ENABLE); + return 0; + case MB_CD: + MB_BIC(bay, HEATHROW_FCR, HRW_IDE1_RESET_N); + MB_BIS(bay, HEATHROW_FCR, HRW_BAY_IDE_ENABLE); + return 0; + case MB_PCI: + MB_BIS(bay, HEATHROW_FCR, HRW_BAY_PCI_ENABLE); + return 0; + } + return -ENODEV; +} + +static int +keylargo_mb_setup_bus(struct media_bay_info* bay, u8 device_id) +{ + switch(device_id) { + case MB_CD: + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_IDE_ENABLE); + MB_BIC(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N); + MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_ENABLE); + return 0; + case MB_PCI: + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_PCI_ENABLE); + return 0; + case MB_SOUND: + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_SOUND_ENABLE); + return 0; + } + return -ENODEV; +} + +/* + * Functions for tweaking resets + */ + +static void +ohare_mb_un_reset(struct media_bay_info* bay) +{ + MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N); +} + +static void keylargo_mb_init(struct media_bay_info *bay) +{ + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE); +} + +static void heathrow_mb_un_reset(struct media_bay_info* bay) +{ + MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N); +} + +static void keylargo_mb_un_reset(struct media_bay_info* bay) +{ + MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET); +} + +static void ohare_mb_un_reset_ide(struct media_bay_info* bay) +{ + MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N); +} + +static void heathrow_mb_un_reset_ide(struct media_bay_info* bay) +{ + MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N); +} + +static void keylargo_mb_un_reset_ide(struct media_bay_info* bay) +{ + MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N); +} + +static inline void set_mb_power(struct media_bay_info* bay, int onoff) +{ + /* Power up up and assert the bay reset line */ + if (onoff) { + bay->ops->power(bay, 1); + bay->state = mb_powering_up; + pr_debug("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; + pr_debug("mediabay%d: powering down\n", bay->index); + } + bay->timer = msecs_to_jiffies(MB_POWER_DELAY); +} + +static void poll_media_bay(struct media_bay_info* bay) +{ + int id = bay->ops->content(bay); + + static char *mb_content_types[] = { + "a floppy drive", + "a floppy drive", + "an unsupported audio device", + "an ATA device", + "an unsupported PCI device", + "an unknown device", + }; + + if (id != bay->last_value) { + bay->last_value = id; + bay->value_count = 0; + return; + } + if (id == bay->content_id) + return; + + 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; + pr_debug("mediabay%d: forcing MB_NO\n", bay->index); + } + pr_debug("mediabay%d: switching to %d\n", bay->index, id); + set_mb_power(bay, id != MB_NO); + bay->content_id = id; + if (id >= MB_NO || id < 0) + printk(KERN_INFO "mediabay%d: Bay is now empty\n", bay->index); + else + printk(KERN_INFO "mediabay%d: Bay contains %s\n", + bay->index, mb_content_types[id]); + } +} + +int check_media_bay(struct macio_dev *baydev) +{ + struct media_bay_info* bay; + int id; + + if (baydev == NULL) + return MB_NO; + + /* This returns an instant snapshot, not locking, sine + * we may be called with the bay lock held. The resulting + * fuzzyness of the result if called at the wrong time is + * not actually a huge deal + */ + bay = macio_get_drvdata(baydev); + if (bay == NULL) + return MB_NO; + id = bay->content_id; + if (bay->state != mb_up) + return MB_NO; + if (id == MB_FD1) + return MB_FD; + return id; +} +EXPORT_SYMBOL_GPL(check_media_bay); + +void lock_media_bay(struct macio_dev *baydev) +{ + struct media_bay_info* bay; + + if (baydev == NULL) + return; + bay = macio_get_drvdata(baydev); + if (bay == NULL) + return; + mutex_lock(&bay->lock); + bay->user_lock = 1; +} +EXPORT_SYMBOL_GPL(lock_media_bay); + +void unlock_media_bay(struct macio_dev *baydev) +{ + struct media_bay_info* bay; + + if (baydev == NULL) + return; + bay = macio_get_drvdata(baydev); + if (bay == NULL) + return; + if (bay->user_lock) { + bay->user_lock = 0; + mutex_unlock(&bay->lock); + } +} +EXPORT_SYMBOL_GPL(unlock_media_bay); + +static int mb_broadcast_hotplug(struct device *dev, void *data) +{ + struct media_bay_info* bay = data; + struct macio_dev *mdev; + struct macio_driver *drv; + int state; + + if (dev->bus != &macio_bus_type) + return 0; + + state = bay->state == mb_up ? bay->content_id : MB_NO; + if (state == MB_FD1) + state = MB_FD; + mdev = to_macio_device(dev); + drv = to_macio_driver(dev->driver); + if (dev->driver && drv->mediabay_event) + drv->mediabay_event(mdev, state); + return 0; +} + +static void media_bay_step(int i) +{ + struct media_bay_info* bay = &media_bays[i]; + + /* We don't poll when powering down */ + if (bay->state != mb_powering_down) + poll_media_bay(bay); + + /* If timer expired run state machine */ + if (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) { + pr_debug("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; + pr_debug("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; + pr_debug("mediabay%d: releasing bay reset (kind:%d)\n", + i, bay->content_id); + break; + case mb_resetting: + if (bay->content_id != MB_CD) { + pr_debug("mediabay%d: bay is up (kind:%d)\n", i, + bay->content_id); + bay->state = mb_up; + device_for_each_child(&bay->mdev->ofdev.dev, + bay, mb_broadcast_hotplug); + break; + } + pr_debug("mediabay%d: releasing ATA 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; + break; + + case mb_ide_resetting: + pr_debug("mediabay%d: bay is up (kind:%d)\n", i, bay->content_id); + bay->state = mb_up; + device_for_each_child(&bay->mdev->ofdev.dev, + bay, mb_broadcast_hotplug); + break; + + case mb_powering_down: + bay->state = mb_empty; + device_for_each_child(&bay->mdev->ofdev.dev, + bay, mb_broadcast_hotplug); + pr_debug("mediabay%d: end of power down\n", i); + break; + } +} + +/* + * This procedure runs as a kernel thread to poll the media bay + * once each tick and register and unregister the IDE interface + * with the IDE driver. It needs to be a thread because + * ide_register can't be called from interrupt context. + */ +static int media_bay_task(void *x) +{ + int i; + + while (!kthread_should_stop()) { + for (i = 0; i < media_bay_count; ++i) { + mutex_lock(&media_bays[i].lock); + if (!media_bays[i].sleeping) + media_bay_step(i); + mutex_unlock(&media_bays[i].lock); + } + + msleep_interruptible(MB_POLL_DELAY); + } + return 0; +} + +static int media_bay_attach(struct macio_dev *mdev, + const struct of_device_id *match) +{ + struct media_bay_info* bay; + u32 __iomem *regbase; + struct device_node *ofnode; + unsigned long base; + int i; + + ofnode = mdev->ofdev.dev.of_node; + + if (macio_resource_count(mdev) < 1) + return -ENODEV; + if (macio_request_resources(mdev, "media-bay")) + return -EBUSY; + /* Media bay registers are located at the beginning of the + * mac-io chip, for now, we trick and align down the first + * resource passed in + */ + base = macio_resource_start(mdev, 0) & 0xffff0000u; + regbase = (u32 __iomem *)ioremap(base, 0x100); + if (regbase == NULL) { + macio_release_resources(mdev); + return -ENOMEM; + } + + i = media_bay_count++; + bay = &media_bays[i]; + bay->mdev = mdev; + bay->base = regbase; + bay->index = i; + bay->ops = match->data; + bay->sleeping = 0; + mutex_init(&bay->lock); + + /* Init HW probing */ + if (bay->ops->init) + bay->ops->init(bay); + + printk(KERN_INFO "mediabay%d: Registered %s media-bay\n", i, bay->ops->name); + + /* Force an immediate detect */ + set_mb_power(bay, 0); + msleep(MB_POWER_DELAY); + bay->content_id = MB_NO; + bay->last_value = bay->ops->content(bay); + bay->value_count = msecs_to_jiffies(MB_STABLE_DELAY); + bay->state = mb_empty; + + /* Mark us ready by filling our mdev data */ + macio_set_drvdata(mdev, bay); + + /* Startup kernel thread */ + if (i == 0) + kthread_run(media_bay_task, NULL, "media-bay"); + + return 0; + +} + +static int media_bay_suspend(struct macio_dev *mdev, pm_message_t state) +{ + struct media_bay_info *bay = macio_get_drvdata(mdev); + + if (state.event != mdev->ofdev.dev.power.power_state.event + && (state.event & PM_EVENT_SLEEP)) { + mutex_lock(&bay->lock); + bay->sleeping = 1; + set_mb_power(bay, 0); + mutex_unlock(&bay->lock); + msleep(MB_POLL_DELAY); + mdev->ofdev.dev.power.power_state = state; + } + return 0; +} + +static int media_bay_resume(struct macio_dev *mdev) +{ + struct media_bay_info *bay = macio_get_drvdata(mdev); + + if (mdev->ofdev.dev.power.power_state.event != PM_EVENT_ON) { + mdev->ofdev.dev.power.power_state = PMSG_ON; + + /* We re-enable the bay using it's previous content + only if it did not change. Note those bozo timings, + they seem to help the 3400 get it right. + */ + /* Force MB power to 0 */ + mutex_lock(&bay->lock); + set_mb_power(bay, 0); + msleep(MB_POWER_DELAY); + if (bay->ops->content(bay) != bay->content_id) { + printk("mediabay%d: Content changed during sleep...\n", bay->index); + mutex_unlock(&bay->lock); + return 0; + } + set_mb_power(bay, 1); + bay->last_value = bay->content_id; + bay->value_count = msecs_to_jiffies(MB_STABLE_DELAY); + bay->timer = msecs_to_jiffies(MB_POWER_DELAY); + do { + msleep(MB_POLL_DELAY); + media_bay_step(bay->index); + } while((bay->state != mb_empty) && + (bay->state != mb_up)); + bay->sleeping = 0; + mutex_unlock(&bay->lock); + } + return 0; +} + + +/* Definitions of "ops" structures. + */ +static const struct mb_ops ohare_mb_ops = { + .name = "Ohare", + .content = ohare_mb_content, + .power = ohare_mb_power, + .setup_bus = ohare_mb_setup_bus, + .un_reset = ohare_mb_un_reset, + .un_reset_ide = ohare_mb_un_reset_ide, +}; + +static const struct mb_ops heathrow_mb_ops = { + .name = "Heathrow", + .content = heathrow_mb_content, + .power = heathrow_mb_power, + .setup_bus = heathrow_mb_setup_bus, + .un_reset = heathrow_mb_un_reset, + .un_reset_ide = heathrow_mb_un_reset_ide, +}; + +static const struct mb_ops keylargo_mb_ops = { + .name = "KeyLargo", + .init = keylargo_mb_init, + .content = keylargo_mb_content, + .power = keylargo_mb_power, + .setup_bus = keylargo_mb_setup_bus, + .un_reset = keylargo_mb_un_reset, + .un_reset_ide = keylargo_mb_un_reset_ide, +}; + +/* + * It seems that the bit for the media-bay interrupt in the IRQ_LEVEL + * register is always set when there is something in the media bay. + * This causes problems for the interrupt code if we attach an interrupt + * handler to the media-bay interrupt, because it tends to go into + * an infinite loop calling the media bay interrupt handler. + * Therefore we do it all by polling the media bay once each tick. + */ + +static const struct of_device_id media_bay_match[] = +{ + { + .name = "media-bay", + .compatible = "keylargo-media-bay", + .data = &keylargo_mb_ops, + }, + { + .name = "media-bay", + .compatible = "heathrow-media-bay", + .data = &heathrow_mb_ops, + }, + { + .name = "media-bay", + .compatible = "ohare-media-bay", + .data = &ohare_mb_ops, + }, + {}, +}; + +static struct macio_driver media_bay_driver = +{ + .driver = { + .name = "media-bay", + .of_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; + } + if (!machine_is(powermac)) + return 0; + + macio_register_driver(&media_bay_driver); + + return 0; +} + +device_initcall(media_bay_init); diff --git a/drivers/macintosh/rack-meter.c b/drivers/macintosh/rack-meter.c new file mode 100644 index 000000000..c28893e41 --- /dev/null +++ b/drivers/macintosh/rack-meter.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RackMac vu-meter driver + * + * (c) Copyright 2006 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Support the CPU-meter LEDs of the Xserve G5 + * + * TODO: Implement PWM to do variable intensity and provide userland + * interface for fun. Also, the CPU-meter could be made nicer by being + * a bit less "immediate" but giving instead a more average load over + * time. Patches welcome :-) + */ +#undef DEBUG + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/dma-mapping.h> +#include <linux/kernel_stat.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <asm/io.h> +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#include <asm/dbdma.h> +#include <asm/macio.h> +#include <asm/keylargo.h> + +/* Number of samples in a sample buffer */ +#define SAMPLE_COUNT 256 + +/* CPU meter sampling rate in ms */ +#define CPU_SAMPLING_RATE 250 + +struct rackmeter_dma { + struct dbdma_cmd cmd[4] ____cacheline_aligned; + u32 mark ____cacheline_aligned; + u32 buf1[SAMPLE_COUNT] ____cacheline_aligned; + u32 buf2[SAMPLE_COUNT] ____cacheline_aligned; +} ____cacheline_aligned; + +struct rackmeter_cpu { + struct delayed_work sniffer; + struct rackmeter *rm; + u64 prev_wall; + u64 prev_idle; + int zero; +} ____cacheline_aligned; + +struct rackmeter { + struct macio_dev *mdev; + unsigned int irq; + struct device_node *i2s; + u8 *ubuf; + struct dbdma_regs __iomem *dma_regs; + void __iomem *i2s_regs; + dma_addr_t dma_buf_p; + struct rackmeter_dma *dma_buf_v; + int stale_irq; + struct rackmeter_cpu cpu[2]; + int paused; + struct mutex sem; +}; + +/* To be set as a tunable */ +static int rackmeter_ignore_nice; + +/* This GPIO is whacked by the OS X driver when initializing */ +#define RACKMETER_MAGIC_GPIO 0x78 + +/* This is copied from cpufreq_ondemand, maybe we should put it in + * a common header somewhere + */ +static inline u64 get_cpu_idle_time(unsigned int cpu) +{ + struct kernel_cpustat *kcpustat = &kcpustat_cpu(cpu); + u64 retval; + + retval = kcpustat->cpustat[CPUTIME_IDLE] + + kcpustat->cpustat[CPUTIME_IOWAIT]; + + if (rackmeter_ignore_nice) + retval += kcpustat_field(kcpustat, CPUTIME_NICE, cpu); + + return retval; +} + +static void rackmeter_setup_i2s(struct rackmeter *rm) +{ + struct macio_chip *macio = rm->mdev->bus->chip; + + /* First whack magic GPIO */ + pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, RACKMETER_MAGIC_GPIO, 5); + + + /* Call feature code to enable the sound channel and the proper + * clock sources + */ + pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, rm->i2s, 0, 1); + + /* Power i2s and stop i2s clock. We whack MacIO FCRs directly for now. + * This is a bit racy, thus we should add new platform functions to + * handle that. snd-aoa needs that too + */ + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_ENABLE); + MACIO_BIC(KEYLARGO_FCR1, KL1_I2S0_CLK_ENABLE_BIT); + (void)MACIO_IN32(KEYLARGO_FCR1); + udelay(10); + + /* Then setup i2s. For now, we use the same magic value that + * the OS X driver seems to use. We might want to play around + * with the clock divisors later + */ + out_le32(rm->i2s_regs + 0x10, 0x01fa0000); + (void)in_le32(rm->i2s_regs + 0x10); + udelay(10); + + /* Fully restart i2s*/ + MACIO_BIS(KEYLARGO_FCR1, KL1_I2S0_CELL_ENABLE | + KL1_I2S0_CLK_ENABLE_BIT); + (void)MACIO_IN32(KEYLARGO_FCR1); + udelay(10); +} + +static void rackmeter_set_default_pattern(struct rackmeter *rm) +{ + int i; + + for (i = 0; i < 16; i++) { + if (i < 8) + rm->ubuf[i] = (i & 1) * 255; + else + rm->ubuf[i] = ((~i) & 1) * 255; + } +} + +static void rackmeter_do_pause(struct rackmeter *rm, int pause) +{ + struct rackmeter_dma *rdma = rm->dma_buf_v; + + pr_debug("rackmeter: %s\n", pause ? "paused" : "started"); + + rm->paused = pause; + if (pause) { + DBDMA_DO_STOP(rm->dma_regs); + return; + } + memset(rdma->buf1, 0, sizeof(rdma->buf1)); + memset(rdma->buf2, 0, sizeof(rdma->buf2)); + + rm->dma_buf_v->mark = 0; + + mb(); + out_le32(&rm->dma_regs->cmdptr_hi, 0); + out_le32(&rm->dma_regs->cmdptr, rm->dma_buf_p); + out_le32(&rm->dma_regs->control, (RUN << 16) | RUN); +} + +static void rackmeter_setup_dbdma(struct rackmeter *rm) +{ + struct rackmeter_dma *db = rm->dma_buf_v; + struct dbdma_cmd *cmd = db->cmd; + + /* Make sure dbdma is reset */ + DBDMA_DO_RESET(rm->dma_regs); + + pr_debug("rackmeter: mark offset=0x%zx\n", + offsetof(struct rackmeter_dma, mark)); + pr_debug("rackmeter: buf1 offset=0x%zx\n", + offsetof(struct rackmeter_dma, buf1)); + pr_debug("rackmeter: buf2 offset=0x%zx\n", + offsetof(struct rackmeter_dma, buf2)); + + /* Prepare 4 dbdma commands for the 2 buffers */ + memset(cmd, 0, 4 * sizeof(struct dbdma_cmd)); + cmd->req_count = cpu_to_le16(4); + cmd->command = cpu_to_le16(STORE_WORD | INTR_ALWAYS | KEY_SYSTEM); + cmd->phy_addr = cpu_to_le32(rm->dma_buf_p + + offsetof(struct rackmeter_dma, mark)); + cmd->cmd_dep = cpu_to_le32(0x02000000); + cmd++; + + cmd->req_count = cpu_to_le16(SAMPLE_COUNT * 4); + cmd->command = cpu_to_le16(OUTPUT_MORE); + cmd->phy_addr = cpu_to_le32(rm->dma_buf_p + + offsetof(struct rackmeter_dma, buf1)); + cmd++; + + cmd->req_count = cpu_to_le16(4); + cmd->command = cpu_to_le16(STORE_WORD | INTR_ALWAYS | KEY_SYSTEM); + cmd->phy_addr = cpu_to_le32(rm->dma_buf_p + + offsetof(struct rackmeter_dma, mark)); + cmd->cmd_dep = cpu_to_le32(0x01000000); + cmd++; + + cmd->req_count = cpu_to_le16(SAMPLE_COUNT * 4); + cmd->command = cpu_to_le16(OUTPUT_MORE | BR_ALWAYS); + cmd->phy_addr = cpu_to_le32(rm->dma_buf_p + + offsetof(struct rackmeter_dma, buf2)); + cmd->cmd_dep = cpu_to_le32(rm->dma_buf_p); + + rackmeter_do_pause(rm, 0); +} + +static void rackmeter_do_timer(struct work_struct *work) +{ + struct rackmeter_cpu *rcpu = + container_of(work, struct rackmeter_cpu, sniffer.work); + struct rackmeter *rm = rcpu->rm; + unsigned int cpu = smp_processor_id(); + u64 cur_nsecs, total_idle_nsecs; + u64 total_nsecs, idle_nsecs; + int i, offset, load, cumm, pause; + + cur_nsecs = jiffies64_to_nsecs(get_jiffies_64()); + total_nsecs = cur_nsecs - rcpu->prev_wall; + rcpu->prev_wall = cur_nsecs; + + total_idle_nsecs = get_cpu_idle_time(cpu); + idle_nsecs = total_idle_nsecs - rcpu->prev_idle; + idle_nsecs = min(idle_nsecs, total_nsecs); + rcpu->prev_idle = total_idle_nsecs; + + /* We do a very dumb calculation to update the LEDs for now, + * we'll do better once we have actual PWM implemented + */ + load = div64_u64(9 * (total_nsecs - idle_nsecs), total_nsecs); + + offset = cpu << 3; + cumm = 0; + for (i = 0; i < 8; i++) { + u8 ub = (load > i) ? 0xff : 0; + rm->ubuf[i + offset] = ub; + cumm |= ub; + } + rcpu->zero = (cumm == 0); + + /* Now check if LEDs are all 0, we can stop DMA */ + pause = (rm->cpu[0].zero && rm->cpu[1].zero); + if (pause != rm->paused) { + mutex_lock(&rm->sem); + pause = (rm->cpu[0].zero && rm->cpu[1].zero); + rackmeter_do_pause(rm, pause); + mutex_unlock(&rm->sem); + } + schedule_delayed_work_on(cpu, &rcpu->sniffer, + msecs_to_jiffies(CPU_SAMPLING_RATE)); +} + +static void rackmeter_init_cpu_sniffer(struct rackmeter *rm) +{ + unsigned int cpu; + + /* This driver works only with 1 or 2 CPUs numbered 0 and 1, + * but that's really all we have on Apple Xserve. It doesn't + * play very nice with CPU hotplug neither but we don't do that + * on those machines yet + */ + + rm->cpu[0].rm = rm; + INIT_DELAYED_WORK(&rm->cpu[0].sniffer, rackmeter_do_timer); + rm->cpu[1].rm = rm; + INIT_DELAYED_WORK(&rm->cpu[1].sniffer, rackmeter_do_timer); + + for_each_online_cpu(cpu) { + struct rackmeter_cpu *rcpu; + + if (cpu > 1) + continue; + rcpu = &rm->cpu[cpu]; + rcpu->prev_idle = get_cpu_idle_time(cpu); + rcpu->prev_wall = jiffies64_to_nsecs(get_jiffies_64()); + schedule_delayed_work_on(cpu, &rm->cpu[cpu].sniffer, + msecs_to_jiffies(CPU_SAMPLING_RATE)); + } +} + +static void rackmeter_stop_cpu_sniffer(struct rackmeter *rm) +{ + cancel_delayed_work_sync(&rm->cpu[0].sniffer); + cancel_delayed_work_sync(&rm->cpu[1].sniffer); +} + +static int rackmeter_setup(struct rackmeter *rm) +{ + pr_debug("rackmeter: setting up i2s..\n"); + rackmeter_setup_i2s(rm); + + pr_debug("rackmeter: setting up default pattern..\n"); + rackmeter_set_default_pattern(rm); + + pr_debug("rackmeter: setting up dbdma..\n"); + rackmeter_setup_dbdma(rm); + + pr_debug("rackmeter: start CPU measurements..\n"); + rackmeter_init_cpu_sniffer(rm); + + printk(KERN_INFO "RackMeter initialized\n"); + + return 0; +} + +/* XXX FIXME: No PWM yet, this is 0/1 */ +static u32 rackmeter_calc_sample(struct rackmeter *rm, unsigned int index) +{ + int led; + u32 sample = 0; + + for (led = 0; led < 16; led++) { + sample >>= 1; + sample |= ((rm->ubuf[led] >= 0x80) << 15); + } + return (sample << 17) | (sample >> 15); +} + +static irqreturn_t rackmeter_irq(int irq, void *arg) +{ + struct rackmeter *rm = arg; + struct rackmeter_dma *db = rm->dma_buf_v; + unsigned int mark, i; + u32 *buf; + + /* Flush PCI buffers with an MMIO read. Maybe we could actually + * check the status one day ... in case things go wrong, though + * this never happened to me + */ + (void)in_le32(&rm->dma_regs->status); + + /* Make sure the CPU gets us in order */ + rmb(); + + /* Read mark */ + mark = db->mark; + if (mark != 1 && mark != 2) { + printk(KERN_WARNING "rackmeter: Incorrect DMA mark 0x%08x\n", + mark); + /* We allow for 3 errors like that (stale DBDMA irqs) */ + if (++rm->stale_irq > 3) { + printk(KERN_ERR "rackmeter: Too many errors," + " stopping DMA\n"); + DBDMA_DO_RESET(rm->dma_regs); + } + return IRQ_HANDLED; + } + + /* Next buffer we need to fill is mark value */ + buf = mark == 1 ? db->buf1 : db->buf2; + + /* Fill it now. This routine converts the 8 bits depth sample array + * into the PWM bitmap for each LED. + */ + for (i = 0; i < SAMPLE_COUNT; i++) + buf[i] = rackmeter_calc_sample(rm, i); + + + return IRQ_HANDLED; +} + +static int rackmeter_probe(struct macio_dev* mdev, + const struct of_device_id *match) +{ + struct device_node *i2s = NULL, *np = NULL; + struct rackmeter *rm = NULL; + struct resource ri2s, rdma; + int rc = -ENODEV; + + pr_debug("rackmeter_probe()\n"); + + /* Get i2s-a node */ + for_each_child_of_node(mdev->ofdev.dev.of_node, i2s) + if (of_node_name_eq(i2s, "i2s-a")) + break; + + if (i2s == NULL) { + pr_debug(" i2s-a child not found\n"); + goto bail; + } + /* Get lightshow or virtual sound */ + for_each_child_of_node(i2s, np) { + if (of_node_name_eq(np, "lightshow")) + break; + if (of_node_name_eq(np, "sound") && + of_get_property(np, "virtual", NULL) != NULL) + break; + } + if (np == NULL) { + pr_debug(" lightshow or sound+virtual child not found\n"); + goto bail; + } + + /* Create and initialize our instance data */ + rm = kzalloc(sizeof(*rm), GFP_KERNEL); + if (rm == NULL) { + printk(KERN_ERR "rackmeter: failed to allocate memory !\n"); + rc = -ENOMEM; + goto bail_release; + } + rm->mdev = mdev; + rm->i2s = i2s; + mutex_init(&rm->sem); + dev_set_drvdata(&mdev->ofdev.dev, rm); + /* Check resources availability. We need at least resource 0 and 1 */ +#if 0 /* Use that when i2s-a is finally an mdev per-se */ + if (macio_resource_count(mdev) < 2 || macio_irq_count(mdev) < 2) { + printk(KERN_ERR + "rackmeter: found match but lacks resources: %pOF" + " (%d resources, %d interrupts)\n", + mdev->ofdev.dev.of_node); + rc = -ENXIO; + goto bail_free; + } + if (macio_request_resources(mdev, "rackmeter")) { + printk(KERN_ERR + "rackmeter: failed to request resources: %pOF\n", + mdev->ofdev.dev.of_node); + rc = -EBUSY; + goto bail_free; + } + rm->irq = macio_irq(mdev, 1); +#else + rm->irq = irq_of_parse_and_map(i2s, 1); + if (!rm->irq || + of_address_to_resource(i2s, 0, &ri2s) || + of_address_to_resource(i2s, 1, &rdma)) { + printk(KERN_ERR + "rackmeter: found match but lacks resources: %pOF", + mdev->ofdev.dev.of_node); + rc = -ENXIO; + goto bail_free; + } +#endif + + pr_debug(" i2s @0x%08x\n", (unsigned int)ri2s.start); + pr_debug(" dma @0x%08x\n", (unsigned int)rdma.start); + pr_debug(" irq %d\n", rm->irq); + + rm->ubuf = (u8 *)__get_free_page(GFP_KERNEL); + if (rm->ubuf == NULL) { + printk(KERN_ERR + "rackmeter: failed to allocate samples page !\n"); + rc = -ENOMEM; + goto bail_release; + } + + rm->dma_buf_v = dma_alloc_coherent(&macio_get_pci_dev(mdev)->dev, + sizeof(struct rackmeter_dma), + &rm->dma_buf_p, GFP_KERNEL); + if (rm->dma_buf_v == NULL) { + printk(KERN_ERR + "rackmeter: failed to allocate dma buffer !\n"); + rc = -ENOMEM; + goto bail_free_samples; + } +#if 0 + rm->i2s_regs = ioremap(macio_resource_start(mdev, 0), 0x1000); +#else + rm->i2s_regs = ioremap(ri2s.start, 0x1000); +#endif + if (rm->i2s_regs == NULL) { + printk(KERN_ERR + "rackmeter: failed to map i2s registers !\n"); + rc = -ENXIO; + goto bail_free_dma; + } +#if 0 + rm->dma_regs = ioremap(macio_resource_start(mdev, 1), 0x100); +#else + rm->dma_regs = ioremap(rdma.start, 0x100); +#endif + if (rm->dma_regs == NULL) { + printk(KERN_ERR + "rackmeter: failed to map dma registers !\n"); + rc = -ENXIO; + goto bail_unmap_i2s; + } + + rc = rackmeter_setup(rm); + if (rc) { + printk(KERN_ERR + "rackmeter: failed to initialize !\n"); + rc = -ENXIO; + goto bail_unmap_dma; + } + + rc = request_irq(rm->irq, rackmeter_irq, 0, "rackmeter", rm); + if (rc != 0) { + printk(KERN_ERR + "rackmeter: failed to request interrupt !\n"); + goto bail_stop_dma; + } + of_node_put(np); + return 0; + + bail_stop_dma: + DBDMA_DO_RESET(rm->dma_regs); + bail_unmap_dma: + iounmap(rm->dma_regs); + bail_unmap_i2s: + iounmap(rm->i2s_regs); + bail_free_dma: + dma_free_coherent(&macio_get_pci_dev(mdev)->dev, + sizeof(struct rackmeter_dma), + rm->dma_buf_v, rm->dma_buf_p); + bail_free_samples: + free_page((unsigned long)rm->ubuf); + bail_release: +#if 0 + macio_release_resources(mdev); +#endif + bail_free: + kfree(rm); + bail: + of_node_put(i2s); + of_node_put(np); + dev_set_drvdata(&mdev->ofdev.dev, NULL); + return rc; +} + +static int rackmeter_remove(struct macio_dev* mdev) +{ + struct rackmeter *rm = dev_get_drvdata(&mdev->ofdev.dev); + + /* Stop CPU sniffer timer & work queues */ + rackmeter_stop_cpu_sniffer(rm); + + /* Clear reference to private data */ + dev_set_drvdata(&mdev->ofdev.dev, NULL); + + /* Stop/reset dbdma */ + DBDMA_DO_RESET(rm->dma_regs); + + /* Release the IRQ */ + free_irq(rm->irq, rm); + + /* Unmap registers */ + iounmap(rm->dma_regs); + iounmap(rm->i2s_regs); + + /* Free DMA */ + dma_free_coherent(&macio_get_pci_dev(mdev)->dev, + sizeof(struct rackmeter_dma), + rm->dma_buf_v, rm->dma_buf_p); + + /* Free samples */ + free_page((unsigned long)rm->ubuf); + +#if 0 + /* Release resources */ + macio_release_resources(mdev); +#endif + + /* Get rid of me */ + kfree(rm); + + return 0; +} + +static int rackmeter_shutdown(struct macio_dev* mdev) +{ + struct rackmeter *rm = dev_get_drvdata(&mdev->ofdev.dev); + + if (rm == NULL) + return -ENODEV; + + /* Stop CPU sniffer timer & work queues */ + rackmeter_stop_cpu_sniffer(rm); + + /* Stop/reset dbdma */ + DBDMA_DO_RESET(rm->dma_regs); + + return 0; +} + +static const struct of_device_id rackmeter_match[] = { + { .name = "i2s" }, + { } +}; +MODULE_DEVICE_TABLE(of, rackmeter_match); + +static struct macio_driver rackmeter_driver = { + .driver = { + .name = "rackmeter", + .owner = THIS_MODULE, + .of_match_table = rackmeter_match, + }, + .probe = rackmeter_probe, + .remove = rackmeter_remove, + .shutdown = rackmeter_shutdown, +}; + + +static int __init rackmeter_init(void) +{ + pr_debug("rackmeter_init()\n"); + + return macio_register_driver(&rackmeter_driver); +} + +static void __exit rackmeter_exit(void) +{ + pr_debug("rackmeter_exit()\n"); + + macio_unregister_driver(&rackmeter_driver); +} + +module_init(rackmeter_init); +module_exit(rackmeter_exit); + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("RackMeter: Support vu-meter on XServe front panel"); diff --git a/drivers/macintosh/smu.c b/drivers/macintosh/smu.c new file mode 100644 index 000000000..b495bfa77 --- /dev/null +++ b/drivers/macintosh/smu.c @@ -0,0 +1,1337 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PowerMac G5 SMU driver + * + * Copyright 2004 J. Mayer <l_indien@magic.fr> + * Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + */ + +/* + * TODO: + * - maybe add timeout to commands ? + * - blocking version of time functions + * - polling version of i2c commands (including timer that works with + * interrupts off) + * - maybe avoid some data copies with i2c by directly using the smu cmd + * buffer and a lower level internal interface + * - understand SMU -> CPU events and implement reception of them via + * the userland interface + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/dmapool.h> +#include <linux/memblock.h> +#include <linux/vmalloc.h> +#include <linux/highmem.h> +#include <linux/jiffies.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> +#include <linux/completion.h> +#include <linux/miscdevice.h> +#include <linux/delay.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#include <asm/smu.h> +#include <asm/sections.h> +#include <linux/uaccess.h> + +#define VERSION "0.7" +#define AUTHOR "(c) 2005 Benjamin Herrenschmidt, IBM Corp." + +#undef DEBUG_SMU + +#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 + */ +#define SMU_MAX_DATA 254 + +struct smu_cmd_buf { + u8 cmd; + u8 length; + u8 data[SMU_MAX_DATA]; +}; + +struct smu_device { + spinlock_t lock; + struct device_node *of_node; + struct platform_device *of_dev; + int doorbell; /* doorbell gpio */ + u32 __iomem *db_buf; /* doorbell buffer */ + struct device_node *db_node; + unsigned int db_irq; + int msg; + struct device_node *msg_node; + unsigned int msg_irq; + struct smu_cmd_buf *cmd_buf; /* command buffer virtual */ + u32 cmd_buf_abs; /* command buffer absolute */ + struct list_head cmd_list; + struct smu_cmd *cmd_cur; /* pending command */ + int broken_nap; + struct list_head cmd_i2c_list; + struct smu_i2c_cmd *cmd_i2c_cur; /* pending i2c command */ + struct timer_list i2c_timer; +}; + +/* + * I don't think there will ever be more than one SMU, so + * for now, just hard code that + */ +static DEFINE_MUTEX(smu_mutex); +static struct smu_device *smu; +static DEFINE_MUTEX(smu_part_access); +static int smu_irq_inited; +static unsigned long smu_cmdbuf_abs; + +static void smu_i2c_retry(struct timer_list *t); + +/* + * SMU driver low level stuff + */ + +static void smu_start_cmd(void) +{ + unsigned long faddr, fend; + struct smu_cmd *cmd; + + if (list_empty(&smu->cmd_list)) + return; + + /* Fetch first command in queue */ + cmd = list_entry(smu->cmd_list.next, struct smu_cmd, link); + smu->cmd_cur = cmd; + list_del(&cmd->link); + + DPRINTK("SMU: starting cmd %x, %d bytes data\n", cmd->cmd, + cmd->data_len); + DPRINTK("SMU: data buffer: %8ph\n", cmd->data_buf); + + /* Fill the SMU command buffer */ + smu->cmd_buf->cmd = cmd->cmd; + smu->cmd_buf->length = cmd->data_len; + memcpy(smu->cmd_buf->data, cmd->data_buf, cmd->data_len); + + /* Flush command and data to RAM */ + faddr = (unsigned long)smu->cmd_buf; + fend = faddr + smu->cmd_buf->length + 2; + flush_dcache_range(faddr, fend); + + + /* We also disable NAP mode for the duration of the command + * on U3 based machines. + * This is slightly racy as it can be written back to 1 by a sysctl + * but that never happens in practice. There seem to be an issue with + * U3 based machines such as the iMac G5 where napping for the + * whole duration of the command prevents the SMU from fetching it + * from memory. This might be related to the strange i2c based + * mechanism the SMU uses to access memory. + */ + if (smu->broken_nap) + powersave_nap = 0; + + /* 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(smu->cmd_buf_abs, smu->db_buf); + + /* Ring the SMU doorbell */ + pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, smu->doorbell, 4); +} + + +static irqreturn_t smu_db_intr(int irq, void *arg) +{ + unsigned long flags; + struct smu_cmd *cmd; + void (*done)(struct smu_cmd *cmd, void *misc) = NULL; + void *misc = NULL; + u8 gpio; + int rc = 0; + + /* SMU completed the command, well, we hope, let's make sure + * of it + */ + spin_lock_irqsave(&smu->lock, flags); + + gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell); + if ((gpio & 7) != 7) { + spin_unlock_irqrestore(&smu->lock, flags); + return IRQ_HANDLED; + } + + cmd = smu->cmd_cur; + smu->cmd_cur = NULL; + if (cmd == NULL) + goto bail; + + if (rc == 0) { + unsigned long faddr; + int reply_len; + u8 ack; + + /* CPU might have brought back the cache line, so we need + * to flush again before peeking at the SMU response. We + * flush the entire buffer for now as we haven't read the + * reply length (it's only 2 cache lines anyway) + */ + faddr = (unsigned long)smu->cmd_buf; + flush_dcache_range(faddr, faddr + 256); + + /* Now check ack */ + ack = (~cmd->cmd) & 0xff; + if (ack != smu->cmd_buf->cmd) { + DPRINTK("SMU: incorrect ack, want %x got %x\n", + ack, smu->cmd_buf->cmd); + rc = -EIO; + } + reply_len = rc == 0 ? smu->cmd_buf->length : 0; + DPRINTK("SMU: reply len: %d\n", reply_len); + if (reply_len > cmd->reply_len) { + printk(KERN_WARNING "SMU: reply buffer too small," + "got %d bytes for a %d bytes buffer\n", + reply_len, cmd->reply_len); + reply_len = cmd->reply_len; + } + cmd->reply_len = reply_len; + if (cmd->reply_buf && reply_len) + memcpy(cmd->reply_buf, smu->cmd_buf->data, reply_len); + } + + /* Now complete the command. Write status last in order as we lost + * ownership of the command structure as soon as it's no longer -1 + */ + done = cmd->done; + misc = cmd->misc; + mb(); + cmd->status = rc; + + /* Re-enable NAP mode */ + if (smu->broken_nap) + powersave_nap = 1; + bail: + /* Start next command if any */ + smu_start_cmd(); + spin_unlock_irqrestore(&smu->lock, flags); + + /* Call command completion handler if any */ + if (done) + done(cmd, misc); + + /* It's an edge interrupt, nothing to do */ + return IRQ_HANDLED; +} + + +static irqreturn_t smu_msg_intr(int irq, void *arg) +{ + /* I don't quite know what to do with this one, we seem to never + * receive it, so I suspect we have to arm it someway in the SMU + * to start getting events that way. + */ + + printk(KERN_INFO "SMU: message interrupt !\n"); + + /* It's an edge interrupt, nothing to do */ + return IRQ_HANDLED; +} + + +/* + * Queued command management. + * + */ + +int smu_queue_cmd(struct smu_cmd *cmd) +{ + unsigned long flags; + + if (smu == NULL) + return -ENODEV; + if (cmd->data_len > SMU_MAX_DATA || + cmd->reply_len > SMU_MAX_DATA) + return -EINVAL; + + cmd->status = 1; + spin_lock_irqsave(&smu->lock, flags); + list_add_tail(&cmd->link, &smu->cmd_list); + if (smu->cmd_cur == NULL) + smu_start_cmd(); + spin_unlock_irqrestore(&smu->lock, flags); + + /* Workaround for early calls when irq isn't available */ + if (!smu_irq_inited || !smu->db_irq) + smu_spinwait_cmd(cmd); + + return 0; +} +EXPORT_SYMBOL(smu_queue_cmd); + + +int smu_queue_simple(struct smu_simple_cmd *scmd, u8 command, + unsigned int data_len, + void (*done)(struct smu_cmd *cmd, void *misc), + void *misc, ...) +{ + struct smu_cmd *cmd = &scmd->cmd; + va_list list; + int i; + + if (data_len > sizeof(scmd->buffer)) + return -EINVAL; + + memset(scmd, 0, sizeof(*scmd)); + cmd->cmd = command; + cmd->data_len = data_len; + cmd->data_buf = scmd->buffer; + cmd->reply_len = sizeof(scmd->buffer); + cmd->reply_buf = scmd->buffer; + cmd->done = done; + cmd->misc = misc; + + va_start(list, misc); + for (i = 0; i < data_len; ++i) + scmd->buffer[i] = (u8)va_arg(list, int); + va_end(list); + + return smu_queue_cmd(cmd); +} +EXPORT_SYMBOL(smu_queue_simple); + + +void smu_poll(void) +{ + u8 gpio; + + if (smu == NULL) + return; + + gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, smu->doorbell); + if ((gpio & 7) == 7) + smu_db_intr(smu->db_irq, smu); +} +EXPORT_SYMBOL(smu_poll); + + +void smu_done_complete(struct smu_cmd *cmd, void *misc) +{ + struct completion *comp = misc; + + complete(comp); +} +EXPORT_SYMBOL(smu_done_complete); + + +void smu_spinwait_cmd(struct smu_cmd *cmd) +{ + while(cmd->status == 1) + smu_poll(); +} +EXPORT_SYMBOL(smu_spinwait_cmd); + + +/* 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); +} + + +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); +} + + +int smu_get_rtc_time(struct rtc_time *time, int spinwait) +{ + struct smu_simple_cmd cmd; + int rc; + + if (smu == NULL) + return -ENODEV; + + memset(time, 0, sizeof(struct rtc_time)); + rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 1, NULL, NULL, + SMU_CMD_RTC_GET_DATETIME); + if (rc) + return rc; + smu_spinwait_simple(&cmd); + + time->tm_sec = bcd2hex(cmd.buffer[0]); + time->tm_min = bcd2hex(cmd.buffer[1]); + time->tm_hour = bcd2hex(cmd.buffer[2]); + time->tm_wday = bcd2hex(cmd.buffer[3]); + time->tm_mday = bcd2hex(cmd.buffer[4]); + time->tm_mon = bcd2hex(cmd.buffer[5]) - 1; + time->tm_year = bcd2hex(cmd.buffer[6]) + 100; + + return 0; +} + + +int smu_set_rtc_time(struct rtc_time *time, int spinwait) +{ + struct smu_simple_cmd cmd; + int rc; + + if (smu == NULL) + return -ENODEV; + + rc = smu_queue_simple(&cmd, SMU_CMD_RTC_COMMAND, 8, NULL, NULL, + SMU_CMD_RTC_SET_DATETIME, + hex2bcd(time->tm_sec), + hex2bcd(time->tm_min), + hex2bcd(time->tm_hour), + time->tm_wday, + hex2bcd(time->tm_mday), + hex2bcd(time->tm_mon) + 1, + hex2bcd(time->tm_year - 100)); + if (rc) + return rc; + smu_spinwait_simple(&cmd); + + return 0; +} + + +void smu_shutdown(void) +{ + struct smu_simple_cmd cmd; + + if (smu == NULL) + return; + + if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 9, NULL, NULL, + 'S', 'H', 'U', 'T', 'D', 'O', 'W', 'N', 0)) + return; + smu_spinwait_simple(&cmd); + for (;;) + ; +} + + +void smu_restart(void) +{ + struct smu_simple_cmd cmd; + + if (smu == NULL) + return; + + if (smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 8, NULL, NULL, + 'R', 'E', 'S', 'T', 'A', 'R', 'T', 0)) + return; + smu_spinwait_simple(&cmd); + for (;;) + ; +} + + +int smu_present(void) +{ + return smu != NULL; +} +EXPORT_SYMBOL(smu_present); + + +int __init smu_init (void) +{ + struct device_node *np; + const u32 *data; + int ret = 0; + + np = of_find_node_by_type(NULL, "smu"); + if (np == NULL) + return -ENODEV; + + printk(KERN_INFO "SMU: Driver %s %s\n", VERSION, AUTHOR); + + /* + * SMU based G5s need some memory below 2Gb. Thankfully this is + * called at a time where memblock is still available. + */ + smu_cmdbuf_abs = memblock_phys_alloc_range(4096, 4096, 0, 0x80000000UL); + if (smu_cmdbuf_abs == 0) { + printk(KERN_ERR "SMU: Command buffer allocation failed !\n"); + ret = -EINVAL; + goto fail_np; + } + + smu = memblock_alloc(sizeof(struct smu_device), SMP_CACHE_BYTES); + if (!smu) + panic("%s: Failed to allocate %zu bytes\n", __func__, + sizeof(struct smu_device)); + + spin_lock_init(&smu->lock); + INIT_LIST_HEAD(&smu->cmd_list); + INIT_LIST_HEAD(&smu->cmd_i2c_list); + smu->of_node = np; + smu->db_irq = 0; + smu->msg_irq = 0; + + /* 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 = __va(smu_cmdbuf_abs); + + smu->db_node = of_find_node_by_name(NULL, "smu-doorbell"); + if (smu->db_node == NULL) { + printk(KERN_ERR "SMU: Can't find doorbell GPIO !\n"); + ret = -ENXIO; + goto fail_bootmem; + } + data = of_get_property(smu->db_node, "reg", NULL); + if (data == NULL) { + printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n"); + ret = -ENXIO; + goto fail_db_node; + } + + /* 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->doorbell = *data; + if (smu->doorbell < 0x50) + smu->doorbell += 0x50; + + /* Now look for the smu-interrupt GPIO */ + do { + smu->msg_node = of_find_node_by_name(NULL, "smu-interrupt"); + if (smu->msg_node == NULL) + break; + data = of_get_property(smu->msg_node, "reg", NULL); + if (data == NULL) { + of_node_put(smu->msg_node); + smu->msg_node = NULL; + break; + } + smu->msg = *data; + if (smu->msg < 0x50) + smu->msg += 0x50; + } while(0); + + /* 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"); + ret = -ENXIO; + goto fail_msg_node; + } + + /* U3 has an issue with NAP mode when issuing SMU commands */ + smu->broken_nap = pmac_get_uninorth_variant() < 4; + if (smu->broken_nap) + printk(KERN_INFO "SMU: using NAP mode workaround\n"); + + sys_ctrler = SYS_CTRLER_SMU; + return 0; + +fail_msg_node: + of_node_put(smu->msg_node); +fail_db_node: + of_node_put(smu->db_node); +fail_bootmem: + memblock_free(smu, sizeof(struct smu_device)); + smu = NULL; +fail_np: + of_node_put(np); + return ret; +} + + +static int smu_late_init(void) +{ + if (!smu) + return 0; + + timer_setup(&smu->i2c_timer, smu_i2c_retry, 0); + + if (smu->db_node) { + smu->db_irq = irq_of_parse_and_map(smu->db_node, 0); + if (!smu->db_irq) + printk(KERN_ERR "smu: failed to map irq for node %pOF\n", + smu->db_node); + } + if (smu->msg_node) { + smu->msg_irq = irq_of_parse_and_map(smu->msg_node, 0); + if (!smu->msg_irq) + printk(KERN_ERR "smu: failed to map irq for node %pOF\n", + smu->msg_node); + } + + /* + * Try to request the interrupts + */ + + if (smu->db_irq) { + if (request_irq(smu->db_irq, smu_db_intr, + IRQF_SHARED, "SMU doorbell", smu) < 0) { + printk(KERN_WARNING "SMU: can't " + "request interrupt %d\n", + smu->db_irq); + smu->db_irq = 0; + } + } + + if (smu->msg_irq) { + if (request_irq(smu->msg_irq, smu_msg_intr, + IRQF_SHARED, "SMU message", smu) < 0) { + printk(KERN_WARNING "SMU: can't " + "request interrupt %d\n", + smu->msg_irq); + smu->msg_irq = 0; + } + } + + smu_irq_inited = 1; + return 0; +} +/* This has to be before arch_initcall as the low i2c stuff relies on the + * above having been done before we reach arch_initcalls + */ +core_initcall(smu_late_init); + +/* + * sysfs visibility + */ + +static void smu_expose_childs(struct work_struct *unused) +{ + struct device_node *np; + + for_each_child_of_node(smu->of_node, np) + if (of_device_is_compatible(np, "smu-sensors")) + of_platform_device_create(np, "smu-sensors", + &smu->of_dev->dev); +} + +static DECLARE_WORK(smu_expose_childs_work, smu_expose_childs); + +static int smu_platform_probe(struct platform_device* dev) +{ + if (!smu) + return -ENODEV; + smu->of_dev = dev; + + /* + * Ok, we are matched, now expose all i2c busses. We have to defer + * that unfortunately or it would deadlock inside the device model + */ + schedule_work(&smu_expose_childs_work); + + return 0; +} + +static const struct of_device_id smu_platform_match[] = +{ + { + .type = "smu", + }, + {}, +}; + +static struct platform_driver smu_of_platform_driver = +{ + .driver = { + .name = "smu", + .of_match_table = smu_platform_match, + }, + .probe = smu_platform_probe, +}; + +static int __init smu_init_sysfs(void) +{ + /* + * For now, we don't power manage machines with an SMU chip, + * I'm a bit too far from figuring out how that works with those + * new chipsets, but that will come back and bite us + */ + platform_driver_register(&smu_of_platform_driver); + return 0; +} + +device_initcall(smu_init_sysfs); + +struct platform_device *smu_get_ofdev(void) +{ + if (!smu) + return NULL; + return smu->of_dev; +} + +EXPORT_SYMBOL_GPL(smu_get_ofdev); + +/* + * i2c interface + */ + +static void smu_i2c_complete_command(struct smu_i2c_cmd *cmd, int fail) +{ + void (*done)(struct smu_i2c_cmd *cmd, void *misc) = cmd->done; + void *misc = cmd->misc; + unsigned long flags; + + /* Check for read case */ + if (!fail && cmd->read) { + if (cmd->pdata[0] < 1) + fail = 1; + else + memcpy(cmd->info.data, &cmd->pdata[1], + cmd->info.datalen); + } + + DPRINTK("SMU: completing, success: %d\n", !fail); + + /* Update status and mark no pending i2c command with lock + * held so nobody comes in while we dequeue an eventual + * pending next i2c command + */ + spin_lock_irqsave(&smu->lock, flags); + smu->cmd_i2c_cur = NULL; + wmb(); + cmd->status = fail ? -EIO : 0; + + /* Is there another i2c command waiting ? */ + if (!list_empty(&smu->cmd_i2c_list)) { + struct smu_i2c_cmd *newcmd; + + /* Fetch it, new current, remove from list */ + newcmd = list_entry(smu->cmd_i2c_list.next, + struct smu_i2c_cmd, link); + smu->cmd_i2c_cur = newcmd; + list_del(&cmd->link); + + /* Queue with low level smu */ + list_add_tail(&cmd->scmd.link, &smu->cmd_list); + if (smu->cmd_cur == NULL) + smu_start_cmd(); + } + spin_unlock_irqrestore(&smu->lock, flags); + + /* Call command completion handler if any */ + if (done) + done(cmd, misc); + +} + + +static void smu_i2c_retry(struct timer_list *unused) +{ + struct smu_i2c_cmd *cmd = smu->cmd_i2c_cur; + + DPRINTK("SMU: i2c failure, requeuing...\n"); + + /* requeue command simply by resetting reply_len */ + cmd->pdata[0] = 0xff; + cmd->scmd.reply_len = sizeof(cmd->pdata); + smu_queue_cmd(&cmd->scmd); +} + + +static void smu_i2c_low_completion(struct smu_cmd *scmd, void *misc) +{ + struct smu_i2c_cmd *cmd = misc; + int fail = 0; + + DPRINTK("SMU: i2c compl. stage=%d status=%x pdata[0]=%x rlen: %x\n", + cmd->stage, scmd->status, cmd->pdata[0], scmd->reply_len); + + /* Check for possible status */ + if (scmd->status < 0) + fail = 1; + else if (cmd->read) { + if (cmd->stage == 0) + fail = cmd->pdata[0] != 0; + else + fail = cmd->pdata[0] >= 0x80; + } else { + fail = cmd->pdata[0] != 0; + } + + /* Handle failures by requeuing command, after 5ms interval + */ + if (fail && --cmd->retries > 0) { + DPRINTK("SMU: i2c failure, starting timer...\n"); + BUG_ON(cmd != smu->cmd_i2c_cur); + if (!smu_irq_inited) { + mdelay(5); + smu_i2c_retry(NULL); + return; + } + mod_timer(&smu->i2c_timer, jiffies + msecs_to_jiffies(5)); + return; + } + + /* If failure or stage 1, command is complete */ + if (fail || cmd->stage != 0) { + smu_i2c_complete_command(cmd, fail); + return; + } + + DPRINTK("SMU: going to stage 1\n"); + + /* Ok, initial command complete, now poll status */ + scmd->reply_buf = cmd->pdata; + scmd->reply_len = sizeof(cmd->pdata); + scmd->data_buf = cmd->pdata; + scmd->data_len = 1; + cmd->pdata[0] = 0; + cmd->stage = 1; + cmd->retries = 20; + smu_queue_cmd(scmd); +} + + +int smu_queue_i2c(struct smu_i2c_cmd *cmd) +{ + unsigned long flags; + + if (smu == NULL) + return -ENODEV; + + /* Fill most fields of scmd */ + cmd->scmd.cmd = SMU_CMD_I2C_COMMAND; + cmd->scmd.done = smu_i2c_low_completion; + cmd->scmd.misc = cmd; + cmd->scmd.reply_buf = cmd->pdata; + cmd->scmd.reply_len = sizeof(cmd->pdata); + cmd->scmd.data_buf = (u8 *)(char *)&cmd->info; + cmd->scmd.status = 1; + cmd->stage = 0; + cmd->pdata[0] = 0xff; + cmd->retries = 20; + cmd->status = 1; + + /* Check transfer type, sanitize some "info" fields + * based on transfer type and do more checking + */ + cmd->info.caddr = cmd->info.devaddr; + cmd->read = cmd->info.devaddr & 0x01; + switch(cmd->info.type) { + case SMU_I2C_TRANSFER_SIMPLE: + cmd->info.sublen = 0; + memset(cmd->info.subaddr, 0, sizeof(cmd->info.subaddr)); + break; + case SMU_I2C_TRANSFER_COMBINED: + cmd->info.devaddr &= 0xfe; + fallthrough; + case SMU_I2C_TRANSFER_STDSUB: + if (cmd->info.sublen > 3) + return -EINVAL; + break; + default: + return -EINVAL; + } + + /* Finish setting up command based on transfer direction + */ + if (cmd->read) { + if (cmd->info.datalen > SMU_I2C_READ_MAX) + return -EINVAL; + memset(cmd->info.data, 0xff, cmd->info.datalen); + cmd->scmd.data_len = 9; + } else { + if (cmd->info.datalen > SMU_I2C_WRITE_MAX) + return -EINVAL; + cmd->scmd.data_len = 9 + cmd->info.datalen; + } + + DPRINTK("SMU: i2c enqueuing command\n"); + DPRINTK("SMU: %s, len=%d bus=%x addr=%x sub0=%x type=%x\n", + cmd->read ? "read" : "write", cmd->info.datalen, + cmd->info.bus, cmd->info.caddr, + cmd->info.subaddr[0], cmd->info.type); + + + /* Enqueue command in i2c list, and if empty, enqueue also in + * main command list + */ + spin_lock_irqsave(&smu->lock, flags); + if (smu->cmd_i2c_cur == NULL) { + smu->cmd_i2c_cur = cmd; + list_add_tail(&cmd->scmd.link, &smu->cmd_list); + if (smu->cmd_cur == NULL) + smu_start_cmd(); + } else + list_add_tail(&cmd->link, &smu->cmd_i2c_list); + spin_unlock_irqrestore(&smu->lock, flags); + + return 0; +} + +/* + * Handling of "partitions" + */ + +static int smu_read_datablock(u8 *dest, unsigned int addr, unsigned int len) +{ + DECLARE_COMPLETION_ONSTACK(comp); + unsigned int chunk; + struct smu_cmd cmd; + int rc; + u8 params[8]; + + /* We currently use a chunk size of 0xe. We could check the + * SMU firmware version and use bigger sizes though + */ + chunk = 0xe; + + while (len) { + unsigned int clen = min(len, chunk); + + cmd.cmd = SMU_CMD_MISC_ee_COMMAND; + cmd.data_len = 7; + cmd.data_buf = params; + cmd.reply_len = chunk; + cmd.reply_buf = dest; + cmd.done = smu_done_complete; + cmd.misc = ∁ + params[0] = SMU_CMD_MISC_ee_GET_DATABLOCK_REC; + params[1] = 0x4; + *((u32 *)¶ms[2]) = addr; + params[6] = clen; + + rc = smu_queue_cmd(&cmd); + if (rc) + return rc; + wait_for_completion(&comp); + if (cmd.status != 0) + return rc; + if (cmd.reply_len != clen) { + printk(KERN_DEBUG "SMU: short read in " + "smu_read_datablock, got: %d, want: %d\n", + cmd.reply_len, clen); + return -EIO; + } + len -= clen; + addr += clen; + dest += clen; + } + return 0; +} + +static struct smu_sdbp_header *smu_create_sdb_partition(int id) +{ + DECLARE_COMPLETION_ONSTACK(comp); + struct smu_simple_cmd cmd; + unsigned int addr, len, tlen; + struct smu_sdbp_header *hdr; + struct property *prop; + + /* First query the partition info */ + DPRINTK("SMU: Query partition infos ... (irq=%d)\n", smu->db_irq); + smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2, + smu_done_complete, &comp, + SMU_CMD_PARTITION_LATEST, id); + wait_for_completion(&comp); + DPRINTK("SMU: done, status: %d, reply_len: %d\n", + cmd.cmd.status, cmd.cmd.reply_len); + + /* Partition doesn't exist (or other error) */ + if (cmd.cmd.status != 0 || cmd.cmd.reply_len != 6) + return NULL; + + /* Fetch address and length from reply */ + addr = *((u16 *)cmd.buffer); + len = cmd.buffer[3] << 2; + /* Calucluate total length to allocate, including the 17 bytes + * for "sdb-partition-XX" that we append at the end of the buffer + */ + tlen = sizeof(struct property) + len + 18; + + prop = kzalloc(tlen, GFP_KERNEL); + if (prop == NULL) + return NULL; + hdr = (struct smu_sdbp_header *)(prop + 1); + prop->name = ((char *)prop) + tlen - 18; + sprintf(prop->name, "sdb-partition-%02x", id); + prop->length = len; + prop->value = hdr; + prop->next = NULL; + + /* Read the datablock */ + if (smu_read_datablock((u8 *)hdr, addr, len)) { + printk(KERN_DEBUG "SMU: datablock read failed while reading " + "partition %02x !\n", id); + goto failure; + } + + /* Got it, check a few things and create the property */ + if (hdr->id != id) { + printk(KERN_DEBUG "SMU: Reading partition %02x and got " + "%02x !\n", id, hdr->id); + goto failure; + } + if (of_add_property(smu->of_node, prop)) { + printk(KERN_DEBUG "SMU: Failed creating sdb-partition-%02x " + "property !\n", id); + goto failure; + } + + return hdr; + failure: + kfree(prop); + return NULL; +} + +/* Note: Only allowed to return error code in pointers (using ERR_PTR) + * when interruptible is 1 + */ +static const struct smu_sdbp_header *__smu_get_sdb_partition(int id, + unsigned int *size, int interruptible) +{ + char pname[32]; + const struct smu_sdbp_header *part; + + if (!smu) + return NULL; + + sprintf(pname, "sdb-partition-%02x", id); + + DPRINTK("smu_get_sdb_partition(%02x)\n", id); + + if (interruptible) { + int rc; + rc = mutex_lock_interruptible(&smu_part_access); + if (rc) + return ERR_PTR(rc); + } else + mutex_lock(&smu_part_access); + + part = of_get_property(smu->of_node, pname, size); + if (part == NULL) { + DPRINTK("trying to extract from SMU ...\n"); + part = smu_create_sdb_partition(id); + if (part != NULL && size) + *size = part->len << 2; + } + mutex_unlock(&smu_part_access); + return part; +} + +const struct smu_sdbp_header *smu_get_sdb_partition(int id, unsigned int *size) +{ + return __smu_get_sdb_partition(id, size, 0); +} +EXPORT_SYMBOL(smu_get_sdb_partition); + + +/* + * Userland driver interface + */ + + +static LIST_HEAD(smu_clist); +static DEFINE_SPINLOCK(smu_clist_lock); + +enum smu_file_mode { + smu_file_commands, + smu_file_events, + smu_file_closing +}; + +struct smu_private +{ + struct list_head list; + enum smu_file_mode mode; + int busy; + struct smu_cmd cmd; + spinlock_t lock; + wait_queue_head_t wait; + u8 buffer[SMU_MAX_DATA]; +}; + + +static int smu_open(struct inode *inode, struct file *file) +{ + struct smu_private *pp; + unsigned long flags; + + pp = kzalloc(sizeof(struct smu_private), GFP_KERNEL); + if (!pp) + return -ENOMEM; + spin_lock_init(&pp->lock); + pp->mode = smu_file_commands; + init_waitqueue_head(&pp->wait); + + mutex_lock(&smu_mutex); + spin_lock_irqsave(&smu_clist_lock, flags); + list_add(&pp->list, &smu_clist); + spin_unlock_irqrestore(&smu_clist_lock, flags); + file->private_data = pp; + mutex_unlock(&smu_mutex); + + return 0; +} + + +static void smu_user_cmd_done(struct smu_cmd *cmd, void *misc) +{ + struct smu_private *pp = misc; + + wake_up_all(&pp->wait); +} + + +static ssize_t smu_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct smu_private *pp = file->private_data; + unsigned long flags; + struct smu_user_cmd_hdr hdr; + int rc = 0; + + if (pp->busy) + return -EBUSY; + else if (copy_from_user(&hdr, buf, sizeof(hdr))) + return -EFAULT; + else if (hdr.cmdtype == SMU_CMDTYPE_WANTS_EVENTS) { + pp->mode = smu_file_events; + return 0; + } else if (hdr.cmdtype == SMU_CMDTYPE_GET_PARTITION) { + const struct smu_sdbp_header *part; + part = __smu_get_sdb_partition(hdr.cmd, NULL, 1); + if (part == NULL) + return -EINVAL; + else if (IS_ERR(part)) + return PTR_ERR(part); + return 0; + } else if (hdr.cmdtype != SMU_CMDTYPE_SMU) + return -EINVAL; + else if (pp->mode != smu_file_commands) + return -EBADFD; + else if (hdr.data_len > SMU_MAX_DATA) + return -EINVAL; + + spin_lock_irqsave(&pp->lock, flags); + if (pp->busy) { + spin_unlock_irqrestore(&pp->lock, flags); + return -EBUSY; + } + pp->busy = 1; + pp->cmd.status = 1; + spin_unlock_irqrestore(&pp->lock, flags); + + if (copy_from_user(pp->buffer, buf + sizeof(hdr), hdr.data_len)) { + pp->busy = 0; + return -EFAULT; + } + + pp->cmd.cmd = hdr.cmd; + pp->cmd.data_len = hdr.data_len; + pp->cmd.reply_len = SMU_MAX_DATA; + pp->cmd.data_buf = pp->buffer; + pp->cmd.reply_buf = pp->buffer; + pp->cmd.done = smu_user_cmd_done; + pp->cmd.misc = pp; + rc = smu_queue_cmd(&pp->cmd); + if (rc < 0) + return rc; + return count; +} + + +static ssize_t smu_read_command(struct file *file, struct smu_private *pp, + char __user *buf, size_t count) +{ + DECLARE_WAITQUEUE(wait, current); + struct smu_user_reply_hdr hdr; + unsigned long flags; + int size, rc = 0; + + if (!pp->busy) + return 0; + if (count < sizeof(struct smu_user_reply_hdr)) + return -EOVERFLOW; + spin_lock_irqsave(&pp->lock, flags); + if (pp->cmd.status == 1) { + if (file->f_flags & O_NONBLOCK) { + spin_unlock_irqrestore(&pp->lock, flags); + return -EAGAIN; + } + add_wait_queue(&pp->wait, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + rc = 0; + if (pp->cmd.status != 1) + break; + rc = -ERESTARTSYS; + if (signal_pending(current)) + break; + spin_unlock_irqrestore(&pp->lock, flags); + schedule(); + spin_lock_irqsave(&pp->lock, flags); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&pp->wait, &wait); + } + spin_unlock_irqrestore(&pp->lock, flags); + if (rc) + return rc; + if (pp->cmd.status != 0) + pp->cmd.reply_len = 0; + size = sizeof(hdr) + pp->cmd.reply_len; + if (count < size) + size = count; + rc = size; + hdr.status = pp->cmd.status; + hdr.reply_len = pp->cmd.reply_len; + if (copy_to_user(buf, &hdr, sizeof(hdr))) + return -EFAULT; + size -= sizeof(hdr); + if (size && copy_to_user(buf + sizeof(hdr), pp->buffer, size)) + return -EFAULT; + pp->busy = 0; + + return rc; +} + + +static ssize_t smu_read_events(struct file *file, struct smu_private *pp, + char __user *buf, size_t count) +{ + /* Not implemented */ + msleep_interruptible(1000); + return 0; +} + + +static ssize_t smu_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct smu_private *pp = file->private_data; + + if (pp->mode == smu_file_commands) + return smu_read_command(file, pp, buf, count); + if (pp->mode == smu_file_events) + return smu_read_events(file, pp, buf, count); + + return -EBADFD; +} + +static __poll_t smu_fpoll(struct file *file, poll_table *wait) +{ + struct smu_private *pp = file->private_data; + __poll_t mask = 0; + unsigned long flags; + + if (!pp) + return 0; + + if (pp->mode == smu_file_commands) { + poll_wait(file, &pp->wait, wait); + + spin_lock_irqsave(&pp->lock, flags); + if (pp->busy && pp->cmd.status != 1) + mask |= EPOLLIN; + spin_unlock_irqrestore(&pp->lock, flags); + } + if (pp->mode == smu_file_events) { + /* Not yet implemented */ + } + return mask; +} + +static int smu_release(struct inode *inode, struct file *file) +{ + struct smu_private *pp = file->private_data; + unsigned long flags; + unsigned int busy; + + if (!pp) + return 0; + + file->private_data = NULL; + + /* Mark file as closing to avoid races with new request */ + spin_lock_irqsave(&pp->lock, flags); + pp->mode = smu_file_closing; + busy = pp->busy; + + /* Wait for any pending request to complete */ + if (busy && pp->cmd.status == 1) { + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(&pp->wait, &wait); + for (;;) { + set_current_state(TASK_UNINTERRUPTIBLE); + if (pp->cmd.status != 1) + break; + spin_unlock_irqrestore(&pp->lock, flags); + schedule(); + spin_lock_irqsave(&pp->lock, flags); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&pp->wait, &wait); + } + spin_unlock_irqrestore(&pp->lock, flags); + + spin_lock_irqsave(&smu_clist_lock, flags); + list_del(&pp->list); + spin_unlock_irqrestore(&smu_clist_lock, flags); + kfree(pp); + + return 0; +} + + +static const struct file_operations smu_device_fops = { + .llseek = no_llseek, + .read = smu_read, + .write = smu_write, + .poll = smu_fpoll, + .open = smu_open, + .release = smu_release, +}; + +static struct miscdevice pmu_device = { + MISC_DYNAMIC_MINOR, "smu", &smu_device_fops +}; + +static int smu_device_init(void) +{ + if (!smu) + return -ENODEV; + if (misc_register(&pmu_device) < 0) + printk(KERN_ERR "via-pmu: cannot register misc device.\n"); + return 0; +} +device_initcall(smu_device_init); diff --git a/drivers/macintosh/therm_adt746x.c b/drivers/macintosh/therm_adt746x.c new file mode 100644 index 000000000..b004ea2a1 --- /dev/null +++ b/drivers/macintosh/therm_adt746x.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 115254175ADT7467_pra.pdf and 3686221171167ADT7460_b.pdf + * https://www.onsemi.com/PowerSolutions/product.do?id=ADT7467 + * https://www.onsemi.com/PowerSolutions/product.do?id=ADT7460 + * + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/suspend.h> +#include <linux/kthread.h> +#include <linux/moduleparam.h> +#include <linux/freezer.h> +#include <linux/of_platform.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> + +#undef DEBUG + +#define CONFIG_REG 0x40 +#define MANUAL_MASK 0xe0 +#define AUTO_MASK 0x20 +#define INVERT_MASK 0x10 + +static u8 TEMP_REG[3] = {0x26, 0x25, 0x27}; /* local, sensor1, sensor2 */ +static u8 LIMIT_REG[3] = {0x6b, 0x6a, 0x6c}; /* local, sensor1, sensor2 */ +static u8 MANUAL_MODE[2] = {0x5c, 0x5d}; +static u8 REM_CONTROL[2] = {0x00, 0x40}; +static u8 FAN_SPEED[2] = {0x28, 0x2a}; +static u8 FAN_SPD_SET[2] = {0x30, 0x31}; + +static u8 default_limits_local[3] = {70, 50, 70}; /* local, sensor1, sensor2 */ +static u8 default_limits_chip[3] = {80, 65, 80}; /* local, sensor1, sensor2 */ +static const char *sensor_location[3] = { "?", "?", "?" }; + +static int limit_adjust; +static int fan_speed = -1; +static bool verbose; + +MODULE_AUTHOR("Colin Leroy <colin@colino.net>"); +MODULE_DESCRIPTION("Driver for ADT746x thermostat in iBook G4 and " + "Powerbook G4 Alu"); +MODULE_LICENSE("GPL"); + +module_param(limit_adjust, int, 0644); +MODULE_PARM_DESC(limit_adjust,"Adjust maximum temperatures (50 sensor1, 70 sensor2) " + "by N degrees."); + +module_param(fan_speed, int, 0644); +MODULE_PARM_DESC(fan_speed,"Specify starting fan speed (0-255) " + "(default 64)"); + +module_param(verbose, bool, 0); +MODULE_PARM_DESC(verbose,"Verbose log operations " + "(default 0)"); + +struct thermostat { + struct i2c_client *clt; + u8 temps[3]; + u8 cached_temp[3]; + u8 initial_limits[3]; + u8 limits[3]; + int last_speed[2]; + int last_var[2]; + int pwm_inv[2]; + struct task_struct *thread; + struct platform_device *pdev; + enum { + ADT7460, + ADT7467 + } type; +}; + +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 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 (th->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 (th->type == ADT7467 && fan == 1) + return; + + if (th->last_speed[fan] != speed) { + if (verbose) { + if (speed == -1) + printk(KERN_DEBUG "adt746x: Setting speed to automatic " + "for %s fan.\n", sensor_location[fan+1]); + else + printk(KERN_DEBUG "adt746x: Setting speed to %d " + "for %s fan.\n", speed, sensor_location[fan+1]); + } + } else + return; + + if (speed >= 0) { + manual = read_reg(th, MANUAL_MODE[fan]); + manual &= ~INVERT_MASK; + write_reg(th, MANUAL_MODE[fan], + manual | MANUAL_MASK | th->pwm_inv[fan]); + write_reg(th, FAN_SPD_SET[fan], speed); + } else { + /* back to automatic */ + if(th->type == ADT7460) { + manual = read_reg(th, + MANUAL_MODE[fan]) & (~MANUAL_MASK); + manual &= ~INVERT_MASK; + manual |= th->pwm_inv[fan]; + write_reg(th, + MANUAL_MODE[fan], manual|REM_CONTROL[fan]); + } else { + manual = read_reg(th, MANUAL_MODE[fan]); + manual &= ~INVERT_MASK; + manual |= th->pwm_inv[fan]; + write_reg(th, MANUAL_MODE[fan], manual&(~AUTO_MASK)); + } + } + + th->last_speed[fan] = speed; +} + +static void read_sensors(struct thermostat *th) +{ + int i = 0; + + for (i = 0; i < 3; i++) + th->temps[i] = read_reg(th, TEMP_REG[i]); +} + +#ifdef DEBUG +static void display_stats(struct thermostat *th) +{ + if (th->temps[0] != th->cached_temp[0] + || th->temps[1] != th->cached_temp[1] + || th->temps[2] != th->cached_temp[2]) { + printk(KERN_INFO "adt746x: Temperature infos:" + " thermostats: %d,%d,%d;" + " limits: %d,%d,%d;" + " fan speed: %d RPM\n", + th->temps[0], th->temps[1], th->temps[2], + th->limits[0], th->limits[1], th->limits[2], + read_fan_speed(th, FAN_SPEED[0])); + } + th->cached_temp[0] = th->temps[0]; + th->cached_temp[1] = th->temps[1]; + th->cached_temp[2] = th->temps[2]; +} +#endif + +static void update_fans_speed (struct thermostat *th) +{ + int lastvar = 0; /* last variation, for iBook */ + int i = 0; + + /* we don't care about local sensor, so we start at sensor 1 */ + for (i = 1; i < 3; i++) { + bool started = false; + int fan_number = (th->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 = true; + new_speed = fan_speed + ((var-1)*step); + + if (new_speed < fan_speed) + new_speed = fan_speed; + if (new_speed > 255) + new_speed = 255; + + if (verbose) + printk(KERN_DEBUG "adt746x: Setting fans speed to %d " + "(limit exceeded by %d on %s)\n", + new_speed, var, + sensor_location[fan_number+1]); + write_both_fan_speed(th, new_speed); + th->last_var[fan_number] = var; + } else if (var < -2) { + /* don't stop fan if sensor2 is cold and sensor1 is not + * so cold (lastvar >= -1) */ + if (i == 2 && lastvar < -1) { + if (th->last_speed[fan_number] != 0) + if (verbose) + printk(KERN_DEBUG "adt746x: Stopping " + "fans.\n"); + write_both_fan_speed(th, 0); + } + } + + lastvar = var; + + if (started) + return; /* we don't want to re-stop the fan + * if sensor1 is heating and sensor2 is not */ + } +} + +static int monitor_task(void *arg) +{ + struct thermostat* th = arg; + + set_freezable(); + while(!kthread_should_stop()) { + try_to_freeze(); + msleep_interruptible(2000); + +#ifndef DEBUG + if (fan_speed != -1) + read_sensors(th); +#else + read_sensors(th); +#endif + + if (fan_speed != -1) + update_fans_speed(th); + +#ifdef DEBUG + display_stats(th); +#endif + + } + + return 0; +} + +static void set_limit(struct thermostat *th, int i) +{ + /* Set sensor1 limit higher to avoid powerdowns */ + th->limits[i] = default_limits_chip[i] + limit_adjust; + write_reg(th, LIMIT_REG[i], th->limits[i]); + + /* set our limits to normal */ + th->limits[i] = default_limits_local[i] + limit_adjust; +} + +#define BUILD_SHOW_FUNC_INT(name, data) \ +static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct thermostat *th = dev_get_drvdata(dev); \ + return sprintf(buf, "%d\n", data); \ +} + +#define BUILD_SHOW_FUNC_INT_LITE(name, data) \ +static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%d\n", data); \ +} + +#define BUILD_SHOW_FUNC_STR(name, data) \ +static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%s\n", data); \ +} + +#define BUILD_SHOW_FUNC_FAN(name, data) \ +static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct thermostat *th = dev_get_drvdata(dev); \ + return sprintf(buf, "%d (%d rpm)\n", \ + th->last_speed[data], \ + read_fan_speed(th, FAN_SPEED[data]) \ + ); \ +} + +#define BUILD_STORE_FUNC_DEG(name, data) \ +static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \ +{ \ + struct thermostat *th = dev_get_drvdata(dev); \ + int val; \ + int i; \ + val = simple_strtol(buf, NULL, 10); \ + printk(KERN_INFO "Adjusting limits by %d degrees\n", val); \ + limit_adjust = val; \ + for (i=0; i < 3; i++) \ + set_limit(th, i); \ + return n; \ +} + +#define BUILD_STORE_FUNC_INT(name, data) \ +static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \ +{ \ + int val; \ + val = simple_strtol(buf, NULL, 10); \ + if (val < 0 || val > 255) \ + return -EINVAL; \ + printk(KERN_INFO "Setting specified fan speed to %d\n", val); \ + data = val; \ + return n; \ +} + +BUILD_SHOW_FUNC_INT(sensor1_temperature, (read_reg(th, TEMP_REG[1]))) +BUILD_SHOW_FUNC_INT(sensor2_temperature, (read_reg(th, TEMP_REG[2]))) +BUILD_SHOW_FUNC_INT(sensor1_limit, th->limits[1]) +BUILD_SHOW_FUNC_INT(sensor2_limit, th->limits[2]) +BUILD_SHOW_FUNC_STR(sensor1_location, sensor_location[1]) +BUILD_SHOW_FUNC_STR(sensor2_location, sensor_location[2]) + +BUILD_SHOW_FUNC_INT_LITE(specified_fan_speed, fan_speed) +BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed) + +BUILD_SHOW_FUNC_FAN(sensor1_fan_speed, 0) +BUILD_SHOW_FUNC_FAN(sensor2_fan_speed, 1) + +BUILD_SHOW_FUNC_INT_LITE(limit_adjust, limit_adjust) +BUILD_STORE_FUNC_DEG(limit_adjust, th) + +static DEVICE_ATTR(sensor1_temperature, S_IRUGO, + show_sensor1_temperature,NULL); +static DEVICE_ATTR(sensor2_temperature, S_IRUGO, + show_sensor2_temperature,NULL); +static DEVICE_ATTR(sensor1_limit, S_IRUGO, + show_sensor1_limit, NULL); +static DEVICE_ATTR(sensor2_limit, S_IRUGO, + show_sensor2_limit, NULL); +static DEVICE_ATTR(sensor1_location, S_IRUGO, + show_sensor1_location, NULL); +static DEVICE_ATTR(sensor2_location, S_IRUGO, + show_sensor2_location, NULL); + +static DEVICE_ATTR(specified_fan_speed, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, + show_specified_fan_speed,store_specified_fan_speed); + +static DEVICE_ATTR(sensor1_fan_speed, S_IRUGO, + show_sensor1_fan_speed, NULL); +static DEVICE_ATTR(sensor2_fan_speed, S_IRUGO, + show_sensor2_fan_speed, NULL); + +static DEVICE_ATTR(limit_adjust, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, + show_limit_adjust, store_limit_adjust); + +static void thermostat_create_files(struct thermostat *th) +{ + struct device_node *np = th->clt->dev.of_node; + struct device *dev; + int err; + + /* To maintain ABI compatibility with userspace, create + * the old style platform driver and attach the attributes + * to it here + */ + th->pdev = of_platform_device_create(np, "temperatures", NULL); + if (!th->pdev) + return; + dev = &th->pdev->dev; + dev_set_drvdata(dev, th); + err = device_create_file(dev, &dev_attr_sensor1_temperature); + err |= device_create_file(dev, &dev_attr_sensor2_temperature); + err |= device_create_file(dev, &dev_attr_sensor1_limit); + err |= device_create_file(dev, &dev_attr_sensor2_limit); + err |= device_create_file(dev, &dev_attr_sensor1_location); + err |= device_create_file(dev, &dev_attr_sensor2_location); + err |= device_create_file(dev, &dev_attr_limit_adjust); + err |= device_create_file(dev, &dev_attr_specified_fan_speed); + err |= device_create_file(dev, &dev_attr_sensor1_fan_speed); + if(th->type == ADT7460) + err |= device_create_file(dev, &dev_attr_sensor2_fan_speed); + if (err) + printk(KERN_WARNING + "Failed to create temperature attribute file(s).\n"); +} + +static void thermostat_remove_files(struct thermostat *th) +{ + struct device *dev; + + if (!th->pdev) + return; + dev = &th->pdev->dev; + device_remove_file(dev, &dev_attr_sensor1_temperature); + device_remove_file(dev, &dev_attr_sensor2_temperature); + device_remove_file(dev, &dev_attr_sensor1_limit); + device_remove_file(dev, &dev_attr_sensor2_limit); + device_remove_file(dev, &dev_attr_sensor1_location); + device_remove_file(dev, &dev_attr_sensor2_location); + device_remove_file(dev, &dev_attr_limit_adjust); + device_remove_file(dev, &dev_attr_specified_fan_speed); + device_remove_file(dev, &dev_attr_sensor1_fan_speed); + if (th->type == ADT7460) + device_remove_file(dev, &dev_attr_sensor2_fan_speed); + of_device_unregister(th->pdev); + +} + +static int probe_thermostat(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + struct thermostat* th; + const __be32 *prop; + int i, rc, vers, offset = 0; + + if (!np) + return -ENXIO; + prop = of_get_property(np, "hwsensor-params-version", NULL); + if (!prop) + return -ENXIO; + vers = be32_to_cpup(prop); + printk(KERN_INFO "adt746x: version %d (%ssupported)\n", + vers, vers == 1 ? "" : "un"); + if (vers != 1) + return -ENXIO; + + if (of_get_property(np, "hwsensor-location", NULL)) { + for (i = 0; i < 3; i++) { + sensor_location[i] = of_get_property(np, + "hwsensor-location", NULL) + offset; + + if (sensor_location[i] == NULL) + sensor_location[i] = ""; + + printk(KERN_INFO "sensor %d: %s\n", i, sensor_location[i]); + offset += strlen(sensor_location[i]) + 1; + } + } + + th = kzalloc(sizeof(struct thermostat), GFP_KERNEL); + if (!th) + return -ENOMEM; + + i2c_set_clientdata(client, th); + th->clt = client; + th->type = id->driver_data; + + rc = read_reg(th, CONFIG_REG); + if (rc < 0) { + dev_err(&client->dev, "Thermostat failed to read config!\n"); + kfree(th); + return -ENODEV; + } + + /* force manual control to start the fan quieter */ + if (fan_speed == -1) + fan_speed = 64; + + if (th->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]); + + /* record invert bit status because fw can corrupt it after suspend */ + th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK; + th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK; + + /* be sure to really write fan speed the first time */ + th->last_speed[0] = -2; + th->last_speed[1] = -2; + th->last_var[0] = -80; + th->last_var[1] = -80; + + if (fan_speed != -1) { + /* manual mode, stop fans */ + write_both_fan_speed(th, 0); + } else { + /* automatic mode */ + write_both_fan_speed(th, -1); + } + + th->thread = kthread_run(monitor_task, th, "kfand"); + if (th->thread == ERR_PTR(-ENOMEM)) { + printk(KERN_INFO "adt746x: Kthread creation failed\n"); + th->thread = NULL; + return -ENOMEM; + } + + thermostat_create_files(th); + + return 0; +} + +static void remove_thermostat(struct i2c_client *client) +{ + struct thermostat *th = i2c_get_clientdata(client); + int i; + + thermostat_remove_files(th); + + if (th->thread != NULL) + kthread_stop(th->thread); + + 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); + + kfree(th); +} + +static const struct i2c_device_id therm_adt746x_id[] = { + { "MAC,adt7460", ADT7460 }, + { "MAC,adt7467", ADT7467 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, therm_adt746x_id); + +static struct i2c_driver thermostat_driver = { + .driver = { + .name = "therm_adt746x", + }, + .probe = probe_thermostat, + .remove = remove_thermostat, + .id_table = therm_adt746x_id, +}; + +static int __init thermostat_init(void) +{ +#ifndef CONFIG_I2C_POWERMAC + request_module("i2c-powermac"); +#endif + + return i2c_add_driver(&thermostat_driver); +} + +static void __exit thermostat_exit(void) +{ + i2c_del_driver(&thermostat_driver); +} + +module_init(thermostat_init); +module_exit(thermostat_exit); diff --git a/drivers/macintosh/therm_windtunnel.c b/drivers/macintosh/therm_windtunnel.c new file mode 100644 index 000000000..b8228ca40 --- /dev/null +++ b/drivers/macintosh/therm_windtunnel.c @@ -0,0 +1,559 @@ +/* + * 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 + * temperature around 57 C. + * + * Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se) + * + * Loosely based upon 'thermostat.c' written by Benjamin Herrenschmidt + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation + * + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kthread.h> +#include <linux/of_platform.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/macio.h> + +#define LOG_TEMP 0 /* continuously log temperature */ + +static struct { + volatile int running; + struct task_struct *poll_task; + + struct mutex lock; + struct platform_device *of_dev; + + struct i2c_client *thermostat; + struct i2c_client *fan; + + int overheat_temp; /* 100% fan at this temp */ + int overheat_hyst; + int temp; + int casetemp; + int fan_level; /* active fan_table setting */ + + int downind; + int upind; + + int r0, r1, r20, r23, r25; /* saved register */ +} x; + +#define T(x,y) (((x)<<8) | (y)*0x100/10 ) + +static struct { + int fan_down_setting; + int temp; + int fan_up_setting; +} fan_table[] = { + { 11, T(0,0), 11 }, /* min fan */ + { 11, T(55,0), 11 }, + { 6, T(55,3), 11 }, + { 7, T(56,0), 11 }, + { 8, T(57,0), 8 }, + { 7, T(58,3), 7 }, + { 6, T(58,8), 6 }, + { 5, T(59,2), 5 }, + { 4, T(59,6), 4 }, + { 3, T(59,9), 3 }, + { 2, T(60,1), 2 }, + { 1, 0xfffff, 1 } /* on fire */ +}; + +static void +print_temp( const char *s, int temp ) +{ + printk("%s%d.%d C", s ? s : "", temp>>8, (temp & 255)*10/256 ); +} + +static ssize_t +show_cpu_temperature( struct device *dev, struct device_attribute *attr, char *buf ) +{ + return sprintf(buf, "%d.%d\n", x.temp>>8, (x.temp & 255)*10/256 ); +} + +static ssize_t +show_case_temperature( struct device *dev, struct device_attribute *attr, char *buf ) +{ + return sprintf(buf, "%d.%d\n", x.casetemp>>8, (x.casetemp & 255)*10/256 ); +} + +static DEVICE_ATTR(cpu_temperature, S_IRUGO, show_cpu_temperature, NULL ); +static DEVICE_ATTR(case_temperature, S_IRUGO, show_case_temperature, NULL ); + + + +/************************************************************************/ +/* controller thread */ +/************************************************************************/ + +static int +write_reg( struct i2c_client *cl, int reg, int data, int len ) +{ + u8 tmp[3]; + + if( len < 1 || len > 2 || data < 0 ) + return -EINVAL; + + tmp[0] = reg; + tmp[1] = (len == 1) ? data : (data >> 8); + tmp[2] = data; + len++; + + if( i2c_master_send(cl, tmp, len) != len ) + return -ENODEV; + return 0; +} + +static int +read_reg( struct i2c_client *cl, int reg, int len ) +{ + u8 buf[2]; + + if( len != 1 && len != 2 ) + return -EINVAL; + buf[0] = reg; + if( i2c_master_send(cl, buf, 1) != 1 ) + return -ENODEV; + if( i2c_master_recv(cl, buf, len) != len ) + return -ENODEV; + return (len == 2)? ((unsigned int)buf[0] << 8) | buf[1] : buf[0]; +} + +static void +tune_fan( int fan_setting ) +{ + int val = (fan_setting << 3) | 7; + + /* write_reg( x.fan, 0x24, val, 1 ); */ + write_reg( x.fan, 0x25, val, 1 ); + write_reg( x.fan, 0x20, 0, 1 ); + print_temp("CPU-temp: ", x.temp ); + if( x.casetemp ) + print_temp(", Case: ", x.casetemp ); + printk(", Fan: %d (tuned %+d)\n", 11-fan_setting, x.fan_level-fan_setting ); + + x.fan_level = fan_setting; +} + +static void +poll_temp( void ) +{ + int temp, i, level, casetemp; + + temp = read_reg( x.thermostat, 0, 2 ); + + /* this actually occurs when the computer is loaded */ + if( temp < 0 ) + return; + + casetemp = read_reg(x.fan, 0x0b, 1) << 8; + casetemp |= (read_reg(x.fan, 0x06, 1) & 0x7) << 5; + + if( LOG_TEMP && x.temp != temp ) { + print_temp("CPU-temp: ", temp ); + print_temp(", Case: ", casetemp ); + printk(", Fan: %d\n", 11-x.fan_level ); + } + x.temp = temp; + x.casetemp = casetemp; + + level = -1; + for( i=0; (temp & 0xffff) > fan_table[i].temp ; i++ ) + ; + if( i < x.downind ) + level = fan_table[i].fan_down_setting; + x.downind = i; + + for( i=0; (temp & 0xffff) >= fan_table[i+1].temp ; i++ ) + ; + if( x.upind < i ) + level = fan_table[i].fan_up_setting; + x.upind = i; + + if( level >= 0 ) + tune_fan( level ); +} + + +static void +setup_hardware( void ) +{ + int val; + int err; + + /* save registers (if we unload the module) */ + x.r0 = read_reg( x.fan, 0x00, 1 ); + x.r1 = read_reg( x.fan, 0x01, 1 ); + x.r20 = read_reg( x.fan, 0x20, 1 ); + x.r23 = read_reg( x.fan, 0x23, 1 ); + x.r25 = read_reg( x.fan, 0x25, 1 ); + + /* improve measurement resolution (convergence time 1.5s) */ + if( (val=read_reg(x.thermostat, 1, 1)) >= 0 ) { + val |= 0x60; + if( write_reg( x.thermostat, 1, val, 1 ) ) + printk("Failed writing config register\n"); + } + /* disable interrupts and TAC input */ + write_reg( x.fan, 0x01, 0x01, 1 ); + /* enable filter */ + write_reg( x.fan, 0x23, 0x91, 1 ); + /* remote temp. controls fan */ + write_reg( x.fan, 0x00, 0x95, 1 ); + + /* The thermostat (which besides measureing temperature controls + * has a THERM output which puts the fan on 100%) is usually + * set to kick in at 80 C (chip default). We reduce this a bit + * to be on the safe side (OSX doesn't)... + */ + if( x.overheat_temp == (80 << 8) ) { + x.overheat_temp = 75 << 8; + x.overheat_hyst = 70 << 8; + write_reg( x.thermostat, 2, x.overheat_hyst, 2 ); + write_reg( x.thermostat, 3, x.overheat_temp, 2 ); + + print_temp("Reducing overheating limit to ", x.overheat_temp ); + print_temp(" (Hyst: ", x.overheat_hyst ); + printk(")\n"); + } + + /* set an initial fan setting */ + x.downind = 0xffff; + x.upind = -1; + /* tune_fan( fan_up_table[x.upind].fan_setting ); */ + + err = device_create_file( &x.of_dev->dev, &dev_attr_cpu_temperature ); + err |= device_create_file( &x.of_dev->dev, &dev_attr_case_temperature ); + if (err) + printk(KERN_WARNING + "Failed to create temperature attribute file(s).\n"); +} + +static void +restore_regs( void ) +{ + device_remove_file( &x.of_dev->dev, &dev_attr_cpu_temperature ); + device_remove_file( &x.of_dev->dev, &dev_attr_case_temperature ); + + write_reg( x.fan, 0x01, x.r1, 1 ); + write_reg( x.fan, 0x20, x.r20, 1 ); + write_reg( x.fan, 0x23, x.r23, 1 ); + write_reg( x.fan, 0x25, x.r25, 1 ); + write_reg( x.fan, 0x00, x.r0, 1 ); +} + +static int control_loop(void *dummy) +{ + mutex_lock(&x.lock); + setup_hardware(); + mutex_unlock(&x.lock); + + for (;;) { + msleep_interruptible(8000); + if (kthread_should_stop()) + break; + + mutex_lock(&x.lock); + poll_temp(); + mutex_unlock(&x.lock); + } + + mutex_lock(&x.lock); + restore_regs(); + mutex_unlock(&x.lock); + + return 0; +} + + +/************************************************************************/ +/* i2c probing and setup */ +/************************************************************************/ + +static void do_attach(struct i2c_adapter *adapter) +{ + struct i2c_board_info info = { }; + struct device_node *np; + + /* scan 0x48-0x4f (DS1775) and 0x2c-2x2f (ADM1030) */ + static const unsigned short scan_ds1775[] = { + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + I2C_CLIENT_END + }; + static const unsigned short scan_adm1030[] = { + 0x2c, 0x2d, 0x2e, 0x2f, + I2C_CLIENT_END + }; + + if (x.running || strncmp(adapter->name, "uni-n", 5)) + return; + + of_node_get(adapter->dev.of_node); + np = of_find_compatible_node(adapter->dev.of_node, NULL, "MAC,ds1775"); + if (np) { + of_node_put(np); + } else { + strscpy(info.type, "MAC,ds1775", I2C_NAME_SIZE); + i2c_new_scanned_device(adapter, &info, scan_ds1775, NULL); + } + + of_node_get(adapter->dev.of_node); + np = of_find_compatible_node(adapter->dev.of_node, NULL, "MAC,adm1030"); + if (np) { + of_node_put(np); + } else { + strscpy(info.type, "MAC,adm1030", I2C_NAME_SIZE); + i2c_new_scanned_device(adapter, &info, scan_adm1030, NULL); + } +} + +static void +do_remove(struct i2c_client *client) +{ + if (x.running) { + x.running = 0; + kthread_stop(x.poll_task); + x.poll_task = NULL; + } + if (client == x.thermostat) + x.thermostat = NULL; + else if (client == x.fan) + x.fan = NULL; + else + printk(KERN_ERR "g4fan: bad client\n"); +} + +static int +attach_fan( struct i2c_client *cl ) +{ + if( x.fan ) + goto out; + + /* check that this is an ADM1030 */ + if( read_reg(cl, 0x3d, 1) != 0x30 || read_reg(cl, 0x3e, 1) != 0x41 ) + goto out; + printk("ADM1030 fan controller [@%02x]\n", cl->addr ); + + x.fan = cl; + out: + return 0; +} + +static int +attach_thermostat( struct i2c_client *cl ) +{ + int hyst_temp, os_temp, temp; + + if( x.thermostat ) + goto out; + + if( (temp=read_reg(cl, 0, 2)) < 0 ) + goto out; + + /* temperature sanity check */ + if( temp < 0x1600 || temp > 0x3c00 ) + goto out; + hyst_temp = read_reg(cl, 2, 2); + os_temp = read_reg(cl, 3, 2); + if( hyst_temp < 0 || os_temp < 0 ) + goto out; + + printk("DS1775 digital thermometer [@%02x]\n", cl->addr ); + print_temp("Temp: ", temp ); + print_temp(" Hyst: ", hyst_temp ); + print_temp(" OS: ", os_temp ); + printk("\n"); + + x.temp = temp; + x.overheat_temp = os_temp; + x.overheat_hyst = hyst_temp; + x.thermostat = cl; +out: + return 0; +} + +enum chip { ds1775, adm1030 }; + +static const struct i2c_device_id therm_windtunnel_id[] = { + { "MAC,ds1775", ds1775 }, + { "MAC,adm1030", adm1030 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, therm_windtunnel_id); + +static int +do_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = cl->adapter; + int ret = 0; + + if( !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE) ) + return 0; + + switch (id->driver_data) { + case adm1030: + ret = attach_fan(cl); + break; + case ds1775: + ret = attach_thermostat(cl); + break; + } + + if (!x.running && x.thermostat && x.fan) { + x.running = 1; + x.poll_task = kthread_run(control_loop, NULL, "g4fand"); + } + + return ret; +} + +static struct i2c_driver g4fan_driver = { + .driver = { + .name = "therm_windtunnel", + }, + .probe = do_probe, + .remove = do_remove, + .id_table = therm_windtunnel_id, +}; + + +/************************************************************************/ +/* initialization / cleanup */ +/************************************************************************/ + +static int therm_of_probe(struct platform_device *dev) +{ + struct i2c_adapter *adap; + int ret, i = 0; + + adap = i2c_get_adapter(0); + if (!adap) + return -EPROBE_DEFER; + + ret = i2c_add_driver(&g4fan_driver); + if (ret) { + i2c_put_adapter(adap); + return ret; + } + + /* We assume Macs have consecutive I2C bus numbers starting at 0 */ + while (adap) { + do_attach(adap); + if (x.running) + return 0; + i2c_put_adapter(adap); + adap = i2c_get_adapter(++i); + } + + return -ENODEV; +} + +static int +therm_of_remove( struct platform_device *dev ) +{ + i2c_del_driver( &g4fan_driver ); + return 0; +} + +static const struct of_device_id therm_of_match[] = {{ + .name = "fan", + .compatible = "adm1030" + }, {} +}; +MODULE_DEVICE_TABLE(of, therm_of_match); + +static struct platform_driver therm_of_driver = { + .driver = { + .name = "temperature", + .of_match_table = therm_of_match, + }, + .probe = therm_of_probe, + .remove = therm_of_remove, +}; + +struct apple_thermal_info { + u8 id; /* implementation ID */ + u8 fan_count; /* number of fans */ + u8 thermostat_count; /* number of thermostats */ + u8 unused; +}; + +static int __init +g4fan_init( void ) +{ + const struct apple_thermal_info *info; + struct device_node *np; + + mutex_init(&x.lock); + + if( !(np=of_find_node_by_name(NULL, "power-mgt")) ) + return -ENODEV; + info = of_get_property(np, "thermal-info", NULL); + of_node_put(np); + + if( !info || !of_machine_is_compatible("PowerMac3,6") ) + return -ENODEV; + + if( info->id != 3 ) { + printk(KERN_ERR "therm_windtunnel: unsupported thermal design %d\n", info->id ); + return -ENODEV; + } + if( !(np=of_find_node_by_name(NULL, "fan")) ) + return -ENODEV; + x.of_dev = of_platform_device_create(np, "temperature", NULL); + of_node_put( np ); + + if( !x.of_dev ) { + printk(KERN_ERR "Can't register fan controller!\n"); + return -ENODEV; + } + + platform_driver_register( &therm_of_driver ); + return 0; +} + +static void __exit +g4fan_exit( void ) +{ + platform_driver_unregister( &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 000000000..507128906 --- /dev/null +++ b/drivers/macintosh/via-cuda.c @@ -0,0 +1,808 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device driver for the Cuda and Egret system controllers found on PowerMacs + * and 68k Macs. + * + * The Cuda or Egret is a 6805 microcontroller interfaced to the 6522 VIA. + * This MCU controls system power, Parameter RAM, Real Time Clock and the + * Apple Desktop Bus (ADB) that connects to the keyboard and mouse. + * + * Copyright (C) 1996 Paul Mackerras. + */ +#include <linux/stdarg.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/adb.h> +#include <linux/cuda.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#ifdef CONFIG_PPC +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#else +#include <asm/macintosh.h> +#include <asm/macints.h> +#include <asm/mac_via.h> +#endif +#include <asm/io.h> +#include <linux/init.h> + +static volatile unsigned char __iomem *via; +static DEFINE_SPINLOCK(cuda_lock); + +/* VIA registers - spaced 0x200 bytes apart */ +#define RS 0x200 /* skip between registers */ +#define B 0 /* B-side data */ +#define A RS /* A-side data */ +#define DIRB (2*RS) /* B-side direction (1=output) */ +#define DIRA (3*RS) /* A-side direction (1=output) */ +#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */ +#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */ +#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */ +#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */ +#define T2CL (8*RS) /* Timer 2 ctr/latch (low 8 bits) */ +#define T2CH (9*RS) /* Timer 2 counter (high 8 bits) */ +#define SR (10*RS) /* Shift register */ +#define ACR (11*RS) /* Auxiliary control register */ +#define PCR (12*RS) /* Peripheral control register */ +#define IFR (13*RS) /* Interrupt flag register */ +#define IER (14*RS) /* Interrupt enable register */ +#define ANH (15*RS) /* A-side data, no handshake */ + +/* + * When the Cuda design replaced the Egret, some signal names and + * logic sense changed. They all serve the same purposes, however. + * + * VIA pin | Egret pin + * ----------------+------------------------------------------ + * PB3 (input) | Transceiver session (active low) + * PB4 (output) | VIA full (active high) + * PB5 (output) | System session (active high) + * + * VIA pin | Cuda pin + * ----------------+------------------------------------------ + * PB3 (input) | Transfer request (active low) + * PB4 (output) | Byte acknowledge (active low) + * PB5 (output) | Transfer in progress (active low) + */ + +/* Bits in Port B data register */ +#define TREQ 0x08 /* Transfer request */ +#define TACK 0x10 /* Transfer acknowledge */ +#define TIP 0x20 /* Transfer in progress */ + +/* 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 */ + +/* Duration of byte acknowledgement pulse (us) */ +#define EGRET_TACK_ASSERTED_DELAY 300 +#define EGRET_TACK_NEGATED_DELAY 400 + +/* Interval from interrupt to start of session (us) */ +#define EGRET_SESSION_DELAY 450 + +#ifdef CONFIG_PPC +#define mcu_is_egret false +#else +static bool mcu_is_egret; +#endif + +static inline bool TREQ_asserted(u8 portb) +{ + return !(portb & TREQ); +} + +static inline void assert_TIP(void) +{ + if (mcu_is_egret) { + udelay(EGRET_SESSION_DELAY); + out_8(&via[B], in_8(&via[B]) | TIP); + } else + out_8(&via[B], in_8(&via[B]) & ~TIP); +} + +static inline void assert_TIP_and_TACK(void) +{ + if (mcu_is_egret) { + udelay(EGRET_SESSION_DELAY); + out_8(&via[B], in_8(&via[B]) | TIP | TACK); + } else + out_8(&via[B], in_8(&via[B]) & ~(TIP | TACK)); +} + +static inline void assert_TACK(void) +{ + if (mcu_is_egret) { + udelay(EGRET_TACK_NEGATED_DELAY); + out_8(&via[B], in_8(&via[B]) | TACK); + } else + out_8(&via[B], in_8(&via[B]) & ~TACK); +} + +static inline void toggle_TACK(void) +{ + out_8(&via[B], in_8(&via[B]) ^ TACK); +} + +static inline void negate_TACK(void) +{ + if (mcu_is_egret) { + udelay(EGRET_TACK_ASSERTED_DELAY); + out_8(&via[B], in_8(&via[B]) & ~TACK); + } else + out_8(&via[B], in_8(&via[B]) | TACK); +} + +static inline void negate_TIP_and_TACK(void) +{ + if (mcu_is_egret) { + udelay(EGRET_TACK_ASSERTED_DELAY); + out_8(&via[B], in_8(&via[B]) & ~(TIP | TACK)); + } else + out_8(&via[B], in_8(&via[B]) | TIP | TACK); +} + +static enum cuda_state { + idle, + sent_first_byte, + sending, + reading, + read_done, + awaiting_reply +} cuda_state; + +static struct adb_request *current_req; +static struct adb_request *last_req; +static unsigned char cuda_rbuf[16]; +static unsigned char *reply_ptr; +static int reading_reply; +static int data_index; +static int cuda_irq; +#ifdef CONFIG_PPC +static struct device_node *vias; +#endif +static int cuda_fully_inited; + +#ifdef CONFIG_ADB +static int cuda_probe(void); +static int cuda_send_request(struct adb_request *req, int sync); +static int cuda_adb_autopoll(int devs); +static int cuda_reset_adb_bus(void); +#endif /* CONFIG_ADB */ + +static int cuda_init_via(void); +static void cuda_start(void); +static irqreturn_t cuda_interrupt(int irq, void *arg); +static void cuda_input(unsigned char *buf, int nb); +void cuda_poll(void); +static int cuda_write(struct adb_request *req); + +int cuda_request(struct adb_request *req, + void (*done)(struct adb_request *), int nbytes, ...); + +#ifdef CONFIG_ADB +struct adb_driver via_cuda_driver = { + .name = "CUDA", + .probe = cuda_probe, + .send_request = cuda_send_request, + .autopoll = cuda_adb_autopoll, + .poll = cuda_poll, + .reset_bus = cuda_reset_adb_bus, +}; +#endif /* CONFIG_ADB */ + +#ifdef CONFIG_MAC +int __init find_via_cuda(void) +{ + struct adb_request req; + int err; + + if (macintosh_config->adb_type != MAC_ADB_CUDA && + macintosh_config->adb_type != MAC_ADB_EGRET) + return 0; + + via = via1; + cuda_state = idle; + mcu_is_egret = macintosh_config->adb_type == MAC_ADB_EGRET; + + err = cuda_init_via(); + if (err) { + printk(KERN_ERR "cuda_init_via() failed\n"); + via = NULL; + return 0; + } + + /* enable autopoll */ + cuda_request(&req, NULL, 3, CUDA_PACKET, CUDA_AUTOPOLL, 1); + while (!req.complete) + cuda_poll(); + + return 1; +} +#else +int __init find_via_cuda(void) +{ + struct adb_request req; + phys_addr_t taddr; + const u32 *reg; + int err; + + if (vias) + return 1; + vias = of_find_node_by_name(NULL, "via-cuda"); + if (!vias) + return 0; + + reg = of_get_property(vias, "reg", NULL); + if (reg == NULL) { + printk(KERN_ERR "via-cuda: No \"reg\" property !\n"); + goto fail; + } + taddr = of_translate_address(vias, reg); + if (taddr == 0) { + printk(KERN_ERR "via-cuda: Can't translate address !\n"); + goto fail; + } + via = ioremap(taddr, 0x2000); + if (via == NULL) { + printk(KERN_ERR "via-cuda: Can't map address !\n"); + goto fail; + } + + cuda_state = idle; + sys_ctrler = SYS_CTRLER_CUDA; + + err = cuda_init_via(); + if (err) { + printk(KERN_ERR "cuda_init_via() failed\n"); + via = NULL; + return 0; + } + + /* Clear and enable interrupts, but only on PPC. On 68K it's done */ + /* for us by the main VIA driver in arch/m68k/mac/via.c */ + + out_8(&via[IFR], 0x7f); /* clear interrupts by writing 1s */ + out_8(&via[IER], IER_SET|SR_INT); /* enable interrupt from SR */ + + /* enable autopoll */ + cuda_request(&req, NULL, 3, CUDA_PACKET, CUDA_AUTOPOLL, 1); + while (!req.complete) + cuda_poll(); + + return 1; + + fail: + of_node_put(vias); + vias = NULL; + return 0; +} +#endif /* !defined CONFIG_MAC */ + +static int __init via_cuda_start(void) +{ + if (via == NULL) + return -ENODEV; + +#ifdef CONFIG_MAC + cuda_irq = IRQ_MAC_ADB; +#else + cuda_irq = irq_of_parse_and_map(vias, 0); + if (!cuda_irq) { + printk(KERN_ERR "via-cuda: can't map interrupts for %pOF\n", + vias); + return -ENODEV; + } +#endif + + if (request_irq(cuda_irq, cuda_interrupt, 0, "ADB", cuda_interrupt)) { + printk(KERN_ERR "via-cuda: can't request irq %d\n", cuda_irq); + return -EAGAIN; + } + + pr_info("Macintosh Cuda and Egret driver.\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 && + macintosh_config->adb_type != MAC_ADB_EGRET) + return -ENODEV; +#endif + if (via == NULL) + return -ENODEV; + return 0; +} +#endif /* CONFIG_ADB */ + +static int __init sync_egret(void) +{ + if (TREQ_asserted(in_8(&via[B]))) { + /* Complete the inbound transfer */ + assert_TIP_and_TACK(); + while (1) { + negate_TACK(); + mdelay(1); + (void)in_8(&via[SR]); + assert_TACK(); + if (!TREQ_asserted(in_8(&via[B]))) + break; + } + negate_TIP_and_TACK(); + } else if (in_8(&via[B]) & TIP) { + /* Terminate the outbound transfer */ + negate_TACK(); + assert_TACK(); + mdelay(1); + negate_TIP_and_TACK(); + } + /* Clear shift register interrupt */ + if (in_8(&via[IFR]) & SR_INT) + (void)in_8(&via[SR]); + return 0; +} + +#define WAIT_FOR(cond, what) \ + do { \ + int x; \ + for (x = 1000; !(cond); --x) { \ + if (x == 0) { \ + pr_err("Timeout waiting for " what "\n"); \ + return -ENXIO; \ + } \ + udelay(100); \ + } \ + } while (0) + +static int +__init cuda_init_via(void) +{ +#ifdef CONFIG_PPC + out_8(&via[IER], 0x7f); /* disable interrupts from VIA */ + (void)in_8(&via[IER]); +#else + out_8(&via[IER], SR_INT); /* disable SR interrupt from VIA */ +#endif + + out_8(&via[DIRB], (in_8(&via[DIRB]) | TACK | TIP) & ~TREQ); /* TACK & TIP out */ + 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 */ + + if (mcu_is_egret) + return sync_egret(); + + negate_TIP_and_TACK(); + + /* delay 4ms and then clear any pending interrupt */ + mdelay(4); + (void)in_8(&via[SR]); + out_8(&via[IFR], SR_INT); + + /* sync with the CUDA - assert TACK without TIP */ + assert_TACK(); + + /* wait for the CUDA to assert TREQ in response */ + WAIT_FOR(TREQ_asserted(in_8(&via[B])), "CUDA response to sync"); + + /* wait for the interrupt and then clear it */ + WAIT_FOR(in_8(&via[IFR]) & SR_INT, "CUDA response to sync (2)"); + (void)in_8(&via[SR]); + out_8(&via[IFR], SR_INT); + + /* finish the sync by negating TACK */ + negate_TACK(); + + /* wait for the CUDA to negate TREQ and the corresponding interrupt */ + WAIT_FOR(!TREQ_asserted(in_8(&via[B])), "CUDA response to sync (3)"); + WAIT_FOR(in_8(&via[IFR]) & SR_INT, "CUDA response to sync (4)"); + (void)in_8(&via[SR]); + out_8(&via[IFR], SR_INT); + + 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); +} +EXPORT_SYMBOL(cuda_request); + +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) { + 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) +{ + /* assert cuda_state == idle */ + if (current_req == NULL) + return; + data_index = 0; + if (TREQ_asserted(in_8(&via[B]))) + 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], current_req->data[data_index++]); + if (mcu_is_egret) + assert_TIP_and_TACK(); + else + assert_TIP(); + cuda_state = sent_first_byte; +} + +void +cuda_poll(void) +{ + cuda_interrupt(0, NULL); +} +EXPORT_SYMBOL(cuda_poll); + +#define ARRAY_FULL(a, p) ((p) - (a) == ARRAY_SIZE(a)) + +static irqreturn_t +cuda_interrupt(int irq, void *arg) +{ + unsigned long flags; + u8 status; + struct adb_request *req = NULL; + unsigned char ibuf[16]; + int ibuf_len = 0; + int complete = 0; + bool full; + + spin_lock_irqsave(&cuda_lock, flags); + + /* On powermacs, this handler is registered for the VIA IRQ. But they use + * just the shift register IRQ -- other VIA interrupt sources are disabled. + * On m68k macs, the VIA IRQ sources are dispatched individually. Unless + * we are polling, the shift register IRQ flag has already been cleared. + */ + +#ifdef CONFIG_MAC + if (!arg) +#endif + { + if ((in_8(&via[IFR]) & SR_INT) == 0) { + spin_unlock_irqrestore(&cuda_lock, flags); + return IRQ_NONE; + } else { + out_8(&via[IFR], SR_INT); + } + } + + status = in_8(&via[B]) & (TIP | TACK | TREQ); + + switch (cuda_state) { + case idle: + /* System controller has unsolicited data for us */ + (void)in_8(&via[SR]); +idle_state: + assert_TIP(); + cuda_state = reading; + reply_ptr = cuda_rbuf; + reading_reply = 0; + break; + + case awaiting_reply: + /* System controller has reply data for us */ + (void)in_8(&via[SR]); + assert_TIP(); + cuda_state = reading; + reply_ptr = current_req->reply; + reading_reply = 1; + break; + + case sent_first_byte: + if (TREQ_asserted(status)) { + /* collision */ + out_8(&via[ACR], in_8(&via[ACR]) & ~SR_OUT); + (void)in_8(&via[SR]); + negate_TIP_and_TACK(); + cuda_state = idle; + /* Egret does not raise an "aborted" interrupt */ + if (mcu_is_egret) + goto idle_state; + } else { + out_8(&via[SR], current_req->data[data_index++]); + toggle_TACK(); + if (mcu_is_egret) + assert_TACK(); + 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]); + negate_TIP_and_TACK(); + 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++]); + toggle_TACK(); + if (mcu_is_egret) + assert_TACK(); + } + break; + + case reading: + full = reading_reply ? ARRAY_FULL(current_req->reply, reply_ptr) + : ARRAY_FULL(cuda_rbuf, reply_ptr); + if (full) + (void)in_8(&via[SR]); + else + *reply_ptr++ = in_8(&via[SR]); + if (!TREQ_asserted(status) || full) { + if (mcu_is_egret) + assert_TACK(); + /* that's all folks */ + negate_TIP_and_TACK(); + cuda_state = read_done; + /* Egret does not raise a "read done" interrupt */ + if (mcu_is_egret) + goto read_done_state; + } else { + toggle_TACK(); + if (mcu_is_egret) + negate_TACK(); + } + break; + + case read_done: + (void)in_8(&via[SR]); +read_done_state: + 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; + reading_reply = 0; + } 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); + } + reply_ptr = cuda_rbuf; + cuda_state = idle; + cuda_start(); + if (cuda_state == idle && TREQ_asserted(in_8(&via[B]))) { + assert_TIP(); + cuda_state = reading; + } + break; + + default: + pr_err("cuda_interrupt: unknown cuda_state %d?\n", cuda_state); + } + spin_unlock_irqrestore(&cuda_lock, flags); + if (complete && req) { + void (*done)(struct adb_request *) = req->done; + mb(); + req->complete = 1; + /* Here, we assume that if the request has a done member, the + * struct request will survive to setting req->complete to 1 + */ + if (done) + (*done)(req); + } + if (ibuf_len) + cuda_input(ibuf, ibuf_len); + return IRQ_HANDLED; +} + +static void +cuda_input(unsigned char *buf, int nb) +{ + switch (buf[0]) { + case ADB_PACKET: +#ifdef CONFIG_XMON + if (nb == 5 && buf[2] == 0x2c) { + extern int xmon_wants_key, xmon_adb_keycode; + if (xmon_wants_key) { + xmon_adb_keycode = buf[3]; + return; + } + } +#endif /* CONFIG_XMON */ +#ifdef CONFIG_ADB + adb_input(buf+2, nb-2, buf[1] & 0x40); +#endif /* CONFIG_ADB */ + break; + + case TIMER_PACKET: + /* Egret sends these periodically. Might be useful as a 'heartbeat' + * to trigger a recovery for the VIA shift register errata. + */ + break; + + default: + print_hex_dump(KERN_INFO, "cuda_input: ", DUMP_PREFIX_NONE, 32, 1, + buf, nb, false); + } +} + +/* Offset between Unix time (1970-based) and Mac time (1904-based) */ +#define RTC_OFFSET 2082844800 + +time64_t cuda_get_time(void) +{ + struct adb_request req; + u32 now; + + if (cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_GET_TIME) < 0) + return 0; + while (!req.complete) + cuda_poll(); + if (req.reply_len != 7) + pr_err("%s: got %d byte reply\n", __func__, req.reply_len); + now = (req.reply[3] << 24) + (req.reply[4] << 16) + + (req.reply[5] << 8) + req.reply[6]; + return (time64_t)now - RTC_OFFSET; +} + +int cuda_set_rtc_time(struct rtc_time *tm) +{ + u32 now; + struct adb_request req; + + now = lower_32_bits(rtc_tm_to_time64(tm) + RTC_OFFSET); + if (cuda_request(&req, NULL, 6, CUDA_PACKET, CUDA_SET_TIME, + now >> 24, now >> 16, now >> 8, now) < 0) + return -ENXIO; + while (!req.complete) + cuda_poll(); + if ((req.reply_len != 3) && (req.reply_len != 7)) + pr_err("%s: got %d byte reply\n", __func__, req.reply_len); + return 0; +} diff --git a/drivers/macintosh/via-macii.c b/drivers/macintosh/via-macii.c new file mode 100644 index 000000000..db9270da5 --- /dev/null +++ b/drivers/macintosh/via-macii.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device driver for the via ADB on (many) Mac II-class machines + * + * Based on the original ADB keyboard handler Copyright (c) 1997 Alan Cox + * Also derived from code Copyright (C) 1996 Paul Mackerras. + * + * With various updates provided over the years by Michael Schmitz, + * Guideo Koerber and others. + * + * Rewrite for Unified ADB by Joshua M. Thompson (funaho@jurai.org) + * + * 1999-08-02 (jmt) - Initial rewrite for Unified ADB. + * 2000-03-29 Tony Mantler <tonym@mac.linux-m68k.org> + * - Big overhaul, should actually work now. + * 2006-12-31 Finn Thain - Another overhaul. + * + * Suggested reading: + * Inside Macintosh, ch. 5 ADB Manager + * Guide to the Macinstosh Family Hardware, ch. 8 Apple Desktop Bus + * Rockwell R6522 VIA datasheet + * + * Apple's "ADB Analyzer" bus sniffer is invaluable: + * ftp://ftp.apple.com/developer/Tool_Chest/Devices_-_Hardware/Apple_Desktop_Bus/ + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/adb.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <asm/macintosh.h> +#include <asm/macints.h> +#include <asm/mac_via.h> + +static volatile unsigned char *via; + +/* VIA registers - spaced 0x200 bytes apart */ +#define RS 0x200 /* skip between registers */ +#define B 0 /* B-side data */ +#define A RS /* A-side data */ +#define DIRB (2*RS) /* B-side direction (1=output) */ +#define DIRA (3*RS) /* A-side direction (1=output) */ +#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */ +#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */ +#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */ +#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */ +#define T2CL (8*RS) /* Timer 2 ctr/latch (low 8 bits) */ +#define T2CH (9*RS) /* Timer 2 counter (high 8 bits) */ +#define SR (10*RS) /* Shift register */ +#define ACR (11*RS) /* Auxiliary control register */ +#define PCR (12*RS) /* Peripheral control register */ +#define IFR (13*RS) /* Interrupt flag register */ +#define IER (14*RS) /* Interrupt enable register */ +#define ANH (15*RS) /* A-side data, no handshake */ + +/* Bits in B data register: all active low */ +#define CTLR_IRQ 0x08 /* Controller rcv status (input) */ +#define ST_MASK 0x30 /* mask for selecting ADB state bits */ + +/* Bits in ACR */ +#define SR_CTRL 0x1c /* Shift register control bits */ +#define SR_EXT 0x0c /* Shift on external clock */ +#define SR_OUT 0x10 /* Shift out if 1 */ + +/* Bits in IFR and IER */ +#define IER_SET 0x80 /* set bits in IER */ +#define IER_CLR 0 /* clear bits in IER */ +#define SR_INT 0x04 /* Shift register full/empty */ + +/* ADB transaction states according to GMHW */ +#define ST_CMD 0x00 /* ADB state: command byte */ +#define ST_EVEN 0x10 /* ADB state: even data byte */ +#define ST_ODD 0x20 /* ADB state: odd data byte */ +#define ST_IDLE 0x30 /* ADB state: idle, nothing to send */ + +/* ADB command byte structure */ +#define ADDR_MASK 0xF0 +#define CMD_MASK 0x0F +#define OP_MASK 0x0C +#define TALK 0x0C + +static int macii_init_via(void); +static void macii_start(void); +static irqreturn_t macii_interrupt(int irq, void *arg); +static void macii_queue_poll(void); + +static int macii_probe(void); +static int macii_init(void); +static int macii_send_request(struct adb_request *req, int sync); +static int macii_write(struct adb_request *req); +static int macii_autopoll(int devs); +static void macii_poll(void); +static int macii_reset_bus(void); + +struct adb_driver via_macii_driver = { + .name = "Mac II", + .probe = macii_probe, + .init = macii_init, + .send_request = macii_send_request, + .autopoll = macii_autopoll, + .poll = macii_poll, + .reset_bus = macii_reset_bus, +}; + +static enum macii_state { + idle, + sending, + reading, +} macii_state; + +static struct adb_request *current_req; /* first request struct in the queue */ +static struct adb_request *last_req; /* last request struct in the queue */ +static unsigned char reply_buf[16]; /* storage for autopolled replies */ +static unsigned char *reply_ptr; /* next byte in reply_buf or req->reply */ +static bool reading_reply; /* store reply in reply_buf else req->reply */ +static int data_index; /* index of the next byte to send from req->data */ +static int reply_len; /* number of bytes received in reply_buf or req->reply */ +static int status; /* VIA's ADB status bits captured upon interrupt */ +static bool bus_timeout; /* no data was sent by the device */ +static bool srq_asserted; /* have to poll for the device that asserted it */ +static u8 last_cmd; /* the most recent command byte transmitted */ +static u8 last_talk_cmd; /* the most recent Talk command byte transmitted */ +static u8 last_poll_cmd; /* the most recent Talk R0 command byte transmitted */ +static unsigned int autopoll_devs; /* bits set are device addresses to poll */ + +/* Check for MacII style ADB */ +static int macii_probe(void) +{ + if (macintosh_config->adb_type != MAC_ADB_II) + return -ENODEV; + + via = via1; + + pr_info("adb: Mac II ADB Driver v1.0 for Unified ADB\n"); + return 0; +} + +/* Initialize the driver */ +static int macii_init(void) +{ + unsigned long flags; + int err; + + local_irq_save(flags); + + err = macii_init_via(); + if (err) + goto out; + + err = request_irq(IRQ_MAC_ADB, macii_interrupt, 0, "ADB", + macii_interrupt); + if (err) + goto out; + + macii_state = idle; +out: + local_irq_restore(flags); + return err; +} + +/* initialize the hardware */ +static int macii_init_via(void) +{ + unsigned char x; + + /* We want CTLR_IRQ as input and ST_EVEN | ST_ODD as output lines. */ + via[DIRB] = (via[DIRB] | ST_EVEN | ST_ODD) & ~CTLR_IRQ; + + /* Set up state: idle */ + via[B] |= ST_IDLE; + + /* Shift register on input */ + via[ACR] = (via[ACR] & ~SR_CTRL) | SR_EXT; + + /* Wipe any pending data and int */ + x = via[SR]; + + return 0; +} + +/* Send an ADB poll (Talk Register 0 command prepended to the request queue) */ +static void macii_queue_poll(void) +{ + static struct adb_request req; + unsigned char poll_command; + unsigned int poll_addr; + + /* This only polls devices in the autopoll list, which assumes that + * unprobed devices never assert SRQ. That could happen if a device was + * plugged in after the adb bus scan. Unplugging it again will resolve + * the problem. This behaviour is similar to MacOS. + */ + if (!autopoll_devs) + return; + + /* The device most recently polled may not be the best device to poll + * right now. Some other device(s) may have signalled SRQ (the active + * device won't do that). Or the autopoll list may have been changed. + * Try polling the next higher address. + */ + poll_addr = (last_poll_cmd & ADDR_MASK) >> 4; + if ((srq_asserted && last_cmd == last_poll_cmd) || + !(autopoll_devs & (1 << poll_addr))) { + unsigned int higher_devs; + + higher_devs = autopoll_devs & -(1 << (poll_addr + 1)); + poll_addr = ffs(higher_devs ? higher_devs : autopoll_devs) - 1; + } + + /* Send a Talk Register 0 command */ + poll_command = ADB_READREG(poll_addr, 0); + + /* No need to repeat this Talk command. The transceiver will do that + * as long as it is idle. + */ + if (poll_command == last_cmd) + return; + + adb_request(&req, NULL, ADBREQ_NOSEND, 1, poll_command); + + req.sent = 0; + req.complete = 0; + req.reply_len = 0; + req.next = current_req; + + if (WARN_ON(current_req)) { + current_req = &req; + } else { + current_req = &req; + last_req = &req; + } +} + +/* Send an ADB request; if sync, poll out the reply 'till it's done */ +static int macii_send_request(struct adb_request *req, int sync) +{ + int err; + + err = macii_write(req); + if (err) + return err; + + if (sync) + while (!req->complete) + macii_poll(); + + return 0; +} + +/* Send an ADB request (append to request queue) */ +static int macii_write(struct adb_request *req) +{ + unsigned long flags; + + if (req->nbytes < 2 || req->data[0] != ADB_PACKET || req->nbytes > 15) { + req->complete = 1; + return -EINVAL; + } + + req->next = NULL; + req->sent = 0; + req->complete = 0; + req->reply_len = 0; + + local_irq_save(flags); + + if (current_req != NULL) { + last_req->next = req; + last_req = req; + } else { + current_req = req; + last_req = req; + if (macii_state == idle) + macii_start(); + } + + local_irq_restore(flags); + + return 0; +} + +/* Start auto-polling */ +static int macii_autopoll(int devs) +{ + unsigned long flags; + + local_irq_save(flags); + + /* bit 1 == device 1, and so on. */ + autopoll_devs = (unsigned int)devs & 0xFFFE; + + if (!current_req) { + macii_queue_poll(); + if (current_req && macii_state == idle) + macii_start(); + } + + local_irq_restore(flags); + + return 0; +} + +/* Prod the chip without interrupts */ +static void macii_poll(void) +{ + macii_interrupt(0, NULL); +} + +/* Reset the bus */ +static int macii_reset_bus(void) +{ + struct adb_request req; + + /* Command = 0, Address = ignored */ + adb_request(&req, NULL, ADBREQ_NOSEND, 1, ADB_BUSRESET); + macii_send_request(&req, 1); + + /* Don't want any more requests during the Global Reset low time. */ + udelay(3000); + + return 0; +} + +/* Start sending ADB packet */ +static void macii_start(void) +{ + struct adb_request *req; + + req = current_req; + + /* Now send it. Be careful though, that first byte of the request + * is actually ADB_PACKET; the real data begins at index 1! + * And req->nbytes is the number of bytes of real data plus one. + */ + + /* 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; + + bus_timeout = false; + srq_asserted = false; +} + +/* + * The notorious ADB interrupt handler - does all of the protocol handling. + * Relies on the ADB controller sending and receiving data, thereby + * generating shift register interrupts (SR_INT) for us. This means there has + * to be activity on the ADB bus. The chip will poll to achieve this. + * + * The VIA Port B output signalling works as follows. After the ADB transceiver + * sees a transition on the PB4 and PB5 lines it will crank over the VIA shift + * register which eventually raises the SR_INT interrupt. The PB4/PB5 outputs + * are toggled with each byte as the ADB transaction progresses. + * + * Request with no reply expected (and empty transceiver buffer): + * CMD -> IDLE + * Request with expected reply packet (or with buffered autopoll packet): + * CMD -> EVEN -> ODD -> EVEN -> ... -> IDLE + * Unsolicited packet: + * IDLE -> EVEN -> ODD -> EVEN -> ... -> IDLE + */ +static irqreturn_t macii_interrupt(int irq, void *arg) +{ + int x; + struct adb_request *req; + unsigned long flags; + + local_irq_save(flags); + + if (!arg) { + /* Clear the SR IRQ flag when polling. */ + if (via[IFR] & SR_INT) + via[IFR] = SR_INT; + else { + local_irq_restore(flags); + return IRQ_NONE; + } + } + + status = via[B] & (ST_MASK | CTLR_IRQ); + + switch (macii_state) { + case idle: + WARN_ON((status & ST_MASK) != ST_IDLE); + + reply_ptr = reply_buf; + reading_reply = false; + + bus_timeout = false; + srq_asserted = false; + + x = via[SR]; + + if (!(status & CTLR_IRQ)) { + /* /CTLR_IRQ asserted in idle state means we must + * read an autopoll reply from the transceiver buffer. + */ + macii_state = reading; + *reply_ptr = x; + reply_len = 1; + } else { + /* bus timeout */ + reply_len = 0; + break; + } + + /* set ADB state = even for first data byte */ + via[B] = (via[B] & ~ST_MASK) | ST_EVEN; + break; + + case sending: + req = current_req; + + if (status == (ST_CMD | CTLR_IRQ)) { + /* /CTLR_IRQ de-asserted after the command byte means + * the host can continue with the transaction. + */ + + /* Store command byte */ + last_cmd = req->data[1]; + if ((last_cmd & OP_MASK) == TALK) { + last_talk_cmd = last_cmd; + if ((last_cmd & CMD_MASK) == ADB_READREG(0, 0)) + last_poll_cmd = last_cmd; + } + } + + if (status == ST_CMD) { + /* /CTLR_IRQ asserted after the command byte means we + * must read an autopoll reply. The first byte was + * lost because the shift register was an output. + */ + macii_state = reading; + + reading_reply = false; + reply_ptr = reply_buf; + *reply_ptr = last_talk_cmd; + reply_len = 1; + + /* reset to shift in */ + via[ACR] &= ~SR_OUT; + x = via[SR]; + } else if (data_index >= req->nbytes) { + req->sent = 1; + + if (req->reply_expected) { + macii_state = reading; + + reading_reply = true; + reply_ptr = req->reply; + *reply_ptr = req->data[1]; + reply_len = 1; + + via[ACR] &= ~SR_OUT; + x = via[SR]; + } else if ((req->data[1] & OP_MASK) == TALK) { + macii_state = reading; + + reading_reply = false; + reply_ptr = reply_buf; + *reply_ptr = req->data[1]; + reply_len = 1; + + via[ACR] &= ~SR_OUT; + x = via[SR]; + + req->complete = 1; + current_req = req->next; + if (req->done) + (*req->done)(req); + } else { + macii_state = idle; + + req->complete = 1; + current_req = req->next; + if (req->done) + (*req->done)(req); + break; + } + } else { + via[SR] = req->data[data_index++]; + } + + if ((via[B] & ST_MASK) == ST_CMD) { + /* just sent the command byte, set to EVEN */ + via[B] = (via[B] & ~ST_MASK) | ST_EVEN; + } else { + /* invert state bits, toggle ODD/EVEN */ + via[B] ^= ST_MASK; + } + break; + + case reading: + x = via[SR]; + WARN_ON((status & ST_MASK) == ST_CMD || + (status & ST_MASK) == ST_IDLE); + + if (!(status & CTLR_IRQ)) { + if (status == ST_EVEN && reply_len == 1) { + bus_timeout = true; + } else if (status == ST_ODD && reply_len == 2) { + srq_asserted = true; + } else { + macii_state = idle; + + if (bus_timeout) + reply_len = 0; + + if (reading_reply) { + struct adb_request *req = current_req; + + req->reply_len = reply_len; + + req->complete = 1; + current_req = req->next; + if (req->done) + (*req->done)(req); + } else if (reply_len && autopoll_devs && + reply_buf[0] == last_poll_cmd) { + adb_input(reply_buf, reply_len, 1); + } + break; + } + } + + if (reply_len < ARRAY_SIZE(reply_buf)) { + reply_ptr++; + *reply_ptr = x; + reply_len++; + } + + /* invert state bits, toggle ODD/EVEN */ + via[B] ^= ST_MASK; + break; + + default: + break; + } + + if (macii_state == idle) { + if (!current_req) + macii_queue_poll(); + + if (current_req) + macii_start(); + + if (macii_state == idle) { + via[ACR] &= ~SR_OUT; + x = via[SR]; + via[B] = (via[B] & ~ST_MASK) | ST_IDLE; + } + } + + local_irq_restore(flags); + return IRQ_HANDLED; +} diff --git a/drivers/macintosh/via-pmu-backlight.c b/drivers/macintosh/via-pmu-backlight.c new file mode 100644 index 000000000..219401612 --- /dev/null +++ b/drivers/macintosh/via-pmu-backlight.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Backlight code for via-pmu + * + * Copyright (C) 1998 Paul Mackerras and Fabio Riccardi. + * Copyright (C) 2001-2002 Benjamin Herrenschmidt + * Copyright (C) 2006 Michael Hanselmann <linux-kernel@hansmi.ch> + * + */ + +#include <asm/ptrace.h> +#include <linux/adb.h> +#include <linux/pmu.h> +#include <asm/backlight.h> + +#define MAX_PMU_LEVEL 0xFF + +static const struct backlight_ops pmu_backlight_data; +static DEFINE_SPINLOCK(pmu_backlight_lock); +static int sleeping, uses_pmu_bl; +static u8 bl_curve[FB_BACKLIGHT_LEVELS]; + +static void pmu_backlight_init_curve(u8 off, u8 min, u8 max) +{ + int i, flat, count, range = (max - min); + + bl_curve[0] = off; + + for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat) + bl_curve[flat] = min; + + count = FB_BACKLIGHT_LEVELS * 15 / 16; + for (i = 0; i < count; ++i) + bl_curve[flat + i] = min + (range * (i + 1) / count); +} + +static int pmu_backlight_curve_lookup(int value) +{ + int level = (FB_BACKLIGHT_LEVELS - 1); + int i, max = 0; + + /* Look for biggest value */ + for (i = 0; i < FB_BACKLIGHT_LEVELS; i++) + max = max((int)bl_curve[i], max); + + /* Look for nearest value */ + for (i = 0; i < FB_BACKLIGHT_LEVELS; i++) { + int diff = abs(bl_curve[i] - value); + if (diff < max) { + max = diff; + level = i; + } + } + return level; +} + +static int pmu_backlight_get_level_brightness(int level) +{ + int pmulevel; + + /* Get and convert the value */ + pmulevel = bl_curve[level] * FB_BACKLIGHT_MAX / MAX_PMU_LEVEL; + if (pmulevel < 0) + pmulevel = 0; + else if (pmulevel > MAX_PMU_LEVEL) + pmulevel = MAX_PMU_LEVEL; + + return pmulevel; +} + +static int __pmu_backlight_update_status(struct backlight_device *bd) +{ + struct adb_request req; + int level = bd->props.brightness; + + + if (bd->props.power != FB_BLANK_UNBLANK || + bd->props.fb_blank != FB_BLANK_UNBLANK) + level = 0; + + if (level > 0) { + int pmulevel = pmu_backlight_get_level_brightness(level); + + pmu_request(&req, NULL, 2, PMU_BACKLIGHT_BRIGHT, pmulevel); + pmu_wait_complete(&req); + + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, + PMU_POW_BACKLIGHT | PMU_POW_ON); + pmu_wait_complete(&req); + } else { + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, + PMU_POW_BACKLIGHT | PMU_POW_OFF); + pmu_wait_complete(&req); + } + + return 0; +} + +static int pmu_backlight_update_status(struct backlight_device *bd) +{ + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&pmu_backlight_lock, flags); + /* Don't update brightness when sleeping */ + if (!sleeping) + rc = __pmu_backlight_update_status(bd); + spin_unlock_irqrestore(&pmu_backlight_lock, flags); + return rc; +} + + +static const struct backlight_ops pmu_backlight_data = { + .update_status = pmu_backlight_update_status, + +}; + +#ifdef CONFIG_PM +void pmu_backlight_set_sleep(int sleep) +{ + unsigned long flags; + + spin_lock_irqsave(&pmu_backlight_lock, flags); + sleeping = sleep; + if (pmac_backlight && uses_pmu_bl) { + if (sleep) { + struct adb_request req; + + pmu_request(&req, NULL, 2, PMU_POWER_CTRL, + PMU_POW_BACKLIGHT | PMU_POW_OFF); + pmu_wait_complete(&req); + } else + __pmu_backlight_update_status(pmac_backlight); + } + spin_unlock_irqrestore(&pmu_backlight_lock, flags); +} +#endif /* CONFIG_PM */ + +void __init pmu_backlight_init(void) +{ + struct backlight_properties props; + struct backlight_device *bd; + char name[10]; + int level, autosave; + + /* Special case for the old PowerBook since I can't test on it */ + autosave = + of_machine_is_compatible("AAPL,3400/2400") || + of_machine_is_compatible("AAPL,3500"); + + if (!autosave && + !pmac_has_backlight_type("pmu") && + !of_machine_is_compatible("AAPL,PowerBook1998") && + !of_machine_is_compatible("PowerBook1,1")) + return; + + snprintf(name, sizeof(name), "pmubl"); + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = FB_BACKLIGHT_LEVELS - 1; + bd = backlight_device_register(name, NULL, NULL, &pmu_backlight_data, + &props); + if (IS_ERR(bd)) { + printk(KERN_ERR "PMU Backlight registration failed\n"); + return; + } + uses_pmu_bl = 1; + pmu_backlight_init_curve(0x7F, 0x46, 0x0E); + + level = bd->props.max_brightness; + + if (autosave) { + /* read autosaved value if available */ + struct adb_request req; + pmu_request(&req, NULL, 2, 0xd9, 0); + pmu_wait_complete(&req); + + level = pmu_backlight_curve_lookup( + (req.reply[0] >> 4) * + bd->props.max_brightness / 15); + } + + bd->props.brightness = level; + bd->props.power = FB_BLANK_UNBLANK; + backlight_update_status(bd); + + printk(KERN_INFO "PMU Backlight initialized (%s)\n", name); +} diff --git a/drivers/macintosh/via-pmu-event.c b/drivers/macintosh/via-pmu-event.c new file mode 100644 index 000000000..25cd56542 --- /dev/null +++ b/drivers/macintosh/via-pmu-event.c @@ -0,0 +1,80 @@ +/* + * via-pmu event device for reporting some events that come through the PMU + * + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <linux/input.h> +#include <linux/adb.h> +#include <linux/pmu.h> +#include "via-pmu-event.h" + +static struct input_dev *pmu_input_dev; + +static int __init via_pmu_event_init(void) +{ + int err; + + /* do other models report button/lid status? */ + if (pmu_get_model() != PMU_KEYLARGO_BASED) + return -ENODEV; + + pmu_input_dev = input_allocate_device(); + if (!pmu_input_dev) + return -ENOMEM; + + pmu_input_dev->name = "PMU"; + pmu_input_dev->id.bustype = BUS_HOST; + pmu_input_dev->id.vendor = 0x0001; + pmu_input_dev->id.product = 0x0001; + pmu_input_dev->id.version = 0x0100; + + set_bit(EV_KEY, pmu_input_dev->evbit); + set_bit(EV_SW, pmu_input_dev->evbit); + set_bit(KEY_POWER, pmu_input_dev->keybit); + set_bit(SW_LID, pmu_input_dev->swbit); + + err = input_register_device(pmu_input_dev); + if (err) + input_free_device(pmu_input_dev); + return err; +} + +void via_pmu_event(int key, int down) +{ + + if (unlikely(!pmu_input_dev)) + return; + + switch (key) { + case PMU_EVT_POWER: + input_report_key(pmu_input_dev, KEY_POWER, down); + break; + case PMU_EVT_LID: + input_report_switch(pmu_input_dev, SW_LID, down); + break; + default: + /* no such key handled */ + return; + } + + input_sync(pmu_input_dev); +} + +late_initcall(via_pmu_event_init); diff --git a/drivers/macintosh/via-pmu-event.h b/drivers/macintosh/via-pmu-event.h new file mode 100644 index 000000000..5e52109eb --- /dev/null +++ b/drivers/macintosh/via-pmu-event.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __VIA_PMU_EVENT_H +#define __VIA_PMU_EVENT_H + +#define PMU_EVT_POWER 0 +#define PMU_EVT_LID 1 +extern void via_pmu_event(int key, int down); + +#endif /* __VIA_PMU_EVENT_H */ diff --git a/drivers/macintosh/via-pmu-led.c b/drivers/macintosh/via-pmu-led.c new file mode 100644 index 000000000..a4fb16d7d --- /dev/null +++ b/drivers/macintosh/via-pmu-led.c @@ -0,0 +1,117 @@ +/* + * via-pmu LED class device + * + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/leds.h> +#include <linux/adb.h> +#include <linux/pmu.h> +#include <linux/of.h> + +static spinlock_t pmu_blink_lock; +static struct adb_request pmu_blink_req; +/* -1: no change, 0: request off, 1: request on */ +static int requested_change; + +static void pmu_req_done(struct adb_request * req) +{ + unsigned long flags; + + spin_lock_irqsave(&pmu_blink_lock, flags); + /* if someone requested a change in the meantime + * (we only see the last one which is fine) + * then apply it now */ + if (requested_change != -1 && !pmu_sys_suspended) + pmu_request(&pmu_blink_req, NULL, 4, 0xee, 4, 0, requested_change); + /* reset requested change */ + requested_change = -1; + spin_unlock_irqrestore(&pmu_blink_lock, flags); +} + +static void pmu_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + unsigned long flags; + + spin_lock_irqsave(&pmu_blink_lock, flags); + switch (brightness) { + case LED_OFF: + requested_change = 0; + break; + case LED_FULL: + requested_change = 1; + break; + default: + goto out; + break; + } + /* if request isn't done, then don't do anything */ + if (pmu_blink_req.complete && !pmu_sys_suspended) + pmu_request(&pmu_blink_req, NULL, 4, 0xee, 4, 0, requested_change); + out: + spin_unlock_irqrestore(&pmu_blink_lock, flags); +} + +static struct led_classdev pmu_led = { + .name = "pmu-led::front", +#ifdef CONFIG_ADB_PMU_LED_DISK + .default_trigger = "disk-activity", +#endif + .brightness_set = pmu_led_set, +}; + +static int __init via_pmu_led_init(void) +{ + struct device_node *dt; + const char *model; + + /* only do this on keylargo based models */ + if (pmu_get_model() != PMU_KEYLARGO_BASED) + return -ENODEV; + + dt = of_find_node_by_path("/"); + if (dt == NULL) + return -ENODEV; + model = of_get_property(dt, "model", NULL); + if (model == NULL) { + of_node_put(dt); + return -ENODEV; + } + if (strncmp(model, "PowerBook", strlen("PowerBook")) != 0 && + strncmp(model, "iBook", strlen("iBook")) != 0 && + strcmp(model, "PowerMac7,2") != 0 && + strcmp(model, "PowerMac7,3") != 0) { + of_node_put(dt); + /* ignore */ + return -ENODEV; + } + of_node_put(dt); + + spin_lock_init(&pmu_blink_lock); + /* no outstanding req */ + pmu_blink_req.complete = 1; + pmu_blink_req.done = pmu_req_done; + + return led_classdev_register(NULL, &pmu_led); +} + +late_initcall(via_pmu_led_init); diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c new file mode 100644 index 000000000..49657962d --- /dev/null +++ b/drivers/macintosh/via-pmu.c @@ -0,0 +1,2668 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device driver for the PMU in Apple PowerBooks and 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 + * Copyright (C) 2006-2007 Johannes Berg + * + * THIS DRIVER IS BECOMING A TOTAL MESS ! + * - Cleanup atomically disabling reply to PMU events after + * a sleep or a freq. switch + * + */ +#include <linux/stdarg.h> +#include <linux/mutex.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/sched/signal.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/module.h> +#include <linux/spinlock.h> +#include <linux/pm.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/syscore_ops.h> +#include <linux/freezer.h> +#include <linux/syscalls.h> +#include <linux/suspend.h> +#include <linux/cpu.h> +#include <linux/compat.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/uaccess.h> +#include <linux/pgtable.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/irq.h> +#ifdef CONFIG_PPC_PMAC +#include <asm/pmac_feature.h> +#include <asm/pmac_pfunc.h> +#include <asm/pmac_low_i2c.h> +#include <asm/mmu_context.h> +#include <asm/cputable.h> +#include <asm/time.h> +#include <asm/backlight.h> +#else +#include <asm/macintosh.h> +#include <asm/macints.h> +#include <asm/mac_via.h> +#endif + +#include "via-pmu-event.h" + +/* Some compile options */ +#undef DEBUG_SLEEP + +/* How many iterations between battery polls */ +#define BATTERY_POLLING_COUNT 2 + +static DEFINE_MUTEX(pmu_info_proc_mutex); + +/* 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 */ +#ifdef CONFIG_PPC_PMAC +#define TACK 0x08 /* Transfer acknowledge (input) */ +#define TREQ 0x10 /* Transfer request (output) */ +#else +#define TACK 0x02 +#define TREQ 0x04 +#endif + +/* 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 { + uninitialized = 0, + 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 int pmu_kind = PMU_UNKNOWN; +static int pmu_fully_inited; +static int pmu_has_adb; +#ifdef CONFIG_PPC_PMAC +static volatile unsigned char __iomem *via1; +static volatile unsigned char __iomem *via2; +static struct device_node *vias; +static struct device_node *gpio_node; +#endif +static unsigned char __iomem *gpio_reg; +static int gpio_irq = 0; +static int gpio_irq_enabled = -1; +static volatile int pmu_suspended; +static DEFINE_SPINLOCK(pmu_lock); +static u8 pmu_intr_mask; +static int pmu_version; +static int drop_interrupts; +#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32) +static int option_lid_wakeup = 1; +#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */ +static unsigned long async_req_locks; + +#define NUM_IRQ_STATS 13 +static unsigned int pmu_irq_stats[NUM_IRQ_STATS]; + +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; + +int pmu_battery_count; +static int pmu_cur_battery; +unsigned int pmu_power_flags = PMU_PWR_AC_PRESENT; +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]; + +int asleep; + +#ifdef CONFIG_ADB +static int adb_dev_map; +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 void pmu_start(void); +static irqreturn_t via_pmu_interrupt(int irq, void *arg); +static irqreturn_t gpio1_interrupt(int irq, void *arg); +static int pmu_info_proc_show(struct seq_file *m, void *v); +static int pmu_irqstats_proc_show(struct seq_file *m, void *v); +static int pmu_battery_proc_show(struct seq_file *m, void *v); +static void pmu_pass_intr(unsigned char *data, int len); +static const struct proc_ops pmu_options_proc_ops; + +#ifdef CONFIG_ADB +const struct adb_driver via_pmu_driver = { + .name = "PMU", + .probe = pmu_probe, + .init = pmu_init, + .send_request = pmu_send_request, + .autopoll = pmu_adb_autopoll, + .poll = pmu_poll_adb, + .reset_bus = 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); +void pmu_blink(int n); +#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] = { +/* 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" +}; + +int __init find_via_pmu(void) +{ +#ifdef CONFIG_PPC_PMAC + u64 taddr; + const u32 *reg; + + if (pmu_state != uninitialized) + return 1; + vias = of_find_node_by_name(NULL, "via-pmu"); + if (vias == NULL) + return 0; + + reg = of_get_property(vias, "reg", NULL); + if (reg == NULL) { + printk(KERN_ERR "via-pmu: No \"reg\" property !\n"); + goto fail; + } + taddr = of_translate_address(vias, reg); + if (taddr == OF_BAD_ADDR) { + printk(KERN_ERR "via-pmu: Can't translate address !\n"); + goto fail; + } + + pmu_has_adb = 1; + + pmu_intr_mask = PMU_INT_PCEJECT | + PMU_INT_SNDBRT | + PMU_INT_ADB | + PMU_INT_TICK; + + if (of_node_name_eq(vias->parent, "ohare") || + of_device_is_compatible(vias->parent, "ohare")) + pmu_kind = PMU_OHARE_BASED; + else if (of_device_is_compatible(vias->parent, "paddington")) + pmu_kind = PMU_PADDINGTON_BASED; + else if (of_device_is_compatible(vias->parent, "heathrow")) + pmu_kind = PMU_HEATHROW_BASED; + else if (of_device_is_compatible(vias->parent, "Keylargo") + || of_device_is_compatible(vias->parent, "K2-Keylargo")) { + struct device_node *gpiop; + struct device_node *adbp; + u64 gaddr = OF_BAD_ADDR; + + pmu_kind = PMU_KEYLARGO_BASED; + adbp = of_find_node_by_type(NULL, "adb"); + pmu_has_adb = (adbp != NULL); + of_node_put(adbp); + pmu_intr_mask = PMU_INT_PCEJECT | + PMU_INT_SNDBRT | + PMU_INT_ADB | + PMU_INT_TICK | + PMU_INT_ENVIRONMENT; + + gpiop = of_find_node_by_name(NULL, "gpio"); + if (gpiop) { + reg = of_get_property(gpiop, "reg", NULL); + if (reg) + gaddr = of_translate_address(gpiop, reg); + if (gaddr != OF_BAD_ADDR) + gpio_reg = ioremap(gaddr, 0x10); + of_node_put(gpiop); + } + if (gpio_reg == NULL) { + printk(KERN_ERR "via-pmu: Can't find GPIO reg !\n"); + goto fail; + } + } else + pmu_kind = PMU_UNKNOWN; + + via1 = via2 = ioremap(taddr, 0x2000); + if (via1 == NULL) { + printk(KERN_ERR "via-pmu: Can't map address !\n"); + goto fail_via_remap; + } + + out_8(&via1[IER], IER_CLR | 0x7f); /* disable all intrs */ + out_8(&via1[IFR], 0x7f); /* clear IFR */ + + pmu_state = idle; + + if (!init_pmu()) + goto fail_init; + + sys_ctrler = SYS_CTRLER_PMU; + + return 1; + + fail_init: + iounmap(via1); + via1 = via2 = NULL; + fail_via_remap: + iounmap(gpio_reg); + gpio_reg = NULL; + fail: + of_node_put(vias); + vias = NULL; + pmu_state = uninitialized; + return 0; +#else + if (macintosh_config->adb_type != MAC_ADB_PB2) + return 0; + + pmu_kind = PMU_UNKNOWN; + + pmu_has_adb = 1; + + pmu_intr_mask = PMU_INT_PCEJECT | + PMU_INT_SNDBRT | + PMU_INT_ADB | + PMU_INT_TICK; + + pmu_state = idle; + + if (!init_pmu()) { + pmu_state = uninitialized; + return 0; + } + + return 1; +#endif /* !CONFIG_PPC_PMAC */ +} + +#ifdef CONFIG_ADB +static int pmu_probe(void) +{ + return pmu_state == uninitialized ? -ENODEV : 0; +} + +static int pmu_init(void) +{ + return pmu_state == uninitialized ? -ENODEV : 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) +{ + unsigned int __maybe_unused irq; + + if (pmu_state == uninitialized) + return -ENODEV; + + batt_req.complete = 1; + +#ifdef CONFIG_PPC_PMAC + irq = irq_of_parse_and_map(vias, 0); + if (!irq) { + printk(KERN_ERR "via-pmu: can't map interrupt\n"); + return -ENODEV; + } + /* We set IRQF_NO_SUSPEND because we don't want the interrupt + * to be disabled between the 2 passes of driver suspend, we + * control our own disabling for that one + */ + if (request_irq(irq, via_pmu_interrupt, IRQF_NO_SUSPEND, + "VIA-PMU", (void *)0)) { + printk(KERN_ERR "via-pmu: can't request irq %d\n", irq); + return -ENODEV; + } + + if (pmu_kind == PMU_KEYLARGO_BASED) { + gpio_node = of_find_node_by_name(NULL, "extint-gpio1"); + if (gpio_node == NULL) + gpio_node = of_find_node_by_name(NULL, + "pmu-interrupt"); + if (gpio_node) + gpio_irq = irq_of_parse_and_map(gpio_node, 0); + + if (gpio_irq) { + if (request_irq(gpio_irq, gpio1_interrupt, + IRQF_NO_SUSPEND, "GPIO1 ADB", + (void *)0)) + printk(KERN_ERR "pmu: can't get irq %d" + " (GPIO1)\n", gpio_irq); + else + gpio_irq_enabled = 1; + } + } + + /* Enable interrupts */ + out_8(&via1[IER], IER_SET | SR_INT | CB1_INT); +#else + if (request_irq(IRQ_MAC_ADB_SR, via_pmu_interrupt, IRQF_NO_SUSPEND, + "VIA-PMU-SR", NULL)) { + pr_err("%s: couldn't get SR irq\n", __func__); + return -ENODEV; + } + if (request_irq(IRQ_MAC_ADB_CL, via_pmu_interrupt, IRQF_NO_SUSPEND, + "VIA-PMU-CL", NULL)) { + pr_err("%s: couldn't get CL irq\n", __func__); + free_irq(IRQ_MAC_ADB_SR, NULL); + return -ENODEV; + } +#endif /* !CONFIG_PPC_PMAC */ + + 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 (pmu_state == uninitialized) + return -ENODEV; + +#ifdef CONFIG_PMAC_BACKLIGHT + /* Initialize backlight */ + pmu_backlight_init(); +#endif + +#ifdef CONFIG_PPC32 + if (of_machine_is_compatible("AAPL,3400/2400") || + of_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 (of_machine_is_compatible("AAPL,PowerBook1998") || + of_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 = + of_find_node_by_name(NULL, "power-mgt"); + const u32 *prim_info = NULL; + if (prim) + prim_info = of_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; + } + of_node_put(prim); + } +#endif /* CONFIG_PPC32 */ + + /* Create /proc/pmu */ + proc_pmu_root = proc_mkdir("pmu", NULL); + if (proc_pmu_root) { + long i; + + for (i=0; i<pmu_battery_count; i++) { + char title[16]; + sprintf(title, "battery_%ld", i); + proc_pmu_batt[i] = proc_create_single_data(title, 0, + proc_pmu_root, pmu_battery_proc_show, + (void *)i); + } + + proc_pmu_info = proc_create_single("info", 0, proc_pmu_root, + pmu_info_proc_show); + proc_pmu_irqstats = proc_create_single("interrupts", 0, + proc_pmu_root, pmu_irqstats_proc_show); + proc_pmu_options = proc_create("options", 0600, proc_pmu_root, + &pmu_options_proc_ops); + } + return 0; +} + +device_initcall(via_pmu_dev_init); + +static int +init_pmu(void) +{ + int timeout; + struct adb_request req; + + /* Negate TREQ. Set TACK to input and TREQ to output. */ + out_8(&via2[B], in_8(&via2[B]) | TREQ); + out_8(&via2[DIRB], (in_8(&via2[DIRB]) | TREQ) & ~TACK); + + 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); + 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"); + } + } + + printk(KERN_INFO "PMU driver v%d initialized for %s, firmware: %02x\n", + PMU_DRIVER_VERSION, pbook_type[pmu_kind], pmu_version); + + return 1; +} + +int +pmu_get_model(void) +{ + return pmu_kind; +} + +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); +} + +/* This new version of the code for 2400/3400/3500 powerbooks + * is inspired from the implementation in gkrellm-pmu + */ +static void +done_battery_state_ohare(struct adb_request* req) +{ +#ifdef CONFIG_PPC_PMAC + /* 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; +#endif /* CONFIG_PPC_PMAC */ + + clear_bit(0, &async_req_locks); +} + +static void +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: + pr_warn("pmu.c: unrecognized battery info, " + "len: %d, %4ph\n", req->reply_len, + req->reply); + 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 +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); +} + +static int pmu_info_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "PMU driver version : %d\n", PMU_DRIVER_VERSION); + seq_printf(m, "PMU firmware version : %02x\n", pmu_version); + seq_printf(m, "AC Power : %d\n", + ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0) || pmu_battery_count == 0); + seq_printf(m, "Battery count : %d\n", pmu_battery_count); + + return 0; +} + +static int pmu_irqstats_proc_show(struct seq_file *m, void *v) +{ + int i; + static const char *irq_names[NUM_IRQ_STATS] = { + "Unknown interrupt (type 0)", + "Unknown interrupt (type 1)", + "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", + "Total CB1 triggered events", + "Total GPIO1 triggered events", + }; + + for (i = 0; i < NUM_IRQ_STATS; i++) { + seq_printf(m, " %2u: %10u (%s)\n", + i, pmu_irq_stats[i], irq_names[i]); + } + return 0; +} + +static int pmu_battery_proc_show(struct seq_file *m, void *v) +{ + long batnum = (long)m->private; + + seq_putc(m, '\n'); + seq_printf(m, "flags : %08x\n", pmu_batteries[batnum].flags); + seq_printf(m, "charge : %d\n", pmu_batteries[batnum].charge); + seq_printf(m, "max_charge : %d\n", pmu_batteries[batnum].max_charge); + seq_printf(m, "current : %d\n", pmu_batteries[batnum].amperage); + seq_printf(m, "voltage : %d\n", pmu_batteries[batnum].voltage); + seq_printf(m, "time rem. : %d\n", pmu_batteries[batnum].time_remaining); + return 0; +} + +static int pmu_options_proc_show(struct seq_file *m, void *v) +{ +#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32) + if (pmu_kind == PMU_KEYLARGO_BASED && + pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0) + seq_printf(m, "lid_wakeup=%d\n", option_lid_wakeup); +#endif + if (pmu_kind == PMU_KEYLARGO_BASED) + seq_printf(m, "server_mode=%d\n", option_server_mode); + + return 0; +} + +static int pmu_options_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, pmu_options_proc_show, NULL); +} + +static ssize_t pmu_options_proc_write(struct file *file, + const char __user *buffer, size_t count, loff_t *pos) +{ + char tmp[33]; + char *label, *val; + size_t 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++; +#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32) + 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 + 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; +} + +static const struct proc_ops pmu_options_proc_ops = { + .proc_open = pmu_options_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, + .proc_write = pmu_options_proc_write, +}; + +#ifdef CONFIG_ADB +/* Send an ADB command */ +static int pmu_send_request(struct adb_request *req, int sync) +{ + int i, ret; + + if (pmu_state == uninitialized || !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 __pmu_adb_autopoll(int devs) +{ + struct adb_request req; + + if (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; +} + +static int pmu_adb_autopoll(int devs) +{ + if (pmu_state == uninitialized || !pmu_fully_inited || !pmu_has_adb) + return -ENXIO; + + adb_dev_map = devs; + return __pmu_adb_autopoll(devs); +} + +/* Reset the ADB bus */ +static int pmu_adb_reset_bus(void) +{ + struct adb_request req; + int save_autopoll = adb_dev_map; + + if (pmu_state == uninitialized || !pmu_fully_inited || !pmu_has_adb) + return -ENXIO; + + /* anyone got a better idea?? */ + __pmu_adb_autopoll(0); + + req.nbytes = 4; + req.done = NULL; + req.data[0] = PMU_ADB_CMD; + req.data[1] = ADB_BUSRESET; + req.data[2] = 0; + 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 +pmu_request(struct adb_request *req, void (*done)(struct adb_request *), + int nbytes, ...) +{ + va_list list; + int i; + + if (pmu_state == uninitialized) + 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 +pmu_queue_request(struct adb_request *req) +{ + unsigned long flags; + int nsend; + + if (pmu_state == uninitialized) { + 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) { + 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(&via2[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) +{ + out_8(&via1[ACR], in_8(&via1[ACR]) | SR_OUT | SR_EXT); + out_8(&via1[SR], x); + out_8(&via2[B], in_8(&via2[B]) & ~TREQ); /* assert TREQ */ + (void)in_8(&via2[B]); +} + +static inline void +recv_byte(void) +{ + out_8(&via1[ACR], (in_8(&via1[ACR]) & ~SR_OUT) | SR_EXT); + in_8(&via1[SR]); /* resets SR */ + out_8(&via2[B], in_8(&via2[B]) & ~TREQ); + (void)in_8(&via2[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 +pmu_start(void) +{ + struct adb_request *req; + + /* assert pmu_state == idle */ + /* get the packet to send */ + req = current_req; + if (!req || 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 +pmu_poll(void) +{ + if (pmu_state == uninitialized) + return; + if (disable_poll) + return; + via_pmu_interrupt(0, NULL); +} + +void +pmu_poll_adb(void) +{ + if (pmu_state == uninitialized) + return; + if (disable_poll) + return; + /* Kicks ADB read when PMU is suspended */ + adb_int_pending = 1; + do { + via_pmu_interrupt(0, NULL); + } while (pmu_suspended && (adb_int_pending || pmu_state != idle + || req_awaiting_reply)); +} + +void +pmu_wait_complete(struct adb_request *req) +{ + if (pmu_state == uninitialized) + return; + while((pmu_state != idle && pmu_state != locked) || !req->complete) + via_pmu_interrupt(0, 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 +pmu_suspend(void) +{ + unsigned long flags; + + if (pmu_state == uninitialized) + 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); + spin_lock_irqsave(&pmu_lock, flags); + if (!adb_int_pending && pmu_state == idle && !req_awaiting_reply) { + if (gpio_irq >= 0) + disable_irq_nosync(gpio_irq); + out_8(&via1[IER], CB1_INT | IER_CLR); + spin_unlock_irqrestore(&pmu_lock, flags); + break; + } + } while (1); +} + +void +pmu_resume(void) +{ + unsigned long flags; + + if (pmu_state == uninitialized || 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; + if (gpio_irq >= 0) + enable_irq(gpio_irq); + out_8(&via1[IER], CB1_INT | IER_SET); + spin_unlock_irqrestore(&pmu_lock, flags); + pmu_poll(); +} + +/* Interrupt data could be the result data from an ADB cmd */ +static void +pmu_handle_data(unsigned char *data, int len) +{ + unsigned char ints; + int idx; + 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; + } + i++; + + idx = ffs(ints) - 1; + ints &= ~BIT(idx); + + pmu_irq_stats[idx]++; + + /* 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 + */ + switch (BIT(idx)) { + case PMU_INT_ADB: + if ((data[0] & PMU_INT_ADB_AUTO) == 0) { + struct adb_request *req = req_awaiting_reply; + if (!req) { + 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 { +#ifdef CONFIG_XMON + 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 /* CONFIG_XMON */ +#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, 1); +#endif /* CONFIG_ADB */ + } + break; + + /* Sound/brightness button pressed */ + case PMU_INT_SNDBRT: +#ifdef CONFIG_PMAC_BACKLIGHT + if (len == 3) + pmac_backlight_set_legacy_brightness_pmu(data[1] >> 4); +#endif + break; + + /* Tick interrupt */ + case PMU_INT_TICK: + /* Environment or tick interrupt, query batteries */ + if (pmu_battery_count) { + if ((--query_batt_timer) == 0) { + query_battery_state(); + query_batt_timer = BATTERY_POLLING_COUNT; + } + } + break; + + case PMU_INT_ENVIRONMENT: + if (pmu_battery_count) + query_battery_state(); + pmu_pass_intr(data, len); + /* len == 6 is probably a bad check. But how do I + * know what PMU versions send what events here? */ + if (IS_ENABLED(CONFIG_ADB_PMU_EVENT) && len == 6) { + via_pmu_event(PMU_EVT_POWER, !!(data[1]&8)); + via_pmu_event(PMU_EVT_LID, data[1]&1); + } + break; + + default: + pmu_pass_intr(data, len); + } + goto next; +} + +static struct adb_request* +pmu_sr_intr(void) +{ + struct adb_request *req; + int bite = 0; + + if (in_8(&via2[B]) & TREQ) { + printk(KERN_ERR "PMU: spurious SR intr (%x)\n", in_8(&via2[B])); + return NULL; + } + /* The ack may not yet be low when we get the interrupt */ + while ((in_8(&via2[B]) & TACK) != 0) + ; + + /* if reading grab the byte, and reset the interrupt */ + if (pmu_state == reading || pmu_state == reading_intr) + bite = in_8(&via1[SR]); + + /* reset TREQ and wait for TACK to go high */ + out_8(&via2[B], in_8(&via2[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 explicitly 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 +via_pmu_interrupt(int irq, void *arg) +{ + 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 (;;) { + /* On 68k Macs, VIA interrupts are dispatched individually. + * Unless we are polling, the relevant IRQ flag has already + * been cleared. + */ + intr = 0; + if (IS_ENABLED(CONFIG_PPC_PMAC) || !irq) { + intr = in_8(&via1[IFR]) & (SR_INT | CB1_INT); + out_8(&via1[IFR], intr); + } +#ifndef CONFIG_PPC_PMAC + switch (irq) { + case IRQ_MAC_ADB_CL: + intr = CB1_INT; + break; + case IRQ_MAC_ADB_SR: + intr = SR_INT; + break; + } +#endif + 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(&via1[IER]), pmu_state); + break; + } + if (intr & CB1_INT) { + adb_int_pending = 1; + pmu_irq_stats[11]++; + } + if (intr & SR_INT) { + req = pmu_sr_intr(); + if (req) + break; + } +#ifndef CONFIG_PPC_PMAC + break; +#endif + } + +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]); + 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 +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 __maybe_unused irqreturn_t +gpio1_interrupt(int irq, void *arg) +{ + 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[12]++; + adb_int_pending = 1; + spin_unlock_irqrestore(&pmu_lock, flags); + via_pmu_interrupt(0, NULL); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +void +pmu_enable_irled(int on) +{ + struct adb_request req; + + if (pmu_state == uninitialized) + 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); +} + +/* Offset between Unix time (1970-based) and Mac time (1904-based) */ +#define RTC_OFFSET 2082844800 + +time64_t pmu_get_time(void) +{ + struct adb_request req; + u32 now; + + if (pmu_request(&req, NULL, 1, PMU_READ_RTC) < 0) + return 0; + pmu_wait_complete(&req); + if (req.reply_len != 4) + pr_err("%s: got %d byte reply\n", __func__, req.reply_len); + now = (req.reply[0] << 24) + (req.reply[1] << 16) + + (req.reply[2] << 8) + req.reply[3]; + return (time64_t)now - RTC_OFFSET; +} + +int pmu_set_rtc_time(struct rtc_time *tm) +{ + u32 now; + struct adb_request req; + + now = lower_32_bits(rtc_tm_to_time64(tm) + RTC_OFFSET); + if (pmu_request(&req, NULL, 5, PMU_SET_RTC, + now >> 24, now >> 16, now >> 8, now) < 0) + return -ENXIO; + pmu_wait_complete(&req); + if (req.reply_len != 0) + pr_err("%s: got %d byte reply\n", __func__, req.reply_len); + return 0; +} + +void +pmu_restart(void) +{ + struct adb_request req; + + if (pmu_state == uninitialized) + 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 +pmu_shutdown(void) +{ + struct adb_request req; + + if (pmu_state == uninitialized) + 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 pmu_state != uninitialized; +} + +#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32) +/* + * Put the powerbook to sleep. + */ + +static u32 save_via[8]; +static int __fake_sleep; + +static void +save_via_state(void) +{ + save_via[0] = in_8(&via1[ANH]); + save_via[1] = in_8(&via1[DIRA]); + save_via[2] = in_8(&via1[B]); + save_via[3] = in_8(&via1[DIRB]); + save_via[4] = in_8(&via1[PCR]); + save_via[5] = in_8(&via1[ACR]); + save_via[6] = in_8(&via1[T1CL]); + save_via[7] = in_8(&via1[T1CH]); +} +static void +restore_via_state(void) +{ + out_8(&via1[ANH], save_via[0]); + out_8(&via1[DIRA], save_via[1]); + out_8(&via1[B], save_via[2]); + out_8(&via1[DIRB], save_via[3]); + out_8(&via1[PCR], save_via[4]); + out_8(&via1[ACR], save_via[5]); + out_8(&via1[T1CL], save_via[6]); + out_8(&via1[T1CH], save_via[7]); + out_8(&via1[IER], IER_CLR | 0x7f); /* disable all intrs */ + out_8(&via1[IFR], 0x7f); /* clear IFR */ + out_8(&via1[IER], IER_SET | SR_INT | CB1_INT); +} + +#define GRACKLE_PM (1<<7) +#define GRACKLE_DOZE (1<<5) +#define GRACKLE_NAP (1<<4) +#define GRACKLE_SLEEP (1<<3) + +static int powerbook_sleep_grackle(void) +{ + unsigned long save_l2cr; + unsigned short pmcr1; + struct adb_request req; + struct pci_dev *grackle; + + grackle = pci_get_domain_bus_and_slot(0, 0, 0); + if (!grackle) + return -ENODEV; + + /* 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); + + pci_dev_put(grackle); + + /* 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 */ + switch_mmu_context(NULL, current->active_mm, NULL); + + /* 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); + + return 0; +} + +static int +powerbook_sleep_Core99(void) +{ + unsigned long save_l2cr; + unsigned long save_l3cr; + struct adb_request req; + + 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; + + /* Stop environment and ADB interrupts */ + pmu_request(&req, NULL, 2, PMU_SET_INTR_MASK, 0); + pmu_wait_complete(&req); + + /* 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(); + + /* tweak LPJ before cpufreq is there */ + loops_per_jiffy *= 2; + + /* 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 */ + switch_mmu_context(NULL, current->active_mm, NULL); + + /* 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); + + /* Restore LPJ, cpufreq will adjust the cpu frequency */ + loops_per_jiffy /= 2; + + return 0; +} + +#define PB3400_MEM_CTRL 0xf8000000 +#define PB3400_MEM_CTRL_SLEEP 0x70 + +static void __iomem *pb3400_mem_ctrl; + +static void powerbook_sleep_init_3400(void) +{ + /* map in the memory controller registers */ + pb3400_mem_ctrl = ioremap(PB3400_MEM_CTRL, 0x100); + if (pb3400_mem_ctrl == NULL) + printk(KERN_WARNING "ioremap failed: sleep won't be possible"); +} + +static int powerbook_sleep_3400(void) +{ + int i, x; + unsigned int hid0; + unsigned long msr; + struct adb_request sleep_req; + unsigned int __iomem *mem_ctrl_sleep; + + if (pb3400_mem_ctrl == NULL) + return -ENOMEM; + mem_ctrl_sleep = pb3400_mem_ctrl + PB3400_MEM_CTRL_SLEEP; + + /* 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'); + pmu_wait_complete(&sleep_req); + pmu_unlock(); + + pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 1); + + asleep = 1; + + /* Put the CPU into sleep mode */ + hid0 = mfspr(SPRN_HID0); + hid0 = (hid0 & ~(HID0_NAP | HID0_DOZE)) | HID0_SLEEP; + mtspr(SPRN_HID0, hid0); + local_irq_enable(); + msr = mfmsr() | MSR_POW; + while (asleep) { + mb(); + mtmsr(msr); + isync(); + } + local_irq_disable(); + + /* OK, we're awake again, start restoring things */ + out_be32(mem_ctrl_sleep, 0x3f); + pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, 0); + + return 0; +} + +#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */ + +/* + * 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 +}; + +static LIST_HEAD(all_pmu_pvt); +static DEFINE_SPINLOCK(all_pvt_lock); + +static void +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 +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) + return -ENOMEM; + pp->rb_get = pp->rb_put = 0; + spin_lock_init(&pp->lock); + init_waitqueue_head(&pp->wait); + mutex_lock(&pmu_info_proc_mutex); + spin_lock_irqsave(&all_pvt_lock, flags); +#if defined(CONFIG_INPUT_ADBHID) && defined(CONFIG_PMAC_BACKLIGHT) + pp->backlight_locker = 0; +#endif + list_add(&pp->list, &all_pmu_pvt); + spin_unlock_irqrestore(&all_pvt_lock, flags); + file->private_data = pp; + mutex_unlock(&pmu_info_proc_mutex); + return 0; +} + +static ssize_t +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) + return -EINVAL; + + spin_lock_irqsave(&pp->lock, flags); + add_wait_queue(&pp->wait, &wait); + set_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); + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&pp->wait, &wait); + spin_unlock_irqrestore(&pp->lock, flags); + + return ret; +} + +static ssize_t +pmu_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static __poll_t +pmu_fpoll(struct file *filp, poll_table *wait) +{ + struct pmu_private *pp = filp->private_data; + __poll_t mask = 0; + unsigned long flags; + + if (!pp) + return 0; + poll_wait(filp, &pp->wait, wait); + spin_lock_irqsave(&pp->lock, flags); + if (pp->rb_get != pp->rb_put) + mask |= EPOLLIN; + spin_unlock_irqrestore(&pp->lock, flags); + return mask; +} + +static int +pmu_release(struct inode *inode, struct file *file) +{ + struct pmu_private *pp = file->private_data; + unsigned long flags; + + if (pp) { + 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) + pmac_backlight_enable(); +#endif + + kfree(pp); + } + return 0; +} + +#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32) +static void pmac_suspend_disable_irqs(void) +{ + /* Call platform functions marked "on sleep" */ + pmac_pfunc_i2c_suspend(); + pmac_pfunc_base_suspend(); +} + +static int powerbook_sleep(suspend_state_t state) +{ + int error = 0; + + /* Wait for completion of async requests */ + while (!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 */ + + 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: + return -ENOSYS; + } + + if (error) + return error; + + mdelay(100); + + return 0; +} + +static void pmac_suspend_enable_irqs(void) +{ + /* Force a poll of ADB interrupts */ + adb_int_pending = 1; + via_pmu_interrupt(0, NULL); + + mdelay(10); + + /* Call platform functions marked "on wake" */ + pmac_pfunc_base_resume(); + pmac_pfunc_i2c_resume(); +} + +static int pmu_sleep_valid(suspend_state_t state) +{ + return state == PM_SUSPEND_MEM + && (pmac_call_feature(PMAC_FTR_SLEEP_STATE, NULL, 0, -1) >= 0); +} + +static const struct platform_suspend_ops pmu_pm_ops = { + .enter = powerbook_sleep, + .valid = pmu_sleep_valid, +}; + +static int register_pmu_pm_ops(void) +{ + if (pmu_kind == PMU_OHARE_BASED) + powerbook_sleep_init_3400(); + ppc_md.suspend_disable_irqs = pmac_suspend_disable_irqs; + ppc_md.suspend_enable_irqs = pmac_suspend_enable_irqs; + suspend_set_ops(&pmu_pm_ops); + + return 0; +} + +device_initcall(register_pmu_pm_ops); +#endif + +static int pmu_ioctl(struct file *filp, + u_int cmd, u_long arg) +{ + __u32 __user *argp = (__u32 __user *)arg; + int error = -EINVAL; + + switch (cmd) { +#ifdef CONFIG_PPC_PMAC + case PMU_IOC_SLEEP: + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + return pm_suspend(PM_SUSPEND_MEM); + 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); +#endif + +#ifdef CONFIG_PMAC_BACKLIGHT_LEGACY + /* Compatibility ioctl's for backlight */ + case PMU_IOC_GET_BACKLIGHT: + { + int brightness; + + brightness = pmac_backlight_get_legacy_brightness(); + if (brightness < 0) + return brightness; + else + return put_user(brightness, argp); + + } + case PMU_IOC_SET_BACKLIGHT: + { + int brightness; + + error = get_user(brightness, argp); + if (error) + return error; + + return pmac_backlight_set_legacy_brightness(brightness); + } +#ifdef CONFIG_INPUT_ADBHID + case PMU_IOC_GRAB_BACKLIGHT: { + struct pmu_private *pp = filp->private_data; + + if (pp->backlight_locker) + return 0; + + pp->backlight_locker = 1; + pmac_backlight_disable(); + + return 0; + } +#endif /* CONFIG_INPUT_ADBHID */ +#endif /* CONFIG_PMAC_BACKLIGHT_LEGACY */ + + case PMU_IOC_GET_MODEL: + return put_user(pmu_kind, argp); + case PMU_IOC_HAS_ADB: + return put_user(pmu_has_adb, argp); + } + return error; +} + +static long pmu_unlocked_ioctl(struct file *filp, + u_int cmd, u_long arg) +{ + int ret; + + mutex_lock(&pmu_info_proc_mutex); + ret = pmu_ioctl(filp, cmd, arg); + mutex_unlock(&pmu_info_proc_mutex); + + return ret; +} + +#ifdef CONFIG_COMPAT +#define PMU_IOC_GET_BACKLIGHT32 _IOR('B', 1, compat_size_t) +#define PMU_IOC_SET_BACKLIGHT32 _IOW('B', 2, compat_size_t) +#define PMU_IOC_GET_MODEL32 _IOR('B', 3, compat_size_t) +#define PMU_IOC_HAS_ADB32 _IOR('B', 4, compat_size_t) +#define PMU_IOC_CAN_SLEEP32 _IOR('B', 5, compat_size_t) +#define PMU_IOC_GRAB_BACKLIGHT32 _IOR('B', 6, compat_size_t) + +static long compat_pmu_ioctl (struct file *filp, u_int cmd, u_long arg) +{ + switch (cmd) { + case PMU_IOC_SLEEP: + break; + case PMU_IOC_GET_BACKLIGHT32: + cmd = PMU_IOC_GET_BACKLIGHT; + break; + case PMU_IOC_SET_BACKLIGHT32: + cmd = PMU_IOC_SET_BACKLIGHT; + break; + case PMU_IOC_GET_MODEL32: + cmd = PMU_IOC_GET_MODEL; + break; + case PMU_IOC_HAS_ADB32: + cmd = PMU_IOC_HAS_ADB; + break; + case PMU_IOC_CAN_SLEEP32: + cmd = PMU_IOC_CAN_SLEEP; + break; + case PMU_IOC_GRAB_BACKLIGHT32: + cmd = PMU_IOC_GRAB_BACKLIGHT; + break; + default: + return -ENOIOCTLCMD; + } + return pmu_unlocked_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static const struct file_operations pmu_device_fops = { + .read = pmu_read, + .write = pmu_write, + .poll = pmu_fpoll, + .unlocked_ioctl = pmu_unlocked_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = compat_pmu_ioctl, +#endif + .open = pmu_open, + .release = pmu_release, + .llseek = noop_llseek, +}; + +static struct miscdevice pmu_device = { + PMU_MINOR, "pmu", &pmu_device_fops +}; + +static int pmu_device_init(void) +{ + if (pmu_state == uninitialized) + return 0; + if (misc_register(&pmu_device) < 0) + printk(KERN_ERR "via-pmu: cannot register misc device.\n"); + return 0; +} +device_initcall(pmu_device_init); + + +#ifdef DEBUG_SLEEP +static inline void +polled_handshake(void) +{ + via2[B] &= ~TREQ; eieio(); + while ((via2[B] & TACK) != 0) + ; + via2[B] |= TREQ; eieio(); + while ((via2[B] & TACK) == 0) + ; +} + +static inline void +polled_send_byte(int x) +{ + via1[ACR] |= SR_OUT | SR_EXT; eieio(); + via1[SR] = x; eieio(); + polled_handshake(); +} + +static inline int +polled_recv_byte(void) +{ + int x; + + via1[ACR] = (via1[ACR] & ~SR_OUT) | SR_EXT; eieio(); + x = via1[SR]; eieio(); + polled_handshake(); + x = via1[SR]; eieio(); + return x; +} + +int +pmu_polled_request(struct adb_request *req) +{ + unsigned long flags; + int i, l, c; + + 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 ((via2[B] & TACK) == 0) + ; + polled_send_byte(c); + if (l < 0) { + l = req->nbytes - 1; + polled_send_byte(l); + } + for (i = 1; i <= l; ++i) + polled_send_byte(req->data[i]); + + l = pmu_data_len[c][1]; + if (l < 0) + l = polled_recv_byte(); + for (i = 0; i < l; ++i) + req->reply[i + req->reply_len] = polled_recv_byte(); + + if (req->done) + (*req->done)(req); + + local_irq_restore(flags); + return 0; +} + +/* N.B. This doesn't work on the 3400 */ +void 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 /* DEBUG_SLEEP */ + +#if defined(CONFIG_SUSPEND) && defined(CONFIG_PPC32) +int pmu_sys_suspended; + +static int pmu_syscore_suspend(void) +{ + /* Suspend PMU event interrupts */ + pmu_suspend(); + pmu_sys_suspended = 1; + +#ifdef CONFIG_PMAC_BACKLIGHT + /* Tell backlight code not to muck around with the chip anymore */ + pmu_backlight_set_sleep(1); +#endif + + return 0; +} + +static void pmu_syscore_resume(void) +{ + struct adb_request req; + + if (!pmu_sys_suspended) + return; + + /* Tell PMU we are ready */ + pmu_request(&req, NULL, 2, PMU_SYSTEM_READY, 2); + pmu_wait_complete(&req); + +#ifdef CONFIG_PMAC_BACKLIGHT + /* Tell backlight code it can use the chip again */ + pmu_backlight_set_sleep(0); +#endif + /* Resume PMU event interrupts */ + pmu_resume(); + pmu_sys_suspended = 0; +} + +static struct syscore_ops pmu_syscore_ops = { + .suspend = pmu_syscore_suspend, + .resume = pmu_syscore_resume, +}; + +static int pmu_syscore_register(void) +{ + register_syscore_ops(&pmu_syscore_ops); + + return 0; +} +subsys_initcall(pmu_syscore_register); +#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */ + +EXPORT_SYMBOL(pmu_request); +EXPORT_SYMBOL(pmu_queue_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); +#if defined(CONFIG_PPC32) +EXPORT_SYMBOL(pmu_enable_irled); +EXPORT_SYMBOL(pmu_battery_count); +EXPORT_SYMBOL(pmu_batteries); +EXPORT_SYMBOL(pmu_power_flags); +#endif /* CONFIG_SUSPEND && CONFIG_PPC32 */ + diff --git a/drivers/macintosh/windfarm.h b/drivers/macintosh/windfarm.h new file mode 100644 index 000000000..5eb480d84 --- /dev/null +++ b/drivers/macintosh/windfarm.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Windfarm PowerMac thermal control. + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + */ + +#ifndef __WINDFARM_H__ +#define __WINDFARM_H__ + +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/device.h> + +/* Display a 16.16 fixed point value */ +#define FIX32TOPRINT(f) (((s32)(f)) >> 16),(((((s32)(f)) & 0xffff) * 1000) >> 16) + +/* + * Control objects + */ + +struct wf_control; + +struct wf_control_ops { + int (*set_value)(struct wf_control *ct, s32 val); + int (*get_value)(struct wf_control *ct, s32 *val); + s32 (*get_min)(struct wf_control *ct); + s32 (*get_max)(struct wf_control *ct); + void (*release)(struct wf_control *ct); + struct module *owner; +}; + +struct wf_control { + struct list_head link; + const struct wf_control_ops *ops; + const char *name; + int type; + struct kref ref; + struct device_attribute attr; + void *priv; +}; + +#define WF_CONTROL_TYPE_GENERIC 0 +#define WF_CONTROL_RPM_FAN 1 +#define WF_CONTROL_PWM_FAN 2 + + +/* Note about lifetime rules: wf_register_control() will initialize + * the kref and wf_unregister_control will decrement it, thus the + * object creating/disposing a given control shouldn't assume it + * still exists after wf_unregister_control has been called. + */ +extern int wf_register_control(struct wf_control *ct); +extern void wf_unregister_control(struct wf_control *ct); +extern int wf_get_control(struct wf_control *ct); +extern void wf_put_control(struct wf_control *ct); + +static inline int wf_control_set_max(struct wf_control *ct) +{ + s32 vmax = ct->ops->get_max(ct); + return ct->ops->set_value(ct, vmax); +} + +static inline int wf_control_set_min(struct wf_control *ct) +{ + s32 vmin = ct->ops->get_min(ct); + return ct->ops->set_value(ct, vmin); +} + +static inline int wf_control_set(struct wf_control *ct, s32 val) +{ + return ct->ops->set_value(ct, val); +} + +static inline int wf_control_get(struct wf_control *ct, s32 *val) +{ + return ct->ops->get_value(ct, val); +} + +static inline s32 wf_control_get_min(struct wf_control *ct) +{ + return ct->ops->get_min(ct); +} + +static inline s32 wf_control_get_max(struct wf_control *ct) +{ + return ct->ops->get_max(ct); +} + +/* + * Sensor objects + */ + +struct wf_sensor; + +struct wf_sensor_ops { + int (*get_value)(struct wf_sensor *sr, s32 *val); + void (*release)(struct wf_sensor *sr); + struct module *owner; +}; + +struct wf_sensor { + struct list_head link; + const struct wf_sensor_ops *ops; + const char *name; + struct kref ref; + struct device_attribute attr; + void *priv; +}; + +/* Same lifetime rules as controls */ +extern int wf_register_sensor(struct wf_sensor *sr); +extern void wf_unregister_sensor(struct wf_sensor *sr); +extern int wf_get_sensor(struct wf_sensor *sr); +extern void wf_put_sensor(struct wf_sensor *sr); + +static inline int wf_sensor_get(struct wf_sensor *sr, s32 *val) +{ + return sr->ops->get_value(sr, val); +} + +/* For use by clients. Note that we are a bit racy here since + * notifier_block doesn't have a module owner field. I may fix + * it one day ... + * + * LOCKING NOTE ! + * + * All "events" except WF_EVENT_TICK are called with an internal mutex + * held which will deadlock if you call basically any core routine. + * So don't ! Just take note of the event and do your actual operations + * from the ticker. + * + */ +extern int wf_register_client(struct notifier_block *nb); +extern int wf_unregister_client(struct notifier_block *nb); + +/* Overtemp conditions. Those are refcounted */ +extern void wf_set_overtemp(void); +extern void wf_clear_overtemp(void); + +#define WF_EVENT_NEW_CONTROL 0 /* param is wf_control * */ +#define WF_EVENT_NEW_SENSOR 1 /* param is wf_sensor * */ +#define WF_EVENT_OVERTEMP 2 /* no param */ +#define WF_EVENT_NORMALTEMP 3 /* overtemp condition cleared */ +#define WF_EVENT_TICK 4 /* 1 second tick */ + +/* Note: If that driver gets more broad use, we could replace the + * simplistic overtemp bits with "environmental conditions". That + * could then be used to also notify of things like fan failure, + * case open, battery conditions, ... + */ + +#endif /* __WINDFARM_H__ */ diff --git a/drivers/macintosh/windfarm_ad7417_sensor.c b/drivers/macintosh/windfarm_ad7417_sensor.c new file mode 100644 index 000000000..c5c54a4ce --- /dev/null +++ b/drivers/macintosh/windfarm_ad7417_sensor.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. AD7417 sensors + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> + +#include "windfarm.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +struct wf_ad7417_priv { + struct kref ref; + struct i2c_client *i2c; + u8 config; + u8 cpu; + const struct mpu_data *mpu; + struct wf_sensor sensors[5]; + struct mutex lock; +}; + +static int wf_ad7417_temp_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_ad7417_priv *pv = sr->priv; + u8 buf[2]; + s16 raw; + int rc; + + *value = 0; + mutex_lock(&pv->lock); + + /* Read temp register */ + buf[0] = 0; + rc = i2c_master_send(pv->i2c, buf, 1); + if (rc < 0) + goto error; + rc = i2c_master_recv(pv->i2c, buf, 2); + if (rc < 0) + goto error; + + /* Read a a 16-bit signed value */ + raw = be16_to_cpup((__le16 *)buf); + + /* Convert 8.8-bit to 16.16 fixed point */ + *value = ((s32)raw) << 8; + + mutex_unlock(&pv->lock); + return 0; + +error: + mutex_unlock(&pv->lock); + return -1; +} + +/* + * 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 */ + +static void wf_ad7417_adc_convert(struct wf_ad7417_priv *pv, + int chan, s32 raw, s32 *value) +{ + switch(chan) { + case 1: /* Diode */ + *value = (raw * (s32)pv->mpu->mdiode + + ((s32)pv->mpu->bdiode << 12)) >> 2; + break; + case 2: /* 12v current */ + *value = raw * ADC_12V_CURRENT_SCALE; + break; + case 3: /* core voltage */ + *value = raw * ADC_CPU_VOLTAGE_SCALE; + break; + case 4: /* core current */ + *value = raw * ADC_CPU_CURRENT_SCALE; + break; + } +} + +static int wf_ad7417_adc_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_ad7417_priv *pv = sr->priv; + int chan = sr - pv->sensors; + int i, rc; + u8 buf[2]; + u16 raw; + + *value = 0; + mutex_lock(&pv->lock); + for (i = 0; i < 10; i++) { + /* Set channel */ + buf[0] = 1; + buf[1] = (pv->config & 0x1f) | (chan << 5); + rc = i2c_master_send(pv->i2c, buf, 2); + if (rc < 0) + goto error; + + /* Wait for conversion */ + msleep(1); + + /* Switch to data register */ + buf[0] = 4; + rc = i2c_master_send(pv->i2c, buf, 1); + if (rc < 0) + goto error; + + /* Read result */ + rc = i2c_master_recv(pv->i2c, buf, 2); + if (rc < 0) + goto error; + + /* Read a a 16-bit signed value */ + raw = be16_to_cpup((__le16 *)buf) >> 6; + wf_ad7417_adc_convert(pv, chan, raw, value); + + dev_vdbg(&pv->i2c->dev, "ADC chan %d [%s]" + " raw value: 0x%x, conv to: 0x%08x\n", + chan, sr->name, raw, *value); + + mutex_unlock(&pv->lock); + return 0; + + error: + dev_dbg(&pv->i2c->dev, + "Error reading ADC, try %d...\n", i); + if (i < 9) + msleep(10); + } + mutex_unlock(&pv->lock); + return -1; +} + +static void wf_ad7417_release(struct kref *ref) +{ + struct wf_ad7417_priv *pv = container_of(ref, + struct wf_ad7417_priv, ref); + kfree(pv); +} + +static void wf_ad7417_sensor_release(struct wf_sensor *sr) +{ + struct wf_ad7417_priv *pv = sr->priv; + + kfree(sr->name); + kref_put(&pv->ref, wf_ad7417_release); +} + +static const struct wf_sensor_ops wf_ad7417_temp_ops = { + .get_value = wf_ad7417_temp_get, + .release = wf_ad7417_sensor_release, + .owner = THIS_MODULE, +}; + +static const struct wf_sensor_ops wf_ad7417_adc_ops = { + .get_value = wf_ad7417_adc_get, + .release = wf_ad7417_sensor_release, + .owner = THIS_MODULE, +}; + +static void wf_ad7417_add_sensor(struct wf_ad7417_priv *pv, + int index, const char *name, + const struct wf_sensor_ops *ops) +{ + pv->sensors[index].name = kasprintf(GFP_KERNEL, "%s-%d", name, pv->cpu); + pv->sensors[index].priv = pv; + pv->sensors[index].ops = ops; + if (!wf_register_sensor(&pv->sensors[index])) + kref_get(&pv->ref); +} + +static void wf_ad7417_init_chip(struct wf_ad7417_priv *pv) +{ + int rc; + u8 buf[2]; + u8 config = 0; + + /* + * 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(pv->i2c, buf, 2); + + /* Read & cache Config1 */ + buf[0] = 1; + rc = i2c_master_send(pv->i2c, buf, 1); + if (rc > 0) { + rc = i2c_master_recv(pv->i2c, buf, 1); + if (rc > 0) { + config = buf[0]; + + dev_dbg(&pv->i2c->dev, "ADC config reg: %02x\n", + config); + + /* Disable shutdown mode */ + config &= 0xfe; + buf[0] = 1; + buf[1] = config; + rc = i2c_master_send(pv->i2c, buf, 2); + } + } + if (rc <= 0) + dev_err(&pv->i2c->dev, "Error reading ADC config\n"); + + pv->config = config; +} + +static int wf_ad7417_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wf_ad7417_priv *pv; + const struct mpu_data *mpu; + const char *loc; + int cpu_nr; + + loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); + if (!loc) { + dev_warn(&client->dev, "Missing hwsensor-location property!\n"); + return -ENXIO; + } + + /* + * Identify which CPU we belong to by looking at the first entry + * in the hwsensor-location list + */ + if (!strncmp(loc, "CPU A", 5)) + cpu_nr = 0; + else if (!strncmp(loc, "CPU B", 5)) + cpu_nr = 1; + else { + pr_err("wf_ad7417: Can't identify location %s\n", loc); + return -ENXIO; + } + mpu = wf_get_mpu(cpu_nr); + if (!mpu) { + dev_err(&client->dev, "Failed to retrieve MPU data\n"); + return -ENXIO; + } + + pv = kzalloc(sizeof(struct wf_ad7417_priv), GFP_KERNEL); + if (pv == NULL) + return -ENODEV; + + kref_init(&pv->ref); + mutex_init(&pv->lock); + pv->i2c = client; + pv->cpu = cpu_nr; + pv->mpu = mpu; + dev_set_drvdata(&client->dev, pv); + + /* Initialize the chip */ + wf_ad7417_init_chip(pv); + + /* + * We cannot rely on Apple device-tree giving us child + * node with the names of the individual sensors so we + * just hard code what we know about them + */ + wf_ad7417_add_sensor(pv, 0, "cpu-amb-temp", &wf_ad7417_temp_ops); + wf_ad7417_add_sensor(pv, 1, "cpu-diode-temp", &wf_ad7417_adc_ops); + wf_ad7417_add_sensor(pv, 2, "cpu-12v-current", &wf_ad7417_adc_ops); + wf_ad7417_add_sensor(pv, 3, "cpu-voltage", &wf_ad7417_adc_ops); + wf_ad7417_add_sensor(pv, 4, "cpu-current", &wf_ad7417_adc_ops); + + return 0; +} + +static void wf_ad7417_remove(struct i2c_client *client) +{ + struct wf_ad7417_priv *pv = dev_get_drvdata(&client->dev); + int i; + + /* Mark client detached */ + pv->i2c = NULL; + + /* Release sensor */ + for (i = 0; i < 5; i++) + wf_unregister_sensor(&pv->sensors[i]); + + kref_put(&pv->ref, wf_ad7417_release); +} + +static const struct i2c_device_id wf_ad7417_id[] = { + { "MAC,ad7417", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_ad7417_id); + +static const struct of_device_id wf_ad7417_of_id[] = { + { .compatible = "ad7417", }, + { } +}; +MODULE_DEVICE_TABLE(of, wf_ad7417_of_id); + +static struct i2c_driver wf_ad7417_driver = { + .driver = { + .name = "wf_ad7417", + .of_match_table = wf_ad7417_of_id, + }, + .probe = wf_ad7417_probe, + .remove = wf_ad7417_remove, + .id_table = wf_ad7417_id, +}; + +static int wf_ad7417_init(void) +{ + /* This is only supported on these machines */ + if (!of_machine_is_compatible("PowerMac7,2") && + !of_machine_is_compatible("PowerMac7,3") && + !of_machine_is_compatible("RackMac3,1")) + return -ENODEV; + + return i2c_add_driver(&wf_ad7417_driver); +} + +static void wf_ad7417_exit(void) +{ + i2c_del_driver(&wf_ad7417_driver); +} + +module_init(wf_ad7417_init); +module_exit(wf_ad7417_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("ad7417 sensor driver for PowerMacs"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c new file mode 100644 index 000000000..5307b1e34 --- /dev/null +++ b/drivers/macintosh/windfarm_core.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. Core + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * This core code tracks the list of sensors & controls, register + * clients, and holds the kernel thread used for control. + * + * TODO: + * + * Add some information about sensor/control type and data format to + * sensors/controls, and have the sysfs attribute stuff be moved + * generically here instead of hard coded in the platform specific + * driver as it us currently + * + * This however requires solving some annoying lifetime issues with + * sysfs which doesn't seem to have lifetime rules for struct attribute, + * I may have to create full features kobjects for every sensor/control + * instead which is a bit of an overkill imho + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/kthread.h> +#include <linux/jiffies.h> +#include <linux/reboot.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/freezer.h> + +#include "windfarm.h" + +#define VERSION "0.2" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +static LIST_HEAD(wf_controls); +static LIST_HEAD(wf_sensors); +static DEFINE_MUTEX(wf_lock); +static BLOCKING_NOTIFIER_HEAD(wf_client_list); +static int wf_client_count; +static unsigned int wf_overtemp; +static unsigned int wf_overtemp_counter; +static struct task_struct *wf_thread; + +static struct platform_device wf_platform_device = { + .name = "windfarm", +}; + +/* + * Utilities & tick thread + */ + +static inline void wf_notify(int event, void *param) +{ + blocking_notifier_call_chain(&wf_client_list, event, param); +} + +static int wf_critical_overtemp(void) +{ + static char const critical_overtemp_path[] = "/sbin/critical_overtemp"; + char *argv[] = { (char *)critical_overtemp_path, NULL }; + static char *envp[] = { "HOME=/", + "TERM=linux", + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", + NULL }; + + return call_usermodehelper(critical_overtemp_path, + argv, envp, UMH_WAIT_EXEC); +} + +static int wf_thread_func(void *data) +{ + unsigned long next, delay; + + next = jiffies; + + DBG("wf: thread started\n"); + + set_freezable(); + while (!kthread_should_stop()) { + try_to_freeze(); + + if (time_after_eq(jiffies, next)) { + wf_notify(WF_EVENT_TICK, NULL); + if (wf_overtemp) { + wf_overtemp_counter++; + /* 10 seconds overtemp, notify userland */ + if (wf_overtemp_counter > 10) + wf_critical_overtemp(); + /* 30 seconds, shutdown */ + if (wf_overtemp_counter > 30) { + printk(KERN_ERR "windfarm: Overtemp " + "for more than 30" + " seconds, shutting down\n"); + machine_power_off(); + } + } + next += HZ; + } + + delay = next - jiffies; + if (delay <= HZ) + schedule_timeout_interruptible(delay); + } + + DBG("wf: thread stopped\n"); + + return 0; +} + +static void wf_start_thread(void) +{ + wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm"); + if (IS_ERR(wf_thread)) { + printk(KERN_ERR "windfarm: failed to create thread,err %ld\n", + PTR_ERR(wf_thread)); + wf_thread = NULL; + } +} + + +static void wf_stop_thread(void) +{ + if (wf_thread) + kthread_stop(wf_thread); + wf_thread = NULL; +} + +/* + * Controls + */ + +static void wf_control_release(struct kref *kref) +{ + struct wf_control *ct = container_of(kref, struct wf_control, ref); + + DBG("wf: Deleting control %s\n", ct->name); + + if (ct->ops && ct->ops->release) + ct->ops->release(ct); + else + kfree(ct); +} + +static ssize_t wf_show_control(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wf_control *ctrl = container_of(attr, struct wf_control, attr); + const char *typestr; + s32 val = 0; + int err; + + err = ctrl->ops->get_value(ctrl, &val); + if (err < 0) { + if (err == -EFAULT) + return sprintf(buf, "<HW FAULT>\n"); + return err; + } + switch(ctrl->type) { + case WF_CONTROL_RPM_FAN: + typestr = " RPM"; + break; + case WF_CONTROL_PWM_FAN: + typestr = " %"; + break; + default: + typestr = ""; + } + return sprintf(buf, "%d%s\n", val, typestr); +} + +/* This is really only for debugging... */ +static ssize_t wf_store_control(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wf_control *ctrl = container_of(attr, struct wf_control, attr); + int val; + int err; + char *endp; + + val = simple_strtoul(buf, &endp, 0); + while (endp < buf + count && (*endp == ' ' || *endp == '\n')) + ++endp; + if (endp - buf < count) + return -EINVAL; + err = ctrl->ops->set_value(ctrl, val); + if (err < 0) + return err; + return count; +} + +int wf_register_control(struct wf_control *new_ct) +{ + struct wf_control *ct; + + mutex_lock(&wf_lock); + list_for_each_entry(ct, &wf_controls, link) { + if (!strcmp(ct->name, new_ct->name)) { + printk(KERN_WARNING "windfarm: trying to register" + " duplicate control %s\n", ct->name); + mutex_unlock(&wf_lock); + return -EEXIST; + } + } + kref_init(&new_ct->ref); + list_add(&new_ct->link, &wf_controls); + + sysfs_attr_init(&new_ct->attr.attr); + new_ct->attr.attr.name = new_ct->name; + new_ct->attr.attr.mode = 0644; + new_ct->attr.show = wf_show_control; + new_ct->attr.store = wf_store_control; + if (device_create_file(&wf_platform_device.dev, &new_ct->attr)) + printk(KERN_WARNING "windfarm: device_create_file failed" + " for %s\n", new_ct->name); + /* the subsystem still does useful work without the file */ + + DBG("wf: Registered control %s\n", new_ct->name); + + wf_notify(WF_EVENT_NEW_CONTROL, new_ct); + mutex_unlock(&wf_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wf_register_control); + +void wf_unregister_control(struct wf_control *ct) +{ + mutex_lock(&wf_lock); + list_del(&ct->link); + mutex_unlock(&wf_lock); + + DBG("wf: Unregistered control %s\n", ct->name); + + kref_put(&ct->ref, wf_control_release); +} +EXPORT_SYMBOL_GPL(wf_unregister_control); + +int wf_get_control(struct wf_control *ct) +{ + if (!try_module_get(ct->ops->owner)) + return -ENODEV; + kref_get(&ct->ref); + return 0; +} +EXPORT_SYMBOL_GPL(wf_get_control); + +void wf_put_control(struct wf_control *ct) +{ + struct module *mod = ct->ops->owner; + kref_put(&ct->ref, wf_control_release); + module_put(mod); +} +EXPORT_SYMBOL_GPL(wf_put_control); + + +/* + * Sensors + */ + + +static void wf_sensor_release(struct kref *kref) +{ + struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref); + + DBG("wf: Deleting sensor %s\n", sr->name); + + if (sr->ops && sr->ops->release) + sr->ops->release(sr); + else + kfree(sr); +} + +static ssize_t wf_show_sensor(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wf_sensor *sens = container_of(attr, struct wf_sensor, attr); + s32 val = 0; + int err; + + err = sens->ops->get_value(sens, &val); + if (err < 0) + return err; + return sprintf(buf, "%d.%03d\n", FIX32TOPRINT(val)); +} + +int wf_register_sensor(struct wf_sensor *new_sr) +{ + struct wf_sensor *sr; + + mutex_lock(&wf_lock); + list_for_each_entry(sr, &wf_sensors, link) { + if (!strcmp(sr->name, new_sr->name)) { + printk(KERN_WARNING "windfarm: trying to register" + " duplicate sensor %s\n", sr->name); + mutex_unlock(&wf_lock); + return -EEXIST; + } + } + kref_init(&new_sr->ref); + list_add(&new_sr->link, &wf_sensors); + + sysfs_attr_init(&new_sr->attr.attr); + new_sr->attr.attr.name = new_sr->name; + new_sr->attr.attr.mode = 0444; + new_sr->attr.show = wf_show_sensor; + new_sr->attr.store = NULL; + if (device_create_file(&wf_platform_device.dev, &new_sr->attr)) + printk(KERN_WARNING "windfarm: device_create_file failed" + " for %s\n", new_sr->name); + /* the subsystem still does useful work without the file */ + + DBG("wf: Registered sensor %s\n", new_sr->name); + + wf_notify(WF_EVENT_NEW_SENSOR, new_sr); + mutex_unlock(&wf_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wf_register_sensor); + +void wf_unregister_sensor(struct wf_sensor *sr) +{ + mutex_lock(&wf_lock); + list_del(&sr->link); + mutex_unlock(&wf_lock); + + DBG("wf: Unregistered sensor %s\n", sr->name); + + wf_put_sensor(sr); +} +EXPORT_SYMBOL_GPL(wf_unregister_sensor); + +int wf_get_sensor(struct wf_sensor *sr) +{ + if (!try_module_get(sr->ops->owner)) + return -ENODEV; + kref_get(&sr->ref); + return 0; +} +EXPORT_SYMBOL_GPL(wf_get_sensor); + +void wf_put_sensor(struct wf_sensor *sr) +{ + struct module *mod = sr->ops->owner; + kref_put(&sr->ref, wf_sensor_release); + module_put(mod); +} +EXPORT_SYMBOL_GPL(wf_put_sensor); + + +/* + * Client & notification + */ + +int wf_register_client(struct notifier_block *nb) +{ + int rc; + struct wf_control *ct; + struct wf_sensor *sr; + + mutex_lock(&wf_lock); + rc = blocking_notifier_chain_register(&wf_client_list, nb); + if (rc != 0) + goto bail; + wf_client_count++; + list_for_each_entry(ct, &wf_controls, link) + wf_notify(WF_EVENT_NEW_CONTROL, ct); + list_for_each_entry(sr, &wf_sensors, link) + wf_notify(WF_EVENT_NEW_SENSOR, sr); + if (wf_client_count == 1) + wf_start_thread(); + bail: + mutex_unlock(&wf_lock); + return rc; +} +EXPORT_SYMBOL_GPL(wf_register_client); + +int wf_unregister_client(struct notifier_block *nb) +{ + mutex_lock(&wf_lock); + blocking_notifier_chain_unregister(&wf_client_list, nb); + wf_client_count--; + if (wf_client_count == 0) + wf_stop_thread(); + mutex_unlock(&wf_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wf_unregister_client); + +void wf_set_overtemp(void) +{ + mutex_lock(&wf_lock); + wf_overtemp++; + if (wf_overtemp == 1) { + printk(KERN_WARNING "windfarm: Overtemp condition detected !\n"); + wf_overtemp_counter = 0; + wf_notify(WF_EVENT_OVERTEMP, NULL); + } + mutex_unlock(&wf_lock); +} +EXPORT_SYMBOL_GPL(wf_set_overtemp); + +void wf_clear_overtemp(void) +{ + mutex_lock(&wf_lock); + WARN_ON(wf_overtemp == 0); + if (wf_overtemp == 0) { + mutex_unlock(&wf_lock); + return; + } + wf_overtemp--; + if (wf_overtemp == 0) { + printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n"); + wf_notify(WF_EVENT_NORMALTEMP, NULL); + } + mutex_unlock(&wf_lock); +} +EXPORT_SYMBOL_GPL(wf_clear_overtemp); + +static int __init windfarm_core_init(void) +{ + DBG("wf: core loaded\n"); + + platform_device_register(&wf_platform_device); + return 0; +} + +static void __exit windfarm_core_exit(void) +{ + BUG_ON(wf_client_count != 0); + + DBG("wf: core unloaded\n"); + + platform_device_unregister(&wf_platform_device); +} + + +module_init(windfarm_core_init); +module_exit(windfarm_core_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Core component of PowerMac thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_cpufreq_clamp.c b/drivers/macintosh/windfarm_cpufreq_clamp.c new file mode 100644 index 000000000..28d18ef22 --- /dev/null +++ b/drivers/macintosh/windfarm_cpufreq_clamp.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/pm_qos.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> + +#include "windfarm.h" + +#define VERSION "0.3" + +static int clamped; +static struct wf_control *clamp_control; +static struct freq_qos_request qos_req; +static unsigned int min_freq, max_freq; + +static int clamp_set(struct wf_control *ct, s32 value) +{ + unsigned int freq; + + if (value) { + freq = min_freq; + printk(KERN_INFO "windfarm: Clamping CPU frequency to " + "minimum !\n"); + } else { + freq = max_freq; + printk(KERN_INFO "windfarm: CPU frequency unclamped !\n"); + } + clamped = value; + + return freq_qos_update_request(&qos_req, freq); +} + +static int clamp_get(struct wf_control *ct, s32 *value) +{ + *value = clamped; + return 0; +} + +static s32 clamp_min(struct wf_control *ct) +{ + return 0; +} + +static s32 clamp_max(struct wf_control *ct) +{ + return 1; +} + +static const struct wf_control_ops clamp_ops = { + .set_value = clamp_set, + .get_value = clamp_get, + .get_min = clamp_min, + .get_max = clamp_max, + .owner = THIS_MODULE, +}; + +static int __init wf_cpufreq_clamp_init(void) +{ + struct cpufreq_policy *policy; + struct wf_control *clamp; + struct device *dev; + int ret; + + policy = cpufreq_cpu_get(0); + if (!policy) { + pr_warn("%s: cpufreq policy not found cpu0\n", __func__); + return -EPROBE_DEFER; + } + + min_freq = policy->cpuinfo.min_freq; + max_freq = policy->cpuinfo.max_freq; + + ret = freq_qos_add_request(&policy->constraints, &qos_req, FREQ_QOS_MAX, + max_freq); + + cpufreq_cpu_put(policy); + + if (ret < 0) { + pr_err("%s: Failed to add freq constraint (%d)\n", __func__, + ret); + return ret; + } + + dev = get_cpu_device(0); + if (unlikely(!dev)) { + pr_warn("%s: No cpu device for cpu0\n", __func__); + ret = -ENODEV; + goto fail; + } + + clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL); + if (clamp == NULL) { + ret = -ENOMEM; + goto fail; + } + + clamp->ops = &clamp_ops; + clamp->name = "cpufreq-clamp"; + ret = wf_register_control(clamp); + if (ret) + goto free; + + clamp_control = clamp; + return 0; + + free: + kfree(clamp); + fail: + freq_qos_remove_request(&qos_req); + return ret; +} + +static void __exit wf_cpufreq_clamp_exit(void) +{ + if (clamp_control) { + wf_unregister_control(clamp_control); + freq_qos_remove_request(&qos_req); + } +} + + +module_init(wf_cpufreq_clamp_init); +module_exit(wf_cpufreq_clamp_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_fcu_controls.c b/drivers/macintosh/windfarm_fcu_controls.c new file mode 100644 index 000000000..c5b1ca5bc --- /dev/null +++ b/drivers/macintosh/windfarm_fcu_controls.c @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. FCU fan control + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + */ +#undef DEBUG + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> + +#include "windfarm.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* + * 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 using 0 for now which is what therm_pm72 used to do and + * what Darwin -apparently- does based on observed behaviour. + */ +#define RPM_PID_USE_ACTUAL_SPEED 0 + +/* Default min/max for pumps */ +#define CPU_PUMP_OUTPUT_MAX 3200 +#define CPU_PUMP_OUTPUT_MIN 1250 + +#define FCU_FAN_RPM 0 +#define FCU_FAN_PWM 1 + +struct wf_fcu_priv { + struct kref ref; + struct i2c_client *i2c; + struct mutex lock; + struct list_head fan_list; + int rpm_shift; +}; + +struct wf_fcu_fan { + struct list_head link; + int id; + s32 min, max, target; + struct wf_fcu_priv *fcu_priv; + struct wf_control ctrl; +}; + +static void wf_fcu_release(struct kref *ref) +{ + struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref); + + kfree(pv); +} + +static void wf_fcu_fan_release(struct wf_control *ct) +{ + struct wf_fcu_fan *fan = ct->priv; + + kref_put(&fan->fcu_priv->ref, wf_fcu_release); + kfree(fan); +} + +static int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg, + unsigned char *buf, int nb) +{ + int tries, nr, nw; + + mutex_lock(&pv->lock); + + buf[0] = reg; + tries = 0; + for (;;) { + nw = i2c_master_send(pv->i2c, buf, 1); + if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nw <= 0) { + pr_err("Failure writing address to FCU: %d", nw); + nr = nw; + goto bail; + } + tries = 0; + for (;;) { + nr = i2c_master_recv(pv->i2c, buf, nb); + if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nr <= 0) + pr_err("wf_fcu: Failure reading data from FCU: %d", nw); + bail: + mutex_unlock(&pv->lock); + return nr; +} + +static int wf_fcu_write_reg(struct wf_fcu_priv *pv, 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(pv->i2c, buf, nb); + if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nw < 0) + pr_err("wf_fcu: Failure writing to FCU: %d", nw); + return nw; +} + +static int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + int rc, shift = pv->rpm_shift; + unsigned char buf[2]; + + if (value < fan->min) + value = fan->min; + if (value > fan->max) + value = fan->max; + + fan->target = value; + + buf[0] = value >> (8 - shift); + buf[1] = value << shift; + rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2); + if (rc < 0) + return -EIO; + return 0; +} + +static int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + int rc, reg_base, shift = pv->rpm_shift; + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + + rc = wf_fcu_read_reg(pv, 0xb, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << fan->id)) != 0) + return -EFAULT; + rc = wf_fcu_read_reg(pv, 0xd, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << fan->id)) == 0) + return -ENXIO; + + /* Programmed value or real current speed */ +#if RPM_PID_USE_ACTUAL_SPEED + reg_base = 0x11; +#else + reg_base = 0x10; +#endif + rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2); + if (rc != 2) + return -EIO; + + *value = (buf[0] << (8 - shift)) | buf[1] >> shift; + + return 0; +} + +static int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + unsigned char buf[2]; + int rc; + + if (value < fan->min) + value = fan->min; + if (value > fan->max) + value = fan->max; + + fan->target = value; + + value = (value * 2559) / 1000; + buf[0] = value; + rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1); + if (rc < 0) + return -EIO; + return 0; +} + +static int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + int rc; + + rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << fan->id)) != 0) + return -EFAULT; + rc = wf_fcu_read_reg(pv, 0x2d, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << fan->id)) == 0) + return -ENXIO; + + rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1); + if (rc != 1) + return -EIO; + + *value = (((s32)buf[0]) * 1000) / 2559; + + return 0; +} + +static s32 wf_fcu_fan_min(struct wf_control *ct) +{ + struct wf_fcu_fan *fan = ct->priv; + + return fan->min; +} + +static s32 wf_fcu_fan_max(struct wf_control *ct) +{ + struct wf_fcu_fan *fan = ct->priv; + + return fan->max; +} + +static const struct wf_control_ops wf_fcu_fan_rpm_ops = { + .set_value = wf_fcu_fan_set_rpm, + .get_value = wf_fcu_fan_get_rpm, + .get_min = wf_fcu_fan_min, + .get_max = wf_fcu_fan_max, + .release = wf_fcu_fan_release, + .owner = THIS_MODULE, +}; + +static const struct wf_control_ops wf_fcu_fan_pwm_ops = { + .set_value = wf_fcu_fan_set_pwm, + .get_value = wf_fcu_fan_get_pwm, + .get_min = wf_fcu_fan_min, + .get_max = wf_fcu_fan_max, + .release = wf_fcu_fan_release, + .owner = THIS_MODULE, +}; + +static void wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan) +{ + const struct mpu_data *mpu = wf_get_mpu(0); + u16 pump_min = 0, pump_max = 0xffff; + u16 tmp[4]; + + /* Try to fetch pumps min/max infos from eeprom */ + if (mpu) { + memcpy(&tmp, 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; + } + + fan->min = pump_min; + fan->max = pump_max; + + DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n", + fan->ctrl.name, pump_min, pump_max); +} + +static void wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan) +{ + struct wf_fcu_priv *pv = fan->fcu_priv; + const struct mpu_data *mpu0 = wf_get_mpu(0); + const struct mpu_data *mpu1 = wf_get_mpu(1); + + /* Default */ + fan->min = 2400 >> pv->rpm_shift; + fan->max = 56000 >> pv->rpm_shift; + + /* CPU fans have min/max in MPU */ + if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) { + fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); + fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); + goto bail; + } + if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) { + fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan); + fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan); + goto bail; + } + if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) { + fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan); + fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan); + goto bail; + } + if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) { + fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan); + fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan); + goto bail; + } + /* Rackmac variants, we just use mpu0 intake */ + if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) { + fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); + fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); + goto bail; + } + bail: + DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n", + fan->ctrl.name, fan->min, fan->max); +} + +static void wf_fcu_add_fan(struct wf_fcu_priv *pv, const char *name, + int type, int id) +{ + struct wf_fcu_fan *fan; + + fan = kzalloc(sizeof(*fan), GFP_KERNEL); + if (!fan) + return; + fan->fcu_priv = pv; + fan->id = id; + fan->ctrl.name = name; + fan->ctrl.priv = fan; + + /* min/max is oddball but the code comes from + * therm_pm72 which seems to work so ... + */ + if (type == FCU_FAN_RPM) { + if (!strncmp(name, "cpu-pump", strlen("cpu-pump"))) + wf_fcu_get_pump_minmax(fan); + else + wf_fcu_get_rpmfan_minmax(fan); + fan->ctrl.type = WF_CONTROL_RPM_FAN; + fan->ctrl.ops = &wf_fcu_fan_rpm_ops; + } else { + fan->min = 10; + fan->max = 100; + fan->ctrl.type = WF_CONTROL_PWM_FAN; + fan->ctrl.ops = &wf_fcu_fan_pwm_ops; + } + + if (wf_register_control(&fan->ctrl)) { + pr_err("wf_fcu: Failed to register fan %s\n", name); + kfree(fan); + return; + } + list_add(&fan->link, &pv->fan_list); + kref_get(&pv->ref); +} + +static void wf_fcu_lookup_fans(struct wf_fcu_priv *pv) +{ + /* Translation of device-tree location properties to + * windfarm fan names + */ + static const struct { + const char *dt_name; /* Device-tree name */ + const char *ct_name; /* Control name */ + } loc_trans[] = { + { "BACKSIDE", "backside-fan", }, + { "SYS CTRLR FAN", "backside-fan", }, + { "DRIVE BAY", "drive-bay-fan", }, + { "SLOT", "slots-fan", }, + { "PCI FAN", "slots-fan", }, + { "CPU A INTAKE", "cpu-front-fan-0", }, + { "CPU A EXHAUST", "cpu-rear-fan-0", }, + { "CPU B INTAKE", "cpu-front-fan-1", }, + { "CPU B EXHAUST", "cpu-rear-fan-1", }, + { "CPU A PUMP", "cpu-pump-0", }, + { "CPU B PUMP", "cpu-pump-1", }, + { "CPU A 1", "cpu-fan-a-0", }, + { "CPU A 2", "cpu-fan-b-0", }, + { "CPU A 3", "cpu-fan-c-0", }, + { "CPU B 1", "cpu-fan-a-1", }, + { "CPU B 2", "cpu-fan-b-1", }, + { "CPU B 3", "cpu-fan-c-1", }, + }; + struct device_node *np, *fcu = pv->i2c->dev.of_node; + int i; + + DBG("Looking up FCU controls in device-tree...\n"); + + for_each_child_of_node(fcu, np) { + int id, type = -1; + const char *loc; + const char *name; + const u32 *reg; + + DBG(" control: %pOFn, type: %s\n", np, of_node_get_device_type(np)); + + /* Detect control type */ + if (of_node_is_type(np, "fan-rpm-control") || + of_node_is_type(np, "fan-rpm")) + type = FCU_FAN_RPM; + if (of_node_is_type(np, "fan-pwm-control") || + of_node_is_type(np, "fan-pwm")) + type = FCU_FAN_PWM; + /* Only care about fans for now */ + if (type == -1) + continue; + + /* Lookup for a matching location */ + loc = of_get_property(np, "location", NULL); + reg = of_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 < ARRAY_SIZE(loc_trans); i++) { + if (strncmp(loc, loc_trans[i].dt_name, + strlen(loc_trans[i].dt_name))) + continue; + name = loc_trans[i].ct_name; + + DBG(" location match, name: %s\n", name); + + if (type == FCU_FAN_RPM) + id = ((*reg) - 0x10) / 2; + else + id = ((*reg) - 0x30) / 2; + if (id > 7) { + pr_warn("wf_fcu: Can't parse fan ID in device-tree for %pOF\n", np); + break; + } + wf_fcu_add_fan(pv, name, type, id); + break; + } + } +} + +static void wf_fcu_default_fans(struct wf_fcu_priv *pv) +{ + /* We only support the default fans for PowerMac7,2 */ + if (!of_machine_is_compatible("PowerMac7,2")) + return; + + wf_fcu_add_fan(pv, "backside-fan", FCU_FAN_PWM, 1); + wf_fcu_add_fan(pv, "drive-bay-fan", FCU_FAN_RPM, 2); + wf_fcu_add_fan(pv, "slots-fan", FCU_FAN_PWM, 2); + wf_fcu_add_fan(pv, "cpu-front-fan-0", FCU_FAN_RPM, 3); + wf_fcu_add_fan(pv, "cpu-rear-fan-0", FCU_FAN_RPM, 4); + wf_fcu_add_fan(pv, "cpu-front-fan-1", FCU_FAN_RPM, 5); + wf_fcu_add_fan(pv, "cpu-rear-fan-1", FCU_FAN_RPM, 6); +} + +static int wf_fcu_init_chip(struct wf_fcu_priv *pv) +{ + unsigned char buf = 0xff; + int rc; + + rc = wf_fcu_write_reg(pv, 0xe, &buf, 1); + if (rc < 0) + return -EIO; + rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1); + if (rc < 0) + return -EIO; + rc = wf_fcu_read_reg(pv, 0, &buf, 1); + if (rc < 0) + return -EIO; + pv->rpm_shift = (buf == 1) ? 2 : 3; + + pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n", + pv->rpm_shift); + + return 0; +} + +static int wf_fcu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wf_fcu_priv *pv; + + pv = kzalloc(sizeof(*pv), GFP_KERNEL); + if (!pv) + return -ENOMEM; + + kref_init(&pv->ref); + mutex_init(&pv->lock); + INIT_LIST_HEAD(&pv->fan_list); + pv->i2c = client; + + /* + * First we must start the FCU which will query the + * shift value to apply to RPMs + */ + if (wf_fcu_init_chip(pv)) { + pr_err("wf_fcu: Initialization failed !\n"); + kfree(pv); + return -ENXIO; + } + + /* First lookup fans in the device-tree */ + wf_fcu_lookup_fans(pv); + + /* + * Older machines don't have the device-tree entries + * we are looking for, just hard code the list + */ + if (list_empty(&pv->fan_list)) + wf_fcu_default_fans(pv); + + /* Still no fans ? FAIL */ + if (list_empty(&pv->fan_list)) { + pr_err("wf_fcu: Failed to find fans for your machine\n"); + kfree(pv); + return -ENODEV; + } + + dev_set_drvdata(&client->dev, pv); + + return 0; +} + +static void wf_fcu_remove(struct i2c_client *client) +{ + struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev); + struct wf_fcu_fan *fan; + + while (!list_empty(&pv->fan_list)) { + fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link); + list_del(&fan->link); + wf_unregister_control(&fan->ctrl); + } + kref_put(&pv->ref, wf_fcu_release); +} + +static const struct i2c_device_id wf_fcu_id[] = { + { "MAC,fcu", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_fcu_id); + +static const struct of_device_id wf_fcu_of_id[] = { + { .compatible = "fcu", }, + { } +}; +MODULE_DEVICE_TABLE(of, wf_fcu_of_id); + +static struct i2c_driver wf_fcu_driver = { + .driver = { + .name = "wf_fcu", + .of_match_table = wf_fcu_of_id, + }, + .probe = wf_fcu_probe, + .remove = wf_fcu_remove, + .id_table = wf_fcu_id, +}; + +module_i2c_driver(wf_fcu_driver); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c new file mode 100644 index 000000000..9dd80837a --- /dev/null +++ b/drivers/macintosh/windfarm_lm75_sensor.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. LM75 sensor + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <linux/of_device.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/pmac_low_i2c.h> + +#include "windfarm.h" + +#define VERSION "1.0" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +struct wf_lm75_sensor { + unsigned int ds1775 : 1; + unsigned int inited : 1; + struct i2c_client *i2c; + struct wf_sensor sens; +}; +#define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens) + +static int wf_lm75_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_lm75_sensor *lm = wf_to_lm75(sr); + s32 data; + + if (lm->i2c == NULL) + return -ENODEV; + + /* Init chip if necessary */ + if (!lm->inited) { + u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(lm->i2c, 1); + + DBG("wf_lm75: Initializing %s, cfg was: %02x\n", + sr->name, cfg); + + /* clear shutdown bit, keep other settings as left by + * the firmware for now + */ + cfg_new = cfg & ~0x01; + i2c_smbus_write_byte_data(lm->i2c, 1, cfg_new); + lm->inited = 1; + + /* If we just powered it up, let's wait 200 ms */ + msleep(200); + } + + /* Read temperature register */ + data = (s32)le16_to_cpu(i2c_smbus_read_word_data(lm->i2c, 0)); + data <<= 8; + *value = data; + + return 0; +} + +static void wf_lm75_release(struct wf_sensor *sr) +{ + struct wf_lm75_sensor *lm = wf_to_lm75(sr); + + kfree(lm); +} + +static const struct wf_sensor_ops wf_lm75_ops = { + .get_value = wf_lm75_get, + .release = wf_lm75_release, + .owner = THIS_MODULE, +}; + +static int wf_lm75_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wf_lm75_sensor *lm; + int rc, ds1775; + const char *name, *loc; + + if (id) + ds1775 = id->driver_data; + else + ds1775 = !!of_device_get_match_data(&client->dev); + + DBG("wf_lm75: creating %s device at address 0x%02x\n", + ds1775 ? "ds1775" : "lm75", client->addr); + + loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); + if (!loc) { + dev_warn(&client->dev, "Missing hwsensor-location property!\n"); + return -ENXIO; + } + + /* Usual rant about sensor names not beeing very consistent in + * the device-tree, oh well ... + * Add more entries below as you deal with more setups + */ + if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY")) + name = "hd-temp"; + else if (!strcmp(loc, "Incoming Air Temp")) + name = "incoming-air-temp"; + else if (!strcmp(loc, "ODD Temp")) + name = "optical-drive-temp"; + else if (!strcmp(loc, "HD Temp")) + name = "hard-drive-temp"; + else if (!strcmp(loc, "PCI SLOTS")) + name = "slots-temp"; + else if (!strcmp(loc, "CPU A INLET")) + name = "cpu-inlet-temp-0"; + else if (!strcmp(loc, "CPU B INLET")) + name = "cpu-inlet-temp-1"; + else + return -ENXIO; + + + lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); + if (lm == NULL) + return -ENODEV; + + lm->inited = 0; + lm->ds1775 = ds1775; + lm->i2c = client; + lm->sens.name = name; + lm->sens.ops = &wf_lm75_ops; + i2c_set_clientdata(client, lm); + + rc = wf_register_sensor(&lm->sens); + if (rc) + kfree(lm); + return rc; +} + +static void wf_lm75_remove(struct i2c_client *client) +{ + struct wf_lm75_sensor *lm = i2c_get_clientdata(client); + + /* Mark client detached */ + lm->i2c = NULL; + + /* release sensor */ + wf_unregister_sensor(&lm->sens); +} + +static const struct i2c_device_id wf_lm75_id[] = { + { "MAC,lm75", 0 }, + { "MAC,ds1775", 1 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_lm75_id); + +static const struct of_device_id wf_lm75_of_id[] = { + { .compatible = "lm75", .data = (void *)0}, + { .compatible = "ds1775", .data = (void *)1 }, + { } +}; +MODULE_DEVICE_TABLE(of, wf_lm75_of_id); + +static struct i2c_driver wf_lm75_driver = { + .driver = { + .name = "wf_lm75", + .of_match_table = wf_lm75_of_id, + }, + .probe = wf_lm75_probe, + .remove = wf_lm75_remove, + .id_table = wf_lm75_id, +}; + +module_i2c_driver(wf_lm75_driver); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_lm87_sensor.c b/drivers/macintosh/windfarm_lm87_sensor.c new file mode 100644 index 000000000..40d254633 --- /dev/null +++ b/drivers/macintosh/windfarm_lm87_sensor.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. LM87 sensor + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/pmac_low_i2c.h> + +#include "windfarm.h" + +#define VERSION "1.0" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +struct wf_lm87_sensor { + struct i2c_client *i2c; + struct wf_sensor sens; +}; +#define wf_to_lm87(c) container_of(c, struct wf_lm87_sensor, sens) + + +static int wf_lm87_read_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("wf_lm87: Error reading LM87, retrying...\n"); + if (++tries > 10) { + printk(KERN_ERR "wf_lm87: Error reading LM87 !\n"); + return -EIO; + } + msleep(10); + } +} + +static int wf_lm87_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_lm87_sensor *lm = sr->priv; + s32 temp; + + if (lm->i2c == NULL) + return -ENODEV; + +#define LM87_INT_TEMP 0x27 + + /* Read temperature register */ + temp = wf_lm87_read_reg(lm->i2c, LM87_INT_TEMP); + if (temp < 0) + return temp; + *value = temp << 16; + + return 0; +} + +static void wf_lm87_release(struct wf_sensor *sr) +{ + struct wf_lm87_sensor *lm = wf_to_lm87(sr); + + kfree(lm); +} + +static const struct wf_sensor_ops wf_lm87_ops = { + .get_value = wf_lm87_get, + .release = wf_lm87_release, + .owner = THIS_MODULE, +}; + +static int wf_lm87_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wf_lm87_sensor *lm; + const char *name = NULL, *loc; + struct device_node *np = NULL; + int rc; + + /* + * The lm87 contains a whole pile of sensors, additionally, + * the Xserve G5 has several lm87's. However, for now we only + * care about the internal temperature sensor + */ + for_each_child_of_node(client->dev.of_node, np) { + if (!of_node_name_eq(np, "int-temp")) + continue; + loc = of_get_property(np, "location", NULL); + if (!loc) + continue; + if (strstr(loc, "DIMM")) + name = "dimms-temp"; + else if (strstr(loc, "Processors")) + name = "between-cpus-temp"; + if (name) { + of_node_put(np); + break; + } + } + if (!name) { + pr_warn("wf_lm87: Unsupported sensor %pOF\n", + client->dev.of_node); + return -ENODEV; + } + + lm = kzalloc(sizeof(struct wf_lm87_sensor), GFP_KERNEL); + if (lm == NULL) + return -ENODEV; + + lm->i2c = client; + lm->sens.name = name; + lm->sens.ops = &wf_lm87_ops; + lm->sens.priv = lm; + i2c_set_clientdata(client, lm); + + rc = wf_register_sensor(&lm->sens); + if (rc) + kfree(lm); + return rc; +} + +static void wf_lm87_remove(struct i2c_client *client) +{ + struct wf_lm87_sensor *lm = i2c_get_clientdata(client); + + /* Mark client detached */ + lm->i2c = NULL; + + /* release sensor */ + wf_unregister_sensor(&lm->sens); +} + +static const struct i2c_device_id wf_lm87_id[] = { + { "MAC,lm87cimt", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_lm87_id); + +static const struct of_device_id wf_lm87_of_id[] = { + { .compatible = "lm87cimt", }, + { } +}; +MODULE_DEVICE_TABLE(of, wf_lm87_of_id); + +static struct i2c_driver wf_lm87_driver = { + .driver = { + .name = "wf_lm87", + .of_match_table = wf_lm87_of_id, + }, + .probe = wf_lm87_probe, + .remove = wf_lm87_remove, + .id_table = wf_lm87_id, +}; + +static int __init wf_lm87_sensor_init(void) +{ + /* We only support this on the Xserve */ + if (!of_machine_is_compatible("RackMac3,1")) + return -ENODEV; + + return i2c_add_driver(&wf_lm87_driver); +} + +static void __exit wf_lm87_sensor_exit(void) +{ + i2c_del_driver(&wf_lm87_driver); +} + + +module_init(wf_lm87_sensor_init); +module_exit(wf_lm87_sensor_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("LM87 sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_max6690_sensor.c b/drivers/macintosh/windfarm_max6690_sensor.c new file mode 100644 index 000000000..c0d404ebc --- /dev/null +++ b/drivers/macintosh/windfarm_max6690_sensor.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. MAX6690 sensor. + * + * Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org> + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> + +#include <asm/pmac_low_i2c.h> + +#include "windfarm.h" + +#define VERSION "1.0" + +/* This currently only exports the external temperature sensor, + since that's all the control loops need. */ + +/* Some MAX6690 register numbers */ +#define MAX6690_INTERNAL_TEMP 0 +#define MAX6690_EXTERNAL_TEMP 1 + +struct wf_6690_sensor { + struct i2c_client *i2c; + struct wf_sensor sens; +}; + +#define wf_to_6690(x) container_of((x), struct wf_6690_sensor, sens) + +static int wf_max6690_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_6690_sensor *max = wf_to_6690(sr); + s32 data; + + if (max->i2c == NULL) + return -ENODEV; + + /* chip gets initialized by firmware */ + data = i2c_smbus_read_byte_data(max->i2c, MAX6690_EXTERNAL_TEMP); + if (data < 0) + return data; + *value = data << 16; + return 0; +} + +static void wf_max6690_release(struct wf_sensor *sr) +{ + struct wf_6690_sensor *max = wf_to_6690(sr); + + kfree(max); +} + +static const struct wf_sensor_ops wf_max6690_ops = { + .get_value = wf_max6690_get, + .release = wf_max6690_release, + .owner = THIS_MODULE, +}; + +static int wf_max6690_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const char *name, *loc; + struct wf_6690_sensor *max; + int rc; + + loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); + if (!loc) { + dev_warn(&client->dev, "Missing hwsensor-location property!\n"); + return -ENXIO; + } + + /* + * We only expose the external temperature register for + * now as this is all we need for our control loops + */ + if (!strcmp(loc, "BACKSIDE") || !strcmp(loc, "SYS CTRLR AMBIENT")) + name = "backside-temp"; + else if (!strcmp(loc, "NB Ambient")) + name = "north-bridge-temp"; + else if (!strcmp(loc, "GPU Ambient")) + name = "gpu-temp"; + else + return -ENXIO; + + max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL); + if (max == NULL) { + printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor: " + "no memory\n"); + return -ENOMEM; + } + + max->i2c = client; + max->sens.name = name; + max->sens.ops = &wf_max6690_ops; + i2c_set_clientdata(client, max); + + rc = wf_register_sensor(&max->sens); + if (rc) + kfree(max); + return rc; +} + +static void wf_max6690_remove(struct i2c_client *client) +{ + struct wf_6690_sensor *max = i2c_get_clientdata(client); + + max->i2c = NULL; + wf_unregister_sensor(&max->sens); +} + +static const struct i2c_device_id wf_max6690_id[] = { + { "MAC,max6690", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_max6690_id); + +static const struct of_device_id wf_max6690_of_id[] = { + { .compatible = "max6690", }, + { } +}; +MODULE_DEVICE_TABLE(of, wf_max6690_of_id); + +static struct i2c_driver wf_max6690_driver = { + .driver = { + .name = "wf_max6690", + .of_match_table = wf_max6690_of_id, + }, + .probe = wf_max6690_probe, + .remove = wf_max6690_remove, + .id_table = wf_max6690_id, +}; + +module_i2c_driver(wf_max6690_driver); + +MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>"); +MODULE_DESCRIPTION("MAX6690 sensor objects for PowerMac thermal control"); +MODULE_LICENSE("GPL"); diff --git a/drivers/macintosh/windfarm_mpu.h b/drivers/macintosh/windfarm_mpu.h new file mode 100644 index 000000000..b5ce347d1 --- /dev/null +++ b/drivers/macintosh/windfarm_mpu.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Windfarm PowerMac thermal control + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + */ + +#ifndef __WINDFARM_MPU_H +#define __WINDFARM_MPU_H + +#include <linux/of.h> + +typedef unsigned short fu16; +typedef int fs32; +typedef short fs16; + +/* Definition of the MPU data structure which contains per CPU + * calibration information (among others) for the G5 machines + */ +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 */ + +static inline const struct mpu_data *wf_get_mpu(int cpu) +{ + struct device_node *np; + char nodename[64]; + const void *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) + return NULL; + data = of_get_property(np, "cpuid", &len); + of_node_put(np); + if (!data) + return NULL; + + /* + * We are naughty, we have dropped the reference to the device + * node and still return a pointer to the content. We know we + * can do that though as this is only ever called on PowerMac + * which cannot remove those nodes + */ + return data; +} + +#endif /* __WINDFARM_MPU_H */ diff --git a/drivers/macintosh/windfarm_pid.c b/drivers/macintosh/windfarm_pid.c new file mode 100644 index 000000000..bb31b0e93 --- /dev/null +++ b/drivers/macintosh/windfarm_pid.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. Generic PID helpers + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> + +#include "windfarm_pid.h" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param) +{ + memset(st, 0, sizeof(struct wf_pid_state)); + st->param = *param; + st->first = 1; +} +EXPORT_SYMBOL_GPL(wf_pid_init); + +s32 wf_pid_run(struct wf_pid_state *st, s32 new_sample) +{ + s64 error, integ, deriv; + s32 target; + int i, hlen = st->param.history_len; + + /* Calculate error term */ + error = new_sample - st->param.itarget; + + /* Get samples into our history buffer */ + if (st->first) { + for (i = 0; i < hlen; i++) { + st->samples[i] = new_sample; + st->errors[i] = error; + } + st->first = 0; + st->index = 0; + } else { + st->index = (st->index + 1) % hlen; + st->samples[st->index] = new_sample; + st->errors[st->index] = error; + } + + /* Calculate integral term */ + for (i = 0, integ = 0; i < hlen; i++) + integ += st->errors[(st->index + hlen - i) % hlen]; + integ *= st->param.interval; + + /* Calculate derivative term */ + deriv = st->errors[st->index] - + st->errors[(st->index + hlen - 1) % hlen]; + deriv /= st->param.interval; + + /* Calculate target */ + target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd + + error * (s64)st->param.gp) >> 36); + if (st->param.additive) + target += st->target; + target = max(target, st->param.min); + target = min(target, st->param.max); + st->target = target; + + return st->target; +} +EXPORT_SYMBOL_GPL(wf_pid_run); + +void wf_cpu_pid_init(struct wf_cpu_pid_state *st, + struct wf_cpu_pid_param *param) +{ + memset(st, 0, sizeof(struct wf_cpu_pid_state)); + st->param = *param; + st->first = 1; +} +EXPORT_SYMBOL_GPL(wf_cpu_pid_init); + +s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp) +{ + s64 integ, deriv, prop; + s32 error, target, sval, adj; + int i, hlen = st->param.history_len; + + /* Calculate error term */ + error = st->param.pmaxadj - new_power; + + /* Get samples into our history buffer */ + if (st->first) { + for (i = 0; i < hlen; i++) { + st->powers[i] = new_power; + st->errors[i] = error; + } + st->temps[0] = st->temps[1] = new_temp; + st->first = 0; + st->index = st->tindex = 0; + } else { + st->index = (st->index + 1) % hlen; + st->powers[st->index] = new_power; + st->errors[st->index] = error; + st->tindex = (st->tindex + 1) % 2; + st->temps[st->tindex] = new_temp; + } + + /* Calculate integral term */ + for (i = 0, integ = 0; i < hlen; i++) + integ += st->errors[(st->index + hlen - i) % hlen]; + integ *= st->param.interval; + integ *= st->param.gr; + sval = st->param.tmax - (s32)(integ >> 20); + adj = min(st->param.ttarget, sval); + + DBG("integ: %lx, sval: %lx, adj: %lx\n", integ, sval, adj); + + /* Calculate derivative term */ + deriv = st->temps[st->tindex] - + st->temps[(st->tindex + 2 - 1) % 2]; + deriv /= st->param.interval; + deriv *= st->param.gd; + + /* Calculate proportional term */ + prop = st->last_delta = (new_temp - adj); + prop *= st->param.gp; + + DBG("deriv: %lx, prop: %lx\n", deriv, prop); + + /* Calculate target */ + target = st->target + (s32)((deriv + prop) >> 36); + target = max(target, st->param.min); + target = min(target, st->param.max); + st->target = target; + + return st->target; +} +EXPORT_SYMBOL_GPL(wf_cpu_pid_run); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("PID algorithm for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); diff --git a/drivers/macintosh/windfarm_pid.h b/drivers/macintosh/windfarm_pid.h new file mode 100644 index 000000000..83f747dbe --- /dev/null +++ b/drivers/macintosh/windfarm_pid.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Windfarm PowerMac thermal control. Generic PID helpers + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * This is a pair of generic PID helpers that can be used by + * control loops. One is the basic PID implementation, the + * other one is more specifically tailored to the loops used + * for CPU control with 2 input sample types (temp and power) + */ + +/* + * *** Simple PID *** + */ + +#define WF_PID_MAX_HISTORY 32 + +/* This parameter array is passed to the PID algorithm. Currently, + * we don't support changing parameters on the fly as it's not needed + * but could be implemented (with necessary adjustment of the history + * buffer + */ +struct wf_pid_param { + int interval; /* Interval between samples in seconds */ + int history_len; /* Size of history buffer */ + int additive; /* 1: target relative to previous value */ + s32 gd, gp, gr; /* PID gains */ + s32 itarget; /* PID input target */ + s32 min,max; /* min and max target values */ +}; + +struct wf_pid_state { + int first; /* first run of the loop */ + int index; /* index of current sample */ + s32 target; /* current target value */ + s32 samples[WF_PID_MAX_HISTORY]; /* samples history buffer */ + s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */ + + struct wf_pid_param param; +}; + +extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param); +extern s32 wf_pid_run(struct wf_pid_state *st, s32 sample); + + +/* + * *** CPU PID *** + */ + +#define WF_CPU_PID_MAX_HISTORY 32 + +/* This parameter array is passed to the CPU PID algorithm. Currently, + * we don't support changing parameters on the fly as it's not needed + * but could be implemented (with necessary adjustment of the history + * buffer + */ +struct wf_cpu_pid_param { + int interval; /* Interval between samples in seconds */ + int history_len; /* Size of history buffer */ + s32 gd, gp, gr; /* PID gains */ + s32 pmaxadj; /* PID max power adjust */ + s32 ttarget; /* PID input target */ + s32 tmax; /* PID input max */ + s32 min,max; /* min and max target values */ +}; + +struct wf_cpu_pid_state { + int first; /* first run of the loop */ + int index; /* index of current power */ + int tindex; /* index of current temp */ + s32 target; /* current target value */ + s32 last_delta; /* last Tactual - Ttarget */ + s32 powers[WF_PID_MAX_HISTORY]; /* power history buffer */ + s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */ + s32 temps[2]; /* temp. history buffer */ + + struct wf_cpu_pid_param param; +}; + +extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st, + struct wf_cpu_pid_param *param); +extern s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 power, s32 temp); diff --git a/drivers/macintosh/windfarm_pm112.c b/drivers/macintosh/windfarm_pm112.c new file mode 100644 index 000000000..d1dec314a --- /dev/null +++ b/drivers/macintosh/windfarm_pm112.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. + * Control loops for machines with SMU and PPC970MP processors. + * + * Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org> + * Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corp. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/of.h> +#include <linux/slab.h> + +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" + +#define VERSION "0.2" + +#define DEBUG +#undef LOTSA_DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +#ifdef LOTSA_DEBUG +#define DBG_LOTS(args...) printk(args) +#else +#define DBG_LOTS(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 60 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* We currently only handle 2 chips, 4 cores... */ +#define NR_CHIPS 2 +#define NR_CORES 4 +#define NR_CPU_FANS 3 * NR_CHIPS + +/* Controls and sensors */ +static struct wf_sensor *sens_cpu_temp[NR_CORES]; +static struct wf_sensor *sens_cpu_power[NR_CORES]; +static struct wf_sensor *hd_temp; +static struct wf_sensor *slots_power; +static struct wf_sensor *u4_temp; + +static struct wf_control *cpu_fans[NR_CPU_FANS]; +static char *cpu_fan_names[NR_CPU_FANS] = { + "cpu-rear-fan-0", + "cpu-rear-fan-1", + "cpu-front-fan-0", + "cpu-front-fan-1", + "cpu-pump-0", + "cpu-pump-1", +}; +static struct wf_control *cpufreq_clamp; + +/* Second pump isn't required (and isn't actually present) */ +#define CPU_FANS_REQD (NR_CPU_FANS - 2) +#define FIRST_PUMP 4 +#define LAST_PUMP 5 + +/* We keep a temperature history for average calculation of 180s */ +#define CPU_TEMP_HIST_SIZE 180 + +/* Scale factor for fan speed, *100 */ +static int cpu_fan_scale[NR_CPU_FANS] = { + 100, + 100, + 97, /* inlet fans run at 97% of exhaust fan */ + 97, + 100, /* updated later */ + 100, /* updated later */ +}; + +static struct wf_control *backside_fan; +static struct wf_control *slots_fan; +static struct wf_control *drive_bay_fan; + +/* PID loop state */ +static struct wf_cpu_pid_state cpu_pid[NR_CORES]; +static u32 cpu_thist[CPU_TEMP_HIST_SIZE]; +static int cpu_thist_pt; +static s64 cpu_thist_total; +static s32 cpu_all_tmax = 100 << 16; +static int cpu_last_target; +static struct wf_pid_state backside_pid; +static int backside_tick; +static struct wf_pid_state slots_pid; +static bool slots_started; +static struct wf_pid_state drive_bay_pid; +static int drive_bay_tick; + +static int nr_cores; +static int have_all_controls; +static int have_all_sensors; +static bool started; + +static int failure_state; +#define FAILURE_SENSOR 1 +#define FAILURE_FAN 2 +#define FAILURE_PERM 4 +#define FAILURE_LOW_OVERTEMP 8 +#define FAILURE_HIGH_OVERTEMP 16 + +/* Overtemp values */ +#define LOW_OVER_AVERAGE 0 +#define LOW_OVER_IMMEDIATE (10 << 16) +#define LOW_OVER_CLEAR ((-10) << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) +#define HIGH_OVER_AVERAGE (10 << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) + + +/* Implementation... */ +static int create_cpu_loop(int cpu) +{ + int chip = cpu / 2; + int core = cpu & 1; + struct smu_sdbp_header *hdr; + struct smu_sdbp_cpupiddata *piddata; + struct wf_cpu_pid_param pid; + struct wf_control *main_fan = cpu_fans[0]; + s32 tmax; + int fmin; + + /* Get FVT params to get Tmax; if not found, assume default */ + hdr = smu_sat_get_sdb_partition(chip, 0xC4 + core, NULL); + if (hdr) { + struct smu_sdbp_fvt *fvt = (struct smu_sdbp_fvt *)&hdr[1]; + tmax = fvt->maxtemp << 16; + } else + tmax = 95 << 16; /* default to 95 degrees C */ + + /* We keep a global tmax for overtemp calculations */ + if (tmax < cpu_all_tmax) + cpu_all_tmax = tmax; + + kfree(hdr); + + /* Get PID params from the appropriate SAT */ + hdr = smu_sat_get_sdb_partition(chip, 0xC8 + core, NULL); + if (hdr == NULL) { + printk(KERN_WARNING"windfarm: can't get CPU PID fan config\n"); + return -EINVAL; + } + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; + + /* + * Darwin has a minimum fan speed of 1000 rpm for the 4-way and + * 515 for the 2-way. That appears to be overkill, so for now, + * impose a minimum of 750 or 515. + */ + fmin = (nr_cores > 2) ? 750 : 515; + + /* Initialize PID loop */ + pid.interval = 1; /* seconds */ + pid.history_len = piddata->history_len; + pid.gd = piddata->gd; + pid.gp = piddata->gp; + pid.gr = piddata->gr / piddata->history_len; + pid.pmaxadj = (piddata->max_power << 16) - (piddata->power_adj << 8); + pid.ttarget = tmax - (piddata->target_temp_delta << 16); + pid.tmax = tmax; + pid.min = main_fan->ops->get_min(main_fan); + pid.max = main_fan->ops->get_max(main_fan); + if (pid.min < fmin) + pid.min = fmin; + + wf_cpu_pid_init(&cpu_pid[cpu], &pid); + + kfree(hdr); + + return 0; +} + +static void cpu_max_all_fans(void) +{ + int i; + + /* We max all CPU fans in case of a sensor error. We also do the + * cpufreq clamping now, even if it's supposedly done later by the + * generic code anyway, we do it earlier here to react faster + */ + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + for (i = 0; i < NR_CPU_FANS; ++i) + if (cpu_fans[i]) + wf_control_set_max(cpu_fans[i]); +} + +static int cpu_check_overtemp(s32 temp) +{ + int new_state = 0; + s32 t_avg, t_old; + + /* First check for immediate overtemps */ + if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to immediate CPU" + " temperature !\n"); + } + if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " immediate CPU temperature !\n"); + } + + /* We calculate a history of max temperatures and use that for the + * overtemp management + */ + t_old = cpu_thist[cpu_thist_pt]; + cpu_thist[cpu_thist_pt] = temp; + cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE; + cpu_thist_total -= t_old; + cpu_thist_total += temp; + t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE; + + DBG_LOTS("t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n", + FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp)); + + /* Now check for average overtemps */ + if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to average CPU" + " temperature !\n"); + } + if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " average CPU temperature !\n"); + } + + /* Now handle overtemp conditions. We don't currently use the windfarm + * overtemp handling core as it's not fully suited to the needs of those + * new machine. This will be fixed later. + */ + if (new_state) { + /* High overtemp -> immediate shutdown */ + if (new_state & FAILURE_HIGH_OVERTEMP) + machine_power_off(); + if ((failure_state & new_state) != new_state) + cpu_max_all_fans(); + failure_state |= new_state; + } else if ((failure_state & FAILURE_LOW_OVERTEMP) && + (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) { + printk(KERN_ERR "windfarm: Overtemp condition cleared !\n"); + failure_state &= ~FAILURE_LOW_OVERTEMP; + } + + return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP); +} + +static void cpu_fans_tick(void) +{ + int err, cpu; + s32 greatest_delta = 0; + s32 temp, power, t_max = 0; + int i, t, target = 0; + struct wf_sensor *sr; + struct wf_control *ct; + struct wf_cpu_pid_state *sp; + + DBG_LOTS(KERN_DEBUG); + for (cpu = 0; cpu < nr_cores; ++cpu) { + /* Get CPU core temperature */ + sr = sens_cpu_temp[cpu]; + err = sr->ops->get_value(sr, &temp); + if (err) { + DBG("\n"); + printk(KERN_WARNING "windfarm: CPU %d temperature " + "sensor error %d\n", cpu, err); + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Keep track of highest temp */ + t_max = max(t_max, temp); + + /* Get CPU power */ + sr = sens_cpu_power[cpu]; + err = sr->ops->get_value(sr, &power); + if (err) { + DBG("\n"); + printk(KERN_WARNING "windfarm: CPU %d power " + "sensor error %d\n", cpu, err); + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Run PID */ + sp = &cpu_pid[cpu]; + t = wf_cpu_pid_run(sp, power, temp); + + if (cpu == 0 || sp->last_delta > greatest_delta) { + greatest_delta = sp->last_delta; + target = t; + } + DBG_LOTS("[%d] P=%d.%.3d T=%d.%.3d ", + cpu, FIX32TOPRINT(power), FIX32TOPRINT(temp)); + } + DBG_LOTS("fans = %d, t_max = %d.%03d\n", target, FIX32TOPRINT(t_max)); + + /* Darwin limits decrease to 20 per iteration */ + if (target < (cpu_last_target - 20)) + target = cpu_last_target - 20; + cpu_last_target = target; + for (cpu = 0; cpu < nr_cores; ++cpu) + cpu_pid[cpu].target = target; + + /* Handle possible overtemps */ + if (cpu_check_overtemp(t_max)) + return; + + /* Set fans */ + for (i = 0; i < NR_CPU_FANS; ++i) { + ct = cpu_fans[i]; + if (ct == NULL) + continue; + err = ct->ops->set_value(ct, target * cpu_fan_scale[i] / 100); + if (err) { + printk(KERN_WARNING "windfarm: fan %s reports " + "error %d\n", ct->name, err); + failure_state |= FAILURE_FAN; + break; + } + } +} + +/* Backside/U4 fan */ +static struct wf_pid_param backside_param = { + .interval = 5, + .history_len = 2, + .gd = 48 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 64 << 16, + .additive = 1, +}; + +static void backside_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!backside_fan || !u4_temp) + return; + if (!backside_tick) { + /* first time; initialize things */ + printk(KERN_INFO "windfarm: Backside control loop started.\n"); + backside_param.min = backside_fan->ops->get_min(backside_fan); + backside_param.max = backside_fan->ops->get_max(backside_fan); + wf_pid_init(&backside_pid, &backside_param); + backside_tick = 1; + } + if (--backside_tick > 0) + return; + backside_tick = backside_pid.param.interval; + + err = u4_temp->ops->get_value(u4_temp, &temp); + if (err) { + printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(backside_fan); + return; + } + speed = wf_pid_run(&backside_pid, temp); + DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = backside_fan->ops->set_value(backside_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: backside fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +/* Drive bay fan */ +static struct wf_pid_param drive_bay_prm = { + .interval = 5, + .history_len = 2, + .gd = 30 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 40 << 16, + .additive = 1, +}; + +static void drive_bay_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!drive_bay_fan || !hd_temp) + return; + if (!drive_bay_tick) { + /* first time; initialize things */ + printk(KERN_INFO "windfarm: Drive bay control loop started.\n"); + drive_bay_prm.min = drive_bay_fan->ops->get_min(drive_bay_fan); + drive_bay_prm.max = drive_bay_fan->ops->get_max(drive_bay_fan); + wf_pid_init(&drive_bay_pid, &drive_bay_prm); + drive_bay_tick = 1; + } + if (--drive_bay_tick > 0) + return; + drive_bay_tick = drive_bay_pid.param.interval; + + err = hd_temp->ops->get_value(hd_temp, &temp); + if (err) { + printk(KERN_WARNING "windfarm: drive bay temp sensor " + "error %d\n", err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(drive_bay_fan); + return; + } + speed = wf_pid_run(&drive_bay_pid, temp); + DBG_LOTS("drive_bay PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = drive_bay_fan->ops->set_value(drive_bay_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +/* PCI slots area fan */ +/* This makes the fan speed proportional to the power consumed */ +static struct wf_pid_param slots_param = { + .interval = 1, + .history_len = 2, + .gd = 0, + .gp = 0, + .gr = 0x1277952, + .itarget = 0, + .min = 1560, + .max = 3510, +}; + +static void slots_fan_tick(void) +{ + s32 power; + int speed; + int err; + + if (!slots_fan || !slots_power) + return; + if (!slots_started) { + /* first time; initialize things */ + printk(KERN_INFO "windfarm: Slots control loop started.\n"); + wf_pid_init(&slots_pid, &slots_param); + slots_started = true; + } + + err = slots_power->ops->get_value(slots_power, &power); + if (err) { + printk(KERN_WARNING "windfarm: slots power sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(slots_fan); + return; + } + speed = wf_pid_run(&slots_pid, power); + DBG_LOTS("slots PID power=%d.%.3d speed=%d\n", + FIX32TOPRINT(power), speed); + + err = slots_fan->ops->set_value(slots_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: slots fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void set_fail_state(void) +{ + int i; + + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + for (i = 0; i < NR_CPU_FANS; ++i) + if (cpu_fans[i]) + wf_control_set_max(cpu_fans[i]); + if (backside_fan) + wf_control_set_max(backside_fan); + if (slots_fan) + wf_control_set_max(slots_fan); + if (drive_bay_fan) + wf_control_set_max(drive_bay_fan); +} + +static void pm112_tick(void) +{ + int i, last_failure; + + if (!started) { + started = true; + printk(KERN_INFO "windfarm: CPUs control loops started.\n"); + for (i = 0; i < nr_cores; ++i) { + if (create_cpu_loop(i) < 0) { + failure_state = FAILURE_PERM; + set_fail_state(); + break; + } + } + DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax)); + +#ifdef HACKED_OVERTEMP + cpu_all_tmax = 60 << 16; +#endif + } + + /* Permanent failure, bail out */ + if (failure_state & FAILURE_PERM) + return; + /* Clear all failure bits except low overtemp which will be eventually + * cleared by the control loop itself + */ + last_failure = failure_state; + failure_state &= FAILURE_LOW_OVERTEMP; + cpu_fans_tick(); + backside_fan_tick(); + slots_fan_tick(); + drive_bay_fan_tick(); + + DBG_LOTS("last_failure: 0x%x, failure_state: %x\n", + last_failure, failure_state); + + /* Check for failures. Any failure causes cpufreq clamping */ + if (failure_state && last_failure == 0 && cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (failure_state == 0 && last_failure && cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + + /* That's it for now, we might want to deal with other failures + * differently in the future though + */ +} + +static void pm112_new_control(struct wf_control *ct) +{ + int i, max_exhaust; + + if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) { + if (wf_get_control(ct) == 0) + cpufreq_clamp = ct; + } + + for (i = 0; i < NR_CPU_FANS; ++i) { + if (!strcmp(ct->name, cpu_fan_names[i])) { + if (cpu_fans[i] == NULL && wf_get_control(ct) == 0) + cpu_fans[i] = ct; + break; + } + } + if (i >= NR_CPU_FANS) { + /* not a CPU fan, try the others */ + if (!strcmp(ct->name, "backside-fan")) { + if (backside_fan == NULL && wf_get_control(ct) == 0) + backside_fan = ct; + } else if (!strcmp(ct->name, "slots-fan")) { + if (slots_fan == NULL && wf_get_control(ct) == 0) + slots_fan = ct; + } else if (!strcmp(ct->name, "drive-bay-fan")) { + if (drive_bay_fan == NULL && wf_get_control(ct) == 0) + drive_bay_fan = ct; + } + return; + } + + for (i = 0; i < CPU_FANS_REQD; ++i) + if (cpu_fans[i] == NULL) + return; + + /* work out pump scaling factors */ + max_exhaust = cpu_fans[0]->ops->get_max(cpu_fans[0]); + for (i = FIRST_PUMP; i <= LAST_PUMP; ++i) + if ((ct = cpu_fans[i]) != NULL) + cpu_fan_scale[i] = + ct->ops->get_max(ct) * 100 / max_exhaust; + + have_all_controls = 1; +} + +static void pm112_new_sensor(struct wf_sensor *sr) +{ + unsigned int i; + + if (!strncmp(sr->name, "cpu-temp-", 9)) { + i = sr->name[9] - '0'; + if (sr->name[10] == 0 && i < NR_CORES && + sens_cpu_temp[i] == NULL && wf_get_sensor(sr) == 0) + sens_cpu_temp[i] = sr; + + } else if (!strncmp(sr->name, "cpu-power-", 10)) { + i = sr->name[10] - '0'; + if (sr->name[11] == 0 && i < NR_CORES && + sens_cpu_power[i] == NULL && wf_get_sensor(sr) == 0) + sens_cpu_power[i] = sr; + } else if (!strcmp(sr->name, "hd-temp")) { + if (hd_temp == NULL && wf_get_sensor(sr) == 0) + hd_temp = sr; + } else if (!strcmp(sr->name, "slots-power")) { + if (slots_power == NULL && wf_get_sensor(sr) == 0) + slots_power = sr; + } else if (!strcmp(sr->name, "backside-temp")) { + if (u4_temp == NULL && wf_get_sensor(sr) == 0) + u4_temp = sr; + } else + return; + + /* check if we have all the sensors we need */ + for (i = 0; i < nr_cores; ++i) + if (sens_cpu_temp[i] == NULL || sens_cpu_power[i] == NULL) + return; + + have_all_sensors = 1; +} + +static int pm112_wf_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch (event) { + case WF_EVENT_NEW_SENSOR: + pm112_new_sensor(data); + break; + case WF_EVENT_NEW_CONTROL: + pm112_new_control(data); + break; + case WF_EVENT_TICK: + if (have_all_controls && have_all_sensors) + pm112_tick(); + } + return 0; +} + +static struct notifier_block pm112_events = { + .notifier_call = pm112_wf_notify, +}; + +static int wf_pm112_probe(struct platform_device *dev) +{ + wf_register_client(&pm112_events); + return 0; +} + +static int wf_pm112_remove(struct platform_device *dev) +{ + wf_unregister_client(&pm112_events); + /* should release all sensors and controls */ + return 0; +} + +static struct platform_driver wf_pm112_driver = { + .probe = wf_pm112_probe, + .remove = wf_pm112_remove, + .driver = { + .name = "windfarm", + }, +}; + +static int __init wf_pm112_init(void) +{ + struct device_node *cpu; + + if (!of_machine_is_compatible("PowerMac11,2")) + return -ENODEV; + + /* Count the number of CPU cores */ + nr_cores = 0; + for_each_node_by_type(cpu, "cpu") + ++nr_cores; + + printk(KERN_INFO "windfarm: initializing for dual-core desktop G5\n"); + +#ifdef MODULE + request_module("windfarm_smu_controls"); + request_module("windfarm_smu_sensors"); + request_module("windfarm_smu_sat"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_max6690_sensor"); + request_module("windfarm_cpufreq_clamp"); + +#endif /* MODULE */ + + platform_driver_register(&wf_pm112_driver); + return 0; +} + +static void __exit wf_pm112_exit(void) +{ + platform_driver_unregister(&wf_pm112_driver); +} + +module_init(wf_pm112_init); +module_exit(wf_pm112_exit); + +MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>"); +MODULE_DESCRIPTION("Thermal control for PowerMac11,2"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_pm121.c b/drivers/macintosh/windfarm_pm121.c new file mode 100644 index 000000000..36312f163 --- /dev/null +++ b/drivers/macintosh/windfarm_pm121.c @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. iMac G5 iSight + * + * (c) Copyright 2007 Étienne Bersac <bersace@gmail.com> + * + * Bits & pieces from windfarm_pm81.c by (c) Copyright 2005 Benjamin + * Herrenschmidt, IBM Corp. <benh@kernel.crashing.org> + * + * PowerMac12,1 + * ============ + * + * The algorithm used is the PID control algorithm, used the same way + * the published Darwin code does, using the same values that are + * present in the Darwin 8.10 snapshot property lists (note however + * that none of the code has been re-used, it's a complete + * re-implementation + * + * There is two models using PowerMac12,1. Model 2 is iMac G5 iSight + * 17" while Model 3 is iMac G5 20". They do have both the same + * controls with a tiny difference. The control-ids of hard-drive-fan + * and cpu-fan is swapped. + * + * Target Correction : + * + * controls have a target correction calculated as : + * + * new_min = ((((average_power * slope) >> 16) + offset) >> 16) + min_value + * new_value = max(new_value, max(new_min, 0)) + * + * OD Fan control correction. + * + * # model_id: 2 + * offset : -19563152 + * slope : 1956315 + * + * # model_id: 3 + * offset : -15650652 + * slope : 1565065 + * + * HD Fan control correction. + * + * # model_id: 2 + * offset : -15650652 + * slope : 1565065 + * + * # model_id: 3 + * offset : -19563152 + * slope : 1956315 + * + * CPU Fan control correction. + * + * # model_id: 2 + * offset : -25431900 + * slope : 2543190 + * + * # model_id: 3 + * offset : -15650652 + * slope : 1565065 + * + * Target rubber-banding : + * + * Some controls have a target correction which depends on another + * control value. The correction is computed in the following way : + * + * new_min = ref_value * slope + offset + * + * ref_value is the value of the reference control. If new_min is + * greater than 0, then we correct the target value using : + * + * new_target = max (new_target, new_min >> 16) + * + * # model_id : 2 + * control : cpu-fan + * ref : optical-drive-fan + * offset : -15650652 + * slope : 1565065 + * + * # model_id : 3 + * control : optical-drive-fan + * ref : hard-drive-fan + * offset : -32768000 + * slope : 65536 + * + * In order to have the moste efficient correction with those + * dependencies, we must trigger HD loop before OD loop before CPU + * loop. + * + * The various control loops found in Darwin config file are: + * + * HD Fan control loop. + * + * # model_id: 2 + * control : hard-drive-fan + * sensor : hard-drive-temp + * PID params : G_d = 0x00000000 + * G_p = 0x002D70A3 + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x370000 + * Interval = 5s + * + * # model_id: 3 + * control : hard-drive-fan + * sensor : hard-drive-temp + * PID params : G_d = 0x00000000 + * G_p = 0x002170A3 + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x370000 + * Interval = 5s + * + * OD Fan control loop. + * + * # model_id: 2 + * control : optical-drive-fan + * sensor : optical-drive-temp + * PID params : G_d = 0x00000000 + * G_p = 0x001FAE14 + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x320000 + * Interval = 5s + * + * # model_id: 3 + * control : optical-drive-fan + * sensor : optical-drive-temp + * PID params : G_d = 0x00000000 + * G_p = 0x001FAE14 + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x320000 + * Interval = 5s + * + * GPU Fan control loop. + * + * # model_id: 2 + * control : hard-drive-fan + * sensor : gpu-temp + * PID params : G_d = 0x00000000 + * G_p = 0x002A6666 + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x5A0000 + * Interval = 5s + * + * # model_id: 3 + * control : cpu-fan + * sensor : gpu-temp + * PID params : G_d = 0x00000000 + * G_p = 0x0010CCCC + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x500000 + * Interval = 5s + * + * KODIAK (aka northbridge) Fan control loop. + * + * # model_id: 2 + * control : optical-drive-fan + * sensor : north-bridge-temp + * PID params : G_d = 0x00000000 + * G_p = 0x003BD70A + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x550000 + * Interval = 5s + * + * # model_id: 3 + * control : hard-drive-fan + * sensor : north-bridge-temp + * PID params : G_d = 0x00000000 + * G_p = 0x0030F5C2 + * G_r = 0x00019999 + * History = 2 entries + * Input target = 0x550000 + * Interval = 5s + * + * CPU Fan control loop. + * + * control : cpu-fan + * sensors : cpu-temp, cpu-power + * PID params : from SDB partition + * + * CPU Slew control loop. + * + * control : cpufreq-clamp + * sensor : cpu-temp + */ + +#undef DEBUG + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/kmod.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" + +#define VERSION "0.3" + +static int pm121_mach_model; /* machine model id */ + +/* Controls & sensors */ +static struct wf_sensor *sensor_cpu_power; +static struct wf_sensor *sensor_cpu_temp; +static struct wf_sensor *sensor_cpu_voltage; +static struct wf_sensor *sensor_cpu_current; +static struct wf_sensor *sensor_gpu_temp; +static struct wf_sensor *sensor_north_bridge_temp; +static struct wf_sensor *sensor_hard_drive_temp; +static struct wf_sensor *sensor_optical_drive_temp; +static struct wf_sensor *sensor_incoming_air_temp; /* unused ! */ + +enum { + FAN_CPU, + FAN_HD, + FAN_OD, + CPUFREQ, + N_CONTROLS +}; +static struct wf_control *controls[N_CONTROLS] = {}; + +/* Set to kick the control loop into life */ +static int pm121_all_controls_ok, pm121_all_sensors_ok; +static bool pm121_started; + +enum { + FAILURE_FAN = 1 << 0, + FAILURE_SENSOR = 1 << 1, + FAILURE_OVERTEMP = 1 << 2 +}; + +/* All sys loops. Note the HD before the OD loop in order to have it + run before. */ +enum { + LOOP_GPU, /* control = hd or cpu, but luckily, + it doesn't matter */ + LOOP_HD, /* control = hd */ + LOOP_KODIAK, /* control = hd or od */ + LOOP_OD, /* control = od */ + N_LOOPS +}; + +static const char *loop_names[N_LOOPS] = { + "GPU", + "HD", + "KODIAK", + "OD", +}; + +#define PM121_NUM_CONFIGS 2 + +static unsigned int pm121_failure_state; +static int pm121_readjust, pm121_skipping; +static bool pm121_overtemp; +static s32 average_power; + +struct pm121_correction { + int offset; + int slope; +}; + +static struct pm121_correction corrections[N_CONTROLS][PM121_NUM_CONFIGS] = { + /* FAN_OD */ + { + /* MODEL 2 */ + { .offset = -19563152, + .slope = 1956315 + }, + /* MODEL 3 */ + { .offset = -15650652, + .slope = 1565065 + }, + }, + /* FAN_HD */ + { + /* MODEL 2 */ + { .offset = -15650652, + .slope = 1565065 + }, + /* MODEL 3 */ + { .offset = -19563152, + .slope = 1956315 + }, + }, + /* FAN_CPU */ + { + /* MODEL 2 */ + { .offset = -25431900, + .slope = 2543190 + }, + /* MODEL 3 */ + { .offset = -15650652, + .slope = 1565065 + }, + }, + /* CPUFREQ has no correction (and is not implemented at all) */ +}; + +struct pm121_connection { + unsigned int control_id; + unsigned int ref_id; + struct pm121_correction correction; +}; + +static struct pm121_connection pm121_connections[] = { + /* MODEL 2 */ + { .control_id = FAN_CPU, + .ref_id = FAN_OD, + { .offset = -32768000, + .slope = 65536 + } + }, + /* MODEL 3 */ + { .control_id = FAN_OD, + .ref_id = FAN_HD, + { .offset = -32768000, + .slope = 65536 + } + }, +}; + +/* pointer to the current model connection */ +static struct pm121_connection *pm121_connection; + +/* + * ****** System Fans Control Loop ****** + * + */ + +/* Since each loop handles only one control and we want to avoid + * writing virtual control, we store the control correction with the + * loop params. Some data are not set, there are common to all loop + * and thus, hardcoded. + */ +struct pm121_sys_param { + /* purely informative since we use mach_model-2 as index */ + int model_id; + struct wf_sensor **sensor; /* use sensor_id instead ? */ + s32 gp, itarget; + unsigned int control_id; +}; + +static struct pm121_sys_param +pm121_sys_all_params[N_LOOPS][PM121_NUM_CONFIGS] = { + /* GPU Fan control loop */ + { + { .model_id = 2, + .sensor = &sensor_gpu_temp, + .gp = 0x002A6666, + .itarget = 0x5A0000, + .control_id = FAN_HD, + }, + { .model_id = 3, + .sensor = &sensor_gpu_temp, + .gp = 0x0010CCCC, + .itarget = 0x500000, + .control_id = FAN_CPU, + }, + }, + /* HD Fan control loop */ + { + { .model_id = 2, + .sensor = &sensor_hard_drive_temp, + .gp = 0x002D70A3, + .itarget = 0x370000, + .control_id = FAN_HD, + }, + { .model_id = 3, + .sensor = &sensor_hard_drive_temp, + .gp = 0x002170A3, + .itarget = 0x370000, + .control_id = FAN_HD, + }, + }, + /* KODIAK Fan control loop */ + { + { .model_id = 2, + .sensor = &sensor_north_bridge_temp, + .gp = 0x003BD70A, + .itarget = 0x550000, + .control_id = FAN_OD, + }, + { .model_id = 3, + .sensor = &sensor_north_bridge_temp, + .gp = 0x0030F5C2, + .itarget = 0x550000, + .control_id = FAN_HD, + }, + }, + /* OD Fan control loop */ + { + { .model_id = 2, + .sensor = &sensor_optical_drive_temp, + .gp = 0x001FAE14, + .itarget = 0x320000, + .control_id = FAN_OD, + }, + { .model_id = 3, + .sensor = &sensor_optical_drive_temp, + .gp = 0x001FAE14, + .itarget = 0x320000, + .control_id = FAN_OD, + }, + }, +}; + +/* the hardcoded values */ +#define PM121_SYS_GD 0x00000000 +#define PM121_SYS_GR 0x00019999 +#define PM121_SYS_HISTORY_SIZE 2 +#define PM121_SYS_INTERVAL 5 + +/* State data used by the system fans control loop + */ +struct pm121_sys_state { + int ticks; + s32 setpoint; + struct wf_pid_state pid; +}; + +static struct pm121_sys_state *pm121_sys_state[N_LOOPS] = {}; + +/* + * ****** CPU Fans Control Loop ****** + * + */ + +#define PM121_CPU_INTERVAL 1 + +/* State data used by the cpu fans control loop + */ +struct pm121_cpu_state { + int ticks; + s32 setpoint; + struct wf_cpu_pid_state pid; +}; + +static struct pm121_cpu_state *pm121_cpu_state; + + + +/* + * ***** Implementation ***** + * + */ + +/* correction the value using the output-low-bound correction algo */ +static s32 pm121_correct(s32 new_setpoint, + unsigned int control_id, + s32 min) +{ + s32 new_min; + struct pm121_correction *correction; + correction = &corrections[control_id][pm121_mach_model - 2]; + + new_min = (average_power * correction->slope) >> 16; + new_min += correction->offset; + new_min = (new_min >> 16) + min; + + return max3(new_setpoint, new_min, 0); +} + +static s32 pm121_connect(unsigned int control_id, s32 setpoint) +{ + s32 new_min, value, new_setpoint; + + if (pm121_connection->control_id == control_id) { + controls[control_id]->ops->get_value(controls[control_id], + &value); + new_min = value * pm121_connection->correction.slope; + new_min += pm121_connection->correction.offset; + if (new_min > 0) { + new_setpoint = max(setpoint, (new_min >> 16)); + if (new_setpoint != setpoint) { + pr_debug("pm121: %s depending on %s, " + "corrected from %d to %d RPM\n", + controls[control_id]->name, + controls[pm121_connection->ref_id]->name, + (int) setpoint, (int) new_setpoint); + } + } else + new_setpoint = setpoint; + } + /* no connection */ + else + new_setpoint = setpoint; + + return new_setpoint; +} + +/* FAN LOOPS */ +static void pm121_create_sys_fans(int loop_id) +{ + struct pm121_sys_param *param = NULL; + struct wf_pid_param pid_param; + struct wf_control *control = NULL; + int i; + + /* First, locate the params for this model */ + for (i = 0; i < PM121_NUM_CONFIGS; i++) { + if (pm121_sys_all_params[loop_id][i].model_id == pm121_mach_model) { + param = &(pm121_sys_all_params[loop_id][i]); + break; + } + } + + /* No params found, put fans to max */ + if (param == NULL) { + printk(KERN_WARNING "pm121: %s fan config not found " + " for this machine model\n", + loop_names[loop_id]); + goto fail; + } + + control = controls[param->control_id]; + + /* Alloc & initialize state */ + pm121_sys_state[loop_id] = kmalloc(sizeof(struct pm121_sys_state), + GFP_KERNEL); + if (pm121_sys_state[loop_id] == NULL) { + printk(KERN_WARNING "pm121: Memory allocation error\n"); + goto fail; + } + pm121_sys_state[loop_id]->ticks = 1; + + /* Fill PID params */ + pid_param.gd = PM121_SYS_GD; + pid_param.gp = param->gp; + pid_param.gr = PM121_SYS_GR; + pid_param.interval = PM121_SYS_INTERVAL; + pid_param.history_len = PM121_SYS_HISTORY_SIZE; + pid_param.itarget = param->itarget; + if(control) + { + pid_param.min = control->ops->get_min(control); + pid_param.max = control->ops->get_max(control); + } else { + /* + * This is probably not the right!? + * Perhaps goto fail if control == NULL above? + */ + pid_param.min = 0; + pid_param.max = 0; + } + + wf_pid_init(&pm121_sys_state[loop_id]->pid, &pid_param); + + pr_debug("pm121: %s Fan control loop initialized.\n" + " itarged=%d.%03d, min=%d RPM, max=%d RPM\n", + loop_names[loop_id], FIX32TOPRINT(pid_param.itarget), + pid_param.min, pid_param.max); + return; + + fail: + /* note that this is not optimal since another loop may still + control the same control */ + printk(KERN_WARNING "pm121: failed to set up %s loop " + "setting \"%s\" to max speed.\n", + loop_names[loop_id], control ? control->name : "uninitialized value"); + + if (control) + wf_control_set_max(control); +} + +static void pm121_sys_fans_tick(int loop_id) +{ + struct pm121_sys_param *param; + struct pm121_sys_state *st; + struct wf_sensor *sensor; + struct wf_control *control; + s32 temp, new_setpoint; + int rc; + + param = &(pm121_sys_all_params[loop_id][pm121_mach_model-2]); + st = pm121_sys_state[loop_id]; + sensor = *(param->sensor); + control = controls[param->control_id]; + + if (--st->ticks != 0) { + if (pm121_readjust) + goto readjust; + return; + } + st->ticks = PM121_SYS_INTERVAL; + + rc = sensor->ops->get_value(sensor, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: %s sensor error %d\n", + sensor->name, rc); + pm121_failure_state |= FAILURE_SENSOR; + return; + } + + pr_debug("pm121: %s Fan tick ! %s: %d.%03d\n", + loop_names[loop_id], sensor->name, + FIX32TOPRINT(temp)); + + new_setpoint = wf_pid_run(&st->pid, temp); + + /* correction */ + new_setpoint = pm121_correct(new_setpoint, + param->control_id, + st->pid.param.min); + /* linked corretion */ + new_setpoint = pm121_connect(param->control_id, new_setpoint); + + if (new_setpoint == st->setpoint) + return; + st->setpoint = new_setpoint; + pr_debug("pm121: %s corrected setpoint: %d RPM\n", + control->name, (int)new_setpoint); + readjust: + if (control && pm121_failure_state == 0) { + rc = control->ops->set_value(control, st->setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: %s fan error %d\n", + control->name, rc); + pm121_failure_state |= FAILURE_FAN; + } + } +} + + +/* CPU LOOP */ +static void pm121_create_cpu_fans(void) +{ + struct wf_cpu_pid_param pid_param; + const struct smu_sdbp_header *hdr; + struct smu_sdbp_cpupiddata *piddata; + struct smu_sdbp_fvt *fvt; + struct wf_control *fan_cpu; + s32 tmax, tdelta, maxpow, powadj; + + fan_cpu = controls[FAN_CPU]; + + /* First, locate the PID params in SMU SBD */ + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); + if (hdr == 0) { + printk(KERN_WARNING "pm121: CPU PID fan config not found.\n"); + goto fail; + } + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; + + /* Get the FVT params for operating point 0 (the only supported one + * for now) in order to get tmax + */ + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); + if (hdr) { + fvt = (struct smu_sdbp_fvt *)&hdr[1]; + tmax = ((s32)fvt->maxtemp) << 16; + } else + tmax = 0x5e0000; /* 94 degree default */ + + /* Alloc & initialize state */ + pm121_cpu_state = kmalloc(sizeof(struct pm121_cpu_state), + GFP_KERNEL); + if (pm121_cpu_state == NULL) + goto fail; + pm121_cpu_state->ticks = 1; + + /* Fill PID params */ + pid_param.interval = PM121_CPU_INTERVAL; + pid_param.history_len = piddata->history_len; + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { + printk(KERN_WARNING "pm121: History size overflow on " + "CPU control loop (%d)\n", piddata->history_len); + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; + } + pid_param.gd = piddata->gd; + pid_param.gp = piddata->gp; + pid_param.gr = piddata->gr / pid_param.history_len; + + tdelta = ((s32)piddata->target_temp_delta) << 16; + maxpow = ((s32)piddata->max_power) << 16; + powadj = ((s32)piddata->power_adj) << 16; + + pid_param.tmax = tmax; + pid_param.ttarget = tmax - tdelta; + pid_param.pmaxadj = maxpow - powadj; + + pid_param.min = fan_cpu->ops->get_min(fan_cpu); + pid_param.max = fan_cpu->ops->get_max(fan_cpu); + + wf_cpu_pid_init(&pm121_cpu_state->pid, &pid_param); + + pr_debug("pm121: CPU Fan control initialized.\n"); + pr_debug(" ttarget=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM,\n", + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), + pid_param.min, pid_param.max); + + return; + + fail: + printk(KERN_WARNING "pm121: CPU fan config not found, max fan speed\n"); + + if (controls[CPUFREQ]) + wf_control_set_max(controls[CPUFREQ]); + if (fan_cpu) + wf_control_set_max(fan_cpu); +} + + +static void pm121_cpu_fans_tick(struct pm121_cpu_state *st) +{ + s32 new_setpoint, temp, power; + struct wf_control *fan_cpu = NULL; + int rc; + + if (--st->ticks != 0) { + if (pm121_readjust) + goto readjust; + return; + } + st->ticks = PM121_CPU_INTERVAL; + + fan_cpu = controls[FAN_CPU]; + + rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); + if (rc) { + printk(KERN_WARNING "pm121: CPU temp sensor error %d\n", + rc); + pm121_failure_state |= FAILURE_SENSOR; + return; + } + + rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); + if (rc) { + printk(KERN_WARNING "pm121: CPU power sensor error %d\n", + rc); + pm121_failure_state |= FAILURE_SENSOR; + return; + } + + pr_debug("pm121: CPU Fans tick ! CPU temp: %d.%03d°C, power: %d.%03d\n", + FIX32TOPRINT(temp), FIX32TOPRINT(power)); + + if (temp > st->pid.param.tmax) + pm121_failure_state |= FAILURE_OVERTEMP; + + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); + + /* correction */ + new_setpoint = pm121_correct(new_setpoint, + FAN_CPU, + st->pid.param.min); + + /* connected correction */ + new_setpoint = pm121_connect(FAN_CPU, new_setpoint); + + if (st->setpoint == new_setpoint) + return; + st->setpoint = new_setpoint; + pr_debug("pm121: CPU corrected setpoint: %d RPM\n", (int)new_setpoint); + + readjust: + if (fan_cpu && pm121_failure_state == 0) { + rc = fan_cpu->ops->set_value(fan_cpu, st->setpoint); + if (rc) { + printk(KERN_WARNING "pm121: %s fan error %d\n", + fan_cpu->name, rc); + pm121_failure_state |= FAILURE_FAN; + } + } +} + +/* + * ****** Common ****** + * + */ + +static void pm121_tick(void) +{ + unsigned int last_failure = pm121_failure_state; + unsigned int new_failure; + s32 total_power; + int i; + + if (!pm121_started) { + pr_debug("pm121: creating control loops !\n"); + for (i = 0; i < N_LOOPS; i++) + pm121_create_sys_fans(i); + + pm121_create_cpu_fans(); + pm121_started = true; + } + + /* skipping ticks */ + if (pm121_skipping && --pm121_skipping) + return; + + /* compute average power */ + total_power = 0; + for (i = 0; i < pm121_cpu_state->pid.param.history_len; i++) + total_power += pm121_cpu_state->pid.powers[i]; + + average_power = total_power / pm121_cpu_state->pid.param.history_len; + + + pm121_failure_state = 0; + for (i = 0 ; i < N_LOOPS; i++) { + if (pm121_sys_state[i]) + pm121_sys_fans_tick(i); + } + + if (pm121_cpu_state) + pm121_cpu_fans_tick(pm121_cpu_state); + + pm121_readjust = 0; + new_failure = pm121_failure_state & ~last_failure; + + /* If entering failure mode, clamp cpufreq and ramp all + * fans to full speed. + */ + if (pm121_failure_state && !last_failure) { + for (i = 0; i < N_CONTROLS; i++) { + if (controls[i]) + wf_control_set_max(controls[i]); + } + } + + /* If leaving failure mode, unclamp cpufreq and readjust + * all fans on next iteration + */ + if (!pm121_failure_state && last_failure) { + if (controls[CPUFREQ]) + wf_control_set_min(controls[CPUFREQ]); + pm121_readjust = 1; + } + + /* Overtemp condition detected, notify and start skipping a couple + * ticks to let the temperature go down + */ + if (new_failure & FAILURE_OVERTEMP) { + wf_set_overtemp(); + pm121_skipping = 2; + pm121_overtemp = true; + } + + /* We only clear the overtemp condition if overtemp is cleared + * _and_ no other failure is present. Since a sensor error will + * clear the overtemp condition (can't measure temperature) at + * the control loop levels, but we don't want to keep it clear + * here in this case + */ + if (!pm121_failure_state && pm121_overtemp) { + wf_clear_overtemp(); + pm121_overtemp = false; + } +} + + +static struct wf_control* pm121_register_control(struct wf_control *ct, + const char *match, + unsigned int id) +{ + if (controls[id] == NULL && !strcmp(ct->name, match)) { + if (wf_get_control(ct) == 0) + controls[id] = ct; + } + return controls[id]; +} + +static void pm121_new_control(struct wf_control *ct) +{ + int all = 1; + + if (pm121_all_controls_ok) + return; + + all = pm121_register_control(ct, "optical-drive-fan", FAN_OD) && all; + all = pm121_register_control(ct, "hard-drive-fan", FAN_HD) && all; + all = pm121_register_control(ct, "cpu-fan", FAN_CPU) && all; + all = pm121_register_control(ct, "cpufreq-clamp", CPUFREQ) && all; + + if (all) + pm121_all_controls_ok = 1; +} + + + + +static struct wf_sensor* pm121_register_sensor(struct wf_sensor *sensor, + const char *match, + struct wf_sensor **var) +{ + if (*var == NULL && !strcmp(sensor->name, match)) { + if (wf_get_sensor(sensor) == 0) + *var = sensor; + } + return *var; +} + +static void pm121_new_sensor(struct wf_sensor *sr) +{ + int all = 1; + + if (pm121_all_sensors_ok) + return; + + all = pm121_register_sensor(sr, "cpu-temp", + &sensor_cpu_temp) && all; + all = pm121_register_sensor(sr, "cpu-current", + &sensor_cpu_current) && all; + all = pm121_register_sensor(sr, "cpu-voltage", + &sensor_cpu_voltage) && all; + all = pm121_register_sensor(sr, "cpu-power", + &sensor_cpu_power) && all; + all = pm121_register_sensor(sr, "hard-drive-temp", + &sensor_hard_drive_temp) && all; + all = pm121_register_sensor(sr, "optical-drive-temp", + &sensor_optical_drive_temp) && all; + all = pm121_register_sensor(sr, "incoming-air-temp", + &sensor_incoming_air_temp) && all; + all = pm121_register_sensor(sr, "north-bridge-temp", + &sensor_north_bridge_temp) && all; + all = pm121_register_sensor(sr, "gpu-temp", + &sensor_gpu_temp) && all; + + if (all) + pm121_all_sensors_ok = 1; +} + + + +static int pm121_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch (event) { + case WF_EVENT_NEW_CONTROL: + pr_debug("pm121: new control %s detected\n", + ((struct wf_control *)data)->name); + pm121_new_control(data); + break; + case WF_EVENT_NEW_SENSOR: + pr_debug("pm121: new sensor %s detected\n", + ((struct wf_sensor *)data)->name); + pm121_new_sensor(data); + break; + case WF_EVENT_TICK: + if (pm121_all_controls_ok && pm121_all_sensors_ok) + pm121_tick(); + break; + } + + return 0; +} + +static struct notifier_block pm121_events = { + .notifier_call = pm121_notify, +}; + +static int pm121_init_pm(void) +{ + const struct smu_sdbp_header *hdr; + + hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL); + if (hdr != 0) { + struct smu_sdbp_sensortree *st = + (struct smu_sdbp_sensortree *)&hdr[1]; + pm121_mach_model = st->model_id; + } + + pm121_connection = &pm121_connections[pm121_mach_model - 2]; + + printk(KERN_INFO "pm121: Initializing for iMac G5 iSight model ID %d\n", + pm121_mach_model); + + return 0; +} + + +static int pm121_probe(struct platform_device *ddev) +{ + wf_register_client(&pm121_events); + + return 0; +} + +static int pm121_remove(struct platform_device *ddev) +{ + wf_unregister_client(&pm121_events); + return 0; +} + +static struct platform_driver pm121_driver = { + .probe = pm121_probe, + .remove = pm121_remove, + .driver = { + .name = "windfarm", + .bus = &platform_bus_type, + }, +}; + + +static int __init pm121_init(void) +{ + int rc = -ENODEV; + + if (of_machine_is_compatible("PowerMac12,1")) + rc = pm121_init_pm(); + + if (rc == 0) { + request_module("windfarm_smu_controls"); + request_module("windfarm_smu_sensors"); + request_module("windfarm_smu_sat"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_max6690_sensor"); + request_module("windfarm_cpufreq_clamp"); + platform_driver_register(&pm121_driver); + } + + return rc; +} + +static void __exit pm121_exit(void) +{ + + platform_driver_unregister(&pm121_driver); +} + + +module_init(pm121_init); +module_exit(pm121_exit); + +MODULE_AUTHOR("Étienne Bersac <bersace@gmail.com>"); +MODULE_DESCRIPTION("Thermal control logic for iMac G5 (iSight)"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_pm72.c b/drivers/macintosh/windfarm_pm72.c new file mode 100644 index 000000000..e21f97355 --- /dev/null +++ b/drivers/macintosh/windfarm_pm72.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. + * Control loops for PowerMac7,2 and 7,3 + * + * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> + +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#undef DEBUG +#undef LOTSA_DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +#ifdef LOTSA_DEBUG +#define DBG_LOTS(args...) printk(args) +#else +#define DBG_LOTS(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 60 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* We currently only handle 2 chips */ +#define NR_CHIPS 2 +#define NR_CPU_FANS 3 * NR_CHIPS + +/* Controls and sensors */ +static struct wf_sensor *sens_cpu_temp[NR_CHIPS]; +static struct wf_sensor *sens_cpu_volts[NR_CHIPS]; +static struct wf_sensor *sens_cpu_amps[NR_CHIPS]; +static struct wf_sensor *backside_temp; +static struct wf_sensor *drives_temp; + +static struct wf_control *cpu_front_fans[NR_CHIPS]; +static struct wf_control *cpu_rear_fans[NR_CHIPS]; +static struct wf_control *cpu_pumps[NR_CHIPS]; +static struct wf_control *backside_fan; +static struct wf_control *drives_fan; +static struct wf_control *slots_fan; +static struct wf_control *cpufreq_clamp; + +/* We keep a temperature history for average calculation of 180s */ +#define CPU_TEMP_HIST_SIZE 180 + +/* Fixed speed for slot fan */ +#define SLOTS_FAN_DEFAULT_PWM 40 + +/* Scale value for CPU intake fans */ +#define CPU_INTAKE_SCALE 0x0000f852 + +/* PID loop state */ +static const struct mpu_data *cpu_mpu_data[NR_CHIPS]; +static struct wf_cpu_pid_state cpu_pid[NR_CHIPS]; +static bool cpu_pid_combined; +static u32 cpu_thist[CPU_TEMP_HIST_SIZE]; +static int cpu_thist_pt; +static s64 cpu_thist_total; +static s32 cpu_all_tmax = 100 << 16; +static struct wf_pid_state backside_pid; +static int backside_tick; +static struct wf_pid_state drives_pid; +static int drives_tick; + +static int nr_chips; +static bool have_all_controls; +static bool have_all_sensors; +static bool started; + +static int failure_state; +#define FAILURE_SENSOR 1 +#define FAILURE_FAN 2 +#define FAILURE_PERM 4 +#define FAILURE_LOW_OVERTEMP 8 +#define FAILURE_HIGH_OVERTEMP 16 + +/* Overtemp values */ +#define LOW_OVER_AVERAGE 0 +#define LOW_OVER_IMMEDIATE (10 << 16) +#define LOW_OVER_CLEAR ((-10) << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) +#define HIGH_OVER_AVERAGE (10 << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) + + +static void cpu_max_all_fans(void) +{ + int i; + + /* We max all CPU fans in case of a sensor error. We also do the + * cpufreq clamping now, even if it's supposedly done later by the + * generic code anyway, we do it earlier here to react faster + */ + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + for (i = 0; i < nr_chips; i++) { + if (cpu_front_fans[i]) + wf_control_set_max(cpu_front_fans[i]); + if (cpu_rear_fans[i]) + wf_control_set_max(cpu_rear_fans[i]); + if (cpu_pumps[i]) + wf_control_set_max(cpu_pumps[i]); + } +} + +static int cpu_check_overtemp(s32 temp) +{ + int new_state = 0; + s32 t_avg, t_old; + static bool first = true; + + /* First check for immediate overtemps */ + if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to immediate CPU" + " temperature !\n"); + } + if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " immediate CPU temperature !\n"); + } + + /* + * The first time around, initialize the array with the first + * temperature reading + */ + if (first) { + int i; + + cpu_thist_total = 0; + for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) { + cpu_thist[i] = temp; + cpu_thist_total += temp; + } + first = false; + } + + /* + * We calculate a history of max temperatures and use that for the + * overtemp management + */ + t_old = cpu_thist[cpu_thist_pt]; + cpu_thist[cpu_thist_pt] = temp; + cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE; + cpu_thist_total -= t_old; + cpu_thist_total += temp; + t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE; + + DBG_LOTS(" t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n", + FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp)); + + /* Now check for average overtemps */ + if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to average CPU" + " temperature !\n"); + } + if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " average CPU temperature !\n"); + } + + /* Now handle overtemp conditions. We don't currently use the windfarm + * overtemp handling core as it's not fully suited to the needs of those + * new machine. This will be fixed later. + */ + if (new_state) { + /* High overtemp -> immediate shutdown */ + if (new_state & FAILURE_HIGH_OVERTEMP) + machine_power_off(); + if ((failure_state & new_state) != new_state) + cpu_max_all_fans(); + failure_state |= new_state; + } else if ((failure_state & FAILURE_LOW_OVERTEMP) && + (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) { + printk(KERN_ERR "windfarm: Overtemp condition cleared !\n"); + failure_state &= ~FAILURE_LOW_OVERTEMP; + } + + return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP); +} + +static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power) +{ + s32 dtemp, volts, amps; + int rc; + + /* Get diode temperature */ + rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp); + if (rc) { + DBG(" CPU%d: temp reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: temp = %d.%03d\n", cpu, FIX32TOPRINT((dtemp))); + *temp = dtemp; + + /* Get voltage */ + rc = wf_sensor_get(sens_cpu_volts[cpu], &volts); + if (rc) { + DBG(" CPU%d, volts reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: volts = %d.%03d\n", cpu, FIX32TOPRINT((volts))); + + /* Get current */ + rc = wf_sensor_get(sens_cpu_amps[cpu], &s); + if (rc) { + DBG(" CPU%d, current reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: amps = %d.%03d\n", cpu, FIX32TOPRINT((amps))); + + /* Calculate power */ + + /* Scale voltage and current raw sensor values according to fixed scales + * obtained in Darwin and calculate power from I and V + */ + *power = (((u64)volts) * ((u64)amps)) >> 16; + + DBG_LOTS(" CPU%d: power = %d.%03d\n", cpu, FIX32TOPRINT((*power))); + + return 0; + +} + +static void cpu_fans_tick_split(void) +{ + int err, cpu; + s32 intake, temp, power, t_max = 0; + + DBG_LOTS("* cpu fans_tick_split()\n"); + + for (cpu = 0; cpu < nr_chips; ++cpu) { + struct wf_cpu_pid_state *sp = &cpu_pid[cpu]; + + /* Read current speed */ + wf_control_get(cpu_rear_fans[cpu], &sp->target); + + DBG_LOTS(" CPU%d: cur_target = %d RPM\n", cpu, sp->target); + + err = read_one_cpu_vals(cpu, &temp, &power); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Keep track of highest temp */ + t_max = max(t_max, temp); + + /* Handle possible overtemps */ + if (cpu_check_overtemp(t_max)) + return; + + /* Run PID */ + wf_cpu_pid_run(sp, power, temp); + + DBG_LOTS(" CPU%d: target = %d RPM\n", cpu, sp->target); + + /* Apply result directly to exhaust fan */ + err = wf_control_set(cpu_rear_fans[cpu], sp->target); + if (err) { + pr_warn("wf_pm72: Fan %s reports error %d\n", + cpu_rear_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + break; + } + + /* Scale result for intake fan */ + intake = (sp->target * CPU_INTAKE_SCALE) >> 16; + DBG_LOTS(" CPU%d: intake = %d RPM\n", cpu, intake); + err = wf_control_set(cpu_front_fans[cpu], intake); + if (err) { + pr_warn("wf_pm72: Fan %s reports error %d\n", + cpu_front_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + break; + } + } +} + +static void cpu_fans_tick_combined(void) +{ + s32 temp0, power0, temp1, power1, t_max = 0; + s32 temp, power, intake, pump; + struct wf_control *pump0, *pump1; + struct wf_cpu_pid_state *sp = &cpu_pid[0]; + int err, cpu; + + DBG_LOTS("* cpu fans_tick_combined()\n"); + + /* Read current speed from cpu 0 */ + wf_control_get(cpu_rear_fans[0], &sp->target); + + DBG_LOTS(" CPUs: cur_target = %d RPM\n", sp->target); + + /* Read values for both CPUs */ + err = read_one_cpu_vals(0, &temp0, &power0); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + err = read_one_cpu_vals(1, &temp1, &power1); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Keep track of highest temp */ + t_max = max(t_max, max(temp0, temp1)); + + /* Handle possible overtemps */ + if (cpu_check_overtemp(t_max)) + return; + + /* Use the max temp & power of both */ + temp = max(temp0, temp1); + power = max(power0, power1); + + /* Run PID */ + wf_cpu_pid_run(sp, power, temp); + + /* Scale result for intake fan */ + intake = (sp->target * CPU_INTAKE_SCALE) >> 16; + + /* Same deal with pump speed */ + pump0 = cpu_pumps[0]; + pump1 = cpu_pumps[1]; + if (!pump0) { + pump0 = pump1; + pump1 = NULL; + } + pump = (sp->target * wf_control_get_max(pump0)) / + cpu_mpu_data[0]->rmaxn_exhaust_fan; + + DBG_LOTS(" CPUs: target = %d RPM\n", sp->target); + DBG_LOTS(" CPUs: intake = %d RPM\n", intake); + DBG_LOTS(" CPUs: pump = %d RPM\n", pump); + + for (cpu = 0; cpu < nr_chips; cpu++) { + err = wf_control_set(cpu_rear_fans[cpu], sp->target); + if (err) { + pr_warn("wf_pm72: Fan %s reports error %d\n", + cpu_rear_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + } + err = wf_control_set(cpu_front_fans[cpu], intake); + if (err) { + pr_warn("wf_pm72: Fan %s reports error %d\n", + cpu_front_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + } + err = 0; + if (cpu_pumps[cpu]) + err = wf_control_set(cpu_pumps[cpu], pump); + if (err) { + pr_warn("wf_pm72: Pump %s reports error %d\n", + cpu_pumps[cpu]->name, err); + failure_state |= FAILURE_FAN; + } + } +} + +/* Implementation... */ +static int cpu_setup_pid(int cpu) +{ + struct wf_cpu_pid_param pid; + const struct mpu_data *mpu = cpu_mpu_data[cpu]; + s32 tmax, ttarget, ptarget; + int fmin, fmax, hsize; + + /* Get PID params from the appropriate MPU EEPROM */ + tmax = mpu->tmax << 16; + ttarget = mpu->ttarget << 16; + ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16; + + DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n", + cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax)); + + /* We keep a global tmax for overtemp calculations */ + if (tmax < cpu_all_tmax) + cpu_all_tmax = tmax; + + /* Set PID min/max by using the rear fan min/max */ + fmin = wf_control_get_min(cpu_rear_fans[cpu]); + fmax = wf_control_get_max(cpu_rear_fans[cpu]); + DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax); + + /* History size */ + hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY); + DBG("wf_72: CPU%d history size = %d\n", cpu, hsize); + + /* Initialize PID loop */ + pid.interval = 1; /* seconds */ + pid.history_len = hsize; + pid.gd = mpu->pid_gd; + pid.gp = mpu->pid_gp; + pid.gr = mpu->pid_gr; + pid.tmax = tmax; + pid.ttarget = ttarget; + pid.pmaxadj = ptarget; + pid.min = fmin; + pid.max = fmax; + + wf_cpu_pid_init(&cpu_pid[cpu], &pid); + cpu_pid[cpu].target = 1000; + + return 0; +} + +/* Backside/U3 fan */ +static struct wf_pid_param backside_u3_param = { + .interval = 5, + .history_len = 2, + .gd = 40 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 65 << 16, + .additive = 1, + .min = 20, + .max = 100, +}; + +static struct wf_pid_param backside_u3h_param = { + .interval = 5, + .history_len = 2, + .gd = 20 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 75 << 16, + .additive = 1, + .min = 20, + .max = 100, +}; + +static void backside_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!backside_fan || !backside_temp || !backside_tick) + return; + if (--backside_tick > 0) + return; + backside_tick = backside_pid.param.interval; + + DBG_LOTS("* backside fans tick\n"); + + /* Update fan speed from actual fans */ + err = wf_control_get(backside_fan, &speed); + if (!err) + backside_pid.target = speed; + + err = wf_sensor_get(backside_temp, &temp); + if (err) { + printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(backside_fan); + return; + } + speed = wf_pid_run(&backside_pid, temp); + + DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = wf_control_set(backside_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: backside fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void backside_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(backside_fan); + s32 fmax = wf_control_get_max(backside_fan); + struct wf_pid_param param; + struct device_node *u3; + int u3h = 1; /* conservative by default */ + + u3 = of_find_node_by_path("/u3@0,f8000000"); + if (u3 != NULL) { + const u32 *vers = of_get_property(u3, "device-rev", NULL); + if (vers) + if (((*vers) & 0x3f) < 0x34) + u3h = 0; + of_node_put(u3); + } + + param = u3h ? backside_u3h_param : backside_u3_param; + + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&backside_pid, ¶m); + backside_tick = 1; + + pr_info("wf_pm72: Backside control loop started.\n"); +} + +/* Drive bay fan */ +static const struct wf_pid_param drives_param = { + .interval = 5, + .history_len = 2, + .gd = 30 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 40 << 16, + .additive = 1, + .min = 300, + .max = 4000, +}; + +static void drives_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!drives_fan || !drives_temp || !drives_tick) + return; + if (--drives_tick > 0) + return; + drives_tick = drives_pid.param.interval; + + DBG_LOTS("* drives fans tick\n"); + + /* Update fan speed from actual fans */ + err = wf_control_get(drives_fan, &speed); + if (!err) + drives_pid.target = speed; + + err = wf_sensor_get(drives_temp, &temp); + if (err) { + pr_warn("wf_pm72: drive bay temp sensor error %d\n", err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(drives_fan); + return; + } + speed = wf_pid_run(&drives_pid, temp); + + DBG_LOTS("drives PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = wf_control_set(drives_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void drives_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(drives_fan); + s32 fmax = wf_control_get_max(drives_fan); + struct wf_pid_param param = drives_param; + + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&drives_pid, ¶m); + drives_tick = 1; + + pr_info("wf_pm72: Drive bay control loop started.\n"); +} + +static void set_fail_state(void) +{ + cpu_max_all_fans(); + + if (backside_fan) + wf_control_set_max(backside_fan); + if (slots_fan) + wf_control_set_max(slots_fan); + if (drives_fan) + wf_control_set_max(drives_fan); +} + +static void pm72_tick(void) +{ + int i, last_failure; + + if (!started) { + started = true; + printk(KERN_INFO "windfarm: CPUs control loops started.\n"); + for (i = 0; i < nr_chips; ++i) { + if (cpu_setup_pid(i) < 0) { + failure_state = FAILURE_PERM; + set_fail_state(); + break; + } + } + DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax)); + + backside_setup_pid(); + drives_setup_pid(); + + /* + * We don't have the right stuff to drive the PCI fan + * so we fix it to a default value + */ + wf_control_set(slots_fan, SLOTS_FAN_DEFAULT_PWM); + +#ifdef HACKED_OVERTEMP + cpu_all_tmax = 60 << 16; +#endif + } + + /* Permanent failure, bail out */ + if (failure_state & FAILURE_PERM) + return; + + /* + * Clear all failure bits except low overtemp which will be eventually + * cleared by the control loop itself + */ + last_failure = failure_state; + failure_state &= FAILURE_LOW_OVERTEMP; + if (cpu_pid_combined) + cpu_fans_tick_combined(); + else + cpu_fans_tick_split(); + backside_fan_tick(); + drives_fan_tick(); + + DBG_LOTS(" last_failure: 0x%x, failure_state: %x\n", + last_failure, failure_state); + + /* Check for failures. Any failure causes cpufreq clamping */ + if (failure_state && last_failure == 0 && cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (failure_state == 0 && last_failure && cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + + /* That's it for now, we might want to deal with other failures + * differently in the future though + */ +} + +static void pm72_new_control(struct wf_control *ct) +{ + bool all_controls; + bool had_pump = cpu_pumps[0] || cpu_pumps[1]; + + if (!strcmp(ct->name, "cpu-front-fan-0")) + cpu_front_fans[0] = ct; + else if (!strcmp(ct->name, "cpu-front-fan-1")) + cpu_front_fans[1] = ct; + else if (!strcmp(ct->name, "cpu-rear-fan-0")) + cpu_rear_fans[0] = ct; + else if (!strcmp(ct->name, "cpu-rear-fan-1")) + cpu_rear_fans[1] = ct; + else if (!strcmp(ct->name, "cpu-pump-0")) + cpu_pumps[0] = ct; + else if (!strcmp(ct->name, "cpu-pump-1")) + cpu_pumps[1] = ct; + else if (!strcmp(ct->name, "backside-fan")) + backside_fan = ct; + else if (!strcmp(ct->name, "slots-fan")) + slots_fan = ct; + else if (!strcmp(ct->name, "drive-bay-fan")) + drives_fan = ct; + else if (!strcmp(ct->name, "cpufreq-clamp")) + cpufreq_clamp = ct; + + all_controls = + cpu_front_fans[0] && + cpu_rear_fans[0] && + backside_fan && + slots_fan && + drives_fan; + if (nr_chips > 1) + all_controls &= + cpu_front_fans[1] && + cpu_rear_fans[1]; + have_all_controls = all_controls; + + if ((cpu_pumps[0] || cpu_pumps[1]) && !had_pump) { + pr_info("wf_pm72: Liquid cooling pump(s) detected," + " using new algorithm !\n"); + cpu_pid_combined = true; + } +} + + +static void pm72_new_sensor(struct wf_sensor *sr) +{ + bool all_sensors; + + if (!strcmp(sr->name, "cpu-diode-temp-0")) + sens_cpu_temp[0] = sr; + else if (!strcmp(sr->name, "cpu-diode-temp-1")) + sens_cpu_temp[1] = sr; + else if (!strcmp(sr->name, "cpu-voltage-0")) + sens_cpu_volts[0] = sr; + else if (!strcmp(sr->name, "cpu-voltage-1")) + sens_cpu_volts[1] = sr; + else if (!strcmp(sr->name, "cpu-current-0")) + sens_cpu_amps[0] = sr; + else if (!strcmp(sr->name, "cpu-current-1")) + sens_cpu_amps[1] = sr; + else if (!strcmp(sr->name, "backside-temp")) + backside_temp = sr; + else if (!strcmp(sr->name, "hd-temp")) + drives_temp = sr; + + all_sensors = + sens_cpu_temp[0] && + sens_cpu_volts[0] && + sens_cpu_amps[0] && + backside_temp && + drives_temp; + if (nr_chips > 1) + all_sensors &= + sens_cpu_temp[1] && + sens_cpu_volts[1] && + sens_cpu_amps[1]; + + have_all_sensors = all_sensors; +} + +static int pm72_wf_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch (event) { + case WF_EVENT_NEW_SENSOR: + pm72_new_sensor(data); + break; + case WF_EVENT_NEW_CONTROL: + pm72_new_control(data); + break; + case WF_EVENT_TICK: + if (have_all_controls && have_all_sensors) + pm72_tick(); + } + return 0; +} + +static struct notifier_block pm72_events = { + .notifier_call = pm72_wf_notify, +}; + +static int wf_pm72_probe(struct platform_device *dev) +{ + wf_register_client(&pm72_events); + return 0; +} + +static int wf_pm72_remove(struct platform_device *dev) +{ + wf_unregister_client(&pm72_events); + + /* should release all sensors and controls */ + return 0; +} + +static struct platform_driver wf_pm72_driver = { + .probe = wf_pm72_probe, + .remove = wf_pm72_remove, + .driver = { + .name = "windfarm", + }, +}; + +static int __init wf_pm72_init(void) +{ + struct device_node *cpu; + int i; + + if (!of_machine_is_compatible("PowerMac7,2") && + !of_machine_is_compatible("PowerMac7,3")) + return -ENODEV; + + /* Count the number of CPU cores */ + nr_chips = 0; + for_each_node_by_type(cpu, "cpu") + ++nr_chips; + if (nr_chips > NR_CHIPS) + nr_chips = NR_CHIPS; + + pr_info("windfarm: Initializing for desktop G5 with %d chips\n", + nr_chips); + + /* Get MPU data for each CPU */ + for (i = 0; i < nr_chips; i++) { + cpu_mpu_data[i] = wf_get_mpu(i); + if (!cpu_mpu_data[i]) { + pr_err("wf_pm72: Failed to find MPU data for CPU %d\n", i); + return -ENXIO; + } + } + +#ifdef MODULE + request_module("windfarm_fcu_controls"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_ad7417_sensor"); + request_module("windfarm_max6690_sensor"); + request_module("windfarm_cpufreq_clamp"); +#endif /* MODULE */ + + platform_driver_register(&wf_pm72_driver); + return 0; +} + +static void __exit wf_pm72_exit(void) +{ + platform_driver_unregister(&wf_pm72_driver); +} + +module_init(wf_pm72_init); +module_exit(wf_pm72_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control for AGP PowerMac G5s"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_pm81.c b/drivers/macintosh/windfarm_pm81.c new file mode 100644 index 000000000..e0f4743f2 --- /dev/null +++ b/drivers/macintosh/windfarm_pm81.c @@ -0,0 +1,812 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. iMac G5 + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <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 8.2 snapshot property lists (note however + * that none of the code has been re-used, it's a complete re-implementation + * + * The various control loops found in Darwin config file are: + * + * PowerMac8,1 and PowerMac8,2 + * =========================== + * + * System Fans control loop. Different based on models. In addition to the + * usual PID algorithm, the control loop gets 2 additional pairs of linear + * scaling factors (scale/offsets) expressed as 4.12 fixed point values + * signed offset, unsigned scale) + * + * The targets are modified such as: + * - the linked control (second control) gets the target value as-is + * (typically the drive fan) + * - the main control (first control) gets the target value scaled with + * the first pair of factors, and is then modified as below + * - the value of the target of the CPU Fan control loop is retrieved, + * scaled with the second pair of factors, and the max of that and + * the scaled target is applied to the main control. + * + * # model_id: 2 + * controls : system-fan, drive-bay-fan + * sensors : hd-temp + * PID params : G_d = 0x15400000 + * G_p = 0x00200000 + * G_r = 0x000002fd + * History = 2 entries + * Input target = 0x3a0000 + * Interval = 5s + * linear-factors : offset = 0xff38 scale = 0x0ccd + * offset = 0x0208 scale = 0x07ae + * + * # model_id: 3 + * controls : system-fan, drive-bay-fan + * sensors : hd-temp + * PID params : G_d = 0x08e00000 + * G_p = 0x00566666 + * G_r = 0x0000072b + * History = 2 entries + * Input target = 0x350000 + * Interval = 5s + * linear-factors : offset = 0xff38 scale = 0x0ccd + * offset = 0x0000 scale = 0x0000 + * + * # model_id: 5 + * controls : system-fan + * sensors : hd-temp + * PID params : G_d = 0x15400000 + * G_p = 0x00233333 + * G_r = 0x000002fd + * History = 2 entries + * Input target = 0x3a0000 + * Interval = 5s + * linear-factors : offset = 0x0000 scale = 0x1000 + * offset = 0x0091 scale = 0x0bae + * + * CPU Fan control loop. The loop is identical for all models. it + * has an additional pair of scaling factor. This is used to scale the + * systems fan control loop target result (the one before it gets scaled + * by the System Fans control loop itself). Then, the max value of the + * calculated target value and system fan value is sent to the fans + * + * controls : cpu-fan + * sensors : cpu-temp cpu-power + * PID params : From SMU sdb partition + * linear-factors : offset = 0xfb50 scale = 0x1000 + * + * CPU Slew control loop. Not implemented. The cpufreq driver in linux is + * completely separate for now, though we could find a way to link it, either + * as a client reacting to overtemp notifications, or directling monitoring + * the CPU temperature + * + * WARNING ! The CPU control loop requires the CPU tmax for the current + * operating point. However, we currently are completely separated from + * the cpufreq driver and thus do not know what the current operating + * point is. Fortunately, we also do not have any hardware supporting anything + * but operating point 0 at the moment, thus we just peek that value directly + * from the SDB partition. If we ever end up with actually slewing the system + * clock and thus changing operating points, we'll have to find a way to + * communicate with the CPU freq driver; + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/kmod.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" + +#define VERSION "0.4" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 74 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +static int wf_smu_mach_model; /* machine model id */ + +/* Controls & sensors */ +static struct wf_sensor *sensor_cpu_power; +static struct wf_sensor *sensor_cpu_temp; +static struct wf_sensor *sensor_hd_temp; +static struct wf_control *fan_cpu_main; +static struct wf_control *fan_hd; +static struct wf_control *fan_system; +static struct wf_control *cpufreq_clamp; + +/* Set to kick the control loop into life */ +static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok; +static bool wf_smu_started; + +/* Failure handling.. could be nicer */ +#define FAILURE_FAN 0x01 +#define FAILURE_SENSOR 0x02 +#define FAILURE_OVERTEMP 0x04 + +static unsigned int wf_smu_failure_state; +static int wf_smu_readjust, wf_smu_skipping; +static bool wf_smu_overtemp; + +/* + * ****** System Fans Control Loop ****** + * + */ + +/* Parameters for the System Fans control loop. Parameters + * not in this table such as interval, history size, ... + * are common to all versions and thus hard coded for now. + */ +struct wf_smu_sys_fans_param { + int model_id; + s32 itarget; + s32 gd, gp, gr; + + s16 offset0; + u16 scale0; + s16 offset1; + u16 scale1; +}; + +#define WF_SMU_SYS_FANS_INTERVAL 5 +#define WF_SMU_SYS_FANS_HISTORY_SIZE 2 + +/* State data used by the system fans control loop + */ +struct wf_smu_sys_fans_state { + int ticks; + s32 sys_setpoint; + s32 hd_setpoint; + s16 offset0; + u16 scale0; + s16 offset1; + u16 scale1; + struct wf_pid_state pid; +}; + +/* + * Configs for SMU System Fan control loop + */ +static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = { + /* Model ID 2 */ + { + .model_id = 2, + .itarget = 0x3a0000, + .gd = 0x15400000, + .gp = 0x00200000, + .gr = 0x000002fd, + .offset0 = 0xff38, + .scale0 = 0x0ccd, + .offset1 = 0x0208, + .scale1 = 0x07ae, + }, + /* Model ID 3 */ + { + .model_id = 3, + .itarget = 0x350000, + .gd = 0x08e00000, + .gp = 0x00566666, + .gr = 0x0000072b, + .offset0 = 0xff38, + .scale0 = 0x0ccd, + .offset1 = 0x0000, + .scale1 = 0x0000, + }, + /* Model ID 5 */ + { + .model_id = 5, + .itarget = 0x3a0000, + .gd = 0x15400000, + .gp = 0x00233333, + .gr = 0x000002fd, + .offset0 = 0x0000, + .scale0 = 0x1000, + .offset1 = 0x0091, + .scale1 = 0x0bae, + }, +}; +#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params) + +static struct wf_smu_sys_fans_state *wf_smu_sys_fans; + +/* + * ****** CPU Fans Control Loop ****** + * + */ + + +#define WF_SMU_CPU_FANS_INTERVAL 1 +#define WF_SMU_CPU_FANS_MAX_HISTORY 16 +#define WF_SMU_CPU_FANS_SIBLING_SCALE 0x00001000 +#define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50 + +/* State data used by the cpu fans control loop + */ +struct wf_smu_cpu_fans_state { + int ticks; + s32 cpu_setpoint; + s32 scale; + s32 offset; + struct wf_cpu_pid_state pid; +}; + +static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; + + + +/* + * ***** Implementation ***** + * + */ + +static void wf_smu_create_sys_fans(void) +{ + struct wf_smu_sys_fans_param *param = NULL; + struct wf_pid_param pid_param; + int i; + + /* First, locate the params for this model */ + for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++) + if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) { + param = &wf_smu_sys_all_params[i]; + break; + } + + /* No params found, put fans to max */ + if (param == NULL) { + printk(KERN_WARNING "windfarm: System fan config not found " + "for this machine model, max fan speed\n"); + goto fail; + } + + /* Alloc & initialize state */ + wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state), + GFP_KERNEL); + if (wf_smu_sys_fans == NULL) { + printk(KERN_WARNING "windfarm: Memory allocation error" + " max fan speed\n"); + goto fail; + } + wf_smu_sys_fans->ticks = 1; + wf_smu_sys_fans->scale0 = param->scale0; + wf_smu_sys_fans->offset0 = param->offset0; + wf_smu_sys_fans->scale1 = param->scale1; + wf_smu_sys_fans->offset1 = param->offset1; + + /* Fill PID params */ + pid_param.gd = param->gd; + pid_param.gp = param->gp; + pid_param.gr = param->gr; + pid_param.interval = WF_SMU_SYS_FANS_INTERVAL; + pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE; + pid_param.itarget = param->itarget; + pid_param.min = wf_control_get_min(fan_system); + pid_param.max = wf_control_get_max(fan_system); + if (fan_hd) { + pid_param.min = + max(pid_param.min, wf_control_get_min(fan_hd)); + pid_param.max = + min(pid_param.max, wf_control_get_max(fan_hd)); + } + wf_pid_init(&wf_smu_sys_fans->pid, &pid_param); + + DBG("wf: System Fan control initialized.\n"); + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max); + return; + + fail: + + if (fan_system) + wf_control_set_max(fan_system); + if (fan_hd) + wf_control_set_max(fan_hd); +} + +static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st) +{ + s32 new_setpoint, temp, scaled, cputarget; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = WF_SMU_SYS_FANS_INTERVAL; + + rc = wf_sensor_get(sensor_hd_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n", + FIX32TOPRINT(temp)); + + if (temp > (st->pid.param.itarget + 0x50000)) + wf_smu_failure_state |= FAILURE_OVERTEMP; + + new_setpoint = wf_pid_run(&st->pid, temp); + + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); + + scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0; + + DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled); + + cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0; + cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1; + scaled = max(scaled, cputarget); + scaled = max(scaled, st->pid.param.min); + scaled = min(scaled, st->pid.param.max); + + DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled); + + if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint) + return; + st->sys_setpoint = scaled; + st->hd_setpoint = new_setpoint; + readjust: + if (fan_system && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_system, st->sys_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: Sys fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } + if (fan_hd && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_hd, st->hd_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: HD fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + +static void wf_smu_create_cpu_fans(void) +{ + struct wf_cpu_pid_param pid_param; + const struct smu_sdbp_header *hdr; + struct smu_sdbp_cpupiddata *piddata; + struct smu_sdbp_fvt *fvt; + s32 tmax, tdelta, maxpow, powadj; + + /* First, locate the PID params in SMU SBD */ + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); + if (hdr == 0) { + printk(KERN_WARNING "windfarm: CPU PID fan config not found " + "max fan speed\n"); + goto fail; + } + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; + + /* Get the FVT params for operating point 0 (the only supported one + * for now) in order to get tmax + */ + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); + if (hdr) { + fvt = (struct smu_sdbp_fvt *)&hdr[1]; + tmax = ((s32)fvt->maxtemp) << 16; + } else + tmax = 0x5e0000; /* 94 degree default */ + + /* Alloc & initialize state */ + wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state), + GFP_KERNEL); + if (wf_smu_cpu_fans == NULL) + goto fail; + wf_smu_cpu_fans->ticks = 1; + + wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE; + wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET; + + /* Fill PID params */ + pid_param.interval = WF_SMU_CPU_FANS_INTERVAL; + pid_param.history_len = piddata->history_len; + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { + printk(KERN_WARNING "windfarm: History size overflow on " + "CPU control loop (%d)\n", piddata->history_len); + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; + } + pid_param.gd = piddata->gd; + pid_param.gp = piddata->gp; + pid_param.gr = piddata->gr / pid_param.history_len; + + tdelta = ((s32)piddata->target_temp_delta) << 16; + maxpow = ((s32)piddata->max_power) << 16; + powadj = ((s32)piddata->power_adj) << 16; + + pid_param.tmax = tmax; + pid_param.ttarget = tmax - tdelta; + pid_param.pmaxadj = maxpow - powadj; + + pid_param.min = wf_control_get_min(fan_cpu_main); + pid_param.max = wf_control_get_max(fan_cpu_main); + + wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); + + DBG("wf: CPU Fan control initialized.\n"); + DBG(" ttarget=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), + pid_param.min, pid_param.max); + + return; + + fail: + printk(KERN_WARNING "windfarm: CPU fan config not found\n" + "for this machine model, max fan speed\n"); + + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); +} + +static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) +{ + s32 new_setpoint, temp, power, systarget; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = WF_SMU_CPU_FANS_INTERVAL; + + rc = wf_sensor_get(sensor_cpu_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + rc = wf_sensor_get(sensor_cpu_power, &power); + if (rc) { + printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n", + FIX32TOPRINT(temp), FIX32TOPRINT(power)); + +#ifdef HACKED_OVERTEMP + if (temp > 0x4a0000) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#else + if (temp > st->pid.param.tmax) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#endif + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); + + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); + + systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0; + systarget = ((((s64)systarget) * (s64)st->scale) >> 12) + + st->offset; + new_setpoint = max(new_setpoint, systarget); + new_setpoint = max(new_setpoint, st->pid.param.min); + new_setpoint = min(new_setpoint, st->pid.param.max); + + DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint); + + if (st->cpu_setpoint == new_setpoint) + return; + st->cpu_setpoint = new_setpoint; + readjust: + if (fan_cpu_main && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_cpu_main, st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU main fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + +/* + * ****** Setup / Init / Misc ... ****** + * + */ + +static void wf_smu_tick(void) +{ + unsigned int last_failure = wf_smu_failure_state; + unsigned int new_failure; + + if (!wf_smu_started) { + DBG("wf: creating control loops !\n"); + wf_smu_create_sys_fans(); + wf_smu_create_cpu_fans(); + wf_smu_started = true; + } + + /* Skipping ticks */ + if (wf_smu_skipping && --wf_smu_skipping) + return; + + wf_smu_failure_state = 0; + if (wf_smu_sys_fans) + wf_smu_sys_fans_tick(wf_smu_sys_fans); + if (wf_smu_cpu_fans) + wf_smu_cpu_fans_tick(wf_smu_cpu_fans); + + wf_smu_readjust = 0; + new_failure = wf_smu_failure_state & ~last_failure; + + /* If entering failure mode, clamp cpufreq and ramp all + * fans to full speed. + */ + if (wf_smu_failure_state && !last_failure) { + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_system) + wf_control_set_max(fan_system); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); + if (fan_hd) + wf_control_set_max(fan_hd); + } + + /* If leaving failure mode, unclamp cpufreq and readjust + * all fans on next iteration + */ + if (!wf_smu_failure_state && last_failure) { + if (cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + wf_smu_readjust = 1; + } + + /* Overtemp condition detected, notify and start skipping a couple + * ticks to let the temperature go down + */ + if (new_failure & FAILURE_OVERTEMP) { + wf_set_overtemp(); + wf_smu_skipping = 2; + wf_smu_overtemp = true; + } + + /* We only clear the overtemp condition if overtemp is cleared + * _and_ no other failure is present. Since a sensor error will + * clear the overtemp condition (can't measure temperature) at + * the control loop levels, but we don't want to keep it clear + * here in this case + */ + if (!wf_smu_failure_state && wf_smu_overtemp) { + wf_clear_overtemp(); + wf_smu_overtemp = false; + } +} + +static void wf_smu_new_control(struct wf_control *ct) +{ + if (wf_smu_all_controls_ok) + return; + + if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) { + if (wf_get_control(ct) == 0) + fan_cpu_main = ct; + } + + if (fan_system == NULL && !strcmp(ct->name, "system-fan")) { + if (wf_get_control(ct) == 0) + fan_system = ct; + } + + if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) { + if (wf_get_control(ct) == 0) + cpufreq_clamp = ct; + } + + /* Darwin property list says the HD fan is only for model ID + * 0, 1, 2 and 3 + */ + + if (wf_smu_mach_model > 3) { + if (fan_system && fan_cpu_main && cpufreq_clamp) + wf_smu_all_controls_ok = 1; + return; + } + + if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) { + if (wf_get_control(ct) == 0) + fan_hd = ct; + } + + if (fan_system && fan_hd && fan_cpu_main && cpufreq_clamp) + wf_smu_all_controls_ok = 1; +} + +static void wf_smu_new_sensor(struct wf_sensor *sr) +{ + if (wf_smu_all_sensors_ok) + return; + + if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) { + if (wf_get_sensor(sr) == 0) + sensor_cpu_power = sr; + } + + if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) { + if (wf_get_sensor(sr) == 0) + sensor_cpu_temp = sr; + } + + if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) { + if (wf_get_sensor(sr) == 0) + sensor_hd_temp = sr; + } + + if (sensor_cpu_power && sensor_cpu_temp && sensor_hd_temp) + wf_smu_all_sensors_ok = 1; +} + + +static int wf_smu_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch(event) { + case WF_EVENT_NEW_CONTROL: + DBG("wf: new control %s detected\n", + ((struct wf_control *)data)->name); + wf_smu_new_control(data); + wf_smu_readjust = 1; + break; + case WF_EVENT_NEW_SENSOR: + DBG("wf: new sensor %s detected\n", + ((struct wf_sensor *)data)->name); + wf_smu_new_sensor(data); + break; + case WF_EVENT_TICK: + if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok) + wf_smu_tick(); + } + + return 0; +} + +static struct notifier_block wf_smu_events = { + .notifier_call = wf_smu_notify, +}; + +static int wf_init_pm(void) +{ + const struct smu_sdbp_header *hdr; + + hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL); + if (hdr != 0) { + struct smu_sdbp_sensortree *st = + (struct smu_sdbp_sensortree *)&hdr[1]; + wf_smu_mach_model = st->model_id; + } + + printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n", + wf_smu_mach_model); + + return 0; +} + +static int wf_smu_probe(struct platform_device *ddev) +{ + wf_register_client(&wf_smu_events); + + return 0; +} + +static int wf_smu_remove(struct platform_device *ddev) +{ + wf_unregister_client(&wf_smu_events); + + /* XXX We don't have yet a guarantee that our callback isn't + * in progress when returning from wf_unregister_client, so + * we add an arbitrary delay. I'll have to fix that in the core + */ + msleep(1000); + + /* Release all sensors */ + /* One more crappy race: I don't think we have any guarantee here + * that the attribute callback won't race with the sensor beeing + * disposed of, and I'm not 100% certain what best way to deal + * with that except by adding locks all over... I'll do that + * eventually but heh, who ever rmmod this module anyway ? + */ + if (sensor_cpu_power) + wf_put_sensor(sensor_cpu_power); + if (sensor_cpu_temp) + wf_put_sensor(sensor_cpu_temp); + if (sensor_hd_temp) + wf_put_sensor(sensor_hd_temp); + + /* Release all controls */ + if (fan_cpu_main) + wf_put_control(fan_cpu_main); + if (fan_hd) + wf_put_control(fan_hd); + if (fan_system) + wf_put_control(fan_system); + if (cpufreq_clamp) + wf_put_control(cpufreq_clamp); + + /* Destroy control loops state structures */ + kfree(wf_smu_sys_fans); + kfree(wf_smu_cpu_fans); + + return 0; +} + +static struct platform_driver wf_smu_driver = { + .probe = wf_smu_probe, + .remove = wf_smu_remove, + .driver = { + .name = "windfarm", + }, +}; + + +static int __init wf_smu_init(void) +{ + int rc = -ENODEV; + + if (of_machine_is_compatible("PowerMac8,1") || + of_machine_is_compatible("PowerMac8,2")) + rc = wf_init_pm(); + + if (rc == 0) { +#ifdef MODULE + request_module("windfarm_smu_controls"); + request_module("windfarm_smu_sensors"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_cpufreq_clamp"); + +#endif /* MODULE */ + platform_driver_register(&wf_smu_driver); + } + + return rc; +} + +static void __exit wf_smu_exit(void) +{ + + platform_driver_unregister(&wf_smu_driver); +} + + +module_init(wf_smu_init); +module_exit(wf_smu_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control logic for iMac G5"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c new file mode 100644 index 000000000..c85358553 --- /dev/null +++ b/drivers/macintosh/windfarm_pm91.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <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 8.2 snapshot property lists (note however + * that none of the code has been re-used, it's a complete re-implementation + * + * The various control loops found in Darwin config file are: + * + * PowerMac9,1 + * =========== + * + * Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't + * try to play with other control loops fans). Drive bay is rather basic PID + * with one sensor and one fan. Slots area is a bit different as the Darwin + * driver is supposed to be capable of working in a special "AGP" mode which + * involves the presence of an AGP sensor and an AGP fan (possibly on the + * AGP card itself). I can't deal with that special mode as I don't have + * access to those additional sensor/fans for now (though ultimately, it would + * be possible to add sensor objects for them) so I'm only implementing the + * basic PCI slot control loop + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/kmod.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" + +#define VERSION "0.4" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 74 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* Controls & sensors */ +static struct wf_sensor *sensor_cpu_power; +static struct wf_sensor *sensor_cpu_temp; +static struct wf_sensor *sensor_hd_temp; +static struct wf_sensor *sensor_slots_power; +static struct wf_control *fan_cpu_main; +static struct wf_control *fan_cpu_second; +static struct wf_control *fan_cpu_third; +static struct wf_control *fan_hd; +static struct wf_control *fan_slots; +static struct wf_control *cpufreq_clamp; + +/* Set to kick the control loop into life */ +static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok; +static bool wf_smu_started; +static bool wf_smu_overtemp; + +/* Failure handling.. could be nicer */ +#define FAILURE_FAN 0x01 +#define FAILURE_SENSOR 0x02 +#define FAILURE_OVERTEMP 0x04 + +static unsigned int wf_smu_failure_state; +static int wf_smu_readjust, wf_smu_skipping; + +/* + * ****** CPU Fans Control Loop ****** + * + */ + + +#define WF_SMU_CPU_FANS_INTERVAL 1 +#define WF_SMU_CPU_FANS_MAX_HISTORY 16 + +/* State data used by the cpu fans control loop + */ +struct wf_smu_cpu_fans_state { + int ticks; + s32 cpu_setpoint; + struct wf_cpu_pid_state pid; +}; + +static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; + + + +/* + * ****** Drive Fan Control Loop ****** + * + */ + +struct wf_smu_drive_fans_state { + int ticks; + s32 setpoint; + struct wf_pid_state pid; +}; + +static struct wf_smu_drive_fans_state *wf_smu_drive_fans; + +/* + * ****** Slots Fan Control Loop ****** + * + */ + +struct wf_smu_slots_fans_state { + int ticks; + s32 setpoint; + struct wf_pid_state pid; +}; + +static struct wf_smu_slots_fans_state *wf_smu_slots_fans; + +/* + * ***** Implementation ***** + * + */ + + +static void wf_smu_create_cpu_fans(void) +{ + struct wf_cpu_pid_param pid_param; + const struct smu_sdbp_header *hdr; + struct smu_sdbp_cpupiddata *piddata; + struct smu_sdbp_fvt *fvt; + s32 tmax, tdelta, maxpow, powadj; + + /* First, locate the PID params in SMU SBD */ + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); + if (hdr == 0) { + printk(KERN_WARNING "windfarm: CPU PID fan config not found " + "max fan speed\n"); + goto fail; + } + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; + + /* Get the FVT params for operating point 0 (the only supported one + * for now) in order to get tmax + */ + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); + if (hdr) { + fvt = (struct smu_sdbp_fvt *)&hdr[1]; + tmax = ((s32)fvt->maxtemp) << 16; + } else + tmax = 0x5e0000; /* 94 degree default */ + + /* Alloc & initialize state */ + wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state), + GFP_KERNEL); + if (wf_smu_cpu_fans == NULL) + goto fail; + wf_smu_cpu_fans->ticks = 1; + + /* Fill PID params */ + pid_param.interval = WF_SMU_CPU_FANS_INTERVAL; + pid_param.history_len = piddata->history_len; + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { + printk(KERN_WARNING "windfarm: History size overflow on " + "CPU control loop (%d)\n", piddata->history_len); + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; + } + pid_param.gd = piddata->gd; + pid_param.gp = piddata->gp; + pid_param.gr = piddata->gr / pid_param.history_len; + + tdelta = ((s32)piddata->target_temp_delta) << 16; + maxpow = ((s32)piddata->max_power) << 16; + powadj = ((s32)piddata->power_adj) << 16; + + pid_param.tmax = tmax; + pid_param.ttarget = tmax - tdelta; + pid_param.pmaxadj = maxpow - powadj; + + pid_param.min = wf_control_get_min(fan_cpu_main); + pid_param.max = wf_control_get_max(fan_cpu_main); + + wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); + + DBG("wf: CPU Fan control initialized.\n"); + DBG(" ttarget=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), + pid_param.min, pid_param.max); + + return; + + fail: + printk(KERN_WARNING "windfarm: CPU fan config not found\n" + "for this machine model, max fan speed\n"); + + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); +} + +static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) +{ + s32 new_setpoint, temp, power; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = WF_SMU_CPU_FANS_INTERVAL; + + rc = wf_sensor_get(sensor_cpu_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + rc = wf_sensor_get(sensor_cpu_power, &power); + if (rc) { + printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n", + FIX32TOPRINT(temp), FIX32TOPRINT(power)); + +#ifdef HACKED_OVERTEMP + if (temp > 0x4a0000) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#else + if (temp > st->pid.param.tmax) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#endif + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); + + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); + + if (st->cpu_setpoint == new_setpoint) + return; + st->cpu_setpoint = new_setpoint; + readjust: + if (fan_cpu_main && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_cpu_main, st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU main fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } + if (fan_cpu_second && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_cpu_second, st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU second fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } + if (fan_cpu_third && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_cpu_third, st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU third fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + +static void wf_smu_create_drive_fans(void) +{ + struct wf_pid_param param = { + .interval = 5, + .history_len = 2, + .gd = 0x01e00000, + .gp = 0x00500000, + .gr = 0x00000000, + .itarget = 0x00200000, + }; + + /* Alloc & initialize state */ + wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state), + GFP_KERNEL); + if (wf_smu_drive_fans == NULL) { + printk(KERN_WARNING "windfarm: Memory allocation error" + " max fan speed\n"); + goto fail; + } + wf_smu_drive_fans->ticks = 1; + + /* Fill PID params */ + param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN); + param.min = wf_control_get_min(fan_hd); + param.max = wf_control_get_max(fan_hd); + wf_pid_init(&wf_smu_drive_fans->pid, ¶m); + + DBG("wf: Drive Fan control initialized.\n"); + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(param.itarget), param.min, param.max); + return; + + fail: + if (fan_hd) + wf_control_set_max(fan_hd); +} + +static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st) +{ + s32 new_setpoint, temp; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = st->pid.param.interval; + + rc = wf_sensor_get(sensor_hd_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n", + FIX32TOPRINT(temp)); + + if (temp > (st->pid.param.itarget + 0x50000)) + wf_smu_failure_state |= FAILURE_OVERTEMP; + + new_setpoint = wf_pid_run(&st->pid, temp); + + DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); + + if (st->setpoint == new_setpoint) + return; + st->setpoint = new_setpoint; + readjust: + if (fan_hd && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_hd, st->setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: HD fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + +static void wf_smu_create_slots_fans(void) +{ + struct wf_pid_param param = { + .interval = 1, + .history_len = 8, + .gd = 0x00000000, + .gp = 0x00000000, + .gr = 0x00020000, + .itarget = 0x00000000 + }; + + /* Alloc & initialize state */ + wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state), + GFP_KERNEL); + if (wf_smu_slots_fans == NULL) { + printk(KERN_WARNING "windfarm: Memory allocation error" + " max fan speed\n"); + goto fail; + } + wf_smu_slots_fans->ticks = 1; + + /* Fill PID params */ + param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN); + param.min = wf_control_get_min(fan_slots); + param.max = wf_control_get_max(fan_slots); + wf_pid_init(&wf_smu_slots_fans->pid, ¶m); + + DBG("wf: Slots Fan control initialized.\n"); + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(param.itarget), param.min, param.max); + return; + + fail: + if (fan_slots) + wf_control_set_max(fan_slots); +} + +static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st) +{ + s32 new_setpoint, power; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = st->pid.param.interval; + + rc = wf_sensor_get(sensor_slots_power, &power); + if (rc) { + printk(KERN_WARNING "windfarm: Slots power sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n", + FIX32TOPRINT(power)); + +#if 0 /* Check what makes a good overtemp condition */ + if (power > (st->pid.param.itarget + 0x50000)) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#endif + + new_setpoint = wf_pid_run(&st->pid, power); + + DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); + + if (st->setpoint == new_setpoint) + return; + st->setpoint = new_setpoint; + readjust: + if (fan_slots && wf_smu_failure_state == 0) { + rc = wf_control_set(fan_slots, st->setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: Slots fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + + +/* + * ****** Setup / Init / Misc ... ****** + * + */ + +static void wf_smu_tick(void) +{ + unsigned int last_failure = wf_smu_failure_state; + unsigned int new_failure; + + if (!wf_smu_started) { + DBG("wf: creating control loops !\n"); + wf_smu_create_drive_fans(); + wf_smu_create_slots_fans(); + wf_smu_create_cpu_fans(); + wf_smu_started = true; + } + + /* Skipping ticks */ + if (wf_smu_skipping && --wf_smu_skipping) + return; + + wf_smu_failure_state = 0; + if (wf_smu_drive_fans) + wf_smu_drive_fans_tick(wf_smu_drive_fans); + if (wf_smu_slots_fans) + wf_smu_slots_fans_tick(wf_smu_slots_fans); + if (wf_smu_cpu_fans) + wf_smu_cpu_fans_tick(wf_smu_cpu_fans); + + wf_smu_readjust = 0; + new_failure = wf_smu_failure_state & ~last_failure; + + /* If entering failure mode, clamp cpufreq and ramp all + * fans to full speed. + */ + if (wf_smu_failure_state && !last_failure) { + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); + if (fan_cpu_second) + wf_control_set_max(fan_cpu_second); + if (fan_cpu_third) + wf_control_set_max(fan_cpu_third); + if (fan_hd) + wf_control_set_max(fan_hd); + if (fan_slots) + wf_control_set_max(fan_slots); + } + + /* If leaving failure mode, unclamp cpufreq and readjust + * all fans on next iteration + */ + if (!wf_smu_failure_state && last_failure) { + if (cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + wf_smu_readjust = 1; + } + + /* Overtemp condition detected, notify and start skipping a couple + * ticks to let the temperature go down + */ + if (new_failure & FAILURE_OVERTEMP) { + wf_set_overtemp(); + wf_smu_skipping = 2; + wf_smu_overtemp = true; + } + + /* We only clear the overtemp condition if overtemp is cleared + * _and_ no other failure is present. Since a sensor error will + * clear the overtemp condition (can't measure temperature) at + * the control loop levels, but we don't want to keep it clear + * here in this case + */ + if (!wf_smu_failure_state && wf_smu_overtemp) { + wf_clear_overtemp(); + wf_smu_overtemp = false; + } +} + + +static void wf_smu_new_control(struct wf_control *ct) +{ + if (wf_smu_all_controls_ok) + return; + + if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) { + if (wf_get_control(ct) == 0) + fan_cpu_main = ct; + } + + if (fan_cpu_second == NULL && !strcmp(ct->name, "cpu-rear-fan-1")) { + if (wf_get_control(ct) == 0) + fan_cpu_second = ct; + } + + if (fan_cpu_third == NULL && !strcmp(ct->name, "cpu-front-fan-0")) { + if (wf_get_control(ct) == 0) + fan_cpu_third = ct; + } + + if (cpufreq_clamp == NULL && !strcmp(ct->name, "cpufreq-clamp")) { + if (wf_get_control(ct) == 0) + cpufreq_clamp = ct; + } + + if (fan_hd == NULL && !strcmp(ct->name, "drive-bay-fan")) { + if (wf_get_control(ct) == 0) + fan_hd = ct; + } + + if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) { + if (wf_get_control(ct) == 0) + fan_slots = ct; + } + + if (fan_cpu_main && (fan_cpu_second || fan_cpu_third) && fan_hd && + fan_slots && cpufreq_clamp) + wf_smu_all_controls_ok = 1; +} + +static void wf_smu_new_sensor(struct wf_sensor *sr) +{ + if (wf_smu_all_sensors_ok) + return; + + if (sensor_cpu_power == NULL && !strcmp(sr->name, "cpu-power")) { + if (wf_get_sensor(sr) == 0) + sensor_cpu_power = sr; + } + + if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) { + if (wf_get_sensor(sr) == 0) + sensor_cpu_temp = sr; + } + + if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) { + if (wf_get_sensor(sr) == 0) + sensor_hd_temp = sr; + } + + if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) { + if (wf_get_sensor(sr) == 0) + sensor_slots_power = sr; + } + + if (sensor_cpu_power && sensor_cpu_temp && + sensor_hd_temp && sensor_slots_power) + wf_smu_all_sensors_ok = 1; +} + + +static int wf_smu_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch(event) { + case WF_EVENT_NEW_CONTROL: + DBG("wf: new control %s detected\n", + ((struct wf_control *)data)->name); + wf_smu_new_control(data); + wf_smu_readjust = 1; + break; + case WF_EVENT_NEW_SENSOR: + DBG("wf: new sensor %s detected\n", + ((struct wf_sensor *)data)->name); + wf_smu_new_sensor(data); + break; + case WF_EVENT_TICK: + if (wf_smu_all_controls_ok && wf_smu_all_sensors_ok) + wf_smu_tick(); + } + + return 0; +} + +static struct notifier_block wf_smu_events = { + .notifier_call = wf_smu_notify, +}; + +static int wf_init_pm(void) +{ + printk(KERN_INFO "windfarm: Initializing for Desktop G5 model\n"); + + return 0; +} + +static int wf_smu_probe(struct platform_device *ddev) +{ + wf_register_client(&wf_smu_events); + + return 0; +} + +static int wf_smu_remove(struct platform_device *ddev) +{ + wf_unregister_client(&wf_smu_events); + + /* XXX We don't have yet a guarantee that our callback isn't + * in progress when returning from wf_unregister_client, so + * we add an arbitrary delay. I'll have to fix that in the core + */ + msleep(1000); + + /* Release all sensors */ + /* One more crappy race: I don't think we have any guarantee here + * that the attribute callback won't race with the sensor beeing + * disposed of, and I'm not 100% certain what best way to deal + * with that except by adding locks all over... I'll do that + * eventually but heh, who ever rmmod this module anyway ? + */ + if (sensor_cpu_power) + wf_put_sensor(sensor_cpu_power); + if (sensor_cpu_temp) + wf_put_sensor(sensor_cpu_temp); + if (sensor_hd_temp) + wf_put_sensor(sensor_hd_temp); + if (sensor_slots_power) + wf_put_sensor(sensor_slots_power); + + /* Release all controls */ + if (fan_cpu_main) + wf_put_control(fan_cpu_main); + if (fan_cpu_second) + wf_put_control(fan_cpu_second); + if (fan_cpu_third) + wf_put_control(fan_cpu_third); + if (fan_hd) + wf_put_control(fan_hd); + if (fan_slots) + wf_put_control(fan_slots); + if (cpufreq_clamp) + wf_put_control(cpufreq_clamp); + + /* Destroy control loops state structures */ + kfree(wf_smu_slots_fans); + kfree(wf_smu_drive_fans); + kfree(wf_smu_cpu_fans); + + return 0; +} + +static struct platform_driver wf_smu_driver = { + .probe = wf_smu_probe, + .remove = wf_smu_remove, + .driver = { + .name = "windfarm", + }, +}; + + +static int __init wf_smu_init(void) +{ + int rc = -ENODEV; + + if (of_machine_is_compatible("PowerMac9,1")) + rc = wf_init_pm(); + + if (rc == 0) { +#ifdef MODULE + request_module("windfarm_smu_controls"); + request_module("windfarm_smu_sensors"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_cpufreq_clamp"); + +#endif /* MODULE */ + platform_driver_register(&wf_smu_driver); + } + + return rc; +} + +static void __exit wf_smu_exit(void) +{ + + platform_driver_unregister(&wf_smu_driver); +} + + +module_init(wf_smu_init); +module_exit(wf_smu_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_rm31.c b/drivers/macintosh/windfarm_rm31.c new file mode 100644 index 000000000..e9eb7fdde --- /dev/null +++ b/drivers/macintosh/windfarm_rm31.c @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. + * Control loops for RackMack3,1 (Xserve G5) + * + * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> + +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#undef DEBUG +#undef LOTSA_DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +#ifdef LOTSA_DEBUG +#define DBG_LOTS(args...) printk(args) +#else +#define DBG_LOTS(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 60 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* We currently only handle 2 chips */ +#define NR_CHIPS 2 +#define NR_CPU_FANS 3 * NR_CHIPS + +/* Controls and sensors */ +static struct wf_sensor *sens_cpu_temp[NR_CHIPS]; +static struct wf_sensor *sens_cpu_volts[NR_CHIPS]; +static struct wf_sensor *sens_cpu_amps[NR_CHIPS]; +static struct wf_sensor *backside_temp; +static struct wf_sensor *slots_temp; +static struct wf_sensor *dimms_temp; + +static struct wf_control *cpu_fans[NR_CHIPS][3]; +static struct wf_control *backside_fan; +static struct wf_control *slots_fan; +static struct wf_control *cpufreq_clamp; + +/* We keep a temperature history for average calculation of 180s */ +#define CPU_TEMP_HIST_SIZE 180 + +/* PID loop state */ +static const struct mpu_data *cpu_mpu_data[NR_CHIPS]; +static struct wf_cpu_pid_state cpu_pid[NR_CHIPS]; +static u32 cpu_thist[CPU_TEMP_HIST_SIZE]; +static int cpu_thist_pt; +static s64 cpu_thist_total; +static s32 cpu_all_tmax = 100 << 16; +static struct wf_pid_state backside_pid; +static int backside_tick; +static struct wf_pid_state slots_pid; +static int slots_tick; +static int slots_speed; +static struct wf_pid_state dimms_pid; +static int dimms_output_clamp; + +static int nr_chips; +static bool have_all_controls; +static bool have_all_sensors; +static bool started; + +static int failure_state; +#define FAILURE_SENSOR 1 +#define FAILURE_FAN 2 +#define FAILURE_PERM 4 +#define FAILURE_LOW_OVERTEMP 8 +#define FAILURE_HIGH_OVERTEMP 16 + +/* Overtemp values */ +#define LOW_OVER_AVERAGE 0 +#define LOW_OVER_IMMEDIATE (10 << 16) +#define LOW_OVER_CLEAR ((-10) << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) +#define HIGH_OVER_AVERAGE (10 << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) + + +static void cpu_max_all_fans(void) +{ + int i; + + /* We max all CPU fans in case of a sensor error. We also do the + * cpufreq clamping now, even if it's supposedly done later by the + * generic code anyway, we do it earlier here to react faster + */ + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + for (i = 0; i < nr_chips; i++) { + if (cpu_fans[i][0]) + wf_control_set_max(cpu_fans[i][0]); + if (cpu_fans[i][1]) + wf_control_set_max(cpu_fans[i][1]); + if (cpu_fans[i][2]) + wf_control_set_max(cpu_fans[i][2]); + } +} + +static int cpu_check_overtemp(s32 temp) +{ + int new_state = 0; + s32 t_avg, t_old; + static bool first = true; + + /* First check for immediate overtemps */ + if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to immediate CPU" + " temperature !\n"); + } + if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " immediate CPU temperature !\n"); + } + + /* + * The first time around, initialize the array with the first + * temperature reading + */ + if (first) { + int i; + + cpu_thist_total = 0; + for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) { + cpu_thist[i] = temp; + cpu_thist_total += temp; + } + first = false; + } + + /* + * We calculate a history of max temperatures and use that for the + * overtemp management + */ + t_old = cpu_thist[cpu_thist_pt]; + cpu_thist[cpu_thist_pt] = temp; + cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE; + cpu_thist_total -= t_old; + cpu_thist_total += temp; + t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE; + + DBG_LOTS(" t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n", + FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp)); + + /* Now check for average overtemps */ + if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to average CPU" + " temperature !\n"); + } + if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " average CPU temperature !\n"); + } + + /* Now handle overtemp conditions. We don't currently use the windfarm + * overtemp handling core as it's not fully suited to the needs of those + * new machine. This will be fixed later. + */ + if (new_state) { + /* High overtemp -> immediate shutdown */ + if (new_state & FAILURE_HIGH_OVERTEMP) + machine_power_off(); + if ((failure_state & new_state) != new_state) + cpu_max_all_fans(); + failure_state |= new_state; + } else if ((failure_state & FAILURE_LOW_OVERTEMP) && + (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) { + printk(KERN_ERR "windfarm: Overtemp condition cleared !\n"); + failure_state &= ~FAILURE_LOW_OVERTEMP; + } + + return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP); +} + +static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power) +{ + s32 dtemp, volts, amps; + int rc; + + /* Get diode temperature */ + rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp); + if (rc) { + DBG(" CPU%d: temp reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: temp = %d.%03d\n", cpu, FIX32TOPRINT((dtemp))); + *temp = dtemp; + + /* Get voltage */ + rc = wf_sensor_get(sens_cpu_volts[cpu], &volts); + if (rc) { + DBG(" CPU%d, volts reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: volts = %d.%03d\n", cpu, FIX32TOPRINT((volts))); + + /* Get current */ + rc = wf_sensor_get(sens_cpu_amps[cpu], &s); + if (rc) { + DBG(" CPU%d, current reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: amps = %d.%03d\n", cpu, FIX32TOPRINT((amps))); + + /* Calculate power */ + + /* Scale voltage and current raw sensor values according to fixed scales + * obtained in Darwin and calculate power from I and V + */ + *power = (((u64)volts) * ((u64)amps)) >> 16; + + DBG_LOTS(" CPU%d: power = %d.%03d\n", cpu, FIX32TOPRINT((*power))); + + return 0; + +} + +static void cpu_fans_tick(void) +{ + int err, cpu, i; + s32 speed, temp, power, t_max = 0; + + DBG_LOTS("* cpu fans_tick_split()\n"); + + for (cpu = 0; cpu < nr_chips; ++cpu) { + struct wf_cpu_pid_state *sp = &cpu_pid[cpu]; + + /* Read current speed */ + wf_control_get(cpu_fans[cpu][0], &sp->target); + + err = read_one_cpu_vals(cpu, &temp, &power); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Keep track of highest temp */ + t_max = max(t_max, temp); + + /* Handle possible overtemps */ + if (cpu_check_overtemp(t_max)) + return; + + /* Run PID */ + wf_cpu_pid_run(sp, power, temp); + + DBG_LOTS(" CPU%d: target = %d RPM\n", cpu, sp->target); + + /* Apply DIMMs clamp */ + speed = max(sp->target, dimms_output_clamp); + + /* Apply result to all cpu fans */ + for (i = 0; i < 3; i++) { + err = wf_control_set(cpu_fans[cpu][i], speed); + if (err) { + pr_warn("wf_rm31: Fan %s reports error %d\n", + cpu_fans[cpu][i]->name, err); + failure_state |= FAILURE_FAN; + } + } + } +} + +/* Implementation... */ +static int cpu_setup_pid(int cpu) +{ + struct wf_cpu_pid_param pid; + const struct mpu_data *mpu = cpu_mpu_data[cpu]; + s32 tmax, ttarget, ptarget; + int fmin, fmax, hsize; + + /* Get PID params from the appropriate MPU EEPROM */ + tmax = mpu->tmax << 16; + ttarget = mpu->ttarget << 16; + ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16; + + DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n", + cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax)); + + /* We keep a global tmax for overtemp calculations */ + if (tmax < cpu_all_tmax) + cpu_all_tmax = tmax; + + /* Set PID min/max by using the rear fan min/max */ + fmin = wf_control_get_min(cpu_fans[cpu][0]); + fmax = wf_control_get_max(cpu_fans[cpu][0]); + DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax); + + /* History size */ + hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY); + DBG("wf_72: CPU%d history size = %d\n", cpu, hsize); + + /* Initialize PID loop */ + pid.interval = 1; /* seconds */ + pid.history_len = hsize; + pid.gd = mpu->pid_gd; + pid.gp = mpu->pid_gp; + pid.gr = mpu->pid_gr; + pid.tmax = tmax; + pid.ttarget = ttarget; + pid.pmaxadj = ptarget; + pid.min = fmin; + pid.max = fmax; + + wf_cpu_pid_init(&cpu_pid[cpu], &pid); + cpu_pid[cpu].target = 4000; + + return 0; +} + +/* Backside/U3 fan */ +static const struct wf_pid_param backside_param = { + .interval = 1, + .history_len = 2, + .gd = 0x00500000, + .gp = 0x0004cccc, + .gr = 0, + .itarget = 70 << 16, + .additive = 0, + .min = 20, + .max = 100, +}; + +/* DIMMs temperature (clamp the backside fan) */ +static const struct wf_pid_param dimms_param = { + .interval = 1, + .history_len = 20, + .gd = 0, + .gp = 0, + .gr = 0x06553600, + .itarget = 50 << 16, + .additive = 0, + .min = 4000, + .max = 14000, +}; + +static void backside_fan_tick(void) +{ + s32 temp, dtemp; + int speed, dspeed, fan_min; + int err; + + if (!backside_fan || !backside_temp || !dimms_temp || !backside_tick) + return; + if (--backside_tick > 0) + return; + backside_tick = backside_pid.param.interval; + + DBG_LOTS("* backside fans tick\n"); + + /* Update fan speed from actual fans */ + err = wf_control_get(backside_fan, &speed); + if (!err) + backside_pid.target = speed; + + err = wf_sensor_get(backside_temp, &temp); + if (err) { + printk(KERN_WARNING "windfarm: U3 temp sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(backside_fan); + return; + } + speed = wf_pid_run(&backside_pid, temp); + + DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = wf_sensor_get(dimms_temp, &dtemp); + if (err) { + printk(KERN_WARNING "windfarm: DIMMs temp sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(backside_fan); + return; + } + dspeed = wf_pid_run(&dimms_pid, dtemp); + dimms_output_clamp = dspeed; + + fan_min = (dspeed * 100) / 14000; + fan_min = max(fan_min, backside_param.min); + speed = max(speed, fan_min); + + err = wf_control_set(backside_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: backside fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void backside_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(backside_fan); + s32 fmax = wf_control_get_max(backside_fan); + struct wf_pid_param param; + + param = backside_param; + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&backside_pid, ¶m); + + param = dimms_param; + wf_pid_init(&dimms_pid, ¶m); + + backside_tick = 1; + + pr_info("wf_rm31: Backside control loop started.\n"); +} + +/* Slots fan */ +static const struct wf_pid_param slots_param = { + .interval = 1, + .history_len = 20, + .gd = 0, + .gp = 0, + .gr = 0x00100000, + .itarget = 3200000, + .additive = 0, + .min = 20, + .max = 100, +}; + +static void slots_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!slots_fan || !slots_temp || !slots_tick) + return; + if (--slots_tick > 0) + return; + slots_tick = slots_pid.param.interval; + + DBG_LOTS("* slots fans tick\n"); + + err = wf_sensor_get(slots_temp, &temp); + if (err) { + pr_warn("wf_rm31: slots temp sensor error %d\n", err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(slots_fan); + return; + } + speed = wf_pid_run(&slots_pid, temp); + + DBG_LOTS("slots PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + slots_speed = speed; + err = wf_control_set(slots_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: slots bay fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void slots_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(slots_fan); + s32 fmax = wf_control_get_max(slots_fan); + struct wf_pid_param param = slots_param; + + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&slots_pid, ¶m); + slots_tick = 1; + + pr_info("wf_rm31: Slots control loop started.\n"); +} + +static void set_fail_state(void) +{ + cpu_max_all_fans(); + + if (backside_fan) + wf_control_set_max(backside_fan); + if (slots_fan) + wf_control_set_max(slots_fan); +} + +static void rm31_tick(void) +{ + int i, last_failure; + + if (!started) { + started = true; + printk(KERN_INFO "windfarm: CPUs control loops started.\n"); + for (i = 0; i < nr_chips; ++i) { + if (cpu_setup_pid(i) < 0) { + failure_state = FAILURE_PERM; + set_fail_state(); + break; + } + } + DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax)); + + backside_setup_pid(); + slots_setup_pid(); + +#ifdef HACKED_OVERTEMP + cpu_all_tmax = 60 << 16; +#endif + } + + /* Permanent failure, bail out */ + if (failure_state & FAILURE_PERM) + return; + + /* + * Clear all failure bits except low overtemp which will be eventually + * cleared by the control loop itself + */ + last_failure = failure_state; + failure_state &= FAILURE_LOW_OVERTEMP; + backside_fan_tick(); + slots_fan_tick(); + + /* We do CPUs last because they can be clamped high by + * DIMM temperature + */ + cpu_fans_tick(); + + DBG_LOTS(" last_failure: 0x%x, failure_state: %x\n", + last_failure, failure_state); + + /* Check for failures. Any failure causes cpufreq clamping */ + if (failure_state && last_failure == 0 && cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (failure_state == 0 && last_failure && cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + + /* That's it for now, we might want to deal with other failures + * differently in the future though + */ +} + +static void rm31_new_control(struct wf_control *ct) +{ + bool all_controls; + + if (!strcmp(ct->name, "cpu-fan-a-0")) + cpu_fans[0][0] = ct; + else if (!strcmp(ct->name, "cpu-fan-b-0")) + cpu_fans[0][1] = ct; + else if (!strcmp(ct->name, "cpu-fan-c-0")) + cpu_fans[0][2] = ct; + else if (!strcmp(ct->name, "cpu-fan-a-1")) + cpu_fans[1][0] = ct; + else if (!strcmp(ct->name, "cpu-fan-b-1")) + cpu_fans[1][1] = ct; + else if (!strcmp(ct->name, "cpu-fan-c-1")) + cpu_fans[1][2] = ct; + else if (!strcmp(ct->name, "backside-fan")) + backside_fan = ct; + else if (!strcmp(ct->name, "slots-fan")) + slots_fan = ct; + else if (!strcmp(ct->name, "cpufreq-clamp")) + cpufreq_clamp = ct; + + all_controls = + cpu_fans[0][0] && + cpu_fans[0][1] && + cpu_fans[0][2] && + backside_fan && + slots_fan; + if (nr_chips > 1) + all_controls &= + cpu_fans[1][0] && + cpu_fans[1][1] && + cpu_fans[1][2]; + have_all_controls = all_controls; +} + + +static void rm31_new_sensor(struct wf_sensor *sr) +{ + bool all_sensors; + + if (!strcmp(sr->name, "cpu-diode-temp-0")) + sens_cpu_temp[0] = sr; + else if (!strcmp(sr->name, "cpu-diode-temp-1")) + sens_cpu_temp[1] = sr; + else if (!strcmp(sr->name, "cpu-voltage-0")) + sens_cpu_volts[0] = sr; + else if (!strcmp(sr->name, "cpu-voltage-1")) + sens_cpu_volts[1] = sr; + else if (!strcmp(sr->name, "cpu-current-0")) + sens_cpu_amps[0] = sr; + else if (!strcmp(sr->name, "cpu-current-1")) + sens_cpu_amps[1] = sr; + else if (!strcmp(sr->name, "backside-temp")) + backside_temp = sr; + else if (!strcmp(sr->name, "slots-temp")) + slots_temp = sr; + else if (!strcmp(sr->name, "dimms-temp")) + dimms_temp = sr; + + all_sensors = + sens_cpu_temp[0] && + sens_cpu_volts[0] && + sens_cpu_amps[0] && + backside_temp && + slots_temp && + dimms_temp; + if (nr_chips > 1) + all_sensors &= + sens_cpu_temp[1] && + sens_cpu_volts[1] && + sens_cpu_amps[1]; + + have_all_sensors = all_sensors; +} + +static int rm31_wf_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch (event) { + case WF_EVENT_NEW_SENSOR: + rm31_new_sensor(data); + break; + case WF_EVENT_NEW_CONTROL: + rm31_new_control(data); + break; + case WF_EVENT_TICK: + if (have_all_controls && have_all_sensors) + rm31_tick(); + } + return 0; +} + +static struct notifier_block rm31_events = { + .notifier_call = rm31_wf_notify, +}; + +static int wf_rm31_probe(struct platform_device *dev) +{ + wf_register_client(&rm31_events); + return 0; +} + +static int wf_rm31_remove(struct platform_device *dev) +{ + wf_unregister_client(&rm31_events); + + /* should release all sensors and controls */ + return 0; +} + +static struct platform_driver wf_rm31_driver = { + .probe = wf_rm31_probe, + .remove = wf_rm31_remove, + .driver = { + .name = "windfarm", + }, +}; + +static int __init wf_rm31_init(void) +{ + struct device_node *cpu; + int i; + + if (!of_machine_is_compatible("RackMac3,1")) + return -ENODEV; + + /* Count the number of CPU cores */ + nr_chips = 0; + for_each_node_by_type(cpu, "cpu") + ++nr_chips; + if (nr_chips > NR_CHIPS) + nr_chips = NR_CHIPS; + + pr_info("windfarm: Initializing for desktop G5 with %d chips\n", + nr_chips); + + /* Get MPU data for each CPU */ + for (i = 0; i < nr_chips; i++) { + cpu_mpu_data[i] = wf_get_mpu(i); + if (!cpu_mpu_data[i]) { + pr_err("wf_rm31: Failed to find MPU data for CPU %d\n", i); + return -ENXIO; + } + } + +#ifdef MODULE + request_module("windfarm_fcu_controls"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_lm87_sensor"); + request_module("windfarm_ad7417_sensor"); + request_module("windfarm_max6690_sensor"); + request_module("windfarm_cpufreq_clamp"); +#endif /* MODULE */ + + platform_driver_register(&wf_rm31_driver); + return 0; +} + +static void __exit wf_rm31_exit(void) +{ + platform_driver_unregister(&wf_rm31_driver); +} + +module_init(wf_rm31_init); +module_exit(wf_rm31_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control for Xserve G5"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_smu_controls.c b/drivers/macintosh/windfarm_smu_controls.c new file mode 100644 index 000000000..e9957ad49 --- /dev/null +++ b/drivers/macintosh/windfarm_smu_controls.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. SMU based controls + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include <linux/of.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" + +#define VERSION "0.4" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +static int smu_supports_new_fans_ops = 1; + +/* + * SMU fans control object + */ + +static LIST_HEAD(smu_fans); + +struct smu_fan_control { + struct list_head link; + int fan_type; /* 0 = rpm, 1 = pwm */ + u32 reg; /* index in SMU */ + s32 value; /* current value */ + s32 min, max; /* min/max values */ + struct wf_control ctrl; +}; +#define to_smu_fan(c) container_of(c, struct smu_fan_control, ctrl) + +static int smu_set_fan(int pwm, u8 id, u16 value) +{ + struct smu_cmd cmd; + u8 buffer[16]; + DECLARE_COMPLETION_ONSTACK(comp); + int rc; + + /* Fill SMU command structure */ + cmd.cmd = SMU_CMD_FAN_COMMAND; + + /* The SMU has an "old" and a "new" way of setting the fan speed + * Unfortunately, I found no reliable way to know which one works + * on a given machine model. After some investigations it appears + * that MacOS X just tries the new one, and if it fails fallbacks + * to the old ones ... Ugh. + */ + retry: + if (smu_supports_new_fans_ops) { + buffer[0] = 0x30; + buffer[1] = id; + *((u16 *)(&buffer[2])) = value; + cmd.data_len = 4; + } else { + if (id > 7) + return -EINVAL; + /* Fill argument buffer */ + memset(buffer, 0, 16); + buffer[0] = pwm ? 0x10 : 0x00; + buffer[1] = 0x01 << id; + *((u16 *)&buffer[2 + id * 2]) = value; + cmd.data_len = 14; + } + + cmd.reply_len = 16; + cmd.data_buf = cmd.reply_buf = buffer; + cmd.status = 0; + cmd.done = smu_done_complete; + cmd.misc = ∁ + + rc = smu_queue_cmd(&cmd); + if (rc) + return rc; + wait_for_completion(&comp); + + /* Handle fallback (see comment above) */ + if (cmd.status != 0 && smu_supports_new_fans_ops) { + printk(KERN_WARNING "windfarm: SMU failed new fan command " + "falling back to old method\n"); + smu_supports_new_fans_ops = 0; + goto retry; + } + + return cmd.status; +} + +static void smu_fan_release(struct wf_control *ct) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + + kfree(fct); +} + +static int smu_fan_set(struct wf_control *ct, s32 value) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + + if (value < fct->min) + value = fct->min; + if (value > fct->max) + value = fct->max; + fct->value = value; + + return smu_set_fan(fct->fan_type, fct->reg, value); +} + +static int smu_fan_get(struct wf_control *ct, s32 *value) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + *value = fct->value; /* todo: read from SMU */ + return 0; +} + +static s32 smu_fan_min(struct wf_control *ct) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + return fct->min; +} + +static s32 smu_fan_max(struct wf_control *ct) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + return fct->max; +} + +static const struct wf_control_ops smu_fan_ops = { + .set_value = smu_fan_set, + .get_value = smu_fan_get, + .get_min = smu_fan_min, + .get_max = smu_fan_max, + .release = smu_fan_release, + .owner = THIS_MODULE, +}; + +static struct smu_fan_control *smu_fan_create(struct device_node *node, + int pwm_fan) +{ + struct smu_fan_control *fct; + const s32 *v; + const u32 *reg; + const char *l; + + fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL); + if (fct == NULL) + return NULL; + fct->ctrl.ops = &smu_fan_ops; + l = of_get_property(node, "location", NULL); + if (l == NULL) + goto fail; + + fct->fan_type = pwm_fan; + fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN; + + /* We use the name & location here the same way we do for SMU sensors, + * see the comment in windfarm_smu_sensors.c. The locations are a bit + * less consistent here between the iMac and the desktop models, but + * that is good enough for our needs for now at least. + * + * One problem though is that Apple seem to be inconsistent with case + * and the kernel doesn't have strcasecmp =P + */ + + fct->ctrl.name = NULL; + + /* Names used on desktop models */ + if (!strcmp(l, "Rear Fan 0") || !strcmp(l, "Rear Fan") || + !strcmp(l, "Rear fan 0") || !strcmp(l, "Rear fan") || + !strcmp(l, "CPU A EXHAUST")) + fct->ctrl.name = "cpu-rear-fan-0"; + else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1") || + !strcmp(l, "CPU B EXHAUST")) + fct->ctrl.name = "cpu-rear-fan-1"; + else if (!strcmp(l, "Front Fan 0") || !strcmp(l, "Front Fan") || + !strcmp(l, "Front fan 0") || !strcmp(l, "Front fan") || + !strcmp(l, "CPU A INTAKE")) + fct->ctrl.name = "cpu-front-fan-0"; + else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1") || + !strcmp(l, "CPU B INTAKE")) + fct->ctrl.name = "cpu-front-fan-1"; + else if (!strcmp(l, "CPU A PUMP")) + fct->ctrl.name = "cpu-pump-0"; + else if (!strcmp(l, "CPU B PUMP")) + fct->ctrl.name = "cpu-pump-1"; + else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan") || + !strcmp(l, "EXPANSION SLOTS INTAKE")) + fct->ctrl.name = "slots-fan"; + else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay") || + !strcmp(l, "DRIVE BAY A INTAKE")) + fct->ctrl.name = "drive-bay-fan"; + else if (!strcmp(l, "BACKSIDE")) + fct->ctrl.name = "backside-fan"; + + /* Names used on iMac models */ + if (!strcmp(l, "System Fan") || !strcmp(l, "System fan")) + fct->ctrl.name = "system-fan"; + else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan")) + fct->ctrl.name = "cpu-fan"; + else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive")) + fct->ctrl.name = "drive-bay-fan"; + else if (!strcmp(l, "HDD Fan")) /* seen on iMac G5 iSight */ + fct->ctrl.name = "hard-drive-fan"; + else if (!strcmp(l, "ODD Fan")) /* same */ + fct->ctrl.name = "optical-drive-fan"; + + /* Unrecognized fan, bail out */ + if (fct->ctrl.name == NULL) + goto fail; + + /* Get min & max values*/ + v = of_get_property(node, "min-value", NULL); + if (v == NULL) + goto fail; + fct->min = *v; + v = of_get_property(node, "max-value", NULL); + if (v == NULL) + goto fail; + fct->max = *v; + + /* Get "reg" value */ + reg = of_get_property(node, "reg", NULL); + if (reg == NULL) + goto fail; + fct->reg = *reg; + + if (wf_register_control(&fct->ctrl)) + goto fail; + + return fct; + fail: + kfree(fct); + return NULL; +} + + +static int __init smu_controls_init(void) +{ + struct device_node *smu, *fans, *fan; + + if (!smu_present()) + return -ENODEV; + + smu = of_find_node_by_type(NULL, "smu"); + if (smu == NULL) + return -ENODEV; + + /* Look for RPM fans */ + for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) + if (of_node_name_eq(fans, "rpm-fans") || + of_device_is_compatible(fans, "smu-rpm-fans")) + break; + for (fan = NULL; + fans && (fan = of_get_next_child(fans, fan)) != NULL;) { + struct smu_fan_control *fct; + + fct = smu_fan_create(fan, 0); + if (fct == NULL) { + printk(KERN_WARNING "windfarm: Failed to create SMU " + "RPM fan %pOFn\n", fan); + continue; + } + list_add(&fct->link, &smu_fans); + } + of_node_put(fans); + + + /* Look for PWM fans */ + for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) + if (of_node_name_eq(fans, "pwm-fans")) + break; + for (fan = NULL; + fans && (fan = of_get_next_child(fans, fan)) != NULL;) { + struct smu_fan_control *fct; + + fct = smu_fan_create(fan, 1); + if (fct == NULL) { + printk(KERN_WARNING "windfarm: Failed to create SMU " + "PWM fan %pOFn\n", fan); + continue; + } + list_add(&fct->link, &smu_fans); + } + of_node_put(fans); + of_node_put(smu); + + return 0; +} + +static void __exit smu_controls_exit(void) +{ + struct smu_fan_control *fct; + + while (!list_empty(&smu_fans)) { + fct = list_entry(smu_fans.next, struct smu_fan_control, link); + list_del(&fct->link); + wf_unregister_control(&fct->ctrl); + } +} + + +module_init(smu_controls_init); +module_exit(smu_controls_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_smu_sat.c b/drivers/macintosh/windfarm_smu_sat.c new file mode 100644 index 000000000..98fb31e00 --- /dev/null +++ b/drivers/macintosh/windfarm_smu_sat.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. SMU "satellite" controller sensors. + * + * Copyright (C) 2005 Paul Mackerras, IBM Corp. <paulus@samba.org> + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +#include <asm/smu.h> +#include <asm/pmac_low_i2c.h> + +#include "windfarm.h" + +#define VERSION "1.0" + +/* If the cache is older than 800ms we'll refetch it */ +#define MAX_AGE msecs_to_jiffies(800) + +struct wf_sat { + struct kref ref; + int nr; + struct mutex mutex; + unsigned long last_read; /* jiffies when cache last updated */ + u8 cache[16]; + struct list_head sensors; + struct i2c_client *i2c; + struct device_node *node; +}; + +static struct wf_sat *sats[2]; + +struct wf_sat_sensor { + struct list_head link; + int index; + int index2; /* used for power sensors */ + int shift; + struct wf_sat *sat; + struct wf_sensor sens; +}; + +#define wf_to_sat(c) container_of(c, struct wf_sat_sensor, sens) + +struct smu_sdbp_header *smu_sat_get_sdb_partition(unsigned int sat_id, int id, + unsigned int *size) +{ + struct wf_sat *sat; + int err; + unsigned int i, len; + u8 *buf; + u8 data[4]; + + /* TODO: Add the resulting partition to the device-tree */ + + if (sat_id > 1 || (sat = sats[sat_id]) == NULL) + return NULL; + + err = i2c_smbus_write_word_data(sat->i2c, 8, id << 8); + if (err) { + printk(KERN_ERR "smu_sat_get_sdb_part wr error %d\n", err); + return NULL; + } + + err = i2c_smbus_read_word_data(sat->i2c, 9); + if (err < 0) { + printk(KERN_ERR "smu_sat_get_sdb_part rd len error\n"); + return NULL; + } + len = err; + if (len == 0) { + printk(KERN_ERR "smu_sat_get_sdb_part no partition %x\n", id); + return NULL; + } + + len = le16_to_cpu(len); + len = (len + 3) & ~3; + buf = kmalloc(len, GFP_KERNEL); + if (buf == NULL) + return NULL; + + for (i = 0; i < len; i += 4) { + err = i2c_smbus_read_i2c_block_data(sat->i2c, 0xa, 4, data); + if (err < 0) { + printk(KERN_ERR "smu_sat_get_sdb_part rd err %d\n", + err); + goto fail; + } + buf[i] = data[1]; + buf[i+1] = data[0]; + buf[i+2] = data[3]; + buf[i+3] = data[2]; + } + + printk(KERN_DEBUG "sat %d partition %x:", sat_id, id); + print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET, + 16, 1, buf, len, false); + if (size) + *size = len; + return (struct smu_sdbp_header *) buf; + + fail: + kfree(buf); + return NULL; +} +EXPORT_SYMBOL_GPL(smu_sat_get_sdb_partition); + +/* refresh the cache */ +static int wf_sat_read_cache(struct wf_sat *sat) +{ + int err; + + err = i2c_smbus_read_i2c_block_data(sat->i2c, 0x3f, 16, sat->cache); + if (err < 0) + return err; + sat->last_read = jiffies; + +#ifdef LOTSA_DEBUG + { + int i; + printk(KERN_DEBUG "wf_sat_get: data is"); + print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET, + 16, 1, sat->cache, 16, false); + } +#endif + return 0; +} + +static int wf_sat_sensor_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_sat_sensor *sens = wf_to_sat(sr); + struct wf_sat *sat = sens->sat; + int i, err; + s32 val; + + if (sat->i2c == NULL) + return -ENODEV; + + mutex_lock(&sat->mutex); + if (time_after(jiffies, (sat->last_read + MAX_AGE))) { + err = wf_sat_read_cache(sat); + if (err) + goto fail; + } + + i = sens->index * 2; + val = ((sat->cache[i] << 8) + sat->cache[i+1]) << sens->shift; + if (sens->index2 >= 0) { + i = sens->index2 * 2; + /* 4.12 * 8.8 -> 12.20; shift right 4 to get 16.16 */ + val = (val * ((sat->cache[i] << 8) + sat->cache[i+1])) >> 4; + } + + *value = val; + err = 0; + + fail: + mutex_unlock(&sat->mutex); + return err; +} + +static void wf_sat_release(struct kref *ref) +{ + struct wf_sat *sat = container_of(ref, struct wf_sat, ref); + + if (sat->nr >= 0) + sats[sat->nr] = NULL; + of_node_put(sat->node); + kfree(sat); +} + +static void wf_sat_sensor_release(struct wf_sensor *sr) +{ + struct wf_sat_sensor *sens = wf_to_sat(sr); + struct wf_sat *sat = sens->sat; + + kfree(sens); + kref_put(&sat->ref, wf_sat_release); +} + +static const struct wf_sensor_ops wf_sat_ops = { + .get_value = wf_sat_sensor_get, + .release = wf_sat_sensor_release, + .owner = THIS_MODULE, +}; + +static int wf_sat_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *dev = client->dev.of_node; + struct wf_sat *sat; + struct wf_sat_sensor *sens; + const u32 *reg; + const char *loc; + u8 chip, core; + struct device_node *child; + int shift, cpu, index; + char *name; + int vsens[2], isens[2]; + + sat = kzalloc(sizeof(struct wf_sat), GFP_KERNEL); + if (sat == NULL) + return -ENOMEM; + sat->nr = -1; + sat->node = of_node_get(dev); + kref_init(&sat->ref); + mutex_init(&sat->mutex); + sat->i2c = client; + INIT_LIST_HEAD(&sat->sensors); + i2c_set_clientdata(client, sat); + + vsens[0] = vsens[1] = -1; + isens[0] = isens[1] = -1; + for_each_child_of_node(dev, child) { + reg = of_get_property(child, "reg", NULL); + loc = of_get_property(child, "location", NULL); + if (reg == NULL || loc == NULL) + continue; + + /* the cooked sensors are between 0x30 and 0x37 */ + if (*reg < 0x30 || *reg > 0x37) + continue; + index = *reg - 0x30; + + /* expect location to be CPU [AB][01] ... */ + if (strncmp(loc, "CPU ", 4) != 0) + continue; + chip = loc[4] - 'A'; + core = loc[5] - '0'; + if (chip > 1 || core > 1) { + printk(KERN_ERR "wf_sat_create: don't understand " + "location %s for %pOF\n", loc, child); + continue; + } + cpu = 2 * chip + core; + if (sat->nr < 0) + sat->nr = chip; + else if (sat->nr != chip) { + printk(KERN_ERR "wf_sat_create: can't cope with " + "multiple CPU chips on one SAT (%s)\n", loc); + continue; + } + + if (of_node_is_type(child, "voltage-sensor")) { + name = "cpu-voltage"; + shift = 4; + vsens[core] = index; + } else if (of_node_is_type(child, "current-sensor")) { + name = "cpu-current"; + shift = 8; + isens[core] = index; + } else if (of_node_is_type(child, "temp-sensor")) { + name = "cpu-temp"; + shift = 10; + } else + continue; /* hmmm shouldn't happen */ + + /* the +16 is enough for "cpu-voltage-n" */ + sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL); + if (sens == NULL) { + printk(KERN_ERR "wf_sat_create: couldn't create " + "%s sensor %d (no memory)\n", name, cpu); + continue; + } + sens->index = index; + sens->index2 = -1; + sens->shift = shift; + sens->sat = sat; + sens->sens.ops = &wf_sat_ops; + sens->sens.name = (char *) (sens + 1); + snprintf((char *)sens->sens.name, 16, "%s-%d", name, cpu); + + if (wf_register_sensor(&sens->sens)) + kfree(sens); + else { + list_add(&sens->link, &sat->sensors); + kref_get(&sat->ref); + } + } + + /* make the power sensors */ + for (core = 0; core < 2; ++core) { + if (vsens[core] < 0 || isens[core] < 0) + continue; + cpu = 2 * sat->nr + core; + sens = kzalloc(sizeof(struct wf_sat_sensor) + 16, GFP_KERNEL); + if (sens == NULL) { + printk(KERN_ERR "wf_sat_create: couldn't create power " + "sensor %d (no memory)\n", cpu); + continue; + } + sens->index = vsens[core]; + sens->index2 = isens[core]; + sens->shift = 0; + sens->sat = sat; + sens->sens.ops = &wf_sat_ops; + sens->sens.name = (char *) (sens + 1); + snprintf((char *)sens->sens.name, 16, "cpu-power-%d", cpu); + + if (wf_register_sensor(&sens->sens)) + kfree(sens); + else { + list_add(&sens->link, &sat->sensors); + kref_get(&sat->ref); + } + } + + if (sat->nr >= 0) + sats[sat->nr] = sat; + + return 0; +} + +static void wf_sat_remove(struct i2c_client *client) +{ + struct wf_sat *sat = i2c_get_clientdata(client); + struct wf_sat_sensor *sens; + + /* release sensors */ + while(!list_empty(&sat->sensors)) { + sens = list_first_entry(&sat->sensors, + struct wf_sat_sensor, link); + list_del(&sens->link); + wf_unregister_sensor(&sens->sens); + } + sat->i2c = NULL; + kref_put(&sat->ref, wf_sat_release); +} + +static const struct i2c_device_id wf_sat_id[] = { + { "MAC,smu-sat", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_sat_id); + +static const struct of_device_id wf_sat_of_id[] = { + { .compatible = "smu-sat", }, + { } +}; +MODULE_DEVICE_TABLE(of, wf_sat_of_id); + +static struct i2c_driver wf_sat_driver = { + .driver = { + .name = "wf_smu_sat", + .of_match_table = wf_sat_of_id, + }, + .probe = wf_sat_probe, + .remove = wf_sat_remove, + .id_table = wf_sat_id, +}; + +module_i2c_driver(wf_sat_driver); + +MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>"); +MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control"); +MODULE_LICENSE("GPL"); diff --git a/drivers/macintosh/windfarm_smu_sensors.c b/drivers/macintosh/windfarm_smu_sensors.c new file mode 100644 index 000000000..2bdb73b34 --- /dev/null +++ b/drivers/macintosh/windfarm_smu_sensors.c @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. SMU based sensors + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include <linux/of.h> + +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" + +#define VERSION "0.2" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* + * Various SMU "partitions" calibration objects for which we + * keep pointers here for use by bits & pieces of the driver + */ +static struct smu_sdbp_cpuvcp *cpuvcp; +static int cpuvcp_version; +static struct smu_sdbp_cpudiode *cpudiode; +static struct smu_sdbp_slotspow *slotspow; +static u8 *debugswitches; + +/* + * SMU basic sensors objects + */ + +static LIST_HEAD(smu_ads); + +struct smu_ad_sensor { + struct list_head link; + u32 reg; /* index in SMU */ + struct wf_sensor sens; +}; +#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens) + +static void smu_ads_release(struct wf_sensor *sr) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + + kfree(ads); +} + +static int smu_read_adc(u8 id, s32 *value) +{ + struct smu_simple_cmd cmd; + DECLARE_COMPLETION_ONSTACK(comp); + int rc; + + rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1, + smu_done_complete, &comp, id); + if (rc) + return rc; + wait_for_completion(&comp); + if (cmd.cmd.status != 0) + return cmd.cmd.status; + if (cmd.cmd.reply_len != 2) { + printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n", + id, cmd.cmd.reply_len); + return -EIO; + } + *value = *((u16 *)cmd.buffer); + return 0; +} + +static int smu_cputemp_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + int rc; + s32 val; + s64 scaled; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s64)(((u64)val) * (u64)cpudiode->m_value); + scaled >>= 3; + scaled += ((s64)cpudiode->b_value) << 9; + *value = (s32)(scaled << 1); + + return 0; +} + +static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + s32 val, scaled; + int rc; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read CPU current failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s32)(val * (u32)cpuvcp->curr_scale); + scaled += (s32)cpuvcp->curr_offset; + *value = scaled << 4; + + return 0; +} + +static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + s32 val, scaled; + int rc; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s32)(val * (u32)cpuvcp->volt_scale); + scaled += (s32)cpuvcp->volt_offset; + *value = scaled << 4; + + return 0; +} + +static int smu_slotspow_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + s32 val, scaled; + int rc; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read slots power failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s32)(val * (u32)slotspow->pow_scale); + scaled += (s32)slotspow->pow_offset; + *value = scaled << 4; + + return 0; +} + + +static const struct wf_sensor_ops smu_cputemp_ops = { + .get_value = smu_cputemp_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; +static const struct wf_sensor_ops smu_cpuamp_ops = { + .get_value = smu_cpuamp_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; +static const struct wf_sensor_ops smu_cpuvolt_ops = { + .get_value = smu_cpuvolt_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; +static const struct wf_sensor_ops smu_slotspow_ops = { + .get_value = smu_slotspow_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; + + +static struct smu_ad_sensor *smu_ads_create(struct device_node *node) +{ + struct smu_ad_sensor *ads; + const char *l; + const u32 *v; + + ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL); + if (ads == NULL) + return NULL; + l = of_get_property(node, "location", NULL); + if (l == NULL) + goto fail; + + /* We currently pick the sensors based on the OF name and location + * properties, while Darwin uses the sensor-id's. + * The problem with the IDs is that they are model specific while it + * looks like apple has been doing a reasonably good job at keeping + * the names and locations consistents so I'll stick with the names + * and locations for now. + */ + if (of_node_is_type(node, "temp-sensor") && + !strcmp(l, "CPU T-Diode")) { + ads->sens.ops = &smu_cputemp_ops; + ads->sens.name = "cpu-temp"; + if (cpudiode == NULL) { + DBG("wf: cpudiode partition (%02x) not found\n", + SMU_SDB_CPUDIODE_ID); + goto fail; + } + } else if (of_node_is_type(node, "current-sensor") && + !strcmp(l, "CPU Current")) { + ads->sens.ops = &smu_cpuamp_ops; + ads->sens.name = "cpu-current"; + if (cpuvcp == NULL) { + DBG("wf: cpuvcp partition (%02x) not found\n", + SMU_SDB_CPUVCP_ID); + goto fail; + } + } else if (of_node_is_type(node, "voltage-sensor") && + !strcmp(l, "CPU Voltage")) { + ads->sens.ops = &smu_cpuvolt_ops; + ads->sens.name = "cpu-voltage"; + if (cpuvcp == NULL) { + DBG("wf: cpuvcp partition (%02x) not found\n", + SMU_SDB_CPUVCP_ID); + goto fail; + } + } else if (of_node_is_type(node, "power-sensor") && + !strcmp(l, "Slots Power")) { + ads->sens.ops = &smu_slotspow_ops; + ads->sens.name = "slots-power"; + if (slotspow == NULL) { + DBG("wf: slotspow partition (%02x) not found\n", + SMU_SDB_SLOTSPOW_ID); + goto fail; + } + } else + goto fail; + + v = of_get_property(node, "reg", NULL); + if (v == NULL) + goto fail; + ads->reg = *v; + + if (wf_register_sensor(&ads->sens)) + goto fail; + return ads; + fail: + kfree(ads); + return NULL; +} + +/* + * SMU Power combo sensor object + */ + +struct smu_cpu_power_sensor { + struct list_head link; + struct wf_sensor *volts; + struct wf_sensor *amps; + unsigned int fake_volts : 1; + unsigned int quadratic : 1; + struct wf_sensor sens; +}; +#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens) + +static struct smu_cpu_power_sensor *smu_cpu_power; + +static void smu_cpu_power_release(struct wf_sensor *sr) +{ + struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr); + + if (pow->volts) + wf_put_sensor(pow->volts); + if (pow->amps) + wf_put_sensor(pow->amps); + kfree(pow); +} + +static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr); + s32 volts, amps, power; + u64 tmps, tmpa, tmpb; + int rc; + + rc = pow->amps->ops->get_value(pow->amps, &s); + if (rc) + return rc; + + if (pow->fake_volts) { + *value = amps * 12 - 0x30000; + return 0; + } + + rc = pow->volts->ops->get_value(pow->volts, &volts); + if (rc) + return rc; + + power = (s32)((((u64)volts) * ((u64)amps)) >> 16); + if (!pow->quadratic) { + *value = power; + return 0; + } + tmps = (((u64)power) * ((u64)power)) >> 16; + tmpa = ((u64)cpuvcp->power_quads[0]) * tmps; + tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power); + *value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12); + + return 0; +} + +static const struct wf_sensor_ops smu_cpu_power_ops = { + .get_value = smu_cpu_power_get, + .release = smu_cpu_power_release, + .owner = THIS_MODULE, +}; + + +static struct smu_cpu_power_sensor * +smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps) +{ + struct smu_cpu_power_sensor *pow; + + pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL); + if (pow == NULL) + return NULL; + pow->sens.ops = &smu_cpu_power_ops; + pow->sens.name = "cpu-power"; + + wf_get_sensor(volts); + pow->volts = volts; + wf_get_sensor(amps); + pow->amps = amps; + + /* Some early machines need a faked voltage */ + if (debugswitches && ((*debugswitches) & 0x80)) { + printk(KERN_INFO "windfarm: CPU Power sensor using faked" + " voltage !\n"); + pow->fake_volts = 1; + } else + pow->fake_volts = 0; + + /* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now, + * I yet have to figure out what's up with 8,2 and will have to + * adjust for later, unless we can 100% trust the SDB partition... + */ + if ((of_machine_is_compatible("PowerMac8,1") || + of_machine_is_compatible("PowerMac8,2") || + of_machine_is_compatible("PowerMac9,1")) && + cpuvcp_version >= 2) { + pow->quadratic = 1; + DBG("windfarm: CPU Power using quadratic transform\n"); + } else + pow->quadratic = 0; + + if (wf_register_sensor(&pow->sens)) + goto fail; + return pow; + fail: + kfree(pow); + return NULL; +} + +static void smu_fetch_param_partitions(void) +{ + const struct smu_sdbp_header *hdr; + + /* Get CPU voltage/current/power calibration data */ + hdr = smu_get_sdb_partition(SMU_SDB_CPUVCP_ID, NULL); + if (hdr != NULL) { + cpuvcp = (struct smu_sdbp_cpuvcp *)&hdr[1]; + /* Keep version around */ + cpuvcp_version = hdr->version; + } + + /* Get CPU diode calibration data */ + hdr = smu_get_sdb_partition(SMU_SDB_CPUDIODE_ID, NULL); + if (hdr != NULL) + cpudiode = (struct smu_sdbp_cpudiode *)&hdr[1]; + + /* Get slots power calibration data if any */ + hdr = smu_get_sdb_partition(SMU_SDB_SLOTSPOW_ID, NULL); + if (hdr != NULL) + slotspow = (struct smu_sdbp_slotspow *)&hdr[1]; + + /* Get debug switches if any */ + hdr = smu_get_sdb_partition(SMU_SDB_DEBUG_SWITCHES_ID, NULL); + if (hdr != NULL) + debugswitches = (u8 *)&hdr[1]; +} + +static int __init smu_sensors_init(void) +{ + struct device_node *smu, *sensors, *s; + struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL; + + if (!smu_present()) + return -ENODEV; + + /* Get parameters partitions */ + smu_fetch_param_partitions(); + + smu = of_find_node_by_type(NULL, "smu"); + if (smu == NULL) + return -ENODEV; + + /* Look for sensors subdir */ + for_each_child_of_node(smu, sensors) + if (of_node_name_eq(sensors, "sensors")) + break; + + of_node_put(smu); + + /* Create basic sensors */ + for (s = NULL; + sensors && (s = of_get_next_child(sensors, s)) != NULL;) { + struct smu_ad_sensor *ads; + + ads = smu_ads_create(s); + if (ads == NULL) + continue; + list_add(&ads->link, &smu_ads); + /* keep track of cpu voltage & current */ + if (!strcmp(ads->sens.name, "cpu-voltage")) + volt_sensor = ads; + else if (!strcmp(ads->sens.name, "cpu-current")) + curr_sensor = ads; + } + + of_node_put(sensors); + + /* Create CPU power sensor if possible */ + if (volt_sensor && curr_sensor) + smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens, + &curr_sensor->sens); + + return 0; +} + +static void __exit smu_sensors_exit(void) +{ + struct smu_ad_sensor *ads; + + /* dispose of power sensor */ + if (smu_cpu_power) + wf_unregister_sensor(&smu_cpu_power->sens); + + /* dispose of basic sensors */ + while (!list_empty(&smu_ads)) { + ads = list_entry(smu_ads.next, struct smu_ad_sensor, link); + list_del(&ads->link); + wf_unregister_sensor(&ads->sens); + } +} + + +module_init(smu_sensors_init); +module_exit(smu_sensors_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + |