diff options
Diffstat (limited to 'drivers/macintosh')
27 files changed, 4998 insertions, 412 deletions
diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig index bc3e096d84f7..a0ea44c3e8b1 100644 --- a/drivers/macintosh/Kconfig +++ b/drivers/macintosh/Kconfig @@ -169,6 +169,25 @@ config THERM_PM72 This driver provides thermostat and fan control for the desktop G5 machines. +config WINDFARM + tristate "New PowerMac thermal control infrastructure" + +config WINDFARM_PM81 + tristate "Support for thermal management on iMac G5" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU + select I2C_PMAC_SMU + help + This driver provides thermal control for the iMacG5 + +config WINDFARM_PM91 + tristate "Support for thermal management on PowerMac9,1" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU + select I2C_PMAC_SMU + help + This driver provides thermal control for the PowerMac9,1 + which is the recent (SMU based) single CPU desktop G5 + + config ANSLCD tristate "Support for ANS LCD display" depends on ADB_CUDA && PPC_PMAC diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile index 236291bd48a4..f4657aa81fb0 100644 --- a/drivers/macintosh/Makefile +++ b/drivers/macintosh/Makefile @@ -26,3 +26,12 @@ obj-$(CONFIG_ADB_MACIO) += macio-adb.o obj-$(CONFIG_THERM_PM72) += therm_pm72.o obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o +obj-$(CONFIG_WINDFARM) += windfarm_core.o +obj-$(CONFIG_WINDFARM_PM81) += windfarm_smu_controls.o \ + windfarm_smu_sensors.o \ + windfarm_lm75_sensor.o windfarm_pid.o \ + windfarm_cpufreq_clamp.o windfarm_pm81.o +obj-$(CONFIG_WINDFARM_PM91) += windfarm_smu_controls.o \ + windfarm_smu_sensors.o \ + windfarm_lm75_sensor.o windfarm_pid.o \ + windfarm_cpufreq_clamp.o windfarm_pm91.o diff --git a/drivers/macintosh/adb.c b/drivers/macintosh/adb.c index c0dc1e3fa58b..d2ead1776c16 100644 --- a/drivers/macintosh/adb.c +++ b/drivers/macintosh/adb.c @@ -905,5 +905,5 @@ adbdev_init(void) adb_dev_class = class_create(THIS_MODULE, "adb"); if (IS_ERR(adb_dev_class)) return; - class_device_create(adb_dev_class, MKDEV(ADB_MAJOR, 0), NULL, "adb"); + class_device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb"); } diff --git a/drivers/macintosh/adbhid.c b/drivers/macintosh/adbhid.c index db654e8bd67e..c0b46bceb5df 100644 --- a/drivers/macintosh/adbhid.c +++ b/drivers/macintosh/adbhid.c @@ -206,7 +206,7 @@ u8 adb_to_linux_keycodes[128] = { }; struct adbhid { - struct input_dev input; + struct input_dev *input; int id; int default_id; int original_handler_id; @@ -291,10 +291,10 @@ adbhid_input_keycode(int id, int keycode, int repeat, struct pt_regs *regs) switch (keycode) { case ADB_KEY_CAPSLOCK: /* Generate down/up events for CapsLock everytime. */ - input_regs(&ahid->input, regs); - input_report_key(&ahid->input, KEY_CAPSLOCK, 1); - input_report_key(&ahid->input, KEY_CAPSLOCK, 0); - input_sync(&ahid->input); + input_regs(ahid->input, regs); + input_report_key(ahid->input, KEY_CAPSLOCK, 1); + input_report_key(ahid->input, KEY_CAPSLOCK, 0); + input_sync(ahid->input); return; #ifdef CONFIG_PPC_PMAC case ADB_KEY_POWER_OLD: /* Power key on PBook 3400 needs remapping */ @@ -347,10 +347,10 @@ adbhid_input_keycode(int id, int keycode, int repeat, struct pt_regs *regs) } if (adbhid[id]->keycode[keycode]) { - input_regs(&adbhid[id]->input, regs); - input_report_key(&adbhid[id]->input, + input_regs(adbhid[id]->input, regs); + input_report_key(adbhid[id]->input, adbhid[id]->keycode[keycode], !up_flag); - input_sync(&adbhid[id]->input); + input_sync(adbhid[id]->input); } else printk(KERN_INFO "Unhandled ADB key (scancode %#02x) %s.\n", keycode, up_flag ? "released" : "pressed"); @@ -441,20 +441,20 @@ adbhid_mouse_input(unsigned char *data, int nb, struct pt_regs *regs, int autopo break; } - input_regs(&adbhid[id]->input, regs); + input_regs(adbhid[id]->input, regs); - input_report_key(&adbhid[id]->input, BTN_LEFT, !((data[1] >> 7) & 1)); - input_report_key(&adbhid[id]->input, BTN_MIDDLE, !((data[2] >> 7) & 1)); + 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_key(adbhid[id]->input, BTN_RIGHT, !((data[3] >> 7) & 1)); - input_report_rel(&adbhid[id]->input, REL_X, + 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, + input_report_rel(adbhid[id]->input, REL_Y, ((data[1]&0x7f) < 64 ? (data[1]&0x7f) : (data[1]&0x7f)-128 )); - input_sync(&adbhid[id]->input); + input_sync(adbhid[id]->input); } static void @@ -467,7 +467,7 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto return; } - input_regs(&adbhid[id]->input, regs); + input_regs(adbhid[id]->input, regs); switch (adbhid[id]->original_handler_id) { default: @@ -477,19 +477,19 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto switch (data[1] & 0x0f) { case 0x0: /* microphone */ - input_report_key(&adbhid[id]->input, KEY_SOUND, down); + input_report_key(adbhid[id]->input, KEY_SOUND, down); break; case 0x1: /* mute */ - input_report_key(&adbhid[id]->input, KEY_MUTE, down); + input_report_key(adbhid[id]->input, KEY_MUTE, down); break; case 0x2: /* volume decrease */ - input_report_key(&adbhid[id]->input, KEY_VOLUMEDOWN, down); + input_report_key(adbhid[id]->input, KEY_VOLUMEDOWN, down); break; case 0x3: /* volume increase */ - input_report_key(&adbhid[id]->input, KEY_VOLUMEUP, down); + input_report_key(adbhid[id]->input, KEY_VOLUMEUP, down); break; default: @@ -513,19 +513,19 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto switch (data[1] & 0x0f) { case 0x8: /* mute */ - input_report_key(&adbhid[id]->input, KEY_MUTE, down); + input_report_key(adbhid[id]->input, KEY_MUTE, down); break; case 0x7: /* volume decrease */ - input_report_key(&adbhid[id]->input, KEY_VOLUMEDOWN, down); + input_report_key(adbhid[id]->input, KEY_VOLUMEDOWN, down); break; case 0x6: /* volume increase */ - input_report_key(&adbhid[id]->input, KEY_VOLUMEUP, down); + input_report_key(adbhid[id]->input, KEY_VOLUMEUP, down); break; case 0xb: /* eject */ - input_report_key(&adbhid[id]->input, KEY_EJECTCD, down); + input_report_key(adbhid[id]->input, KEY_EJECTCD, down); break; case 0xa: /* brightness decrease */ @@ -539,7 +539,7 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto } } #endif /* CONFIG_PMAC_BACKLIGHT */ - input_report_key(&adbhid[id]->input, KEY_BRIGHTNESSDOWN, down); + input_report_key(adbhid[id]->input, KEY_BRIGHTNESSDOWN, down); break; case 0x9: /* brightness increase */ @@ -553,19 +553,19 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto } } #endif /* CONFIG_PMAC_BACKLIGHT */ - input_report_key(&adbhid[id]->input, KEY_BRIGHTNESSUP, down); + input_report_key(adbhid[id]->input, KEY_BRIGHTNESSUP, down); break; case 0xc: /* videomode switch */ - input_report_key(&adbhid[id]->input, KEY_SWITCHVIDEOMODE, down); + input_report_key(adbhid[id]->input, KEY_SWITCHVIDEOMODE, down); break; case 0xd: /* keyboard illumination toggle */ - input_report_key(&adbhid[id]->input, KEY_KBDILLUMTOGGLE, down); + input_report_key(adbhid[id]->input, KEY_KBDILLUMTOGGLE, down); break; case 0xe: /* keyboard illumination decrease */ - input_report_key(&adbhid[id]->input, KEY_KBDILLUMDOWN, down); + input_report_key(adbhid[id]->input, KEY_KBDILLUMDOWN, down); break; case 0xf: @@ -573,7 +573,7 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto case 0x8f: case 0x0f: /* keyboard illumination increase */ - input_report_key(&adbhid[id]->input, KEY_KBDILLUMUP, down); + input_report_key(adbhid[id]->input, KEY_KBDILLUMUP, down); break; case 0x7f: @@ -596,7 +596,7 @@ adbhid_buttons_input(unsigned char *data, int nb, struct pt_regs *regs, int auto break; } - input_sync(&adbhid[id]->input); + input_sync(adbhid[id]->input); } static struct adb_request led_request; @@ -683,7 +683,7 @@ adb_message_handler(struct notifier_block *this, unsigned long code, void *x) int i; for (i = 1; i < 16; i++) { if (adbhid[i]) - del_timer_sync(&adbhid[i]->input.timer); + del_timer_sync(&adbhid[i]->input->timer); } } @@ -699,155 +699,165 @@ adb_message_handler(struct notifier_block *this, unsigned long code, void *x) return NOTIFY_DONE; } -static void +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; if (adbhid[id]) { printk(KERN_ERR "Trying to reregister ADB HID on ID %d\n", id); - return; + return -EEXIST; } - if (!(adbhid[id] = kmalloc(sizeof(struct adbhid), GFP_KERNEL))) - return; - - memset(adbhid[id], 0, sizeof(struct adbhid)); - sprintf(adbhid[id]->phys, "adb%d:%d.%02x/input", id, default_id, original_handler_id); + adbhid[id] = hid = kzalloc(sizeof(struct adbhid), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!hid || !input_dev) { + err = -ENOMEM; + goto fail; - init_input_dev(&adbhid[id]->input); + } - adbhid[id]->id = default_id; - adbhid[id]->original_handler_id = original_handler_id; - adbhid[id]->current_handler_id = current_handler_id; - adbhid[id]->mouse_kind = mouse_kind; - adbhid[id]->flags = 0; - adbhid[id]->input.private = adbhid[id]; - adbhid[id]->input.name = adbhid[id]->name; - adbhid[id]->input.phys = adbhid[id]->phys; - adbhid[id]->input.id.bustype = BUS_ADB; - adbhid[id]->input.id.vendor = 0x0001; - adbhid[id]->input.id.product = (id << 12) | (default_id << 8) | original_handler_id; - adbhid[id]->input.id.version = 0x0100; + 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_dev->private = 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: - if (!(adbhid[id]->keycode = kmalloc(sizeof(adb_to_linux_keycodes), GFP_KERNEL))) { - kfree(adbhid[id]); - return; + hid->keycode = kmalloc(sizeof(adb_to_linux_keycodes), GFP_KERNEL); + if (!hid->keycode) { + err = -ENOMEM; + goto fail; } - sprintf(adbhid[id]->name, "ADB keyboard"); + sprintf(hid->name, "ADB keyboard"); - memcpy(adbhid[id]->keycode, adb_to_linux_keycodes, sizeof(adb_to_linux_keycodes)); + memcpy(hid->keycode, adb_to_linux_keycodes, sizeof(adb_to_linux_keycodes)); printk(KERN_INFO "Detected ADB keyboard, type "); switch (original_handler_id) { default: printk("<unknown>.\n"); - adbhid[id]->input.id.version = ADB_KEYBOARD_UNKNOWN; + 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: printk("ANSI.\n"); - adbhid[id]->input.id.version = ADB_KEYBOARD_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: printk("ISO, swapping keys.\n"); - adbhid[id]->input.id.version = ADB_KEYBOARD_ISO; - i = adbhid[id]->keycode[10]; - adbhid[id]->keycode[10] = adbhid[id]->keycode[50]; - adbhid[id]->keycode[50] = i; + input_dev->id.version = ADB_KEYBOARD_ISO; + i = hid->keycode[10]; + hid->keycode[10] = hid->keycode[50]; + hid->keycode[50] = i; break; case 0x12: case 0x15: case 0x16: case 0x17: case 0x1A: case 0x1E: case 0xC2: case 0xC5: case 0xC8: case 0xC9: printk("JIS.\n"); - adbhid[id]->input.id.version = ADB_KEYBOARD_JIS; + input_dev->id.version = ADB_KEYBOARD_JIS; break; } for (i = 0; i < 128; i++) - if (adbhid[id]->keycode[i]) - set_bit(adbhid[id]->keycode[i], adbhid[id]->input.keybit); - - adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP); - adbhid[id]->input.ledbit[0] = BIT(LED_SCROLLL) | BIT(LED_CAPSL) | BIT(LED_NUML); - adbhid[id]->input.event = adbhid_kbd_event; - adbhid[id]->input.keycodemax = 127; - adbhid[id]->input.keycodesize = 1; + if (hid->keycode[i]) + set_bit(hid->keycode[i], input_dev->keybit); + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP); + input_dev->ledbit[0] = BIT(LED_SCROLLL) | BIT(LED_CAPSL) | BIT(LED_NUML); + input_dev->event = adbhid_kbd_event; + input_dev->keycodemax = 127; + input_dev->keycodesize = 1; break; case ADB_MOUSE: - sprintf(adbhid[id]->name, "ADB mouse"); + sprintf(hid->name, "ADB mouse"); - adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); - adbhid[id]->input.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); - adbhid[id]->input.relbit[0] = BIT(REL_X) | BIT(REL_Y); + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + input_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + input_dev->relbit[0] = BIT(REL_X) | BIT(REL_Y); break; case ADB_MISC: switch (original_handler_id) { case 0x02: /* Adjustable keyboard button device */ - sprintf(adbhid[id]->name, "ADB adjustable keyboard buttons"); - adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); - set_bit(KEY_SOUND, adbhid[id]->input.keybit); - set_bit(KEY_MUTE, adbhid[id]->input.keybit); - set_bit(KEY_VOLUMEUP, adbhid[id]->input.keybit); - set_bit(KEY_VOLUMEDOWN, adbhid[id]->input.keybit); + sprintf(hid->name, "ADB adjustable keyboard buttons"); + input_dev->evbit[0] = BIT(EV_KEY) | BIT(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(adbhid[id]->name, "ADB Powerbook buttons"); - adbhid[id]->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); - set_bit(KEY_MUTE, adbhid[id]->input.keybit); - set_bit(KEY_VOLUMEUP, adbhid[id]->input.keybit); - set_bit(KEY_VOLUMEDOWN, adbhid[id]->input.keybit); - set_bit(KEY_BRIGHTNESSUP, adbhid[id]->input.keybit); - set_bit(KEY_BRIGHTNESSDOWN, adbhid[id]->input.keybit); - set_bit(KEY_EJECTCD, adbhid[id]->input.keybit); - set_bit(KEY_SWITCHVIDEOMODE, adbhid[id]->input.keybit); - set_bit(KEY_KBDILLUMTOGGLE, adbhid[id]->input.keybit); - set_bit(KEY_KBDILLUMDOWN, adbhid[id]->input.keybit); - set_bit(KEY_KBDILLUMUP, adbhid[id]->input.keybit); + sprintf(hid->name, "ADB Powerbook buttons"); + input_dev->evbit[0] = BIT(EV_KEY) | BIT(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 (adbhid[id]->name[0]) + if (hid->name[0]) break; /* else fall through */ default: printk(KERN_INFO "Trying to register unknown ADB device to input layer.\n"); - kfree(adbhid[id]); - return; + err = -ENODEV; + goto fail; } - adbhid[id]->input.keycode = adbhid[id]->keycode; - - input_register_device(&adbhid[id]->input); + input_dev->keycode = hid->keycode; - printk(KERN_INFO "input: %s on %s\n", - adbhid[id]->name, adbhid[id]->phys); + input_register_device(input_dev); if (default_id == ADB_KEYBOARD) { /* HACK WARNING!! This should go away as soon there is an utility * to control that for event devices. */ - adbhid[id]->input.rep[REP_DELAY] = 500; /* input layer default: 250 */ - adbhid[id]->input.rep[REP_PERIOD] = 66; /* input layer default: 33 */ + 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); + kfree(hid); + adbhid[id] = NULL; + return err; } static void adbhid_input_unregister(int id) { - input_unregister_device(&adbhid[id]->input); - if (adbhid[id]->keycode) - kfree(adbhid[id]->keycode); + input_unregister_device(adbhid[id]->input); + kfree(adbhid[id]->keycode); kfree(adbhid[id]); adbhid[id] = NULL; } @@ -858,7 +868,7 @@ 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 != + 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, diff --git a/drivers/macintosh/ans-lcd.c b/drivers/macintosh/ans-lcd.c index 5e0811dc6536..2b8a6e821d44 100644 --- a/drivers/macintosh/ans-lcd.c +++ b/drivers/macintosh/ans-lcd.c @@ -27,7 +27,7 @@ static volatile unsigned char __iomem *anslcd_ptr; #undef DEBUG -static void __pmac +static void anslcd_write_byte_ctrl ( unsigned char c ) { #ifdef DEBUG @@ -43,14 +43,14 @@ anslcd_write_byte_ctrl ( unsigned char c ) } } -static void __pmac +static void anslcd_write_byte_data ( unsigned char c ) { out_8(anslcd_ptr + ANSLCD_DATA_IX, c); udelay(anslcd_short_delay); } -static ssize_t __pmac +static ssize_t anslcd_write( struct file * file, const char __user * buf, size_t count, loff_t *ppos ) { @@ -73,7 +73,7 @@ anslcd_write( struct file * file, const char __user * buf, return p - buf; } -static int __pmac +static int anslcd_ioctl( struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg ) { @@ -115,7 +115,7 @@ anslcd_ioctl( struct inode * inode, struct file * file, } } -static int __pmac +static int anslcd_open( struct inode * inode, struct file * file ) { return 0; diff --git a/drivers/macintosh/apm_emu.c b/drivers/macintosh/apm_emu.c index 19d3e05d6825..e5a2bbf99399 100644 --- a/drivers/macintosh/apm_emu.c +++ b/drivers/macintosh/apm_emu.c @@ -430,8 +430,8 @@ static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length) -1: Unknown 8) min = minutes; sec = seconds */ - unsigned short ac_line_status = 0xff; - unsigned short battery_status = 0xff; + unsigned short ac_line_status; + unsigned short battery_status = 0; unsigned short battery_flag = 0xff; int percentage = -1; int time_units = -1; @@ -446,6 +446,7 @@ static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length) ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0); for (i=0; i<pmu_battery_count; i++) { if (pmu_batteries[i].flags & PMU_BATT_PRESENT) { + battery_status++; if (percentage < 0) percentage = 0; if (charge < 0) @@ -461,6 +462,9 @@ static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length) charging++; } } + if (0 == battery_status) + ac_line_status = 1; + battery_status = 0xff; if (real_count) { if (amperage < 0) { if (btype == PMU_BATT_TYPE_SMART) diff --git a/drivers/macintosh/mac_hid.c b/drivers/macintosh/mac_hid.c index 5ad3a5a9eb7f..a66636116f0b 100644 --- a/drivers/macintosh/mac_hid.c +++ b/drivers/macintosh/mac_hid.c @@ -16,8 +16,8 @@ #include <linux/module.h> -static struct input_dev emumousebtn; -static void emumousebtn_input_register(void); +static struct input_dev *emumousebtn; +static int emumousebtn_input_register(void); static int mouse_emulate_buttons = 0; static int mouse_button2_keycode = KEY_RIGHTCTRL; /* right control key */ static int mouse_button3_keycode = KEY_RIGHTALT; /* right option key */ @@ -90,10 +90,10 @@ int mac_hid_mouse_emulate_buttons(int caller, unsigned int keycode, int down) && (keycode == mouse_button2_keycode || keycode == mouse_button3_keycode)) { if (mouse_emulate_buttons == 1) { - input_report_key(&emumousebtn, + input_report_key(emumousebtn, keycode == mouse_button2_keycode ? BTN_MIDDLE : BTN_RIGHT, down); - input_sync(&emumousebtn); + input_sync(emumousebtn); return 1; } mouse_last_keycode = down ? keycode : 0; @@ -105,30 +105,34 @@ int mac_hid_mouse_emulate_buttons(int caller, unsigned int keycode, int down) EXPORT_SYMBOL(mac_hid_mouse_emulate_buttons); -static void emumousebtn_input_register(void) +static int emumousebtn_input_register(void) { - emumousebtn.name = "Macintosh mouse button emulation"; + emumousebtn = input_allocate_device(); + if (!emumousebtn) + return -ENOMEM; - init_input_dev(&emumousebtn); + emumousebtn->name = "Macintosh mouse button emulation"; + emumousebtn->id.bustype = BUS_ADB; + emumousebtn->id.vendor = 0x0001; + emumousebtn->id.product = 0x0001; + emumousebtn->id.version = 0x0100; - emumousebtn.evbit[0] = BIT(EV_KEY) | BIT(EV_REL); - emumousebtn.keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); - emumousebtn.relbit[0] = BIT(REL_X) | BIT(REL_Y); + emumousebtn->evbit[0] = BIT(EV_KEY) | BIT(EV_REL); + emumousebtn->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_MIDDLE) | BIT(BTN_RIGHT); + emumousebtn->relbit[0] = BIT(REL_X) | BIT(REL_Y); - emumousebtn.id.bustype = BUS_ADB; - emumousebtn.id.vendor = 0x0001; - emumousebtn.id.product = 0x0001; - emumousebtn.id.version = 0x0100; + input_register_device(emumousebtn); - input_register_device(&emumousebtn); - - printk(KERN_INFO "input: Macintosh mouse button emulation\n"); + return 0; } int __init mac_hid_init(void) { + int err; - emumousebtn_input_register(); + err = emumousebtn_input_register(); + if (err) + return err; #if defined(CONFIG_SYSCTL) mac_hid_sysctl_header = register_sysctl_table(mac_hid_root_dir, 1); diff --git a/drivers/macintosh/macio_asic.c b/drivers/macintosh/macio_asic.c index 1ee003346923..c34c96d18907 100644 --- a/drivers/macintosh/macio_asic.c +++ b/drivers/macintosh/macio_asic.c @@ -17,6 +17,8 @@ #include <linux/pci_ids.h> #include <linux/init.h> #include <linux/module.h> +#include <linux/slab.h> + #include <asm/machdep.h> #include <asm/macio.h> #include <asm/pmac_feature.h> diff --git a/drivers/macintosh/macio_sysfs.c b/drivers/macintosh/macio_sysfs.c index 97d22bb4516a..7f7d4eaca870 100644 --- a/drivers/macintosh/macio_sysfs.c +++ b/drivers/macintosh/macio_sysfs.c @@ -39,6 +39,31 @@ compatible_show (struct device *dev, struct device_attribute *attr, char *buf) return length; } +static ssize_t modalias_show (struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct of_device *of; + char *compat; + int cplen; + int length; + + of = &to_macio_device (dev)->ofdev; + compat = (char *) get_property (of->node, "compatible", &cplen); + if (!compat) compat = "", cplen = 1; + length = sprintf (buf, "of:N%sT%s", of->node->name, of->node->type); + buf += length; + while (cplen > 0) { + int l; + length += sprintf (buf, "C%s", compat); + buf += length; + l = strlen (compat) + 1; + compat += l; + cplen -= l; + } + + return length; +} + macio_config_of_attr (name, "%s\n"); macio_config_of_attr (type, "%s\n"); @@ -46,5 +71,6 @@ struct device_attribute macio_dev_attrs[] = { __ATTR_RO(name), __ATTR_RO(type), __ATTR_RO(compatible), + __ATTR_RO(modalias), __ATTR_NULL }; diff --git a/drivers/macintosh/mediabay.c b/drivers/macintosh/mediabay.c index c0712a1ea5af..b856bb67169c 100644 --- a/drivers/macintosh/mediabay.c +++ b/drivers/macintosh/mediabay.c @@ -167,19 +167,19 @@ enum { * Functions for polling content of media bay */ -static u8 __pmac +static u8 ohare_mb_content(struct media_bay_info *bay) { return (MB_IN32(bay, OHARE_MBCR) >> 12) & 7; } -static u8 __pmac +static u8 heathrow_mb_content(struct media_bay_info *bay) { return (MB_IN32(bay, HEATHROW_MBCR) >> 12) & 7; } -static u8 __pmac +static u8 keylargo_mb_content(struct media_bay_info *bay) { int new_gpio; @@ -205,7 +205,7 @@ keylargo_mb_content(struct media_bay_info *bay) * into reset state as well */ -static void __pmac +static void ohare_mb_power(struct media_bay_info* bay, int on_off) { if (on_off) { @@ -224,7 +224,7 @@ ohare_mb_power(struct media_bay_info* bay, int on_off) MB_BIC(bay, OHARE_MBCR, 0x00000F00); } -static void __pmac +static void heathrow_mb_power(struct media_bay_info* bay, int on_off) { if (on_off) { @@ -243,7 +243,7 @@ heathrow_mb_power(struct media_bay_info* bay, int on_off) MB_BIC(bay, HEATHROW_MBCR, 0x00000F00); } -static void __pmac +static void keylargo_mb_power(struct media_bay_info* bay, int on_off) { if (on_off) { @@ -267,7 +267,7 @@ keylargo_mb_power(struct media_bay_info* bay, int on_off) * enable the related busses */ -static int __pmac +static int ohare_mb_setup_bus(struct media_bay_info* bay, u8 device_id) { switch(device_id) { @@ -287,7 +287,7 @@ ohare_mb_setup_bus(struct media_bay_info* bay, u8 device_id) return -ENODEV; } -static int __pmac +static int heathrow_mb_setup_bus(struct media_bay_info* bay, u8 device_id) { switch(device_id) { @@ -307,7 +307,7 @@ heathrow_mb_setup_bus(struct media_bay_info* bay, u8 device_id) return -ENODEV; } -static int __pmac +static int keylargo_mb_setup_bus(struct media_bay_info* bay, u8 device_id) { switch(device_id) { @@ -330,43 +330,43 @@ keylargo_mb_setup_bus(struct media_bay_info* bay, u8 device_id) * Functions for tweaking resets */ -static void __pmac +static void ohare_mb_un_reset(struct media_bay_info* bay) { MB_BIS(bay, OHARE_FCR, OH_BAY_RESET_N); } -static void __pmac keylargo_mb_init(struct media_bay_info *bay) +static void keylargo_mb_init(struct media_bay_info *bay) { MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_ENABLE); } -static void __pmac heathrow_mb_un_reset(struct media_bay_info* bay) +static void heathrow_mb_un_reset(struct media_bay_info* bay) { MB_BIS(bay, HEATHROW_FCR, HRW_BAY_RESET_N); } -static void __pmac keylargo_mb_un_reset(struct media_bay_info* bay) +static void keylargo_mb_un_reset(struct media_bay_info* bay) { MB_BIS(bay, KEYLARGO_MBCR, KL_MBCR_MB0_DEV_RESET); } -static void __pmac ohare_mb_un_reset_ide(struct media_bay_info* bay) +static void ohare_mb_un_reset_ide(struct media_bay_info* bay) { MB_BIS(bay, OHARE_FCR, OH_IDE1_RESET_N); } -static void __pmac heathrow_mb_un_reset_ide(struct media_bay_info* bay) +static void heathrow_mb_un_reset_ide(struct media_bay_info* bay) { MB_BIS(bay, HEATHROW_FCR, HRW_IDE1_RESET_N); } -static void __pmac keylargo_mb_un_reset_ide(struct media_bay_info* bay) +static void keylargo_mb_un_reset_ide(struct media_bay_info* bay) { MB_BIS(bay, KEYLARGO_FCR1, KL1_EIDE0_RESET_N); } -static inline void __pmac set_mb_power(struct media_bay_info* bay, int onoff) +static inline void set_mb_power(struct media_bay_info* bay, int onoff) { /* Power up up and assert the bay reset line */ if (onoff) { @@ -382,7 +382,7 @@ static inline void __pmac set_mb_power(struct media_bay_info* bay, int onoff) bay->timer = msecs_to_jiffies(MB_POWER_DELAY); } -static void __pmac poll_media_bay(struct media_bay_info* bay) +static void poll_media_bay(struct media_bay_info* bay) { int id = bay->ops->content(bay); @@ -415,7 +415,7 @@ static void __pmac poll_media_bay(struct media_bay_info* bay) } } -int __pmac check_media_bay(struct device_node *which_bay, int what) +int check_media_bay(struct device_node *which_bay, int what) { #ifdef CONFIG_BLK_DEV_IDE int i; @@ -432,7 +432,7 @@ int __pmac check_media_bay(struct device_node *which_bay, int what) } EXPORT_SYMBOL(check_media_bay); -int __pmac check_media_bay_by_base(unsigned long base, int what) +int check_media_bay_by_base(unsigned long base, int what) { #ifdef CONFIG_BLK_DEV_IDE int i; @@ -449,7 +449,7 @@ int __pmac check_media_bay_by_base(unsigned long base, int what) return -ENODEV; } -int __pmac media_bay_set_ide_infos(struct device_node* which_bay, unsigned long base, +int media_bay_set_ide_infos(struct device_node* which_bay, unsigned long base, int irq, int index) { #ifdef CONFIG_BLK_DEV_IDE @@ -489,7 +489,7 @@ int __pmac media_bay_set_ide_infos(struct device_node* which_bay, unsigned long return -ENODEV; } -static void __pmac media_bay_step(int i) +static void media_bay_step(int i) { struct media_bay_info* bay = &media_bays[i]; @@ -619,7 +619,7 @@ static void __pmac media_bay_step(int i) * with the IDE driver. It needs to be a thread because * ide_register can't be called from interrupt context. */ -static int __pmac media_bay_task(void *x) +static int media_bay_task(void *x) { int i; @@ -704,7 +704,7 @@ static int __devinit media_bay_attach(struct macio_dev *mdev, const struct of_de } -static int __pmac media_bay_suspend(struct macio_dev *mdev, pm_message_t state) +static int media_bay_suspend(struct macio_dev *mdev, pm_message_t state) { struct media_bay_info *bay = macio_get_drvdata(mdev); @@ -719,7 +719,7 @@ static int __pmac media_bay_suspend(struct macio_dev *mdev, pm_message_t state) return 0; } -static int __pmac media_bay_resume(struct macio_dev *mdev) +static int media_bay_resume(struct macio_dev *mdev) { struct media_bay_info *bay = macio_get_drvdata(mdev); @@ -760,7 +760,7 @@ static int __pmac media_bay_resume(struct macio_dev *mdev) /* Definitions of "ops" structures. */ -static struct mb_ops ohare_mb_ops __pmacdata = { +static struct mb_ops ohare_mb_ops = { .name = "Ohare", .content = ohare_mb_content, .power = ohare_mb_power, @@ -769,7 +769,7 @@ static struct mb_ops ohare_mb_ops __pmacdata = { .un_reset_ide = ohare_mb_un_reset_ide, }; -static struct mb_ops heathrow_mb_ops __pmacdata = { +static struct mb_ops heathrow_mb_ops = { .name = "Heathrow", .content = heathrow_mb_content, .power = heathrow_mb_power, @@ -778,7 +778,7 @@ static struct mb_ops heathrow_mb_ops __pmacdata = { .un_reset_ide = heathrow_mb_un_reset_ide, }; -static struct mb_ops keylargo_mb_ops __pmacdata = { +static struct mb_ops keylargo_mb_ops = { .name = "KeyLargo", .init = keylargo_mb_init, .content = keylargo_mb_content, diff --git a/drivers/macintosh/smu.c b/drivers/macintosh/smu.c index fb535737d17d..e8378274d710 100644 --- a/drivers/macintosh/smu.c +++ b/drivers/macintosh/smu.c @@ -8,21 +8,15 @@ */ /* - * For now, this driver includes: - * - RTC get & set - * - reboot & shutdown commands - * all synchronous with IRQ disabled (ugh) - * * TODO: - * rework in a way the PMU driver works, that is asynchronous - * with a queue of commands. I'll do that as soon as I have an - * SMU based machine at hand. Some more cleanup is needed too, - * like maybe fitting it into a platform device, etc... - * Also check what's up with cache coherency, and if we really - * can't do better than flushing the cache, maybe build a table - * of command len/reply len like the PMU driver to only flush - * what is actually necessary. - * --BenH. + * - maybe add timeout to commands ? + * - blocking version of time functions + * - polling version of i2c commands (including timer that works with + * interrutps off) + * - maybe avoid some data copies with i2c by directly using the smu cmd + * buffer and a lower level internal interface + * - understand SMU -> CPU events and implement reception of them via + * the userland interface */ #include <linux/config.h> @@ -36,6 +30,11 @@ #include <linux/jiffies.h> #include <linux/interrupt.h> #include <linux/rtc.h> +#include <linux/completion.h> +#include <linux/miscdevice.h> +#include <linux/delay.h> +#include <linux/sysdev.h> +#include <linux/poll.h> #include <asm/byteorder.h> #include <asm/io.h> @@ -45,11 +44,16 @@ #include <asm/smu.h> #include <asm/sections.h> #include <asm/abs_addr.h> +#include <asm/uaccess.h> +#include <asm/of_device.h> + +#define VERSION "0.7" +#define AUTHOR "(c) 2005 Benjamin Herrenschmidt, IBM Corp." -#define DEBUG_SMU 1 +#undef DEBUG_SMU #ifdef DEBUG_SMU -#define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0) +#define DPRINTK(fmt, args...) do { udbg_printf(KERN_DEBUG fmt , ##args); } while (0) #else #define DPRINTK(fmt, args...) do { } while (0) #endif @@ -57,20 +61,30 @@ /* * 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[0x0FFE]; + u8 data[SMU_MAX_DATA]; }; struct smu_device { spinlock_t lock; struct device_node *of_node; - int db_ack; /* doorbell ack GPIO */ - int db_req; /* doorbell req GPIO */ + struct of_device *of_dev; + int doorbell; /* doorbell gpio */ u32 __iomem *db_buf; /* doorbell buffer */ + int db_irq; + int msg; + 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 */ + struct list_head cmd_i2c_list; + struct smu_i2c_cmd *cmd_i2c_cur; /* pending i2c command */ + struct timer_list i2c_timer; }; /* @@ -78,114 +92,248 @@ struct smu_device { * for now, just hard code that */ static struct smu_device *smu; +static DECLARE_MUTEX(smu_part_access); /* - * SMU low level communication stuff + * SMU driver low level stuff */ -static inline int smu_cmd_stat(struct smu_cmd_buf *cmd_buf, u8 cmd_ack) -{ - rmb(); - return cmd_buf->cmd == cmd_ack && cmd_buf->length != 0; -} -static inline u8 smu_save_ack_cmd(struct smu_cmd_buf *cmd_buf) +static void smu_start_cmd(void) { - return (~cmd_buf->cmd) & 0xff; -} + unsigned long faddr, fend; + struct smu_cmd *cmd; -static void smu_send_cmd(struct smu_device *dev) -{ - /* SMU command buf is currently cacheable, we need a physical - * address. This isn't exactly a DMA mapping here, I suspect + 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: %02x %02x %02x %02x %02x %02x %02x %02x\n", + ((u8 *)cmd->data_buf)[0], ((u8 *)cmd->data_buf)[1], + ((u8 *)cmd->data_buf)[2], ((u8 *)cmd->data_buf)[3], + ((u8 *)cmd->data_buf)[4], ((u8 *)cmd->data_buf)[5], + ((u8 *)cmd->data_buf)[6], ((u8 *)cmd->data_buf)[7]); + + /* 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_inval_dcache_range(faddr, fend); + + /* This isn't exactly a DMA mapping here, I suspect * the SMU is actually communicating with us via i2c to the * northbridge or the CPU to access RAM. */ - writel(dev->cmd_buf_abs, dev->db_buf); + writel(smu->cmd_buf_abs, smu->db_buf); /* Ring the SMU doorbell */ - pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, dev->db_req, 4); - pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, dev->db_req, 4); + pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, smu->doorbell, 4); } -static int smu_cmd_done(struct smu_device *dev) + +static irqreturn_t smu_db_intr(int irq, void *arg, struct pt_regs *regs) { - unsigned long wait = 0; - int gpio; + unsigned long flags; + struct smu_cmd *cmd; + void (*done)(struct smu_cmd *cmd, void *misc) = NULL; + void *misc = NULL; + u8 gpio; + int rc = 0; - /* Check the SMU doorbell */ - do { - gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, - NULL, dev->db_ack); - if ((gpio & 7) == 7) - return 0; - udelay(100); - } while(++wait < 10000); + /* SMU completed the command, well, we hope, let's make sure + * of it + */ + spin_lock_irqsave(&smu->lock, flags); - printk(KERN_ERR "SMU timeout !\n"); - return -ENXIO; + 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 lenght (it's only 2 cache lines anyway) + */ + faddr = (unsigned long)smu->cmd_buf; + flush_inval_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; + 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 int smu_do_cmd(struct smu_device *dev) + +static irqreturn_t smu_msg_intr(int irq, void *arg, struct pt_regs *regs) { - int rc; - u8 cmd_ack; + /* 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. + */ - DPRINTK("SMU do_cmd %02x len=%d %02x\n", - dev->cmd_buf->cmd, dev->cmd_buf->length, - dev->cmd_buf->data[0]); + printk(KERN_INFO "SMU: message interrupt !\n"); - cmd_ack = smu_save_ack_cmd(dev->cmd_buf); + /* It's an edge interrupt, nothing to do */ + return IRQ_HANDLED; +} - /* Clear cmd_buf cache lines */ - flush_inval_dcache_range((unsigned long)dev->cmd_buf, - ((unsigned long)dev->cmd_buf) + - sizeof(struct smu_cmd_buf)); - smu_send_cmd(dev); - rc = smu_cmd_done(dev); - if (rc == 0) - rc = smu_cmd_stat(dev->cmd_buf, cmd_ack) ? 0 : -1; - DPRINTK("SMU do_cmd %02x len=%d %02x => %d (%02x)\n", - dev->cmd_buf->cmd, dev->cmd_buf->length, - dev->cmd_buf->data[0], rc, cmd_ack); +/* + * Queued command management. + * + */ - return rc; +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); + + return 0; } +EXPORT_SYMBOL(smu_queue_cmd); -/* RTC low level commands */ -static inline int bcd2hex (int n) + +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, ...) { - return (((n & 0xf0) >> 4) * 10) + (n & 0xf); + 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); -static inline int hex2bcd (int n) + +void smu_poll(void) { - return ((n / 10) << 4) + (n % 10); + 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, NULL); } +EXPORT_SYMBOL(smu_poll); -#if 0 -static inline void smu_fill_set_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf) + +void smu_done_complete(struct smu_cmd *cmd, void *misc) { - cmd_buf->cmd = 0x8e; - cmd_buf->length = 8; - cmd_buf->data[0] = 0x00; - memset(cmd_buf->data + 1, 0, 7); + 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); -static inline void smu_fill_get_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf) + +/* RTC low level commands */ +static inline int bcd2hex (int n) { - cmd_buf->cmd = 0x8e; - cmd_buf->length = 1; - cmd_buf->data[0] = 0x01; + return (((n & 0xf0) >> 4) * 10) + (n & 0xf); } -static inline void smu_fill_dis_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf) + +static inline int hex2bcd (int n) { - cmd_buf->cmd = 0x8e; - cmd_buf->length = 1; - cmd_buf->data[0] = 0x02; + return ((n / 10) << 4) + (n % 10); } -#endif + static inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf, struct rtc_time *time) @@ -202,103 +350,99 @@ static inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf, cmd_buf->data[7] = hex2bcd(time->tm_year - 100); } -static inline void smu_fill_get_rtc_cmd(struct smu_cmd_buf *cmd_buf) -{ - cmd_buf->cmd = 0x8e; - cmd_buf->length = 1; - cmd_buf->data[0] = 0x81; -} -static void smu_parse_get_rtc_reply(struct smu_cmd_buf *cmd_buf, - struct rtc_time *time) +int smu_get_rtc_time(struct rtc_time *time, int spinwait) { - time->tm_sec = bcd2hex(cmd_buf->data[0]); - time->tm_min = bcd2hex(cmd_buf->data[1]); - time->tm_hour = bcd2hex(cmd_buf->data[2]); - time->tm_wday = bcd2hex(cmd_buf->data[3]); - time->tm_mday = bcd2hex(cmd_buf->data[4]); - time->tm_mon = bcd2hex(cmd_buf->data[5]) - 1; - time->tm_year = bcd2hex(cmd_buf->data[6]) + 100; -} - -int smu_get_rtc_time(struct rtc_time *time) -{ - unsigned long flags; + struct smu_simple_cmd cmd; int rc; if (smu == NULL) return -ENODEV; memset(time, 0, sizeof(struct rtc_time)); - spin_lock_irqsave(&smu->lock, flags); - smu_fill_get_rtc_cmd(smu->cmd_buf); - rc = smu_do_cmd(smu); - if (rc == 0) - smu_parse_get_rtc_reply(smu->cmd_buf, time); - spin_unlock_irqrestore(&smu->lock, flags); + 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); - return rc; + 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 smu_set_rtc_time(struct rtc_time *time, int spinwait) { - unsigned long flags; + struct smu_simple_cmd cmd; int rc; if (smu == NULL) return -ENODEV; - spin_lock_irqsave(&smu->lock, flags); - smu_fill_set_rtc_cmd(smu->cmd_buf, time); - rc = smu_do_cmd(smu); - spin_unlock_irqrestore(&smu->lock, flags); + 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 rc; + return 0; } + void smu_shutdown(void) { - const unsigned char *command = "SHUTDOWN"; - unsigned long flags; + struct smu_simple_cmd cmd; if (smu == NULL) return; - spin_lock_irqsave(&smu->lock, flags); - smu->cmd_buf->cmd = 0xaa; - smu->cmd_buf->length = strlen(command); - strcpy(smu->cmd_buf->data, command); - smu_do_cmd(smu); + 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 (;;) ; - spin_unlock_irqrestore(&smu->lock, flags); } + void smu_restart(void) { - const unsigned char *command = "RESTART"; - unsigned long flags; + struct smu_simple_cmd cmd; if (smu == NULL) return; - spin_lock_irqsave(&smu->lock, flags); - smu->cmd_buf->cmd = 0xaa; - smu->cmd_buf->length = strlen(command); - strcpy(smu->cmd_buf->data, command); - smu_do_cmd(smu); + 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 (;;) ; - spin_unlock_irqrestore(&smu->lock, flags); } + int smu_present(void) { return smu != NULL; } +EXPORT_SYMBOL(smu_present); -int smu_init (void) +int __init smu_init (void) { struct device_node *np; u32 *data; @@ -307,6 +451,8 @@ int smu_init (void) if (np == NULL) return -ENODEV; + printk(KERN_INFO "SMU driver %s %s\n", VERSION, AUTHOR); + if (smu_cmdbuf_abs == 0) { printk(KERN_ERR "SMU: Command buffer not allocated !\n"); return -EINVAL; @@ -318,7 +464,13 @@ int smu_init (void) memset(smu, 0, sizeof(*smu)); 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 = NO_IRQ; + smu->msg_irq = NO_IRQ; + init_timer(&smu->i2c_timer); + /* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a * 32 bits value safely */ @@ -331,8 +483,8 @@ int smu_init (void) goto fail; } data = (u32 *)get_property(np, "reg", NULL); - of_node_put(np); if (data == NULL) { + of_node_put(np); printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n"); goto fail; } @@ -341,8 +493,31 @@ int smu_init (void) * and ack. GPIOs are at 0x50, best would be to find that out * in the device-tree though. */ - smu->db_req = 0x50 + *data; - smu->db_ack = 0x50 + *data; + smu->doorbell = *data; + if (smu->doorbell < 0x50) + smu->doorbell += 0x50; + if (np->n_intrs > 0) + smu->db_irq = np->intrs[0].line; + + of_node_put(np); + + /* Now look for the smu-interrupt GPIO */ + do { + np = of_find_node_by_name(NULL, "smu-interrupt"); + if (np == NULL) + break; + data = (u32 *)get_property(np, "reg", NULL); + if (data == NULL) { + of_node_put(np); + break; + } + smu->msg = *data; + if (smu->msg < 0x50) + smu->msg += 0x50; + if (np->n_intrs > 0) + smu->msg_irq = np->intrs[0].line; + of_node_put(np); + } 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 @@ -362,3 +537,744 @@ int smu_init (void) return -ENXIO; } + + +static int smu_late_init(void) +{ + if (!smu) + return 0; + + /* + * Try to request the interrupts + */ + + if (smu->db_irq != NO_IRQ) { + if (request_irq(smu->db_irq, smu_db_intr, + SA_SHIRQ, "SMU doorbell", smu) < 0) { + printk(KERN_WARNING "SMU: can't " + "request interrupt %d\n", + smu->db_irq); + smu->db_irq = NO_IRQ; + } + } + + if (smu->msg_irq != NO_IRQ) { + if (request_irq(smu->msg_irq, smu_msg_intr, + SA_SHIRQ, "SMU message", smu) < 0) { + printk(KERN_WARNING "SMU: can't " + "request interrupt %d\n", + smu->msg_irq); + smu->msg_irq = NO_IRQ; + } + } + + return 0; +} +arch_initcall(smu_late_init); + +/* + * sysfs visibility + */ + +static void smu_expose_childs(void *unused) +{ + struct device_node *np; + + for (np = NULL; (np = of_get_next_child(smu->of_node, np)) != NULL;) { + if (device_is_compatible(np, "smu-i2c")) { + char name[32]; + u32 *reg = (u32 *)get_property(np, "reg", NULL); + + if (reg == NULL) + continue; + sprintf(name, "smu-i2c-%02x", *reg); + of_platform_device_create(np, name, &smu->of_dev->dev); + } + if (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, NULL); + +static int smu_platform_probe(struct of_device* dev, + const struct of_device_id *match) +{ + 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 struct of_device_id smu_platform_match[] = +{ + { + .type = "smu", + }, + {}, +}; + +static struct of_platform_driver smu_of_platform_driver = +{ + .name = "smu", + .match_table = smu_platform_match, + .probe = smu_platform_probe, +}; + +static int __init smu_init_sysfs(void) +{ + int rc; + + /* + * Due to sysfs bogosity, a sysdev is not a real device, so + * we should in fact create both if we want sysdev semantics + * for power management. + * 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 + */ + rc = of_register_driver(&smu_of_platform_driver); + return 0; +} + +device_initcall(smu_init_sysfs); + +struct of_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(unsigned long data) +{ + struct smu_i2c_cmd *cmd = (struct smu_i2c_cmd *)data; + + DPRINTK("SMU: i2c failure, requeuing...\n"); + + /* requeue command simply by resetting reply_len */ + cmd->pdata[0] = 0xff; + cmd->scmd.reply_len = 0x10; + 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"); + smu->i2c_timer.function = smu_i2c_retry; + smu->i2c_timer.data = (unsigned long)cmd; + smu->i2c_timer.expires = jiffies + msecs_to_jiffies(5); + add_timer(&smu->i2c_timer); + 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 = 0x10; + 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 = 0x10; + 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: + memset(&cmd->info.sublen, 0, 4); + break; + case SMU_I2C_TRANSFER_COMBINED: + cmd->info.devaddr &= 0xfe; + 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(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(comp); + struct smu_simple_cmd cmd; + unsigned int addr, len, tlen; + struct smu_sdbp_header *hdr; + struct property *prop; + + /* First query the partition info */ + smu_queue_simple(&cmd, SMU_CMD_PARTITION_COMMAND, 2, + smu_done_complete, &comp, + SMU_CMD_PARTITION_LATEST, id); + wait_for_completion(&comp); + + /* 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 = kcalloc(tlen, 1, 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 = (unsigned char *)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 (prom_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 + */ +struct smu_sdbp_header *__smu_get_sdb_partition(int id, unsigned int *size, + int interruptible) +{ + char pname[32]; + struct smu_sdbp_header *part; + + if (!smu) + return NULL; + + sprintf(pname, "sdb-partition-%02x", id); + + if (interruptible) { + int rc; + rc = down_interruptible(&smu_part_access); + if (rc) + return ERR_PTR(rc); + } else + down(&smu_part_access); + + part = (struct smu_sdbp_header *)get_property(smu->of_node, + pname, size); + if (part == NULL) { + part = smu_create_sdb_partition(id); + if (part != NULL && size) + *size = part->len << 2; + } + up(&smu_part_access); + return part; +} + +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 = kmalloc(sizeof(struct smu_private), GFP_KERNEL); + if (pp == 0) + return -ENOMEM; + memset(pp, 0, sizeof(struct smu_private)); + spin_lock_init(&pp->lock); + pp->mode = smu_file_commands; + init_waitqueue_head(&pp->wait); + + spin_lock_irqsave(&smu_clist_lock, flags); + list_add(&pp->list, &smu_clist); + spin_unlock_irqrestore(&smu_clist_lock, flags); + file->private_data = pp; + + 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) { + 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) + 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 unsigned int smu_fpoll(struct file *file, poll_table *wait) +{ + struct smu_private *pp = file->private_data; + unsigned int mask = 0; + unsigned long flags; + + if (pp == 0) + 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 |= POLLIN; + 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 == 0) + 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_lock_irqsave(&pp->lock, flags); + schedule(); + spin_unlock_irqrestore(&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 struct file_operations smu_device_fops = { + .llseek = no_llseek, + .read = smu_read, + .write = smu_write, + .poll = smu_fpoll, + .open = smu_open, + .release = smu_release, +}; + +static struct miscdevice pmu_device = { + MISC_DYNAMIC_MINOR, "smu", &smu_device_fops +}; + +static int smu_device_init(void) +{ + if (!smu) + return -ENODEV; + if (misc_register(&pmu_device) < 0) + printk(KERN_ERR "via-pmu: cannot register misc device.\n"); + return 0; +} +device_initcall(smu_device_init); diff --git a/drivers/macintosh/therm_adt746x.c b/drivers/macintosh/therm_adt746x.c index c9ca1118e449..f38696622eb4 100644 --- a/drivers/macintosh/therm_adt746x.c +++ b/drivers/macintosh/therm_adt746x.c @@ -599,7 +599,7 @@ thermostat_init(void) sensor_location[2] = "?"; } - of_dev = of_platform_device_create(np, "temperatures"); + of_dev = of_platform_device_create(np, "temperatures", NULL); if (of_dev == NULL) { printk(KERN_ERR "Can't register temperatures device !\n"); diff --git a/drivers/macintosh/therm_pm72.c b/drivers/macintosh/therm_pm72.c index 703e31973314..3fc8cdd94c3d 100644 --- a/drivers/macintosh/therm_pm72.c +++ b/drivers/macintosh/therm_pm72.c @@ -1678,10 +1678,9 @@ static int main_control_loop(void *x) } // FIXME: Deal with signals - set_current_state(TASK_INTERRUPTIBLE); elapsed = jiffies - start; if (elapsed < HZ) - schedule_timeout(HZ - elapsed); + schedule_timeout_interruptible(HZ - elapsed); } out: @@ -2051,7 +2050,7 @@ static int __init therm_pm72_init(void) return -ENODEV; } } - of_dev = of_platform_device_create(np, "temperature"); + of_dev = of_platform_device_create(np, "temperature", NULL); if (of_dev == NULL) { printk(KERN_ERR "Can't register FCU platform device !\n"); return -ENODEV; diff --git a/drivers/macintosh/therm_windtunnel.c b/drivers/macintosh/therm_windtunnel.c index cbb72eb0426d..6aaa1df1a64e 100644 --- a/drivers/macintosh/therm_windtunnel.c +++ b/drivers/macintosh/therm_windtunnel.c @@ -504,7 +504,7 @@ g4fan_init( void ) } if( !(np=of_find_node_by_name(NULL, "fan")) ) return -ENODEV; - x.of_dev = of_platform_device_create( np, "temperature" ); + x.of_dev = of_platform_device_create(np, "temperature", NULL); of_node_put( np ); if( !x.of_dev ) { diff --git a/drivers/macintosh/via-cuda.c b/drivers/macintosh/via-cuda.c index 417deb5de108..d843a6c9c6df 100644 --- a/drivers/macintosh/via-cuda.c +++ b/drivers/macintosh/via-cuda.c @@ -37,7 +37,6 @@ static DEFINE_SPINLOCK(cuda_lock); #ifdef CONFIG_MAC #define CUDA_IRQ IRQ_MAC_ADB -#define __openfirmware #define eieio() #else #define CUDA_IRQ vias->intrs[0].line diff --git a/drivers/macintosh/via-pmu.c b/drivers/macintosh/via-pmu.c index 645a2e5c70ab..564043508569 100644 --- a/drivers/macintosh/via-pmu.c +++ b/drivers/macintosh/via-pmu.c @@ -155,10 +155,10 @@ static spinlock_t pmu_lock; static u8 pmu_intr_mask; static int pmu_version; static int drop_interrupts; -#ifdef CONFIG_PM +#if defined(CONFIG_PM) && defined(CONFIG_PPC32) static int option_lid_wakeup = 1; static int sleep_in_progress; -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM && CONFIG_PPC32 */ static unsigned long async_req_locks; static unsigned int pmu_irq_stats[11]; @@ -244,7 +244,7 @@ int pmu_wink(struct adb_request *req); * - the number of response bytes which the PMU will return, or * -1 if it will send a length byte. */ -static const s8 pmu_data_len[256][2] __openfirmwaredata = { +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}, @@ -295,7 +295,7 @@ static struct backlight_controller pmu_backlight_controller = { }; #endif /* CONFIG_PMAC_BACKLIGHT */ -int __openfirmware +int find_via_pmu(void) { if (via != 0) @@ -374,7 +374,7 @@ find_via_pmu(void) } #ifdef CONFIG_ADB -static int __openfirmware +static int pmu_probe(void) { return vias == NULL? -ENODEV: 0; @@ -405,7 +405,7 @@ static int __init via_pmu_start(void) bright_req_2.complete = 1; batt_req.complete = 1; -#ifdef CONFIG_PPC32 +#if defined(CONFIG_PPC32) && !defined(CONFIG_PPC_MERGE) if (pmu_kind == PMU_KEYLARGO_BASED) openpic_set_irq_priority(vias->intrs[0].line, OPENPIC_PRIORITY_DEFAULT + 1); @@ -520,7 +520,7 @@ static int __init via_pmu_dev_init(void) device_initcall(via_pmu_dev_init); -static int __openfirmware +static int init_pmu(void) { int timeout; @@ -588,17 +588,6 @@ pmu_get_model(void) return pmu_kind; } -#ifndef CONFIG_PPC64 -static inline void wakeup_decrementer(void) -{ - set_dec(tb_ticks_per_jiffy); - /* No currently-supported powerbook has a 601, - * so use get_tbl, not native - */ - last_jiffy_stamp(0) = tb_last_stamp = get_tbl(); -} -#endif - static void pmu_set_server_mode(int server_mode) { struct adb_request req; @@ -625,7 +614,7 @@ static void pmu_set_server_mode(int server_mode) /* This new version of the code for 2400/3400/3500 powerbooks * is inspired from the implementation in gkrellm-pmu */ -static void __pmac +static void done_battery_state_ohare(struct adb_request* req) { /* format: @@ -713,7 +702,7 @@ done_battery_state_ohare(struct adb_request* req) clear_bit(0, &async_req_locks); } -static void __pmac +static void done_battery_state_smart(struct adb_request* req) { /* format: @@ -791,7 +780,7 @@ done_battery_state_smart(struct adb_request* req) clear_bit(0, &async_req_locks); } -static void __pmac +static void query_battery_state(void) { if (test_and_set_bit(0, &async_req_locks)) @@ -804,7 +793,7 @@ query_battery_state(void) 2, PMU_SMART_BATTERY_STATE, pmu_cur_battery+1); } -static int __pmac +static int proc_get_info(char *page, char **start, off_t off, int count, int *eof, void *data) { @@ -819,7 +808,7 @@ proc_get_info(char *page, char **start, off_t off, return p - page; } -static int __pmac +static int proc_get_irqstats(char *page, char **start, off_t off, int count, int *eof, void *data) { @@ -846,7 +835,7 @@ proc_get_irqstats(char *page, char **start, off_t off, return p - page; } -static int __pmac +static int proc_get_batt(char *page, char **start, off_t off, int count, int *eof, void *data) { @@ -870,13 +859,13 @@ proc_get_batt(char *page, char **start, off_t off, return p - page; } -static int __pmac +static int proc_read_options(char *page, char **start, off_t off, int count, int *eof, void *data) { char *p = page; -#ifdef CONFIG_PM +#if defined(CONFIG_PM) && defined(CONFIG_PPC32) if (pmu_kind == PMU_KEYLARGO_BASED && pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,0,-1) >= 0) p += sprintf(p, "lid_wakeup=%d\n", option_lid_wakeup); @@ -887,7 +876,7 @@ proc_read_options(char *page, char **start, off_t off, return p - page; } -static int __pmac +static int proc_write_options(struct file *file, const char __user *buffer, unsigned long count, void *data) { @@ -917,7 +906,7 @@ proc_write_options(struct file *file, const char __user *buffer, *(val++) = 0; while(*val == ' ') val++; -#ifdef CONFIG_PM +#if defined(CONFIG_PM) && 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")) @@ -934,7 +923,7 @@ proc_write_options(struct file *file, const char __user *buffer, #ifdef CONFIG_ADB /* Send an ADB command */ -static int __pmac +static int pmu_send_request(struct adb_request *req, int sync) { int i, ret; @@ -1014,7 +1003,7 @@ pmu_send_request(struct adb_request *req, int sync) } /* Enable/disable autopolling */ -static int __pmac +static int pmu_adb_autopoll(int devs) { struct adb_request req; @@ -1037,7 +1026,7 @@ pmu_adb_autopoll(int devs) } /* Reset the ADB bus */ -static int __pmac +static int pmu_adb_reset_bus(void) { struct adb_request req; @@ -1072,7 +1061,7 @@ pmu_adb_reset_bus(void) #endif /* CONFIG_ADB */ /* Construct and send a pmu request */ -int __openfirmware +int pmu_request(struct adb_request *req, void (*done)(struct adb_request *), int nbytes, ...) { @@ -1098,7 +1087,7 @@ pmu_request(struct adb_request *req, void (*done)(struct adb_request *), return pmu_queue_request(req); } -int __pmac +int pmu_queue_request(struct adb_request *req) { unsigned long flags; @@ -1190,7 +1179,7 @@ pmu_done(struct adb_request *req) (*done)(req); } -static void __pmac +static void pmu_start(void) { struct adb_request *req; @@ -1214,7 +1203,7 @@ pmu_start(void) send_byte(req->data[0]); } -void __openfirmware +void pmu_poll(void) { if (!via) @@ -1224,7 +1213,7 @@ pmu_poll(void) via_pmu_interrupt(0, NULL, NULL); } -void __openfirmware +void pmu_poll_adb(void) { if (!via) @@ -1239,7 +1228,7 @@ pmu_poll_adb(void) || req_awaiting_reply)); } -void __openfirmware +void pmu_wait_complete(struct adb_request *req) { if (!via) @@ -1253,7 +1242,7 @@ pmu_wait_complete(struct adb_request *req) * This is done to avoid spurrious shutdowns when we know we'll have * interrupts switched off for a long time */ -void __openfirmware +void pmu_suspend(void) { unsigned long flags; @@ -1293,7 +1282,7 @@ pmu_suspend(void) } while (1); } -void __openfirmware +void pmu_resume(void) { unsigned long flags; @@ -1323,7 +1312,7 @@ pmu_resume(void) } /* Interrupt data could be the result data from an ADB cmd */ -static void __pmac +static void pmu_handle_data(unsigned char *data, int len, struct pt_regs *regs) { unsigned char ints, pirq; @@ -1435,7 +1424,7 @@ next: goto next; } -static struct adb_request* __pmac +static struct adb_request* pmu_sr_intr(struct pt_regs *regs) { struct adb_request *req; @@ -1541,7 +1530,7 @@ pmu_sr_intr(struct pt_regs *regs) return NULL; } -static irqreturn_t __pmac +static irqreturn_t via_pmu_interrupt(int irq, void *arg, struct pt_regs *regs) { unsigned long flags; @@ -1629,7 +1618,7 @@ no_free_slot: return IRQ_RETVAL(handled); } -void __pmac +void pmu_unlock(void) { unsigned long flags; @@ -1642,7 +1631,7 @@ pmu_unlock(void) } -static irqreturn_t __pmac +static irqreturn_t gpio1_interrupt(int irq, void *arg, struct pt_regs *regs) { unsigned long flags; @@ -1663,12 +1652,12 @@ gpio1_interrupt(int irq, void *arg, struct pt_regs *regs) } #ifdef CONFIG_PMAC_BACKLIGHT -static int backlight_to_bright[] __pmacdata = { +static int backlight_to_bright[] = { 0x7f, 0x46, 0x42, 0x3e, 0x3a, 0x36, 0x32, 0x2e, 0x2a, 0x26, 0x22, 0x1e, 0x1a, 0x16, 0x12, 0x0e }; -static int __openfirmware +static int pmu_set_backlight_enable(int on, int level, void* data) { struct adb_request req; @@ -1688,7 +1677,7 @@ pmu_set_backlight_enable(int on, int level, void* data) return 0; } -static void __openfirmware +static void pmu_bright_complete(struct adb_request *req) { if (req == &bright_req_1) @@ -1697,7 +1686,7 @@ pmu_bright_complete(struct adb_request *req) clear_bit(2, &async_req_locks); } -static int __openfirmware +static int pmu_set_backlight_level(int level, void* data) { if (vias == NULL) @@ -1717,7 +1706,7 @@ pmu_set_backlight_level(int level, void* data) } #endif /* CONFIG_PMAC_BACKLIGHT */ -void __pmac +void pmu_enable_irled(int on) { struct adb_request req; @@ -1732,7 +1721,7 @@ pmu_enable_irled(int on) pmu_wait_complete(&req); } -void __pmac +void pmu_restart(void) { struct adb_request req; @@ -1757,7 +1746,7 @@ pmu_restart(void) ; } -void __pmac +void pmu_shutdown(void) { struct adb_request req; @@ -2064,6 +2053,7 @@ pmu_register_sleep_notifier(struct pmu_sleep_notifier *n) __list_add(&n->list, list->prev, list); return 0; } +EXPORT_SYMBOL(pmu_register_sleep_notifier); int pmu_unregister_sleep_notifier(struct pmu_sleep_notifier* n) @@ -2074,9 +2064,13 @@ pmu_unregister_sleep_notifier(struct pmu_sleep_notifier* n) n->list.next = NULL; return 0; } +EXPORT_SYMBOL(pmu_unregister_sleep_notifier); +#endif /* CONFIG_PM */ + +#if defined(CONFIG_PM) && defined(CONFIG_PPC32) /* Sleep is broadcast last-to-first */ -static int __pmac +static int broadcast_sleep(int when, int fallback) { int ret = PBOOK_SLEEP_OK; @@ -2101,7 +2095,7 @@ broadcast_sleep(int when, int fallback) } /* Wake is broadcast first-to-last */ -static int __pmac +static int broadcast_wake(void) { int ret = PBOOK_SLEEP_OK; @@ -2132,7 +2126,7 @@ static struct pci_save { } *pbook_pci_saves; static int pbook_npci_saves; -static void __pmac +static void pbook_alloc_pci_save(void) { int npci; @@ -2149,7 +2143,7 @@ pbook_alloc_pci_save(void) pbook_npci_saves = npci; } -static void __pmac +static void pbook_free_pci_save(void) { if (pbook_pci_saves == NULL) @@ -2159,7 +2153,7 @@ pbook_free_pci_save(void) pbook_npci_saves = 0; } -static void __pmac +static void pbook_pci_save(void) { struct pci_save *ps = pbook_pci_saves; @@ -2190,7 +2184,7 @@ pbook_pci_save(void) * during boot, it will be in the pci dev list. If it's disabled at this point * (and it will probably be), then you can't access it's config space. */ -static void __pmac +static void pbook_pci_restore(void) { u16 cmd; @@ -2238,7 +2232,7 @@ pbook_pci_restore(void) #ifdef DEBUG_SLEEP /* N.B. This doesn't work on the 3400 */ -void __pmac +void pmu_blink(int n) { struct adb_request req; @@ -2277,9 +2271,9 @@ pmu_blink(int n) * Put the powerbook to sleep. */ -static u32 save_via[8] __pmacdata; +static u32 save_via[8]; -static void __pmac +static void save_via_state(void) { save_via[0] = in_8(&via[ANH]); @@ -2291,7 +2285,7 @@ save_via_state(void) save_via[6] = in_8(&via[T1CL]); save_via[7] = in_8(&via[T1CH]); } -static void __pmac +static void restore_via_state(void) { out_8(&via[ANH], save_via[0]); @@ -2307,7 +2301,7 @@ restore_via_state(void) out_8(&via[IER], IER_SET | SR_INT | CB1_INT); } -static int __pmac +static int pmac_suspend_devices(void) { int ret; @@ -2397,7 +2391,7 @@ pmac_suspend_devices(void) return 0; } -static int __pmac +static int pmac_wakeup_devices(void) { mdelay(100); @@ -2436,7 +2430,7 @@ pmac_wakeup_devices(void) #define GRACKLE_NAP (1<<4) #define GRACKLE_SLEEP (1<<3) -int __pmac +int powerbook_sleep_grackle(void) { unsigned long save_l2cr; @@ -2520,7 +2514,7 @@ powerbook_sleep_grackle(void) return 0; } -static int __pmac +static int powerbook_sleep_Core99(void) { unsigned long save_l2cr; @@ -2620,7 +2614,7 @@ powerbook_sleep_Core99(void) #define PB3400_MEM_CTRL 0xf8000000 #define PB3400_MEM_CTRL_SLEEP 0x70 -static int __pmac +static int powerbook_sleep_3400(void) { int ret, i, x; @@ -2675,10 +2669,10 @@ powerbook_sleep_3400(void) asleep = 1; /* Put the CPU into sleep mode */ - asm volatile("mfspr %0,1008" : "=r" (hid0) :); + hid0 = mfspr(SPRN_HID0); hid0 = (hid0 & ~(HID0_NAP | HID0_DOZE)) | HID0_SLEEP; - asm volatile("mtspr 1008,%0" : : "r" (hid0)); - _nmask_and_or_msr(0, MSR_POW | MSR_EE); + mtspr(SPRN_HID0, hid0); + mtmsr(mfmsr() | MSR_POW | MSR_EE); udelay(10); /* OK, we're awake again, start restoring things */ @@ -2698,7 +2692,7 @@ powerbook_sleep_3400(void) return 0; } -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM && CONFIG_PPC32 */ /* * Support for /dev/pmu device @@ -2720,9 +2714,9 @@ struct pmu_private { }; static LIST_HEAD(all_pmu_pvt); -static DEFINE_SPINLOCK(all_pvt_lock __pmacdata); +static DEFINE_SPINLOCK(all_pvt_lock); -static void __pmac +static void pmu_pass_intr(unsigned char *data, int len) { struct pmu_private *pp; @@ -2751,7 +2745,7 @@ pmu_pass_intr(unsigned char *data, int len) spin_unlock_irqrestore(&all_pvt_lock, flags); } -static int __pmac +static int pmu_open(struct inode *inode, struct file *file) { struct pmu_private *pp; @@ -2773,7 +2767,7 @@ pmu_open(struct inode *inode, struct file *file) return 0; } -static ssize_t __pmac +static ssize_t pmu_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { @@ -2825,14 +2819,14 @@ pmu_read(struct file *file, char __user *buf, return ret; } -static ssize_t __pmac +static ssize_t pmu_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { return 0; } -static unsigned int __pmac +static unsigned int pmu_fpoll(struct file *filp, poll_table *wait) { struct pmu_private *pp = filp->private_data; @@ -2849,7 +2843,7 @@ pmu_fpoll(struct file *filp, poll_table *wait) return mask; } -static int __pmac +static int pmu_release(struct inode *inode, struct file *file) { struct pmu_private *pp = file->private_data; @@ -2874,8 +2868,7 @@ pmu_release(struct inode *inode, struct file *file) return 0; } -/* Note: removed __openfirmware here since it causes link errors */ -static int __pmac +static int pmu_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) { @@ -2883,7 +2876,7 @@ pmu_ioctl(struct inode * inode, struct file *filp, int error = -EINVAL; switch (cmd) { -#ifdef CONFIG_PM +#if defined(CONFIG_PM) && defined(CONFIG_PPC32) case PMU_IOC_SLEEP: if (!capable(CAP_SYS_ADMIN)) return -EACCES; @@ -2911,7 +2904,7 @@ pmu_ioctl(struct inode * inode, struct file *filp, return put_user(0, argp); else return put_user(1, argp); -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM && CONFIG_PPC32 */ #ifdef CONFIG_PMAC_BACKLIGHT /* Backlight should have its own device or go via @@ -2957,7 +2950,7 @@ pmu_ioctl(struct inode * inode, struct file *filp, return error; } -static struct file_operations pmu_device_fops __pmacdata = { +static struct file_operations pmu_device_fops = { .read = pmu_read, .write = pmu_write, .poll = pmu_fpoll, @@ -2966,7 +2959,7 @@ static struct file_operations pmu_device_fops __pmacdata = { .release = pmu_release, }; -static struct miscdevice pmu_device __pmacdata = { +static struct miscdevice pmu_device = { PMU_MINOR, "pmu", &pmu_device_fops }; @@ -2982,7 +2975,7 @@ device_initcall(pmu_device_init); #ifdef DEBUG_SLEEP -static inline void __pmac +static inline void polled_handshake(volatile unsigned char __iomem *via) { via[B] &= ~TREQ; eieio(); @@ -2993,7 +2986,7 @@ polled_handshake(volatile unsigned char __iomem *via) ; } -static inline void __pmac +static inline void polled_send_byte(volatile unsigned char __iomem *via, int x) { via[ACR] |= SR_OUT | SR_EXT; eieio(); @@ -3001,7 +2994,7 @@ polled_send_byte(volatile unsigned char __iomem *via, int x) polled_handshake(via); } -static inline int __pmac +static inline int polled_recv_byte(volatile unsigned char __iomem *via) { int x; @@ -3013,7 +3006,7 @@ polled_recv_byte(volatile unsigned char __iomem *via) return x; } -int __pmac +int pmu_polled_request(struct adb_request *req) { unsigned long flags; @@ -3059,7 +3052,7 @@ pmu_polled_request(struct adb_request *req) * to do suspend-to-disk. */ -#ifdef CONFIG_PM +#if defined(CONFIG_PM) && defined(CONFIG_PPC32) static int pmu_sys_suspended = 0; @@ -3094,7 +3087,7 @@ static int pmu_sys_resume(struct sys_device *sysdev) return 0; } -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM && CONFIG_PPC32 */ static struct sysdev_class pmu_sysclass = { set_kset_name("pmu"), @@ -3106,10 +3099,10 @@ static struct sys_device device_pmu = { }; static struct sysdev_driver driver_pmu = { -#ifdef CONFIG_PM +#if defined(CONFIG_PM) && defined(CONFIG_PPC32) .suspend = &pmu_sys_suspend, .resume = &pmu_sys_resume, -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM && CONFIG_PPC32 */ }; static int __init init_pmu_sysfs(void) @@ -3147,12 +3140,10 @@ EXPORT_SYMBOL(pmu_i2c_combined_read); EXPORT_SYMBOL(pmu_i2c_stdsub_write); EXPORT_SYMBOL(pmu_i2c_simple_read); EXPORT_SYMBOL(pmu_i2c_simple_write); -#ifdef CONFIG_PM -EXPORT_SYMBOL(pmu_register_sleep_notifier); -EXPORT_SYMBOL(pmu_unregister_sleep_notifier); +#if defined(CONFIG_PM) && defined(CONFIG_PPC32) EXPORT_SYMBOL(pmu_enable_irled); EXPORT_SYMBOL(pmu_battery_count); EXPORT_SYMBOL(pmu_batteries); EXPORT_SYMBOL(pmu_power_flags); -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM && CONFIG_PPC32 */ diff --git a/drivers/macintosh/via-pmu68k.c b/drivers/macintosh/via-pmu68k.c index 820dc52e30bc..6f80d76ac17c 100644 --- a/drivers/macintosh/via-pmu68k.c +++ b/drivers/macintosh/via-pmu68k.c @@ -835,7 +835,7 @@ static struct pci_save { } *pbook_pci_saves; static int n_pbook_pci_saves; -static inline void __openfirmware +static inline void pbook_pci_save(void) { int npci; @@ -863,7 +863,7 @@ pbook_pci_save(void) } } -static inline void __openfirmware +static inline void pbook_pci_restore(void) { u16 cmd; @@ -902,7 +902,7 @@ pbook_pci_restore(void) #define IRQ_ENABLE ((unsigned int *)0xf3000024) #define MEM_CTRL ((unsigned int *)0xf8000070) -int __openfirmware powerbook_sleep(void) +int powerbook_sleep(void) { int ret, i, x; static int save_backlight; @@ -1001,25 +1001,24 @@ int __openfirmware powerbook_sleep(void) /* * Support for /dev/pmu device */ -static int __openfirmware pmu_open(struct inode *inode, struct file *file) +static int pmu_open(struct inode *inode, struct file *file) { return 0; } -static ssize_t __openfirmware pmu_read(struct file *file, char *buf, +static ssize_t pmu_read(struct file *file, char *buf, size_t count, loff_t *ppos) { return 0; } -static ssize_t __openfirmware pmu_write(struct file *file, const char *buf, +static ssize_t pmu_write(struct file *file, const char *buf, size_t count, loff_t *ppos) { return 0; } -/* Note: removed __openfirmware here since it causes link errors */ -static int /*__openfirmware*/ pmu_ioctl(struct inode * inode, struct file *filp, +static int pmu_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) { int error; diff --git a/drivers/macintosh/windfarm.h b/drivers/macintosh/windfarm.h new file mode 100644 index 000000000000..3f0cb0312ea3 --- /dev/null +++ b/drivers/macintosh/windfarm.h @@ -0,0 +1,131 @@ +/* + * Windfarm PowerMac thermal control. + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + */ + +#ifndef __WINDFARM_H__ +#define __WINDFARM_H__ + +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/notifier.h> + +/* Display a 16.16 fixed point value */ +#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16) + +/* + * Control objects + */ + +struct wf_control; + +struct wf_control_ops { + int (*set_value)(struct wf_control *ct, s32 val); + int (*get_value)(struct wf_control *ct, s32 *val); + s32 (*get_min)(struct wf_control *ct); + s32 (*get_max)(struct wf_control *ct); + void (*release)(struct wf_control *ct); + struct module *owner; +}; + +struct wf_control { + struct list_head link; + struct wf_control_ops *ops; + char *name; + int type; + struct kref ref; +}; + +#define WF_CONTROL_TYPE_GENERIC 0 +#define WF_CONTROL_RPM_FAN 1 +#define WF_CONTROL_PWM_FAN 2 + + +/* Note about lifetime rules: wf_register_control() will initialize + * the kref and wf_unregister_control will decrement it, thus the + * object creating/disposing a given control shouldn't assume it + * still exists after wf_unregister_control has been called. + * wf_find_control will inc the refcount for you + */ +extern int wf_register_control(struct wf_control *ct); +extern void wf_unregister_control(struct wf_control *ct); +extern struct wf_control * wf_find_control(const char *name); +extern int wf_get_control(struct wf_control *ct); +extern void wf_put_control(struct wf_control *ct); + +static inline int wf_control_set_max(struct wf_control *ct) +{ + s32 vmax = ct->ops->get_max(ct); + return ct->ops->set_value(ct, vmax); +} + +static inline int wf_control_set_min(struct wf_control *ct) +{ + s32 vmin = ct->ops->get_min(ct); + return ct->ops->set_value(ct, vmin); +} + +/* + * Sensor objects + */ + +struct wf_sensor; + +struct wf_sensor_ops { + int (*get_value)(struct wf_sensor *sr, s32 *val); + void (*release)(struct wf_sensor *sr); + struct module *owner; +}; + +struct wf_sensor { + struct list_head link; + struct wf_sensor_ops *ops; + char *name; + struct kref ref; +}; + +/* Same lifetime rules as controls */ +extern int wf_register_sensor(struct wf_sensor *sr); +extern void wf_unregister_sensor(struct wf_sensor *sr); +extern struct wf_sensor * wf_find_sensor(const char *name); +extern int wf_get_sensor(struct wf_sensor *sr); +extern void wf_put_sensor(struct wf_sensor *sr); + +/* For use by clients. Note that we are a bit racy here since + * notifier_block doesn't have a module owner field. I may fix + * it one day ... + * + * LOCKING NOTE ! + * + * All "events" except WF_EVENT_TICK are called with an internal mutex + * held which will deadlock if you call basically any core routine. + * So don't ! Just take note of the event and do your actual operations + * from the ticker. + * + */ +extern int wf_register_client(struct notifier_block *nb); +extern int wf_unregister_client(struct notifier_block *nb); + +/* Overtemp conditions. Those are refcounted */ +extern void wf_set_overtemp(void); +extern void wf_clear_overtemp(void); +extern int wf_is_overtemp(void); + +#define WF_EVENT_NEW_CONTROL 0 /* param is wf_control * */ +#define WF_EVENT_NEW_SENSOR 1 /* param is wf_sensor * */ +#define WF_EVENT_OVERTEMP 2 /* no param */ +#define WF_EVENT_NORMALTEMP 3 /* overtemp condition cleared */ +#define WF_EVENT_TICK 4 /* 1 second tick */ + +/* Note: If that driver gets more broad use, we could replace the + * simplistic overtemp bits with "environmental conditions". That + * could then be used to also notify of things like fan failure, + * case open, battery conditions, ... + */ + +#endif /* __WINDFARM_H__ */ diff --git a/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c new file mode 100644 index 000000000000..6c2a471ea6c0 --- /dev/null +++ b/drivers/macintosh/windfarm_core.c @@ -0,0 +1,426 @@ +/* + * Windfarm PowerMac thermal control. Core + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + * + * This core code tracks the list of sensors & controls, register + * clients, and holds the kernel thread used for control. + * + * TODO: + * + * Add some information about sensor/control type and data format to + * sensors/controls, and have the sysfs attribute stuff be moved + * generically here instead of hard coded in the platform specific + * driver as it us currently + * + * This however requires solving some annoying lifetime issues with + * sysfs which doesn't seem to have lifetime rules for struct attribute, + * I may have to create full features kobjects for every sensor/control + * instead which is a bit of an overkill imho + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/smp_lock.h> +#include <linux/kthread.h> +#include <linux/jiffies.h> +#include <linux/reboot.h> +#include <linux/device.h> +#include <linux/platform_device.h> + +#include "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 DECLARE_MUTEX(wf_lock); +static struct notifier_block *wf_client_list; +static int wf_client_count; +static unsigned int wf_overtemp; +static unsigned int wf_overtemp_counter; +struct task_struct *wf_thread; + +/* + * Utilities & tick thread + */ + +static inline void wf_notify(int event, void *param) +{ + notifier_call_chain(&wf_client_list, event, param); +} + +int wf_critical_overtemp(void) +{ + static char * critical_overtemp_path = "/sbin/critical_overtemp"; + char *argv[] = { critical_overtemp_path, NULL }; + static char *envp[] = { "HOME=/", + "TERM=linux", + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", + NULL }; + + return call_usermodehelper(critical_overtemp_path, argv, envp, 0); +} +EXPORT_SYMBOL_GPL(wf_critical_overtemp); + +static int wf_thread_func(void *data) +{ + unsigned long next, delay; + + next = jiffies; + + DBG("wf: thread started\n"); + + while(!kthread_should_stop()) { + 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); + + /* there should be no signal, but oh well */ + if (signal_pending(current)) { + printk(KERN_WARNING "windfarm: thread got sigl !\n"); + break; + } + } + + DBG("wf: thread stopped\n"); + + return 0; +} + +static void wf_start_thread(void) +{ + wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm"); + if (IS_ERR(wf_thread)) { + printk(KERN_ERR "windfarm: failed to create thread,err %ld\n", + PTR_ERR(wf_thread)); + wf_thread = NULL; + } +} + + +static void wf_stop_thread(void) +{ + if (wf_thread) + kthread_stop(wf_thread); + wf_thread = NULL; +} + +/* + * Controls + */ + +static void wf_control_release(struct kref *kref) +{ + struct wf_control *ct = container_of(kref, struct wf_control, ref); + + DBG("wf: Deleting control %s\n", ct->name); + + if (ct->ops && ct->ops->release) + ct->ops->release(ct); + else + kfree(ct); +} + +int wf_register_control(struct wf_control *new_ct) +{ + struct wf_control *ct; + + down(&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); + up(&wf_lock); + return -EEXIST; + } + } + kref_init(&new_ct->ref); + list_add(&new_ct->link, &wf_controls); + + DBG("wf: Registered control %s\n", new_ct->name); + + wf_notify(WF_EVENT_NEW_CONTROL, new_ct); + up(&wf_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wf_register_control); + +void wf_unregister_control(struct wf_control *ct) +{ + down(&wf_lock); + list_del(&ct->link); + up(&wf_lock); + + DBG("wf: Unregistered control %s\n", ct->name); + + kref_put(&ct->ref, wf_control_release); +} +EXPORT_SYMBOL_GPL(wf_unregister_control); + +struct wf_control * wf_find_control(const char *name) +{ + struct wf_control *ct; + + down(&wf_lock); + list_for_each_entry(ct, &wf_controls, link) { + if (!strcmp(ct->name, name)) { + if (wf_get_control(ct)) + ct = NULL; + up(&wf_lock); + return ct; + } + } + up(&wf_lock); + return NULL; +} +EXPORT_SYMBOL_GPL(wf_find_control); + +int wf_get_control(struct wf_control *ct) +{ + if (!try_module_get(ct->ops->owner)) + return -ENODEV; + kref_get(&ct->ref); + return 0; +} +EXPORT_SYMBOL_GPL(wf_get_control); + +void wf_put_control(struct wf_control *ct) +{ + struct module *mod = ct->ops->owner; + kref_put(&ct->ref, wf_control_release); + module_put(mod); +} +EXPORT_SYMBOL_GPL(wf_put_control); + + +/* + * Sensors + */ + + +static void wf_sensor_release(struct kref *kref) +{ + struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref); + + DBG("wf: Deleting sensor %s\n", sr->name); + + if (sr->ops && sr->ops->release) + sr->ops->release(sr); + else + kfree(sr); +} + +int wf_register_sensor(struct wf_sensor *new_sr) +{ + struct wf_sensor *sr; + + down(&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); + up(&wf_lock); + return -EEXIST; + } + } + kref_init(&new_sr->ref); + list_add(&new_sr->link, &wf_sensors); + + DBG("wf: Registered sensor %s\n", new_sr->name); + + wf_notify(WF_EVENT_NEW_SENSOR, new_sr); + up(&wf_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wf_register_sensor); + +void wf_unregister_sensor(struct wf_sensor *sr) +{ + down(&wf_lock); + list_del(&sr->link); + up(&wf_lock); + + DBG("wf: Unregistered sensor %s\n", sr->name); + + wf_put_sensor(sr); +} +EXPORT_SYMBOL_GPL(wf_unregister_sensor); + +struct wf_sensor * wf_find_sensor(const char *name) +{ + struct wf_sensor *sr; + + down(&wf_lock); + list_for_each_entry(sr, &wf_sensors, link) { + if (!strcmp(sr->name, name)) { + if (wf_get_sensor(sr)) + sr = NULL; + up(&wf_lock); + return sr; + } + } + up(&wf_lock); + return NULL; +} +EXPORT_SYMBOL_GPL(wf_find_sensor); + +int wf_get_sensor(struct wf_sensor *sr) +{ + if (!try_module_get(sr->ops->owner)) + return -ENODEV; + kref_get(&sr->ref); + return 0; +} +EXPORT_SYMBOL_GPL(wf_get_sensor); + +void wf_put_sensor(struct wf_sensor *sr) +{ + struct module *mod = sr->ops->owner; + kref_put(&sr->ref, wf_sensor_release); + module_put(mod); +} +EXPORT_SYMBOL_GPL(wf_put_sensor); + + +/* + * Client & notification + */ + +int wf_register_client(struct notifier_block *nb) +{ + int rc; + struct wf_control *ct; + struct wf_sensor *sr; + + down(&wf_lock); + rc = 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: + up(&wf_lock); + return rc; +} +EXPORT_SYMBOL_GPL(wf_register_client); + +int wf_unregister_client(struct notifier_block *nb) +{ + down(&wf_lock); + notifier_chain_unregister(&wf_client_list, nb); + wf_client_count++; + if (wf_client_count == 0) + wf_stop_thread(); + up(&wf_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(wf_unregister_client); + +void wf_set_overtemp(void) +{ + down(&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); + } + up(&wf_lock); +} +EXPORT_SYMBOL_GPL(wf_set_overtemp); + +void wf_clear_overtemp(void) +{ + down(&wf_lock); + WARN_ON(wf_overtemp == 0); + if (wf_overtemp == 0) { + up(&wf_lock); + return; + } + wf_overtemp--; + if (wf_overtemp == 0) { + printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n"); + wf_notify(WF_EVENT_NORMALTEMP, NULL); + } + up(&wf_lock); +} +EXPORT_SYMBOL_GPL(wf_clear_overtemp); + +int wf_is_overtemp(void) +{ + return (wf_overtemp != 0); +} +EXPORT_SYMBOL_GPL(wf_is_overtemp); + +static struct platform_device wf_platform_device = { + .name = "windfarm", +}; + +static int __init windfarm_core_init(void) +{ + DBG("wf: core loaded\n"); + + platform_device_register(&wf_platform_device); + return 0; +} + +static void __exit windfarm_core_exit(void) +{ + BUG_ON(wf_client_count != 0); + + DBG("wf: core unloaded\n"); + + platform_device_unregister(&wf_platform_device); +} + + +module_init(windfarm_core_init); +module_exit(windfarm_core_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Core component of PowerMac thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_cpufreq_clamp.c b/drivers/macintosh/windfarm_cpufreq_clamp.c new file mode 100644 index 000000000000..607dbaca69c9 --- /dev/null +++ b/drivers/macintosh/windfarm_cpufreq_clamp.c @@ -0,0 +1,105 @@ +#include <linux/config.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/cpufreq.h> + +#include "windfarm.h" + +#define VERSION "0.3" + +static int clamped; +static struct wf_control *clamp_control; + +static int clamp_notifier_call(struct notifier_block *self, + unsigned long event, void *data) +{ + struct cpufreq_policy *p = data; + unsigned long max_freq; + + if (event != CPUFREQ_ADJUST) + return 0; + + max_freq = clamped ? (p->cpuinfo.min_freq) : (p->cpuinfo.max_freq); + cpufreq_verify_within_limits(p, 0, max_freq); + + return 0; +} + +static struct notifier_block clamp_notifier = { + .notifier_call = clamp_notifier_call, +}; + +static int clamp_set(struct wf_control *ct, s32 value) +{ + if (value) + printk(KERN_INFO "windfarm: Clamping CPU frequency to " + "minimum !\n"); + else + printk(KERN_INFO "windfarm: CPU frequency unclamped !\n"); + clamped = value; + cpufreq_update_policy(0); + return 0; +} + +static int clamp_get(struct wf_control *ct, s32 *value) +{ + *value = clamped; + return 0; +} + +static s32 clamp_min(struct wf_control *ct) +{ + return 0; +} + +static s32 clamp_max(struct wf_control *ct) +{ + return 1; +} + +static struct wf_control_ops clamp_ops = { + .set_value = clamp_set, + .get_value = clamp_get, + .get_min = clamp_min, + .get_max = clamp_max, + .owner = THIS_MODULE, +}; + +static int __init wf_cpufreq_clamp_init(void) +{ + struct wf_control *clamp; + + clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL); + if (clamp == NULL) + return -ENOMEM; + cpufreq_register_notifier(&clamp_notifier, CPUFREQ_POLICY_NOTIFIER); + clamp->ops = &clamp_ops; + clamp->name = "cpufreq-clamp"; + if (wf_register_control(clamp)) + goto fail; + clamp_control = clamp; + return 0; + fail: + kfree(clamp); + return -ENODEV; +} + +static void __exit wf_cpufreq_clamp_exit(void) +{ + if (clamp_control) + wf_unregister_control(clamp_control); +} + + +module_init(wf_cpufreq_clamp_init); +module_exit(wf_cpufreq_clamp_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("CPU frequency clamp for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c new file mode 100644 index 000000000000..a0a41ad0f2b5 --- /dev/null +++ b/drivers/macintosh/windfarm_lm75_sensor.c @@ -0,0 +1,263 @@ +/* + * Windfarm PowerMac thermal control. LM75 sensor + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> + +#include "windfarm.h" + +#define VERSION "0.1" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +struct wf_lm75_sensor { + int ds1775 : 1; + int inited : 1; + struct i2c_client i2c; + struct wf_sensor sens; +}; +#define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens) +#define i2c_to_lm75(c) container_of(c, struct wf_lm75_sensor, i2c) + +static int wf_lm75_attach(struct i2c_adapter *adapter); +static int wf_lm75_detach(struct i2c_client *client); + +static struct i2c_driver wf_lm75_driver = { + .owner = THIS_MODULE, + .name = "wf_lm75", + .flags = I2C_DF_NOTIFY, + .attach_adapter = wf_lm75_attach, + .detach_client = wf_lm75_detach, +}; + +static int wf_lm75_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_lm75_sensor *lm = wf_to_lm75(sr); + s32 data; + + if (lm->i2c.adapter == NULL) + return -ENODEV; + + /* Init chip if necessary */ + if (!lm->inited) { + u8 cfg_new, cfg = (u8)i2c_smbus_read_byte_data(&lm->i2c, 1); + + DBG("wf_lm75: Initializing %s, cfg was: %02x\n", + sr->name, cfg); + + /* clear shutdown bit, keep other settings as left by + * the firmware for now + */ + cfg_new = cfg & ~0x01; + i2c_smbus_write_byte_data(&lm->i2c, 1, cfg_new); + lm->inited = 1; + + /* If we just powered it up, let's wait 200 ms */ + msleep(200); + } + + /* Read temperature register */ + data = (s32)le16_to_cpu(i2c_smbus_read_word_data(&lm->i2c, 0)); + data <<= 8; + *value = data; + + return 0; +} + +static void wf_lm75_release(struct wf_sensor *sr) +{ + struct wf_lm75_sensor *lm = wf_to_lm75(sr); + + /* check if client is registered and detach from i2c */ + if (lm->i2c.adapter) { + i2c_detach_client(&lm->i2c); + lm->i2c.adapter = NULL; + } + + kfree(lm); +} + +static struct wf_sensor_ops wf_lm75_ops = { + .get_value = wf_lm75_get, + .release = wf_lm75_release, + .owner = THIS_MODULE, +}; + +static struct wf_lm75_sensor *wf_lm75_create(struct i2c_adapter *adapter, + u8 addr, int ds1775, + const char *loc) +{ + struct wf_lm75_sensor *lm; + + DBG("wf_lm75: creating %s device at address 0x%02x\n", + ds1775 ? "ds1775" : "lm75", addr); + + lm = kmalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); + if (lm == NULL) + return NULL; + memset(lm, 0, sizeof(struct wf_lm75_sensor)); + + /* Usual rant about sensor names not beeing very consistent in + * the device-tree, oh well ... + * Add more entries below as you deal with more setups + */ + if (!strcmp(loc, "Hard drive") || !strcmp(loc, "DRIVE BAY")) + lm->sens.name = "hd-temp"; + else + goto fail; + + lm->inited = 0; + lm->sens.ops = &wf_lm75_ops; + lm->ds1775 = ds1775; + lm->i2c.addr = (addr >> 1) & 0x7f; + lm->i2c.adapter = adapter; + lm->i2c.driver = &wf_lm75_driver; + strncpy(lm->i2c.name, lm->sens.name, I2C_NAME_SIZE-1); + + if (i2c_attach_client(&lm->i2c)) { + printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n", + ds1775 ? "ds1775" : "lm75", lm->i2c.name); + goto fail; + } + + if (wf_register_sensor(&lm->sens)) { + i2c_detach_client(&lm->i2c); + goto fail; + } + + return lm; + fail: + kfree(lm); + return NULL; +} + +static int wf_lm75_attach(struct i2c_adapter *adapter) +{ + u8 bus_id; + struct device_node *smu, *bus, *dev; + + /* We currently only deal with LM75's hanging off the SMU + * i2c busses. If we extend that driver to other/older + * machines, we should split this function into SMU-i2c, + * keywest-i2c, PMU-i2c, ... + */ + + DBG("wf_lm75: adapter %s detected\n", adapter->name); + + if (strncmp(adapter->name, "smu-i2c-", 8) != 0) + return 0; + smu = of_find_node_by_type(NULL, "smu"); + if (smu == NULL) + return 0; + + /* Look for the bus in the device-tree */ + bus_id = (u8)simple_strtoul(adapter->name + 8, NULL, 16); + + DBG("wf_lm75: bus ID is %x\n", bus_id); + + /* Look for sensors subdir */ + for (bus = NULL; + (bus = of_get_next_child(smu, bus)) != NULL;) { + u32 *reg; + + if (strcmp(bus->name, "i2c")) + continue; + reg = (u32 *)get_property(bus, "reg", NULL); + if (reg == NULL) + continue; + if (bus_id == *reg) + break; + } + of_node_put(smu); + if (bus == NULL) { + printk(KERN_WARNING "windfarm: SMU i2c bus 0x%x not found" + " in device-tree !\n", bus_id); + return 0; + } + + DBG("wf_lm75: bus found, looking for device...\n"); + + /* Now look for lm75(s) in there */ + for (dev = NULL; + (dev = of_get_next_child(bus, dev)) != NULL;) { + const char *loc = + get_property(dev, "hwsensor-location", NULL); + u32 *reg = (u32 *)get_property(dev, "reg", NULL); + DBG(" dev: %s... (loc: %p, reg: %p)\n", dev->name, loc, reg); + if (loc == NULL || reg == NULL) + continue; + /* real lm75 */ + if (device_is_compatible(dev, "lm75")) + wf_lm75_create(adapter, *reg, 0, loc); + /* ds1775 (compatible, better resolution */ + else if (device_is_compatible(dev, "ds1775")) + wf_lm75_create(adapter, *reg, 1, loc); + } + + of_node_put(bus); + + return 0; +} + +static int wf_lm75_detach(struct i2c_client *client) +{ + struct wf_lm75_sensor *lm = i2c_to_lm75(client); + + DBG("wf_lm75: i2c detatch called for %s\n", lm->sens.name); + + /* Mark client detached */ + lm->i2c.adapter = NULL; + + /* release sensor */ + wf_unregister_sensor(&lm->sens); + + return 0; +} + +static int __init wf_lm75_sensor_init(void) +{ + int rc; + + rc = i2c_add_driver(&wf_lm75_driver); + if (rc < 0) + return rc; + return 0; +} + +static void __exit wf_lm75_sensor_exit(void) +{ + i2c_del_driver(&wf_lm75_driver); +} + + +module_init(wf_lm75_sensor_init); +module_exit(wf_lm75_sensor_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("LM75 sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_pid.c b/drivers/macintosh/windfarm_pid.c new file mode 100644 index 000000000000..2e803b368757 --- /dev/null +++ b/drivers/macintosh/windfarm_pid.c @@ -0,0 +1,145 @@ +/* + * Windfarm PowerMac thermal control. Generic PID helpers + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/module.h> + +#include "windfarm_pid.h" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param) +{ + memset(st, 0, sizeof(struct wf_pid_state)); + st->param = *param; + st->first = 1; +} +EXPORT_SYMBOL_GPL(wf_pid_init); + +s32 wf_pid_run(struct wf_pid_state *st, s32 new_sample) +{ + s64 error, integ, deriv; + s32 target; + int i, hlen = st->param.history_len; + + /* Calculate error term */ + error = new_sample - st->param.itarget; + + /* Get samples into our history buffer */ + if (st->first) { + for (i = 0; i < hlen; i++) { + st->samples[i] = new_sample; + st->errors[i] = error; + } + st->first = 0; + st->index = 0; + } else { + st->index = (st->index + 1) % hlen; + st->samples[st->index] = new_sample; + st->errors[st->index] = error; + } + + /* Calculate integral term */ + for (i = 0, integ = 0; i < hlen; i++) + integ += st->errors[(st->index + hlen - i) % hlen]; + integ *= st->param.interval; + + /* Calculate derivative term */ + deriv = st->errors[st->index] - + st->errors[(st->index + hlen - 1) % hlen]; + deriv /= st->param.interval; + + /* Calculate target */ + target = (s32)((integ * (s64)st->param.gr + deriv * (s64)st->param.gd + + error * (s64)st->param.gp) >> 36); + if (st->param.additive) + target += st->target; + target = max(target, st->param.min); + target = min(target, st->param.max); + st->target = target; + + return st->target; +} +EXPORT_SYMBOL_GPL(wf_pid_run); + +void wf_cpu_pid_init(struct wf_cpu_pid_state *st, + struct wf_cpu_pid_param *param) +{ + memset(st, 0, sizeof(struct wf_cpu_pid_state)); + st->param = *param; + st->first = 1; +} +EXPORT_SYMBOL_GPL(wf_cpu_pid_init); + +s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 new_power, s32 new_temp) +{ + s64 error, integ, deriv, prop; + s32 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 - ((integ >> 20) & 0xffffffff); + 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 = (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); diff --git a/drivers/macintosh/windfarm_pid.h b/drivers/macintosh/windfarm_pid.h new file mode 100644 index 000000000000..a364c2a2499c --- /dev/null +++ b/drivers/macintosh/windfarm_pid.h @@ -0,0 +1,84 @@ +/* + * Windfarm PowerMac thermal control. Generic PID helpers + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + * + * This is a pair of generic PID helpers that can be used by + * control loops. One is the basic PID implementation, the + * other one is more specifically tailored to the loops used + * for CPU control with 2 input sample types (temp and power) + */ + +/* + * *** Simple PID *** + */ + +#define WF_PID_MAX_HISTORY 32 + +/* This parameter array is passed to the PID algorithm. Currently, + * we don't support changing parameters on the fly as it's not needed + * but could be implemented (with necessary adjustment of the history + * buffer + */ +struct wf_pid_param { + int interval; /* Interval between samples in seconds */ + int history_len; /* Size of history buffer */ + int additive; /* 1: target relative to previous value */ + s32 gd, gp, gr; /* PID gains */ + s32 itarget; /* PID input target */ + s32 min,max; /* min and max target values */ +}; + +struct wf_pid_state { + int first; /* first run of the loop */ + int index; /* index of current sample */ + s32 target; /* current target value */ + s32 samples[WF_PID_MAX_HISTORY]; /* samples history buffer */ + s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */ + + struct wf_pid_param param; +}; + +extern void wf_pid_init(struct wf_pid_state *st, struct wf_pid_param *param); +extern s32 wf_pid_run(struct wf_pid_state *st, s32 sample); + + +/* + * *** CPU PID *** + */ + +#define WF_CPU_PID_MAX_HISTORY 32 + +/* This parameter array is passed to the CPU PID algorithm. Currently, + * we don't support changing parameters on the fly as it's not needed + * but could be implemented (with necessary adjustment of the history + * buffer + */ +struct wf_cpu_pid_param { + int interval; /* Interval between samples in seconds */ + int history_len; /* Size of history buffer */ + s32 gd, gp, gr; /* PID gains */ + s32 pmaxadj; /* PID max power adjust */ + s32 ttarget; /* PID input target */ + s32 tmax; /* PID input max */ + s32 min,max; /* min and max target values */ +}; + +struct wf_cpu_pid_state { + int first; /* first run of the loop */ + int index; /* index of current power */ + int tindex; /* index of current temp */ + s32 target; /* current target value */ + s32 powers[WF_PID_MAX_HISTORY]; /* power history buffer */ + s32 errors[WF_PID_MAX_HISTORY]; /* error history buffer */ + s32 temps[2]; /* temp. history buffer */ + + struct wf_cpu_pid_param param; +}; + +extern void wf_cpu_pid_init(struct wf_cpu_pid_state *st, + struct wf_cpu_pid_param *param); +extern s32 wf_cpu_pid_run(struct wf_cpu_pid_state *st, s32 power, s32 temp); diff --git a/drivers/macintosh/windfarm_pm81.c b/drivers/macintosh/windfarm_pm81.c new file mode 100644 index 000000000000..322c74b2687f --- /dev/null +++ b/drivers/macintosh/windfarm_pm81.c @@ -0,0 +1,879 @@ +/* + * Windfarm PowerMac thermal control. iMac G5 + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + * + * The algorithm used is the PID control algorithm, used the same + * way the published Darwin code does, using the same values that + * are present in the Darwin 8.2 snapshot property lists (note however + * that none of the code has been re-used, it's a complete re-implementation + * + * The various control loops found in Darwin config file are: + * + * PowerMac8,1 and PowerMac8,2 + * =========================== + * + * System Fans control loop. Different based on models. In addition to the + * usual PID algorithm, the control loop gets 2 additional pairs of linear + * scaling factors (scale/offsets) expressed as 4.12 fixed point values + * signed offset, unsigned scale) + * + * The targets are modified such as: + * - the linked control (second control) gets the target value as-is + * (typically the drive fan) + * - the main control (first control) gets the target value scaled with + * the first pair of factors, and is then modified as below + * - the value of the target of the CPU Fan control loop is retreived, + * scaled with the second pair of factors, and the max of that and + * the scaled target is applied to the main control. + * + * # model_id: 2 + * controls : system-fan, drive-bay-fan + * sensors : hd-temp + * PID params : G_d = 0x15400000 + * G_p = 0x00200000 + * G_r = 0x000002fd + * History = 2 entries + * Input target = 0x3a0000 + * Interval = 5s + * linear-factors : offset = 0xff38 scale = 0x0ccd + * offset = 0x0208 scale = 0x07ae + * + * # model_id: 3 + * controls : system-fan, drive-bay-fan + * sensors : hd-temp + * PID params : G_d = 0x08e00000 + * G_p = 0x00566666 + * G_r = 0x0000072b + * History = 2 entries + * Input target = 0x350000 + * Interval = 5s + * linear-factors : offset = 0xff38 scale = 0x0ccd + * offset = 0x0000 scale = 0x0000 + * + * # model_id: 5 + * controls : system-fan + * sensors : hd-temp + * PID params : G_d = 0x15400000 + * G_p = 0x00233333 + * G_r = 0x000002fd + * History = 2 entries + * Input target = 0x3a0000 + * Interval = 5s + * linear-factors : offset = 0x0000 scale = 0x1000 + * offset = 0x0091 scale = 0x0bae + * + * CPU Fan control loop. The loop is identical for all models. it + * has an additional pair of scaling factor. This is used to scale the + * systems fan control loop target result (the one before it gets scaled + * by the System Fans control loop itself). Then, the max value of the + * calculated target value and system fan value is sent to the fans + * + * controls : cpu-fan + * sensors : cpu-temp cpu-power + * PID params : From SMU sdb partition + * linear-factors : offset = 0xfb50 scale = 0x1000 + * + * CPU Slew control loop. Not implemented. The cpufreq driver in linux is + * completely separate for now, though we could find a way to link it, either + * as a client reacting to overtemp notifications, or directling monitoring + * the CPU temperature + * + * WARNING ! The CPU control loop requires the CPU tmax for the current + * operating point. However, we currently are completely separated from + * the cpufreq driver and thus do not know what the current operating + * point is. Fortunately, we also do not have any hardware supporting anything + * but operating point 0 at the moment, thus we just peek that value directly + * from the SDB partition. If we ever end up with actually slewing the system + * clock and thus changing operating points, we'll have to find a way to + * communicate with the CPU freq driver; + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/kmod.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" + +#define VERSION "0.4" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 74 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +static int wf_smu_mach_model; /* machine model id */ + +static struct device *wf_smu_dev; + +/* Controls & sensors */ +static struct wf_sensor *sensor_cpu_power; +static struct wf_sensor *sensor_cpu_temp; +static struct wf_sensor *sensor_hd_temp; +static struct wf_control *fan_cpu_main; +static struct wf_control *fan_hd; +static struct wf_control *fan_system; +static struct wf_control *cpufreq_clamp; + +/* Set to kick the control loop into life */ +static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started; + +/* Failure handling.. could be nicer */ +#define FAILURE_FAN 0x01 +#define FAILURE_SENSOR 0x02 +#define FAILURE_OVERTEMP 0x04 + +static unsigned int wf_smu_failure_state; +static int wf_smu_readjust, wf_smu_skipping; + +/* + * ****** System Fans Control Loop ****** + * + */ + +/* Parameters for the System Fans control loop. Parameters + * not in this table such as interval, history size, ... + * are common to all versions and thus hard coded for now. + */ +struct wf_smu_sys_fans_param { + int model_id; + s32 itarget; + s32 gd, gp, gr; + + s16 offset0; + u16 scale0; + s16 offset1; + u16 scale1; +}; + +#define WF_SMU_SYS_FANS_INTERVAL 5 +#define WF_SMU_SYS_FANS_HISTORY_SIZE 2 + +/* State data used by the system fans control loop + */ +struct wf_smu_sys_fans_state { + int ticks; + s32 sys_setpoint; + s32 hd_setpoint; + s16 offset0; + u16 scale0; + s16 offset1; + u16 scale1; + struct wf_pid_state pid; +}; + +/* + * Configs for SMU Sytem Fan control loop + */ +static struct wf_smu_sys_fans_param wf_smu_sys_all_params[] = { + /* Model ID 2 */ + { + .model_id = 2, + .itarget = 0x3a0000, + .gd = 0x15400000, + .gp = 0x00200000, + .gr = 0x000002fd, + .offset0 = 0xff38, + .scale0 = 0x0ccd, + .offset1 = 0x0208, + .scale1 = 0x07ae, + }, + /* Model ID 3 */ + { + .model_id = 2, + .itarget = 0x350000, + .gd = 0x08e00000, + .gp = 0x00566666, + .gr = 0x0000072b, + .offset0 = 0xff38, + .scale0 = 0x0ccd, + .offset1 = 0x0000, + .scale1 = 0x0000, + }, + /* Model ID 5 */ + { + .model_id = 2, + .itarget = 0x3a0000, + .gd = 0x15400000, + .gp = 0x00233333, + .gr = 0x000002fd, + .offset0 = 0x0000, + .scale0 = 0x1000, + .offset1 = 0x0091, + .scale1 = 0x0bae, + }, +}; +#define WF_SMU_SYS_FANS_NUM_CONFIGS ARRAY_SIZE(wf_smu_sys_all_params) + +static struct wf_smu_sys_fans_state *wf_smu_sys_fans; + +/* + * ****** CPU Fans Control Loop ****** + * + */ + + +#define WF_SMU_CPU_FANS_INTERVAL 1 +#define WF_SMU_CPU_FANS_MAX_HISTORY 16 +#define WF_SMU_CPU_FANS_SIBLING_SCALE 0x00001000 +#define WF_SMU_CPU_FANS_SIBLING_OFFSET 0xfffffb50 + +/* State data used by the cpu fans control loop + */ +struct wf_smu_cpu_fans_state { + int ticks; + s32 cpu_setpoint; + s32 scale; + s32 offset; + struct wf_cpu_pid_state pid; +}; + +static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; + + + +/* + * ***** Implementation ***** + * + */ + +static void wf_smu_create_sys_fans(void) +{ + struct wf_smu_sys_fans_param *param = NULL; + struct wf_pid_param pid_param; + int i; + + /* First, locate the params for this model */ + for (i = 0; i < WF_SMU_SYS_FANS_NUM_CONFIGS; i++) + if (wf_smu_sys_all_params[i].model_id == wf_smu_mach_model) { + param = &wf_smu_sys_all_params[i]; + break; + } + + /* No params found, put fans to max */ + if (param == NULL) { + printk(KERN_WARNING "windfarm: System fan config not found " + "for this machine model, max fan speed\n"); + goto fail; + } + + /* Alloc & initialize state */ + wf_smu_sys_fans = kmalloc(sizeof(struct wf_smu_sys_fans_state), + GFP_KERNEL); + if (wf_smu_sys_fans == NULL) { + printk(KERN_WARNING "windfarm: Memory allocation error" + " max fan speed\n"); + goto fail; + } + wf_smu_sys_fans->ticks = 1; + wf_smu_sys_fans->scale0 = param->scale0; + wf_smu_sys_fans->offset0 = param->offset0; + wf_smu_sys_fans->scale1 = param->scale1; + wf_smu_sys_fans->offset1 = param->offset1; + + /* Fill PID params */ + pid_param.gd = param->gd; + pid_param.gp = param->gp; + pid_param.gr = param->gr; + pid_param.interval = WF_SMU_SYS_FANS_INTERVAL; + pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE; + pid_param.itarget = param->itarget; + pid_param.min = fan_system->ops->get_min(fan_system); + pid_param.max = fan_system->ops->get_max(fan_system); + if (fan_hd) { + pid_param.min = + max(pid_param.min,fan_hd->ops->get_min(fan_hd)); + pid_param.max = + min(pid_param.max,fan_hd->ops->get_max(fan_hd)); + } + wf_pid_init(&wf_smu_sys_fans->pid, &pid_param); + + DBG("wf: System Fan control initialized.\n"); + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(pid_param.itarget), pid_param.min, pid_param.max); + return; + + fail: + + if (fan_system) + wf_control_set_max(fan_system); + if (fan_hd) + wf_control_set_max(fan_hd); +} + +static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st) +{ + s32 new_setpoint, temp, scaled, cputarget; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = WF_SMU_SYS_FANS_INTERVAL; + + rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: System Fans tick ! HD temp: %d.%03d\n", + FIX32TOPRINT(temp)); + + if (temp > (st->pid.param.itarget + 0x50000)) + wf_smu_failure_state |= FAILURE_OVERTEMP; + + new_setpoint = wf_pid_run(&st->pid, temp); + + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); + + scaled = ((((s64)new_setpoint) * (s64)st->scale0) >> 12) + st->offset0; + + DBG("wf_smu: scaled setpoint: %d RPM\n", (int)scaled); + + cputarget = wf_smu_cpu_fans ? wf_smu_cpu_fans->pid.target : 0; + cputarget = ((((s64)cputarget) * (s64)st->scale1) >> 12) + st->offset1; + scaled = max(scaled, cputarget); + scaled = max(scaled, st->pid.param.min); + scaled = min(scaled, st->pid.param.max); + + DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)scaled); + + if (st->sys_setpoint == scaled && new_setpoint == st->hd_setpoint) + return; + st->sys_setpoint = scaled; + st->hd_setpoint = new_setpoint; + readjust: + if (fan_system && wf_smu_failure_state == 0) { + rc = fan_system->ops->set_value(fan_system, st->sys_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: Sys fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } + if (fan_hd && wf_smu_failure_state == 0) { + rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: HD fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + +static void wf_smu_create_cpu_fans(void) +{ + struct wf_cpu_pid_param pid_param; + struct smu_sdbp_header *hdr; + struct smu_sdbp_cpupiddata *piddata; + struct smu_sdbp_fvt *fvt; + s32 tmax, tdelta, maxpow, powadj; + + /* First, locate the PID params in SMU SBD */ + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); + if (hdr == 0) { + printk(KERN_WARNING "windfarm: CPU PID fan config not found " + "max fan speed\n"); + goto fail; + } + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; + + /* Get the FVT params for operating point 0 (the only supported one + * for now) in order to get tmax + */ + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); + if (hdr) { + fvt = (struct smu_sdbp_fvt *)&hdr[1]; + tmax = ((s32)fvt->maxtemp) << 16; + } else + tmax = 0x5e0000; /* 94 degree default */ + + /* Alloc & initialize state */ + wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state), + GFP_KERNEL); + if (wf_smu_cpu_fans == NULL) + goto fail; + wf_smu_cpu_fans->ticks = 1; + + wf_smu_cpu_fans->scale = WF_SMU_CPU_FANS_SIBLING_SCALE; + wf_smu_cpu_fans->offset = WF_SMU_CPU_FANS_SIBLING_OFFSET; + + /* Fill PID params */ + pid_param.interval = WF_SMU_CPU_FANS_INTERVAL; + pid_param.history_len = piddata->history_len; + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { + printk(KERN_WARNING "windfarm: History size overflow on " + "CPU control loop (%d)\n", piddata->history_len); + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; + } + pid_param.gd = piddata->gd; + pid_param.gp = piddata->gp; + pid_param.gr = piddata->gr / pid_param.history_len; + + tdelta = ((s32)piddata->target_temp_delta) << 16; + maxpow = ((s32)piddata->max_power) << 16; + powadj = ((s32)piddata->power_adj) << 16; + + pid_param.tmax = tmax; + pid_param.ttarget = tmax - tdelta; + pid_param.pmaxadj = maxpow - powadj; + + pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); + pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); + + wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); + + DBG("wf: CPU Fan control initialized.\n"); + DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), + pid_param.min, pid_param.max); + + return; + + fail: + printk(KERN_WARNING "windfarm: CPU fan config not found\n" + "for this machine model, max fan speed\n"); + + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); +} + +static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) +{ + s32 new_setpoint, temp, power, systarget; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = WF_SMU_CPU_FANS_INTERVAL; + + rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); + if (rc) { + printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n", + FIX32TOPRINT(temp), FIX32TOPRINT(power)); + +#ifdef HACKED_OVERTEMP + if (temp > 0x4a0000) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#else + if (temp > st->pid.param.tmax) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#endif + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); + + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); + + systarget = wf_smu_sys_fans ? wf_smu_sys_fans->pid.target : 0; + systarget = ((((s64)systarget) * (s64)st->scale) >> 12) + + st->offset; + new_setpoint = max(new_setpoint, systarget); + new_setpoint = max(new_setpoint, st->pid.param.min); + new_setpoint = min(new_setpoint, st->pid.param.max); + + DBG("wf_smu: adjusted setpoint: %d RPM\n", (int)new_setpoint); + + if (st->cpu_setpoint == new_setpoint) + return; + st->cpu_setpoint = new_setpoint; + readjust: + if (fan_cpu_main && wf_smu_failure_state == 0) { + rc = fan_cpu_main->ops->set_value(fan_cpu_main, + st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU main fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + + +/* + * ****** Attributes ****** + * + */ + +#define BUILD_SHOW_FUNC_FIX(name, data) \ +static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + ssize_t r; \ + s32 val = 0; \ + data->ops->get_value(data, &val); \ + r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \ + return r; \ +} \ +static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); + + +#define BUILD_SHOW_FUNC_INT(name, data) \ +static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + s32 val = 0; \ + data->ops->get_value(data, &val); \ + return sprintf(buf, "%d", val); \ +} \ +static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); + +BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main); +BUILD_SHOW_FUNC_INT(sys_fan, fan_system); +BUILD_SHOW_FUNC_INT(hd_fan, fan_hd); + +BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp); +BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power); +BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp); + +/* + * ****** Setup / Init / Misc ... ****** + * + */ + +static void wf_smu_tick(void) +{ + unsigned int last_failure = wf_smu_failure_state; + unsigned int new_failure; + + if (!wf_smu_started) { + DBG("wf: creating control loops !\n"); + wf_smu_create_sys_fans(); + wf_smu_create_cpu_fans(); + wf_smu_started = 1; + } + + /* Skipping ticks */ + if (wf_smu_skipping && --wf_smu_skipping) + return; + + wf_smu_failure_state = 0; + if (wf_smu_sys_fans) + wf_smu_sys_fans_tick(wf_smu_sys_fans); + if (wf_smu_cpu_fans) + wf_smu_cpu_fans_tick(wf_smu_cpu_fans); + + wf_smu_readjust = 0; + new_failure = wf_smu_failure_state & ~last_failure; + + /* If entering failure mode, clamp cpufreq and ramp all + * fans to full speed. + */ + if (wf_smu_failure_state && !last_failure) { + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_system) + wf_control_set_max(fan_system); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); + if (fan_hd) + wf_control_set_max(fan_hd); + } + + /* If leaving failure mode, unclamp cpufreq and readjust + * all fans on next iteration + */ + if (!wf_smu_failure_state && last_failure) { + if (cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + wf_smu_readjust = 1; + } + + /* Overtemp condition detected, notify and start skipping a couple + * ticks to let the temperature go down + */ + if (new_failure & FAILURE_OVERTEMP) { + wf_set_overtemp(); + wf_smu_skipping = 2; + } + + /* We only clear the overtemp condition if overtemp is cleared + * _and_ no other failure is present. Since a sensor error will + * clear the overtemp condition (can't measure temperature) at + * the control loop levels, but we don't want to keep it clear + * here in this case + */ + if (new_failure == 0 && last_failure & FAILURE_OVERTEMP) + wf_clear_overtemp(); +} + +static void wf_smu_new_control(struct wf_control *ct) +{ + if (wf_smu_all_controls_ok) + return; + + if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-fan")) { + if (wf_get_control(ct) == 0) { + fan_cpu_main = ct; + device_create_file(wf_smu_dev, &dev_attr_cpu_fan); + } + } + + if (fan_system == NULL && !strcmp(ct->name, "system-fan")) { + if (wf_get_control(ct) == 0) { + fan_system = ct; + device_create_file(wf_smu_dev, &dev_attr_sys_fan); + } + } + + 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; + device_create_file(wf_smu_dev, &dev_attr_hd_fan); + } + } + + 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; + device_create_file(wf_smu_dev, &dev_attr_cpu_power); + } + } + + if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) { + if (wf_get_sensor(sr) == 0) { + sensor_cpu_temp = sr; + device_create_file(wf_smu_dev, &dev_attr_cpu_temp); + } + } + + if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) { + if (wf_get_sensor(sr) == 0) { + sensor_hd_temp = sr; + device_create_file(wf_smu_dev, &dev_attr_hd_temp); + } + } + + 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) +{ + struct smu_sdbp_header *hdr; + + hdr = smu_get_sdb_partition(SMU_SDB_SENSORTREE_ID, NULL); + if (hdr != 0) { + struct smu_sdbp_sensortree *st = + (struct smu_sdbp_sensortree *)&hdr[1]; + wf_smu_mach_model = st->model_id; + } + + printk(KERN_INFO "windfarm: Initializing for iMacG5 model ID %d\n", + wf_smu_mach_model); + + return 0; +} + +static int wf_smu_probe(struct device *ddev) +{ + wf_smu_dev = ddev; + + wf_register_client(&wf_smu_events); + + return 0; +} + +static int wf_smu_remove(struct 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) { + device_remove_file(wf_smu_dev, &dev_attr_cpu_power); + wf_put_sensor(sensor_cpu_power); + } + if (sensor_cpu_temp) { + device_remove_file(wf_smu_dev, &dev_attr_cpu_temp); + wf_put_sensor(sensor_cpu_temp); + } + if (sensor_hd_temp) { + device_remove_file(wf_smu_dev, &dev_attr_hd_temp); + wf_put_sensor(sensor_hd_temp); + } + + /* Release all controls */ + if (fan_cpu_main) { + device_remove_file(wf_smu_dev, &dev_attr_cpu_fan); + wf_put_control(fan_cpu_main); + } + if (fan_hd) { + device_remove_file(wf_smu_dev, &dev_attr_hd_fan); + wf_put_control(fan_hd); + } + if (fan_system) { + device_remove_file(wf_smu_dev, &dev_attr_sys_fan); + wf_put_control(fan_system); + } + if (cpufreq_clamp) + wf_put_control(cpufreq_clamp); + + /* Destroy control loops state structures */ + if (wf_smu_sys_fans) + kfree(wf_smu_sys_fans); + if (wf_smu_cpu_fans) + kfree(wf_smu_cpu_fans); + + wf_smu_dev = NULL; + + return 0; +} + +static struct device_driver wf_smu_driver = { + .name = "windfarm", + .bus = &platform_bus_type, + .probe = wf_smu_probe, + .remove = wf_smu_remove, +}; + + +static int __init wf_smu_init(void) +{ + int rc = -ENODEV; + + if (machine_is_compatible("PowerMac8,1") || + machine_is_compatible("PowerMac8,2")) + rc = wf_init_pm(); + + if (rc == 0) { +#ifdef MODULE + request_module("windfarm_smu_controls"); + request_module("windfarm_smu_sensors"); + request_module("windfarm_lm75_sensor"); + +#endif /* MODULE */ + driver_register(&wf_smu_driver); + } + + return rc; +} + +static void __exit wf_smu_exit(void) +{ + + driver_unregister(&wf_smu_driver); +} + + +module_init(wf_smu_init); +module_exit(wf_smu_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control logic for iMac G5"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c new file mode 100644 index 000000000000..43243cf7410b --- /dev/null +++ b/drivers/macintosh/windfarm_pm91.c @@ -0,0 +1,814 @@ +/* + * Windfarm PowerMac thermal control. SMU based 1 CPU desktop control loops + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + * + * The algorithm used is the PID control algorithm, used the same + * way the published Darwin code does, using the same values that + * are present in the Darwin 8.2 snapshot property lists (note however + * that none of the code has been re-used, it's a complete re-implementation + * + * The various control loops found in Darwin config file are: + * + * PowerMac9,1 + * =========== + * + * Has 3 control loops: CPU fans is similar to PowerMac8,1 (though it doesn't + * try to play with other control loops fans). Drive bay is rather basic PID + * with one sensor and one fan. Slots area is a bit different as the Darwin + * driver is supposed to be capable of working in a special "AGP" mode which + * involves the presence of an AGP sensor and an AGP fan (possibly on the + * AGP card itself). I can't deal with that special mode as I don't have + * access to those additional sensor/fans for now (though ultimately, it would + * be possible to add sensor objects for them) so I'm only implementing the + * basic PCI slot control loop + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/kmod.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" + +#define VERSION "0.4" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 74 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +static struct device *wf_smu_dev; + +/* Controls & sensors */ +static struct wf_sensor *sensor_cpu_power; +static struct wf_sensor *sensor_cpu_temp; +static struct wf_sensor *sensor_hd_temp; +static struct wf_sensor *sensor_slots_power; +static struct wf_control *fan_cpu_main; +static struct wf_control *fan_cpu_second; +static struct wf_control *fan_cpu_third; +static struct wf_control *fan_hd; +static struct wf_control *fan_slots; +static struct wf_control *cpufreq_clamp; + +/* Set to kick the control loop into life */ +static int wf_smu_all_controls_ok, wf_smu_all_sensors_ok, wf_smu_started; + +/* Failure handling.. could be nicer */ +#define FAILURE_FAN 0x01 +#define FAILURE_SENSOR 0x02 +#define FAILURE_OVERTEMP 0x04 + +static unsigned int wf_smu_failure_state; +static int wf_smu_readjust, wf_smu_skipping; + +/* + * ****** CPU Fans Control Loop ****** + * + */ + + +#define WF_SMU_CPU_FANS_INTERVAL 1 +#define WF_SMU_CPU_FANS_MAX_HISTORY 16 + +/* State data used by the cpu fans control loop + */ +struct wf_smu_cpu_fans_state { + int ticks; + s32 cpu_setpoint; + struct wf_cpu_pid_state pid; +}; + +static struct wf_smu_cpu_fans_state *wf_smu_cpu_fans; + + + +/* + * ****** Drive Fan Control Loop ****** + * + */ + +struct wf_smu_drive_fans_state { + int ticks; + s32 setpoint; + struct wf_pid_state pid; +}; + +static struct wf_smu_drive_fans_state *wf_smu_drive_fans; + +/* + * ****** Slots Fan Control Loop ****** + * + */ + +struct wf_smu_slots_fans_state { + int ticks; + s32 setpoint; + struct wf_pid_state pid; +}; + +static struct wf_smu_slots_fans_state *wf_smu_slots_fans; + +/* + * ***** Implementation ***** + * + */ + + +static void wf_smu_create_cpu_fans(void) +{ + struct wf_cpu_pid_param pid_param; + struct smu_sdbp_header *hdr; + struct smu_sdbp_cpupiddata *piddata; + struct smu_sdbp_fvt *fvt; + s32 tmax, tdelta, maxpow, powadj; + + /* First, locate the PID params in SMU SBD */ + hdr = smu_get_sdb_partition(SMU_SDB_CPUPIDDATA_ID, NULL); + if (hdr == 0) { + printk(KERN_WARNING "windfarm: CPU PID fan config not found " + "max fan speed\n"); + goto fail; + } + piddata = (struct smu_sdbp_cpupiddata *)&hdr[1]; + + /* Get the FVT params for operating point 0 (the only supported one + * for now) in order to get tmax + */ + hdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL); + if (hdr) { + fvt = (struct smu_sdbp_fvt *)&hdr[1]; + tmax = ((s32)fvt->maxtemp) << 16; + } else + tmax = 0x5e0000; /* 94 degree default */ + + /* Alloc & initialize state */ + wf_smu_cpu_fans = kmalloc(sizeof(struct wf_smu_cpu_fans_state), + GFP_KERNEL); + if (wf_smu_cpu_fans == NULL) + goto fail; + wf_smu_cpu_fans->ticks = 1; + + /* Fill PID params */ + pid_param.interval = WF_SMU_CPU_FANS_INTERVAL; + pid_param.history_len = piddata->history_len; + if (pid_param.history_len > WF_CPU_PID_MAX_HISTORY) { + printk(KERN_WARNING "windfarm: History size overflow on " + "CPU control loop (%d)\n", piddata->history_len); + pid_param.history_len = WF_CPU_PID_MAX_HISTORY; + } + pid_param.gd = piddata->gd; + pid_param.gp = piddata->gp; + pid_param.gr = piddata->gr / pid_param.history_len; + + tdelta = ((s32)piddata->target_temp_delta) << 16; + maxpow = ((s32)piddata->max_power) << 16; + powadj = ((s32)piddata->power_adj) << 16; + + pid_param.tmax = tmax; + pid_param.ttarget = tmax - tdelta; + pid_param.pmaxadj = maxpow - powadj; + + pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); + pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); + + wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); + + DBG("wf: CPU Fan control initialized.\n"); + DBG(" ttarged=%d.%03d, tmax=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(pid_param.ttarget), FIX32TOPRINT(pid_param.tmax), + pid_param.min, pid_param.max); + + return; + + fail: + printk(KERN_WARNING "windfarm: CPU fan config not found\n" + "for this machine model, max fan speed\n"); + + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); +} + +static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) +{ + s32 new_setpoint, temp, power; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = WF_SMU_CPU_FANS_INTERVAL; + + rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); + if (rc) { + printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: CPU Fans tick ! CPU temp: %d.%03d, power: %d.%03d\n", + FIX32TOPRINT(temp), FIX32TOPRINT(power)); + +#ifdef HACKED_OVERTEMP + if (temp > 0x4a0000) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#else + if (temp > st->pid.param.tmax) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#endif + new_setpoint = wf_cpu_pid_run(&st->pid, power, temp); + + DBG("wf_smu: new_setpoint: %d RPM\n", (int)new_setpoint); + + if (st->cpu_setpoint == new_setpoint) + return; + st->cpu_setpoint = new_setpoint; + readjust: + if (fan_cpu_main && wf_smu_failure_state == 0) { + rc = fan_cpu_main->ops->set_value(fan_cpu_main, + st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU main fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } + if (fan_cpu_second && wf_smu_failure_state == 0) { + rc = fan_cpu_second->ops->set_value(fan_cpu_second, + st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU second fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } + if (fan_cpu_third && wf_smu_failure_state == 0) { + rc = fan_cpu_main->ops->set_value(fan_cpu_third, + st->cpu_setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: CPU third fan" + " error %d\n", rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + +static void wf_smu_create_drive_fans(void) +{ + struct wf_pid_param param = { + .interval = 5, + .history_len = 2, + .gd = 0x01e00000, + .gp = 0x00500000, + .gr = 0x00000000, + .itarget = 0x00200000, + }; + + /* Alloc & initialize state */ + wf_smu_drive_fans = kmalloc(sizeof(struct wf_smu_drive_fans_state), + GFP_KERNEL); + if (wf_smu_drive_fans == NULL) { + printk(KERN_WARNING "windfarm: Memory allocation error" + " max fan speed\n"); + goto fail; + } + wf_smu_drive_fans->ticks = 1; + + /* Fill PID params */ + param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN); + param.min = fan_hd->ops->get_min(fan_hd); + param.max = fan_hd->ops->get_max(fan_hd); + wf_pid_init(&wf_smu_drive_fans->pid, ¶m); + + DBG("wf: Drive Fan control initialized.\n"); + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(param.itarget), param.min, param.max); + return; + + fail: + if (fan_hd) + wf_control_set_max(fan_hd); +} + +static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st) +{ + s32 new_setpoint, temp; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = st->pid.param.interval; + + rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); + if (rc) { + printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: Drive Fans tick ! HD temp: %d.%03d\n", + FIX32TOPRINT(temp)); + + if (temp > (st->pid.param.itarget + 0x50000)) + wf_smu_failure_state |= FAILURE_OVERTEMP; + + new_setpoint = wf_pid_run(&st->pid, temp); + + DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); + + if (st->setpoint == new_setpoint) + return; + st->setpoint = new_setpoint; + readjust: + if (fan_hd && wf_smu_failure_state == 0) { + rc = fan_hd->ops->set_value(fan_hd, st->setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: HD fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + +static void wf_smu_create_slots_fans(void) +{ + struct wf_pid_param param = { + .interval = 1, + .history_len = 8, + .gd = 0x00000000, + .gp = 0x00000000, + .gr = 0x00020000, + .itarget = 0x00000000 + }; + + /* Alloc & initialize state */ + wf_smu_slots_fans = kmalloc(sizeof(struct wf_smu_slots_fans_state), + GFP_KERNEL); + if (wf_smu_slots_fans == NULL) { + printk(KERN_WARNING "windfarm: Memory allocation error" + " max fan speed\n"); + goto fail; + } + wf_smu_slots_fans->ticks = 1; + + /* Fill PID params */ + param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN); + param.min = fan_slots->ops->get_min(fan_slots); + param.max = fan_slots->ops->get_max(fan_slots); + wf_pid_init(&wf_smu_slots_fans->pid, ¶m); + + DBG("wf: Slots Fan control initialized.\n"); + DBG(" itarged=%d.%03d, min=%d RPM, max=%d RPM\n", + FIX32TOPRINT(param.itarget), param.min, param.max); + return; + + fail: + if (fan_slots) + wf_control_set_max(fan_slots); +} + +static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st) +{ + s32 new_setpoint, power; + int rc; + + if (--st->ticks != 0) { + if (wf_smu_readjust) + goto readjust; + return; + } + st->ticks = st->pid.param.interval; + + rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power); + if (rc) { + printk(KERN_WARNING "windfarm: Slots power sensor error %d\n", + rc); + wf_smu_failure_state |= FAILURE_SENSOR; + return; + } + + DBG("wf_smu: Slots Fans tick ! Slots power: %d.%03d\n", + FIX32TOPRINT(power)); + +#if 0 /* Check what makes a good overtemp condition */ + if (power > (st->pid.param.itarget + 0x50000)) + wf_smu_failure_state |= FAILURE_OVERTEMP; +#endif + + new_setpoint = wf_pid_run(&st->pid, power); + + DBG("wf_smu: new_setpoint: %d\n", (int)new_setpoint); + + if (st->setpoint == new_setpoint) + return; + st->setpoint = new_setpoint; + readjust: + if (fan_slots && wf_smu_failure_state == 0) { + rc = fan_slots->ops->set_value(fan_slots, st->setpoint); + if (rc) { + printk(KERN_WARNING "windfarm: Slots fan error %d\n", + rc); + wf_smu_failure_state |= FAILURE_FAN; + } + } +} + + +/* + * ****** Attributes ****** + * + */ + +#define BUILD_SHOW_FUNC_FIX(name, data) \ +static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + ssize_t r; \ + s32 val = 0; \ + data->ops->get_value(data, &val); \ + r = sprintf(buf, "%d.%03d", FIX32TOPRINT(val)); \ + return r; \ +} \ +static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); + + +#define BUILD_SHOW_FUNC_INT(name, data) \ +static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + s32 val = 0; \ + data->ops->get_value(data, &val); \ + return sprintf(buf, "%d", val); \ +} \ +static DEVICE_ATTR(name,S_IRUGO,show_##name, NULL); + +BUILD_SHOW_FUNC_INT(cpu_fan, fan_cpu_main); +BUILD_SHOW_FUNC_INT(hd_fan, fan_hd); +BUILD_SHOW_FUNC_INT(slots_fan, fan_slots); + +BUILD_SHOW_FUNC_FIX(cpu_temp, sensor_cpu_temp); +BUILD_SHOW_FUNC_FIX(cpu_power, sensor_cpu_power); +BUILD_SHOW_FUNC_FIX(hd_temp, sensor_hd_temp); +BUILD_SHOW_FUNC_FIX(slots_power, sensor_slots_power); + +/* + * ****** Setup / Init / Misc ... ****** + * + */ + +static void wf_smu_tick(void) +{ + unsigned int last_failure = wf_smu_failure_state; + unsigned int new_failure; + + if (!wf_smu_started) { + DBG("wf: creating control loops !\n"); + wf_smu_create_drive_fans(); + wf_smu_create_slots_fans(); + wf_smu_create_cpu_fans(); + wf_smu_started = 1; + } + + /* Skipping ticks */ + if (wf_smu_skipping && --wf_smu_skipping) + return; + + wf_smu_failure_state = 0; + if (wf_smu_drive_fans) + wf_smu_drive_fans_tick(wf_smu_drive_fans); + if (wf_smu_slots_fans) + wf_smu_slots_fans_tick(wf_smu_slots_fans); + if (wf_smu_cpu_fans) + wf_smu_cpu_fans_tick(wf_smu_cpu_fans); + + wf_smu_readjust = 0; + new_failure = wf_smu_failure_state & ~last_failure; + + /* If entering failure mode, clamp cpufreq and ramp all + * fans to full speed. + */ + if (wf_smu_failure_state && !last_failure) { + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (fan_cpu_main) + wf_control_set_max(fan_cpu_main); + if (fan_cpu_second) + wf_control_set_max(fan_cpu_second); + if (fan_cpu_third) + wf_control_set_max(fan_cpu_third); + if (fan_hd) + wf_control_set_max(fan_hd); + if (fan_slots) + wf_control_set_max(fan_slots); + } + + /* If leaving failure mode, unclamp cpufreq and readjust + * all fans on next iteration + */ + if (!wf_smu_failure_state && last_failure) { + if (cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + wf_smu_readjust = 1; + } + + /* Overtemp condition detected, notify and start skipping a couple + * ticks to let the temperature go down + */ + if (new_failure & FAILURE_OVERTEMP) { + wf_set_overtemp(); + wf_smu_skipping = 2; + } + + /* We only clear the overtemp condition if overtemp is cleared + * _and_ no other failure is present. Since a sensor error will + * clear the overtemp condition (can't measure temperature) at + * the control loop levels, but we don't want to keep it clear + * here in this case + */ + if (new_failure == 0 && last_failure & FAILURE_OVERTEMP) + wf_clear_overtemp(); +} + + +static void wf_smu_new_control(struct wf_control *ct) +{ + if (wf_smu_all_controls_ok) + return; + + if (fan_cpu_main == NULL && !strcmp(ct->name, "cpu-rear-fan-0")) { + if (wf_get_control(ct) == 0) { + fan_cpu_main = ct; + device_create_file(wf_smu_dev, &dev_attr_cpu_fan); + } + } + + 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; + device_create_file(wf_smu_dev, &dev_attr_hd_fan); + } + } + + if (fan_slots == NULL && !strcmp(ct->name, "slots-fan")) { + if (wf_get_control(ct) == 0) { + fan_slots = ct; + device_create_file(wf_smu_dev, &dev_attr_slots_fan); + } + } + + 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; + device_create_file(wf_smu_dev, &dev_attr_cpu_power); + } + } + + if (sensor_cpu_temp == NULL && !strcmp(sr->name, "cpu-temp")) { + if (wf_get_sensor(sr) == 0) { + sensor_cpu_temp = sr; + device_create_file(wf_smu_dev, &dev_attr_cpu_temp); + } + } + + if (sensor_hd_temp == NULL && !strcmp(sr->name, "hd-temp")) { + if (wf_get_sensor(sr) == 0) { + sensor_hd_temp = sr; + device_create_file(wf_smu_dev, &dev_attr_hd_temp); + } + } + + if (sensor_slots_power == NULL && !strcmp(sr->name, "slots-power")) { + if (wf_get_sensor(sr) == 0) { + sensor_slots_power = sr; + device_create_file(wf_smu_dev, &dev_attr_slots_power); + } + } + + 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 device *ddev) +{ + wf_smu_dev = ddev; + + wf_register_client(&wf_smu_events); + + return 0; +} + +static int wf_smu_remove(struct 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) { + device_remove_file(wf_smu_dev, &dev_attr_cpu_power); + wf_put_sensor(sensor_cpu_power); + } + if (sensor_cpu_temp) { + device_remove_file(wf_smu_dev, &dev_attr_cpu_temp); + wf_put_sensor(sensor_cpu_temp); + } + if (sensor_hd_temp) { + device_remove_file(wf_smu_dev, &dev_attr_hd_temp); + wf_put_sensor(sensor_hd_temp); + } + if (sensor_slots_power) { + device_remove_file(wf_smu_dev, &dev_attr_slots_power); + wf_put_sensor(sensor_slots_power); + } + + /* Release all controls */ + if (fan_cpu_main) { + device_remove_file(wf_smu_dev, &dev_attr_cpu_fan); + 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) { + device_remove_file(wf_smu_dev, &dev_attr_hd_fan); + wf_put_control(fan_hd); + } + if (fan_slots) { + device_remove_file(wf_smu_dev, &dev_attr_slots_fan); + wf_put_control(fan_slots); + } + if (cpufreq_clamp) + wf_put_control(cpufreq_clamp); + + /* Destroy control loops state structures */ + if (wf_smu_slots_fans) + kfree(wf_smu_cpu_fans); + if (wf_smu_drive_fans) + kfree(wf_smu_cpu_fans); + if (wf_smu_cpu_fans) + kfree(wf_smu_cpu_fans); + + wf_smu_dev = NULL; + + return 0; +} + +static struct device_driver wf_smu_driver = { + .name = "windfarm", + .bus = &platform_bus_type, + .probe = wf_smu_probe, + .remove = wf_smu_remove, +}; + + +static int __init wf_smu_init(void) +{ + int rc = -ENODEV; + + if (machine_is_compatible("PowerMac9,1")) + rc = wf_init_pm(); + + if (rc == 0) { +#ifdef MODULE + request_module("windfarm_smu_controls"); + request_module("windfarm_smu_sensors"); + request_module("windfarm_lm75_sensor"); + +#endif /* MODULE */ + driver_register(&wf_smu_driver); + } + + return rc; +} + +static void __exit wf_smu_exit(void) +{ + + driver_unregister(&wf_smu_driver); +} + + +module_init(wf_smu_init); +module_exit(wf_smu_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control logic for PowerMac9,1"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_smu_controls.c b/drivers/macintosh/windfarm_smu_controls.c new file mode 100644 index 000000000000..2c3158c81ff2 --- /dev/null +++ b/drivers/macintosh/windfarm_smu_controls.c @@ -0,0 +1,282 @@ +/* + * Windfarm PowerMac thermal control. SMU based controls + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" + +#define VERSION "0.3" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* + * 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(comp); + int rc; + + /* Fill SMU command structure */ + cmd.cmd = SMU_CMD_FAN_COMMAND; + 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 = ∁ + + /* Fill argument buffer */ + memset(buffer, 0, 16); + buffer[0] = pwm ? 0x10 : 0x00; + buffer[1] = 0x01 << id; + *((u16 *)&buffer[2 + id * 2]) = value; + + rc = smu_queue_cmd(&cmd); + if (rc) + return rc; + wait_for_completion(&comp); + return cmd.status; +} + +static void smu_fan_release(struct wf_control *ct) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + + kfree(fct); +} + +static int smu_fan_set(struct wf_control *ct, s32 value) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + + if (value < fct->min) + value = fct->min; + if (value > fct->max) + value = fct->max; + fct->value = value; + + return smu_set_fan(fct->fan_type, fct->reg, value); +} + +static int smu_fan_get(struct wf_control *ct, s32 *value) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + *value = fct->value; /* todo: read from SMU */ + return 0; +} + +static s32 smu_fan_min(struct wf_control *ct) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + return fct->min; +} + +static s32 smu_fan_max(struct wf_control *ct) +{ + struct smu_fan_control *fct = to_smu_fan(ct); + return fct->max; +} + +static struct wf_control_ops smu_fan_ops = { + .set_value = smu_fan_set, + .get_value = smu_fan_get, + .get_min = smu_fan_min, + .get_max = smu_fan_max, + .release = smu_fan_release, + .owner = THIS_MODULE, +}; + +static struct smu_fan_control *smu_fan_create(struct device_node *node, + int pwm_fan) +{ + struct smu_fan_control *fct; + s32 *v; u32 *reg; + char *l; + + fct = kmalloc(sizeof(struct smu_fan_control), GFP_KERNEL); + if (fct == NULL) + return NULL; + fct->ctrl.ops = &smu_fan_ops; + l = (char *)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")) + fct->ctrl.name = "cpu-rear-fan-0"; + else if (!strcmp(l, "Rear Fan 1") || !strcmp(l, "Rear fan 1")) + 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")) + fct->ctrl.name = "cpu-front-fan-0"; + else if (!strcmp(l, "Front Fan 1") || !strcmp(l, "Front fan 1")) + fct->ctrl.name = "cpu-front-fan-1"; + else if (!strcmp(l, "Slots Fan") || !strcmp(l, "Slots fan")) + fct->ctrl.name = "slots-fan"; + else if (!strcmp(l, "Drive Bay") || !strcmp(l, "Drive bay")) + fct->ctrl.name = "drive-bay-fan"; + + /* Names used on iMac models */ + if (!strcmp(l, "System Fan") || !strcmp(l, "System fan")) + fct->ctrl.name = "system-fan"; + else if (!strcmp(l, "CPU Fan") || !strcmp(l, "CPU fan")) + fct->ctrl.name = "cpu-fan"; + else if (!strcmp(l, "Hard Drive") || !strcmp(l, "Hard drive")) + fct->ctrl.name = "drive-bay-fan"; + + /* Unrecognized fan, bail out */ + if (fct->ctrl.name == NULL) + goto fail; + + /* Get min & max values*/ + v = (s32 *)get_property(node, "min-value", NULL); + if (v == NULL) + goto fail; + fct->min = *v; + v = (s32 *)get_property(node, "max-value", NULL); + if (v == NULL) + goto fail; + fct->max = *v; + + /* Get "reg" value */ + reg = (u32 *)get_property(node, "reg", NULL); + if (reg == NULL) + goto fail; + fct->reg = *reg; + + if (wf_register_control(&fct->ctrl)) + goto fail; + + return fct; + fail: + kfree(fct); + return NULL; +} + + +static int __init smu_controls_init(void) +{ + struct device_node *smu, *fans, *fan; + + if (!smu_present()) + return -ENODEV; + + smu = of_find_node_by_type(NULL, "smu"); + if (smu == NULL) + return -ENODEV; + + /* Look for RPM fans */ + for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) + if (!strcmp(fans->name, "rpm-fans")) + break; + for (fan = NULL; + fans && (fan = of_get_next_child(fans, fan)) != NULL;) { + struct smu_fan_control *fct; + + fct = smu_fan_create(fan, 0); + if (fct == NULL) { + printk(KERN_WARNING "windfarm: Failed to create SMU " + "RPM fan %s\n", fan->name); + continue; + } + list_add(&fct->link, &smu_fans); + } + of_node_put(fans); + + + /* Look for PWM fans */ + for (fans = NULL; (fans = of_get_next_child(smu, fans)) != NULL;) + if (!strcmp(fans->name, "pwm-fans")) + break; + for (fan = NULL; + fans && (fan = of_get_next_child(fans, fan)) != NULL;) { + struct smu_fan_control *fct; + + fct = smu_fan_create(fan, 1); + if (fct == NULL) { + printk(KERN_WARNING "windfarm: Failed to create SMU " + "PWM fan %s\n", fan->name); + continue; + } + list_add(&fct->link, &smu_fans); + } + of_node_put(fans); + of_node_put(smu); + + return 0; +} + +static void __exit smu_controls_exit(void) +{ + struct smu_fan_control *fct; + + while (!list_empty(&smu_fans)) { + fct = list_entry(smu_fans.next, struct smu_fan_control, link); + list_del(&fct->link); + wf_unregister_control(&fct->ctrl); + } +} + + +module_init(smu_controls_init); +module_exit(smu_controls_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("SMU control objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_smu_sensors.c b/drivers/macintosh/windfarm_smu_sensors.c new file mode 100644 index 000000000000..b558cc209d49 --- /dev/null +++ b/drivers/macintosh/windfarm_smu_sensors.c @@ -0,0 +1,479 @@ +/* + * Windfarm PowerMac thermal control. SMU based sensors + * + * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. + * <benh@kernel.crashing.org> + * + * Released under the term of the GNU GPL v2. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/sections.h> +#include <asm/smu.h> + +#include "windfarm.h" + +#define VERSION "0.2" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* + * Various SMU "partitions" calibration objects for which we + * keep pointers here for use by bits & pieces of the driver + */ +static struct smu_sdbp_cpuvcp *cpuvcp; +static int cpuvcp_version; +static struct smu_sdbp_cpudiode *cpudiode; +static struct smu_sdbp_slotspow *slotspow; +static u8 *debugswitches; + +/* + * SMU basic sensors objects + */ + +static LIST_HEAD(smu_ads); + +struct smu_ad_sensor { + struct list_head link; + u32 reg; /* index in SMU */ + struct wf_sensor sens; +}; +#define to_smu_ads(c) container_of(c, struct smu_ad_sensor, sens) + +static void smu_ads_release(struct wf_sensor *sr) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + + kfree(ads); +} + +static int smu_read_adc(u8 id, s32 *value) +{ + struct smu_simple_cmd cmd; + DECLARE_COMPLETION(comp); + int rc; + + rc = smu_queue_simple(&cmd, SMU_CMD_READ_ADC, 1, + smu_done_complete, &comp, id); + if (rc) + return rc; + wait_for_completion(&comp); + if (cmd.cmd.status != 0) + return cmd.cmd.status; + if (cmd.cmd.reply_len != 2) { + printk(KERN_ERR "winfarm: read ADC 0x%x returned %d bytes !\n", + id, cmd.cmd.reply_len); + return -EIO; + } + *value = *((u16 *)cmd.buffer); + return 0; +} + +static int smu_cputemp_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + int rc; + s32 val; + s64 scaled; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read CPU temp failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s64)(((u64)val) * (u64)cpudiode->m_value); + scaled >>= 3; + scaled += ((s64)cpudiode->b_value) << 9; + *value = (s32)(scaled << 1); + + return 0; +} + +static int smu_cpuamp_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + s32 val, scaled; + int rc; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read CPU current failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s32)(val * (u32)cpuvcp->curr_scale); + scaled += (s32)cpuvcp->curr_offset; + *value = scaled << 4; + + return 0; +} + +static int smu_cpuvolt_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + s32 val, scaled; + int rc; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read CPU voltage failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s32)(val * (u32)cpuvcp->volt_scale); + scaled += (s32)cpuvcp->volt_offset; + *value = scaled << 4; + + return 0; +} + +static int smu_slotspow_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_ad_sensor *ads = to_smu_ads(sr); + s32 val, scaled; + int rc; + + rc = smu_read_adc(ads->reg, &val); + if (rc) { + printk(KERN_ERR "windfarm: read slots power failed, err %d\n", + rc); + return rc; + } + + /* Ok, we have to scale & adjust, taking units into account */ + scaled = (s32)(val * (u32)slotspow->pow_scale); + scaled += (s32)slotspow->pow_offset; + *value = scaled << 4; + + return 0; +} + + +static struct wf_sensor_ops smu_cputemp_ops = { + .get_value = smu_cputemp_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; +static struct wf_sensor_ops smu_cpuamp_ops = { + .get_value = smu_cpuamp_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; +static struct wf_sensor_ops smu_cpuvolt_ops = { + .get_value = smu_cpuvolt_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; +static struct wf_sensor_ops smu_slotspow_ops = { + .get_value = smu_slotspow_get, + .release = smu_ads_release, + .owner = THIS_MODULE, +}; + + +static struct smu_ad_sensor *smu_ads_create(struct device_node *node) +{ + struct smu_ad_sensor *ads; + char *c, *l; + u32 *v; + + ads = kmalloc(sizeof(struct smu_ad_sensor), GFP_KERNEL); + if (ads == NULL) + return NULL; + c = (char *)get_property(node, "device_type", NULL); + l = (char *)get_property(node, "location", NULL); + if (c == NULL || l == NULL) + goto fail; + + /* We currently pick the sensors based on the OF name and location + * properties, while Darwin uses the sensor-id's. + * The problem with the IDs is that they are model specific while it + * looks like apple has been doing a reasonably good job at keeping + * the names and locations consistents so I'll stick with the names + * and locations for now. + */ + if (!strcmp(c, "temp-sensor") && + !strcmp(l, "CPU T-Diode")) { + ads->sens.ops = &smu_cputemp_ops; + ads->sens.name = "cpu-temp"; + } else if (!strcmp(c, "current-sensor") && + !strcmp(l, "CPU Current")) { + ads->sens.ops = &smu_cpuamp_ops; + ads->sens.name = "cpu-current"; + } else if (!strcmp(c, "voltage-sensor") && + !strcmp(l, "CPU Voltage")) { + ads->sens.ops = &smu_cpuvolt_ops; + ads->sens.name = "cpu-voltage"; + } else if (!strcmp(c, "power-sensor") && + !strcmp(l, "Slots Power")) { + ads->sens.ops = &smu_slotspow_ops; + ads->sens.name = "slots-power"; + if (slotspow == NULL) { + DBG("wf: slotspow partition (%02x) not found\n", + SMU_SDB_SLOTSPOW_ID); + goto fail; + } + } else + goto fail; + + v = (u32 *)get_property(node, "reg", NULL); + if (v == NULL) + goto fail; + ads->reg = *v; + + if (wf_register_sensor(&ads->sens)) + goto fail; + return ads; + fail: + kfree(ads); + return NULL; +} + +/* + * SMU Power combo sensor object + */ + +struct smu_cpu_power_sensor { + struct list_head link; + struct wf_sensor *volts; + struct wf_sensor *amps; + int fake_volts : 1; + int quadratic : 1; + struct wf_sensor sens; +}; +#define to_smu_cpu_power(c) container_of(c, struct smu_cpu_power_sensor, sens) + +static struct smu_cpu_power_sensor *smu_cpu_power; + +static void smu_cpu_power_release(struct wf_sensor *sr) +{ + struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr); + + if (pow->volts) + wf_put_sensor(pow->volts); + if (pow->amps) + wf_put_sensor(pow->amps); + kfree(pow); +} + +static int smu_cpu_power_get(struct wf_sensor *sr, s32 *value) +{ + struct smu_cpu_power_sensor *pow = to_smu_cpu_power(sr); + s32 volts, amps, power; + u64 tmps, tmpa, tmpb; + int rc; + + rc = pow->amps->ops->get_value(pow->amps, &s); + if (rc) + return rc; + + if (pow->fake_volts) { + *value = amps * 12 - 0x30000; + return 0; + } + + rc = pow->volts->ops->get_value(pow->volts, &volts); + if (rc) + return rc; + + power = (s32)((((u64)volts) * ((u64)amps)) >> 16); + if (!pow->quadratic) { + *value = power; + return 0; + } + tmps = (((u64)power) * ((u64)power)) >> 16; + tmpa = ((u64)cpuvcp->power_quads[0]) * tmps; + tmpb = ((u64)cpuvcp->power_quads[1]) * ((u64)power); + *value = (tmpa >> 28) + (tmpb >> 28) + (cpuvcp->power_quads[2] >> 12); + + return 0; +} + +static struct wf_sensor_ops smu_cpu_power_ops = { + .get_value = smu_cpu_power_get, + .release = smu_cpu_power_release, + .owner = THIS_MODULE, +}; + + +static struct smu_cpu_power_sensor * +smu_cpu_power_create(struct wf_sensor *volts, struct wf_sensor *amps) +{ + struct smu_cpu_power_sensor *pow; + + pow = kmalloc(sizeof(struct smu_cpu_power_sensor), GFP_KERNEL); + if (pow == NULL) + return NULL; + pow->sens.ops = &smu_cpu_power_ops; + pow->sens.name = "cpu-power"; + + wf_get_sensor(volts); + pow->volts = volts; + wf_get_sensor(amps); + pow->amps = amps; + + /* Some early machines need a faked voltage */ + if (debugswitches && ((*debugswitches) & 0x80)) { + printk(KERN_INFO "windfarm: CPU Power sensor using faked" + " voltage !\n"); + pow->fake_volts = 1; + } else + pow->fake_volts = 0; + + /* Try to use quadratic transforms on PowerMac8,1 and 9,1 for now, + * I yet have to figure out what's up with 8,2 and will have to + * adjust for later, unless we can 100% trust the SDB partition... + */ + if ((machine_is_compatible("PowerMac8,1") || + machine_is_compatible("PowerMac8,2") || + machine_is_compatible("PowerMac9,1")) && + cpuvcp_version >= 2) { + pow->quadratic = 1; + DBG("windfarm: CPU Power using quadratic transform\n"); + } else + pow->quadratic = 0; + + if (wf_register_sensor(&pow->sens)) + goto fail; + return pow; + fail: + kfree(pow); + return NULL; +} + +static int smu_fetch_param_partitions(void) +{ + 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) { + DBG("wf: cpuvcp partition (%02x) not found\n", + SMU_SDB_CPUVCP_ID); + return -ENODEV; + } + 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) { + DBG("wf: cpudiode partition (%02x) not found\n", + SMU_SDB_CPUDIODE_ID); + return -ENODEV; + } + 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]; + + return 0; +} + +static int __init smu_sensors_init(void) +{ + struct device_node *smu, *sensors, *s; + struct smu_ad_sensor *volt_sensor = NULL, *curr_sensor = NULL; + int rc; + + if (!smu_present()) + return -ENODEV; + + /* Get parameters partitions */ + rc = smu_fetch_param_partitions(); + if (rc) + return rc; + + smu = of_find_node_by_type(NULL, "smu"); + if (smu == NULL) + return -ENODEV; + + /* Look for sensors subdir */ + for (sensors = NULL; + (sensors = of_get_next_child(smu, sensors)) != NULL;) + if (!strcmp(sensors->name, "sensors")) + break; + + of_node_put(smu); + + /* Create basic sensors */ + for (s = NULL; + sensors && (s = of_get_next_child(sensors, s)) != NULL;) { + struct smu_ad_sensor *ads; + + ads = smu_ads_create(s); + if (ads == NULL) + continue; + list_add(&ads->link, &smu_ads); + /* keep track of cpu voltage & current */ + if (!strcmp(ads->sens.name, "cpu-voltage")) + volt_sensor = ads; + else if (!strcmp(ads->sens.name, "cpu-current")) + curr_sensor = ads; + } + + of_node_put(sensors); + + /* Create CPU power sensor if possible */ + if (volt_sensor && curr_sensor) + smu_cpu_power = smu_cpu_power_create(&volt_sensor->sens, + &curr_sensor->sens); + + return 0; +} + +static void __exit smu_sensors_exit(void) +{ + struct smu_ad_sensor *ads; + + /* dispose of power sensor */ + if (smu_cpu_power) + wf_unregister_sensor(&smu_cpu_power->sens); + + /* dispose of basic sensors */ + while (!list_empty(&smu_ads)) { + ads = list_entry(smu_ads.next, struct smu_ad_sensor, link); + list_del(&ads->link); + wf_unregister_sensor(&ads->sens); + } +} + + +module_init(smu_sensors_init); +module_exit(smu_sensors_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("SMU sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + |