From 8d836581f9b1f57ceaa2b47654754ef1260b410b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 16 Sep 2026 15:00:32 +0200 Subject: ata: libata-scsi: fix ata_dsm_trim_pages() kernel-doc make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/macintosh/Kconfig | 280 +++ drivers/macintosh/Makefile | 66 + drivers/macintosh/adb-iop.c | 297 +++ drivers/macintosh/adb.c | 898 +++++++++ drivers/macintosh/adbhid.c | 1273 +++++++++++++ drivers/macintosh/ams/Makefile | 9 + drivers/macintosh/ams/ams-core.c | 237 +++ drivers/macintosh/ams/ams-i2c.c | 264 +++ drivers/macintosh/ams/ams-input.c | 156 ++ drivers/macintosh/ams/ams-pmu.c | 197 ++ drivers/macintosh/ams/ams.h | 76 + drivers/macintosh/ans-lcd.c | 205 ++ drivers/macintosh/ans-lcd.h | 10 + drivers/macintosh/apm_emu.c | 108 ++ drivers/macintosh/mac_hid.c | 262 +++ drivers/macintosh/macio-adb.c | 288 +++ drivers/macintosh/macio_asic.c | 798 ++++++++ drivers/macintosh/macio_sysfs.c | 82 + drivers/macintosh/mediabay.c | 753 ++++++++ drivers/macintosh/rack-meter.c | 617 +++++++ drivers/macintosh/smu.c | 1332 +++++++++++++ drivers/macintosh/therm_adt746x.c | 623 +++++++ drivers/macintosh/therm_windtunnel.c | 560 ++++++ drivers/macintosh/via-cuda.c | 802 ++++++++ drivers/macintosh/via-macii.c | 559 ++++++ drivers/macintosh/via-pmu-backlight.c | 187 ++ drivers/macintosh/via-pmu-event.c | 80 + drivers/macintosh/via-pmu-event.h | 9 + drivers/macintosh/via-pmu-led.c | 118 ++ drivers/macintosh/via-pmu.c | 2669 +++++++++++++++++++++++++++ drivers/macintosh/windfarm.h | 156 ++ drivers/macintosh/windfarm_ad7417_sensor.c | 350 ++++ drivers/macintosh/windfarm_core.c | 461 +++++ drivers/macintosh/windfarm_cpufreq_clamp.c | 134 ++ drivers/macintosh/windfarm_fcu_controls.c | 602 ++++++ drivers/macintosh/windfarm_lm75_sensor.c | 190 ++ drivers/macintosh/windfarm_lm87_sensor.c | 201 ++ drivers/macintosh/windfarm_max6690_sensor.c | 140 ++ drivers/macintosh/windfarm_mpu.h | 106 ++ drivers/macintosh/windfarm_pid.c | 148 ++ drivers/macintosh/windfarm_pid.h | 89 + drivers/macintosh/windfarm_pm112.c | 717 +++++++ drivers/macintosh/windfarm_pm121.c | 1041 +++++++++++ drivers/macintosh/windfarm_pm72.c | 842 +++++++++ drivers/macintosh/windfarm_pm81.c | 808 ++++++++ drivers/macintosh/windfarm_pm91.c | 737 ++++++++ drivers/macintosh/windfarm_rm31.c | 735 ++++++++ drivers/macintosh/windfarm_smu_controls.c | 326 ++++ drivers/macintosh/windfarm_smu_sat.c | 361 ++++ drivers/macintosh/windfarm_smu_sensors.c | 480 +++++ 50 files changed, 22439 insertions(+) create mode 100644 drivers/macintosh/Kconfig create mode 100644 drivers/macintosh/Makefile create mode 100644 drivers/macintosh/adb-iop.c create mode 100644 drivers/macintosh/adb.c create mode 100644 drivers/macintosh/adbhid.c create mode 100644 drivers/macintosh/ams/Makefile create mode 100644 drivers/macintosh/ams/ams-core.c create mode 100644 drivers/macintosh/ams/ams-i2c.c create mode 100644 drivers/macintosh/ams/ams-input.c create mode 100644 drivers/macintosh/ams/ams-pmu.c create mode 100644 drivers/macintosh/ams/ams.h create mode 100644 drivers/macintosh/ans-lcd.c create mode 100644 drivers/macintosh/ans-lcd.h create mode 100644 drivers/macintosh/apm_emu.c create mode 100644 drivers/macintosh/mac_hid.c create mode 100644 drivers/macintosh/macio-adb.c create mode 100644 drivers/macintosh/macio_asic.c create mode 100644 drivers/macintosh/macio_sysfs.c create mode 100644 drivers/macintosh/mediabay.c create mode 100644 drivers/macintosh/rack-meter.c create mode 100644 drivers/macintosh/smu.c create mode 100644 drivers/macintosh/therm_adt746x.c create mode 100644 drivers/macintosh/therm_windtunnel.c create mode 100644 drivers/macintosh/via-cuda.c create mode 100644 drivers/macintosh/via-macii.c create mode 100644 drivers/macintosh/via-pmu-backlight.c create mode 100644 drivers/macintosh/via-pmu-event.c create mode 100644 drivers/macintosh/via-pmu-event.h create mode 100644 drivers/macintosh/via-pmu-led.c create mode 100644 drivers/macintosh/via-pmu.c create mode 100644 drivers/macintosh/windfarm.h create mode 100644 drivers/macintosh/windfarm_ad7417_sensor.c create mode 100644 drivers/macintosh/windfarm_core.c create mode 100644 drivers/macintosh/windfarm_cpufreq_clamp.c create mode 100644 drivers/macintosh/windfarm_fcu_controls.c create mode 100644 drivers/macintosh/windfarm_lm75_sensor.c create mode 100644 drivers/macintosh/windfarm_lm87_sensor.c create mode 100644 drivers/macintosh/windfarm_max6690_sensor.c create mode 100644 drivers/macintosh/windfarm_mpu.h create mode 100644 drivers/macintosh/windfarm_pid.c create mode 100644 drivers/macintosh/windfarm_pid.h create mode 100644 drivers/macintosh/windfarm_pm112.c create mode 100644 drivers/macintosh/windfarm_pm121.c create mode 100644 drivers/macintosh/windfarm_pm72.c create mode 100644 drivers/macintosh/windfarm_pm81.c create mode 100644 drivers/macintosh/windfarm_pm91.c create mode 100644 drivers/macintosh/windfarm_rm31.c create mode 100644 drivers/macintosh/windfarm_smu_controls.c create mode 100644 drivers/macintosh/windfarm_smu_sat.c create mode 100644 drivers/macintosh/windfarm_smu_sensors.c (limited to 'drivers/macintosh') diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig new file mode 100644 index 000000000..d00e713c1 --- /dev/null +++ b/drivers/macintosh/Kconfig @@ -0,0 +1,280 @@ +# 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 + 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) + depends on BACKLIGHT_CLASS_DEVICE=y + 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" if SENSORS_AMS_I2C + 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..126dd1cfb --- /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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +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..fe150125e --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef CONFIG_PPC +#include +#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 const struct class adb_dev_class = { + .name = "adb", +}; + +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, 1); +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) { + 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_obj(struct adbdev_state); + if (!state) { + 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_obj(struct adb_request); + 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, + .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; + } + + if (class_register(&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..6155da580 --- /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 + * - MacAlly 2-buttons mouse (needs testing) + * + * To do: + * + * Improve Kensington support. + * Split mouse/kbd + * Move to syfs + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#ifdef CONFIG_PPC_PMAC +#include +#include +#endif + +MODULE_AUTHOR("Franz Sirl "); + +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]) + timer_delete_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_obj(struct adbhid); + 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 = ""; + 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<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..c978b4272 --- /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 +#include +#include +#include +#include +#include + +#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; +} + +static 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..d5cdbba6e --- /dev/null +++ b/drivers/macintosh/ams/ams-i2c.c @@ -0,0 +1,264 @@ +// 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 , documentation available at + * http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification + */ + +#include +#include +#include +#include +#include + +#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); +static void ams_i2c_remove(struct i2c_client *client); + +static const struct i2c_device_id ams_id[] = { + { "MAC,accelerometer_1" }, + { } +}; +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) +{ + 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 + +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#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..5b295f5ee --- /dev/null +++ b/drivers/macintosh/ams/ams.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _AMS_H +#define _AMS_H + +#include +#include +#include +#include +#include +#include +#include + +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); + +#endif /* _AMS_H */ 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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; iac_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..1fdf3855f --- /dev/null +++ b/drivers/macintosh/mac_hid.c @@ -0,0 +1,262 @@ +// 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 +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("Mouse button 2+3 emulation"); +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_obj(struct input_handle); + 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(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + int *valp = table->data; + int old_val; + int rc; + + rc = mutex_lock_killable(&mac_hid_emumouse_mutex); + if (rc) + return rc; + + old_val = *valp; + 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 const 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..19c63959e --- /dev/null +++ b/drivers/macintosh/macio-adb.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the ADB controller in the Mac I/O (Hydra) chip. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 __free(device_node) = + of_find_compatible_node(NULL, "adb", "chrp,adb0"); + + if (np) + return 0; + + return -ENODEV; +} + +int macio_init(void) +{ + struct device_node *adbs __free(device_node) = + of_find_compatible_node(NULL, "adb", "chrp,adb0"); + struct resource r; + unsigned int irq; + + if (!adbs) + return -ENXIO; + + if (of_address_to_resource(adbs, 0, &r)) + return -ENXIO; + + adb = ioremap(r.start, sizeof(struct adb_regs)); + if (!adb) + 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); + if (request_irq(irq, macio_adb_interrupt, 0, "ADB", (void *)0)) { + iounmap(adb); + 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) { + 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; + req = current_req; + if (req) { + /* 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..ee3caa3d8 --- /dev/null +++ b/drivers/macintosh/macio_asic.c @@ -0,0 +1,798 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#undef DEBUG + +#define MAX_NODE_NAME_SIZE (20 - 12) + +static struct macio_chip *macio_on_hold; + +static int macio_bus_match(struct device *dev, const 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; +} + +static int macio_device_modalias(const struct device *dev, struct kobj_uevent_env *env) +{ + return of_device_uevent_modalias(dev, env); +} + +extern const struct attribute_group *macio_dev_groups[]; + +const struct bus_type macio_bus_type = { + .name = "macio", + .match = macio_bus_match, + .uevent = macio_device_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_obj(*dev); + 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_ARCH_HAS_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_ARCH_HAS_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 +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* 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(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_property_present(np, "virtual")) + break; + } + if (np == NULL) { + pr_debug(" lightshow or sound+virtual child not found\n"); + goto bail; + } + + /* Create and initialize our instance data */ + rm = kzalloc_obj(*rm); + 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 void 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); +} + +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 "); +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..3e6dfb99a --- /dev/null +++ b/drivers/macintosh/smu.c @@ -0,0 +1,1332 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PowerMac G5 SMU driver + * + * Copyright 2004 J. Mayer + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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; + u64 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_or_panic(sizeof(struct smu_device), SMP_CACHE_BYTES); + 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; + } + if (of_property_read_reg(smu->db_node, 0, &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; + if (of_property_read_reg(smu->msg_node, 0, &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_obj(struct smu_private); + 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 = { + .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..1ac29a5cd --- /dev/null +++ b/drivers/macintosh/therm_adt746x.c @@ -0,0 +1,623 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 "); +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 = i2c_client_get_device_id(client); + 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_property_present(np, "hwsensor-location")) { + 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_obj(struct thermostat); + 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..0b2e08a1b --- /dev/null +++ b/drivers/macintosh/therm_windtunnel.c @@ -0,0 +1,560 @@ +/* + * Creation Date: <2003/03/14 20:54:13 samuel> + * Time-stamp: <2004/03/20 14:20:59 samuel> + * + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 = i2c_client_get_device_id(cl); + 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 void therm_of_remove(struct platform_device *dev) +{ + i2c_del_driver( &g4fan_driver ); +} + +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_platform_device_destroy(&x.of_dev->dev, NULL); +} + +module_init(g4fan_init); +module_exit(g4fan_exit); + +MODULE_AUTHOR("Samuel Rydh "); +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..f8dd1e831 --- /dev/null +++ b/drivers/macintosh/via-cuda.c @@ -0,0 +1,802 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_PPC +#include +#include +#else +#include +#include +#include +#endif +#include +#include + +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; + struct resource res; + int err; + + if (vias) + return 1; + vias = of_find_node_by_name(NULL, "via-cuda"); + if (!vias) + return 0; + + err = of_address_to_resource(vias, 0, &res); + if (err) { + printk(KERN_ERR "via-cuda: Error getting \"reg\" property !\n"); + goto fail; + } + via = ioremap(res.start, 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..b6ddf1d47 --- /dev/null +++ b/drivers/macintosh/via-macii.c @@ -0,0 +1,559 @@ +// 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 + * - 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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) +{ + int err; + + err = macii_init_via(); + if (err) + return err; + + err = request_irq(IRQ_MAC_ADB, macii_interrupt, 0, "ADB", + macii_interrupt); + if (err) + return err; + + macii_state = idle; + return 0; +} + +/* 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..d91825bb0 --- /dev/null +++ b/drivers/macintosh/via-pmu-backlight.c @@ -0,0 +1,187 @@ +// 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#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 = backlight_get_brightness(bd); + + 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 = BACKLIGHT_POWER_ON; + 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 + * + * 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 +#include +#include +#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..fc1af74b6 --- /dev/null +++ b/drivers/macintosh/via-pmu-led.c @@ -0,0 +1,118 @@ +/* + * via-pmu LED class device + * + * Copyright 2006 Johannes Berg + * + * 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 +#include +#include +#include +#include +#include +#include + +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) + goto put_node; + + if (strncmp(model, "PowerBook", strlen("PowerBook")) != 0 && + strncmp(model, "iBook", strlen("iBook")) != 0 && + strcmp(model, "PowerMac7,2") != 0 && + strcmp(model, "PowerMac7,3") != 0) + goto put_node; + + 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); + +put_node: + of_node_put(dt); + return -ENODEV; +} + +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..8c652c150 --- /dev/null +++ b/drivers/macintosh/via-pmu.c @@ -0,0 +1,2669 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PPC_PMAC +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#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); +#ifdef CONFIG_PROC_FS +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); +#endif +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 + int err; + u64 taddr; + struct resource res; + + if (pmu_state != uninitialized) + return 1; + vias = of_find_node_by_name(NULL, "via-pmu"); + if (vias == NULL) + return 0; + + err = of_address_to_resource(vias, 0, &res); + if (err) { + printk(KERN_ERR "via-pmu: Error getting \"reg\" property !\n"); + goto fail; + } + taddr = res.start; + + 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; + + 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) { + if (!of_address_to_resource(gpiop, 0, &res)) + gpio_reg = ioremap(res.start, 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 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); +} + +#ifdef CONFIG_PROC_FS +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, +}; +#endif + +#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_obj(struct pmu_private); + 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 __init 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 *data) +{ + /* 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 *data) +{ + 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 const struct syscore_ops pmu_syscore_ops = { + .suspend = pmu_syscore_suspend, + .resume = pmu_syscore_resume, +}; + +static struct syscore pmu_syscore = { + .ops = &pmu_syscore_ops, +}; + +static int pmu_syscore_register(void) +{ + register_syscore(&pmu_syscore); + + 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. + * + */ + +#ifndef __WINDFARM_H__ +#define __WINDFARM_H__ + +#include +#include +#include +#include +#include + +/* 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..4bae07ca6 --- /dev/null +++ b/drivers/macintosh/windfarm_ad7417_sensor.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. AD7417 sensors + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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) +{ + 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_obj(struct wf_ad7417_priv); + 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" }, + { } +}; +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 "); +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. + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, "\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 "); +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..13ba7488c --- /dev/null +++ b/drivers/macintosh/windfarm_cpufreq_clamp.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_obj(struct wf_control); + 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 "); +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..9def86e0c --- /dev/null +++ b/drivers/macintosh/windfarm_fcu_controls.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. FCU fan control + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + */ +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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_obj(*fan); + 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) +{ + struct wf_fcu_priv *pv; + + pv = kzalloc_obj(*pv); + 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" }, + { } +}; +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 "); +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..128e99444 --- /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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = i2c_client_get_device_id(client); + 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_obj(struct wf_lm75_sensor); + 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 "); +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..a571035da --- /dev/null +++ b/drivers/macintosh/windfarm_lm87_sensor.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. LM87 sensor + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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) +{ + 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_obj(struct wf_lm87_sensor); + 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" }, + { } +}; +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 "); +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..26b6436e0 --- /dev/null +++ b/drivers/macintosh/windfarm_max6690_sensor.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. MAX6690 sensor. + * + * Copyright (C) 2005 Paul Mackerras, IBM Corp. + */ +#include +#include +#include +#include +#include +#include + +#include + +#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 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_obj(struct wf_6690_sensor); + 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" }, + { } +}; +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 "); +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 + +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. + * + */ + +#include +#include +#include +#include +#include + +#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 "); +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..335613a20 --- /dev/null +++ b/drivers/macintosh/windfarm_pid.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _WINDFARM_PID_H +#define _WINDFARM_PID_H + +/* + * Windfarm PowerMac thermal control. Generic PID helpers + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * + * + * 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); + +#endif /* _WINDFARM_PID_H */ diff --git a/drivers/macintosh/windfarm_pm112.c b/drivers/macintosh/windfarm_pm112.c new file mode 100644 index 000000000..5bd6d1ccf --- /dev/null +++ b/drivers/macintosh/windfarm_pm112.c @@ -0,0 +1,717 @@ +// 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. + * Copyright (C) 2006 Benjamin Herrenschmidt, IBM Corp. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 void wf_pm112_remove(struct platform_device *dev) +{ + wf_unregister_client(&pm112_events); +} + +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 "); +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..ccdacb6d3 --- /dev/null +++ b/drivers/macintosh/windfarm_pm121.c @@ -0,0 +1,1041 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. iMac G5 iSight + * + * (c) Copyright 2007 Étienne Bersac + * + * Bits & pieces from windfarm_pm81.c by (c) Copyright 2005 Benjamin + * Herrenschmidt, IBM Corp. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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_obj(struct pm121_sys_state); + 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) { + 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_obj(struct pm121_cpu_state); + 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) { + 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 void pm121_remove(struct platform_device *ddev) +{ + wf_unregister_client(&pm121_events); +} + +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 "); +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..10aa14074 --- /dev/null +++ b/drivers/macintosh/windfarm_pm72.c @@ -0,0 +1,842 @@ +// 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 +#include +#include +#include +#include +#include + +#include + +#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 void wf_pm72_remove(struct platform_device *dev) +{ + wf_unregister_client(&pm72_events); +} + +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 "); +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..2a4a3746f --- /dev/null +++ b/drivers/macintosh/windfarm_pm81.c @@ -0,0 +1,808 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. iMac G5 + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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_obj(struct wf_smu_sys_fans_state); + 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) { + 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_obj(struct wf_smu_cpu_fans_state); + 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) { + 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 void 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); +} + +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 "); +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..14089d57e --- /dev/null +++ b/drivers/macintosh/windfarm_pm91.c @@ -0,0 +1,737 @@ +// 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. + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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) { + 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_obj(struct wf_smu_cpu_fans_state); + 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_obj(struct wf_smu_drive_fans_state); + 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_obj(struct wf_smu_slots_fans_state); + 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 void 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); +} + +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 "); +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..44d86a410 --- /dev/null +++ b/drivers/macintosh/windfarm_rm31.c @@ -0,0 +1,735 @@ +// 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 +#include +#include +#include +#include +#include + +#include + +#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 void wf_rm31_remove(struct platform_device *dev) +{ + wf_unregister_client(&rm31_events); +} + +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 "); +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..9adceae74 --- /dev/null +++ b/drivers/macintosh/windfarm_smu_controls.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. SMU based controls + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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_obj(struct smu_fan_control); + 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_each_child_of_node(smu, fans) + if (of_node_name_eq(fans, "rpm-fans") || + of_device_is_compatible(fans, "smu-rpm-fans")) + break; + for_each_child_of_node(fans, fan) { + 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_each_child_of_node(smu, fans) + if (of_node_name_eq(fans, "pwm-fans")) + break; + for_each_child_of_node(fans, fan) { + 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 "); +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..256c1654c --- /dev/null +++ b/drivers/macintosh/windfarm_smu_sat.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Windfarm PowerMac thermal control. SMU "satellite" controller sensors. + * + * Copyright (C) 2005 Paul Mackerras, IBM Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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) +{ + 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_obj(struct wf_sat); + 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" }, + { } +}; +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 "); +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..03fda09de --- /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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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_obj(struct smu_ad_sensor); + 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_obj(struct smu_cpu_power_sensor); + 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 "); +MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + -- cgit v1.3.1